# Object parameter schemas (strat bytecode analysis) Registry in code: `editor.tools.objects.Schemas` (named, typed, documented parameters with ranges, choices and waypoint meaning), statistics of all original instances: `src/main/resources/.../editor/tools/objects/type-stats.tsv` (`TypeStats`). Types without a schema fall back to raw values plus "values used in the original levels". Shown in the Inspector section "Object parameters" and checked by the Problems panel (`ObjectChecks`). ## How parameters reach the script * MAP strat record: count n + index into the parameter pool. The level spawner FUN_0054cfc0 passes a fake stack, FUN_0054bfc0 pops the n values into the new strat's **variables 0..n−1** (`strat+0xF4` = variable array). So *parameter i = variable i* of the strat code. * Values are 20.12 fixed point by convention (0x1000 = 1.0); booleans are 0 / 0x1000, counts and world/chapter numbers `n << 12`. ## Strat VM (FUN_0054d180) – enough to disassemble Word-coded: `op = w & 0xFFFF`. `op < 0x45`: table 0x5796BC, operand = signed high 16 bits, one word. `op ≥ 0x45`: table 0x578FF4 + op·4 (up to 0x1F6), inline operand words read via the pc. Inline lengths (found by decompiling all 337 handlers with headless Ghidra and following helper calls): 1 word for 0x45 0xA5 0xA6 0xB1 0xB2 0xD0 0xD3 0xD7 0xDB 0x115 0x11C 0x14B 0x14D 0x162 0x167 0x175; 2 for 0xD4 (name: `d4 ea rel`); 5 for 0x94 and 0xE0 (spawn: FUN_00553170 reads nParams + 4 type fields); 6 for 0x95 (spawn at offset); short 0x0C (event slot) has 2; 0x02/0x03 (debug print: count then items, 0x45/0xEA items have one extra word) are variable. A linear sweep restarted at every `d4` name site disassembles all 31 level WADs without desync and hits every MAP strat entry (the entry is the name site). Strings live between strats. | short op | meaning | short op | meaning | |---|---|---|---| | 04 n / 41 n | push / pop variable n | 06 n / 43 n | push / pop global n (0x6D9E8C) | | 07 n / 40 n | push / pop own property n (FUN_00550E60 / FUN_005509F0) | 1C n / 37 n | player property | | 44 v | push 16-bit constant | 14 / 15 / 16 | jz / jnz / jmp (pc+4+v·4) | | 12 / 13 / 2B | jz / jnz / jmp + yield | 17 | switch (table at pc+4+v·4) | Long ops used for the decoding: 45 push word, 49 add, 4A sub, 4B mul (fixed), 4C div, 4E eq, 4F lt, 53 gt, BC ne, BD/BE ge/le, AF not, B6 and-logical, B7/B8 bit or/and, 50/51 shl/shr, AD/AE sin/cos (table 0x574318), AC floor, BF rand, 16E lerp, 54 set model, 8B radius (+0xFE), 83 +0xD2 (collision radius), A5/A6 set/clear strat flags (0x8000 floor collision, 0x40000 walls), C2 update floor, 73/75/177 waypoint first/next/advance (+0x118 first, +0x120 current; next wraps to the first node), FE move to waypoint, 178 "inside the box of the current and next waypoint (x/z)", 192 level change (pops tribe, world, chapter), B4/8E die, E0/95 spawn. Properties: 16 floor height, 17 floor material, 34–36 x/y/z, 37–39 rx/ry/rz, 3 waypoint first/last bits, 4 waypoint field +26, 6–8 waypoint x/y/z, 9/10 waypoint rx/ry, 41 distance to the player, 61–64 tribe/world/chapter/map, 202 distance to player (squared compare). Camera globals: 47 horizontal distance, 48 height above the target, 29/44 yaw, 36–38 position and 40–42 look-at of cut-scene cameras, 66 camera flag word, 95 progress bits. Patterns: `push var i; push 30; mul` = seconds → frames; `push var; push var; mul; pop var` = squared distance (radius). ## Decoded types Confidence: **high** = code read and behaviour seen in game; **medium** = code read; **low** = values only. | Type | Parameters | Waypoints | |---|---|---| | Coin (high) | p0 snap to floor (drops onto floor, random spin), p1 falls (floor collision flag 0x8000), p2 no shadow | – | | Coin5 (high) | p0 snap, p1 falls | – | | Health | p0 snap, p1 no shadow, p2 falls | – | | HealthFull, HealthGenerator, Wompy | p0 snap, p1 falls | – | | Grapes, HealthBanana, ChargeBooster | p0 snap | – | | RubyIdol | p0 falls, p1 snap | – | | Secret (high) | p0 box mode (player inside the box of the 2 nodes, op 0x178) | box (2 nodes) | | CoinLine / CoinCircle (low) | count (counted down), spacing / radius | – | | Llama | p0 spawns a helper, p1 unused, p2 camera mode (global 107) | first node = camera start | | CheckPointPost (high) | p0 collision radius (+0xD2, 0.2) | 1 node = respawn position + yaw (98 of 102) | | LevelDoor (high) | tribe, world, chapter of the next level (7,7,7 = world finished); fires on material-16 floor of a tile in its cell (any height; often only in the swap model a RubyIdolDoor/other door object switches to when it opens) | – | | CameraSide | p0 height (g48), p1 distance (g47), p2 yaw mode (1 node yaw, 2 node yaw or ±180°), p3 only below node y, p4 flag 0x10000, p10 several boxes, p11 height offset, p12/p13 alt. height/distance | box pairs, node 0 yaw = camera yaw | | CameraLift | p0 yaw source (1 object, 2 player), p1 box test on helper strat, p3 smooth (g97), p4 height limit, p5 flag | box | | CameraSetDistance | g82, g68, g83, height g48, g73 (each with a flag bit in g66) | – | | CameraFocusPoint | radius², min height², max angle, height limit, 3 flags, blend factor | – | | CutSceneCameraDirectional | p0 event bit (1..31), p1 mode, p2 wait, p3 disabled, p5 end mode | camera nodes: position, yaw/pitch = view (globals 36–42) | | CutSceneTrigger | p0 character (floor snap, g72 counter), p2 radius², p3 event bit (skip if set in g95), p5 height check, p6 bits (1 face player, 2 not skippable) | camera path | | ForcePlayerMovement | p0 active, p1 radius², p2 game state, p3 lock, p4 align yaw | target points | | ObjectDontDropInBox, FallOffNoHurt | box mode (+ game state) | box | | WaterReboundPoints | active | rebound points | | RubyIdolDoor | p1 idols needed, p4 sign offset | – | | CityGuard family (high) | p0 view range², p1 chase range² (view capped to it), p2 event bit (0 = active at once), p3 wall collision (CityGuard only) | **boxes** (node pairs), must start inside one – see below | | JumpingBeetleBlue, Spider, Bunny | times in seconds (×30), ranges² | patrol path | ### Guards (CityGuard, CityGuardWithRubyIdol, CowGuard(NoCoin), GorillaGuard, LizardGuard(NoCoin), WarthogGuard) One compiled template (same code in t1l1m002 unit 20 and the others). Start of the script: 1. `if (p2 > 0) { hide; wait until g95 & (1 << (p2−1)); unhide } else wait until playerPtr ≠ 0` – p2 is an **event bit**: bits of global 95 are set by talking characters (TipoVillage, ChacaVillage), smash statues, LabBigCutscene, LabSlidingDoorPad … with the same number. All village guards have p2 = 0; 4 lab guards wait for an event. 2. Waypoint scan: `wpFirst`, then pairs (`wpNext` twice): odd count → debug "Uneven number of WPs" + **remove**; the start box = first pair whose x/z box (`inWaypointBoxXZ`, op 0x178, inclusive, fixed point) contains the guard and whose first node has field +0x28 (`wpParam`, editor `unk2`) = 0; none → "Guard not in WP box" + **remove**. So the waypoints are **walking areas**, not a patrol path: the guard wanders (random turns) inside the current box; when it leaves it, `waypointStep(2)` looks for another box containing it (boxes with `wpParam ≠ 0` only from another such box), else it turns back (`clampToWaypointBox`). Chasing stays clamped to the boxes; it gives up beyond p1 or when the player leaves. All 41 original guards satisfy this (unit test `GuardsTest`). The editor: placing a guard gives it one 4×4 box and p2 = 0; `ObjectChecks` reports an error when the game would remove it and an info for p2 > 0; the Inspector shows the state plus a **Box around it** button (`Guards`, `Setups.guardBox`). Verified in game: CityGuard with its box on a new level (base village 2) appears and attacks the llama; a second one with a 4-node "square path" next to it never appears. The rest (~150 placed types) shows raw values with the original statistics. Next candidates: Alligator (also box pairs: "Forcing alligator inside first WP box"), PressurePadDoor (bit field), TargetRopePlatform, CataSwingingBlade (speeds/angles), RiverLog. Tooling used (not in the repo): Ghidra script decompiling the handler table, a Python sweep disassembler with an op-name table, "parameter use" context dumps per type.