# 0004: The Router API: data first, Lua 5.4 for router scripts

Date: 2026-10-04

## Context

Every kit needs the same way to say "this is the game's player, these are its NPCs,
this is how to get into its world". Before 5.2, VEIN's router was a 227-line Lua module
with its own contract (`update(ctx)`, `describe`, `spawn`), and about a third of it was
generic Unreal code (traces, finding open ground, widget lookup). Agents writing routers
pay for every line ([`docs/agent-efficiency.md`](https://steonmod.com/docs/agent-efficiency)), so the API has to make most routers
data, and the rest short.

VISION.md §15 also left open which Lua runs router scripts on Unreal: Luau (what Fusion's
addons use) or UE4SS's built-in Lua 5.4.

## Decision

- **Data first.** A router's `router.toml` gets `[block.bind]`: the world's map and player
  class, `[[block.bind.entity]]` rules (class or base class, kind, name, health field
  paths, team, the asset to spawn). Fusion checks it with the rest of the manifest
  (unknown fields are errors). The kit receives the whole manifest as a Lua table
  (`RouterManifest::lua_source`, through `FusionKit_Router()`), so there is no second
  copy to keep in sync.
- **Kit defaults from engine conventions.** The Unreal kit does everything it can by
  itself: player controller 0's pawn is the player, it opens the bound map from a main
  menu, waits for World Partition and ground, picks open ground for Fusion's origin, hides
  the player and makes it unhurtable, lists characters, spawns bound assets feet-first,
  and keeps the game running when its window isn't in front (`t.IdleWhenNotForeground 0`).
  Ports a block gives turn the kit's work on (`collision` → traces, `entities` → lists).
- **Lua only for what data can't say**, through `Router.block "<id>" { ... }` (functions
  for `enter`, `ready`, `anchor`, `hidden`, `describe`, `spawn.<kind>`), checked when the
  script runs: an unknown field, wrong type or unknown block id fails with the script's
  line and the list of what's allowed.
- **`Game.*`** is the reflection API, with the same names on every kit: `find`,
  `find_all`, `object`, `class`, `get`/`set` by field path, `call`, `is_a`, `player`,
  `world`, `map`, `console`, `trace`, `spawn`, `widget`, `loaded`, `time`, `log`.
- **Sandbox.** The script runs in its own environment: `Router`, `Game` and Lua's safe
  libraries. No files, no OS, no `require`/`load`, no UE4SS globals.
- **Router scripts stay on Lua 5.4** (UE4SS's own on Unreal). The Unity kit (M7) embeds
  Lua 5.4 too, so router scripts are one dialect everywhere. Addons stay Luau (ADR 0002).
  Types come from LuaLS annotations: `docs/types/router-api.lua` is a `---@meta`
  definition file that `lua-language-server --check` reads (wired into `router check`
  in 5.7).

## Why Lua 5.4, not Luau

- On Unreal, reflection lives in UE4SS's Lua: every `obj.Field` and `obj:Function()` goes
  through UE4SS's bindings. Luau there means re-binding all of Unreal's reflection to a
  second VM ourselves, without UE4SS's source (ADR 0003): a large job for no new ability.
- Typed checks were the reason to prefer Luau ([`docs/agent-efficiency.md`](https://steonmod.com/docs/agent-efficiency) §3). LuaLS
  annotations give the same class of checks (unknown fields, wrong argument types,
  undefined globals) from a definition file, and router scripts are now short.
- Router scripts are written by router makers, not players; Luau's player-facing benefits
  (its sandboxing, gradual types in the editor) matter more for addons, which stay Luau.
- The common subset of Lua 5.4 and Luau is large; the API itself is dialect-neutral.

## Consequences

- VEIN's router is now 25 lines of data in `router.toml` and 36 lines of Lua (only its
  character creation screen), down from 227 lines of Lua.
- Reflection is reflection: a router can still call any of the game's own functions.
  The sandbox keeps it away from files and the OS; a deny list of dangerous game
  functions (opening URLs and the like) belongs with trust tiers (6.2).
- The kit's checks run when the script loads in the game. `router check` (5.7) will run
  them, plus the LuaLS types, before the game starts.
