# Runtime Movement

- update function: `0x00411476`
- per-frame calls: `0x004114e2 -> 0x00422d74` input/action mask update, `0x004114e7 -> 0x00435c04` active object callback update, `0x004114ec -> 0x00432ff0` object script/timer update
- draw prep call: `0x0041150d -> 0x00424a2a`
- input action packer: `0x00422d91 -> 0x0042f6d2`
- continuous input mask: `0x0059e310`; pressed-edge mask: `0x0059e312`
- direction bits: left 0x0001, right 0x0002, up 0x0004, down 0x0008
- tile projection: object `+0xe8`/`+0xea` minus camera `0x004576dc`/`0x004576de`
- tile size: `16` px; web tile size matches: True
- draw projection grounded: prep True; party actors True; +0x68 animation state values grounded: True
- sprite frame selector grounded: True; selector `+0x28` low 16 frame index, bits 16..23 surface table `0x0055abd8`; source-rect grounded: True; script timer `0x00432ff0 -> 0x00402321`, opcode 0x21 handler `0x004044bd`; script timer grounded: True; direct +0x68 mapping eliminated: True; party +0x68-to-frame-script grounded: True; remaining +0x68-to-+0x28 gap: the 12-row descriptor table is classified: rows 0..2 ground +0x68 -> +0x64 state-table frame scripts for Ataho/Rinshan/Smashu, while rows 3..11 are battle/effect/object resource descriptors without +0x68 state-table selectors or field-map refs; save-load active-order materialization is grounded, but current selector 2:0 active order and scripted behavior for non-player descriptors remain inferred
- party actor frame scripts: descriptor table `0x00442d95`, frame pointer opcode 0x20 handler `0x0040448a`, state table opcode 0x18 handler `0x0040408f`, command `18 a0 64 68 <state-table-va>`; party actor descriptors initialize +0x64 with opcode 0x20, then use opcode 0x18 command bytes 18 a0 64 68 to select +0x64 = stateTable[object+0x68]; states 1..8 decode to opcode 0x21 +0x28 selector sequences that loop through opcode 0x03; the full 12-row descriptor table is classified so rows 3..11 stay separate as battle/effect/object resource descriptors
- object descriptor table classified: True; rows 12; party state-table rows [0, 1, 2]; non-party +0x68 state tables absent: True; non-party field-map CNS refs absent: True; the 12-row descriptor table has +0x68 animation state-table selectors only in rows 0..2 for the party walking descriptors; rows 3..11 are battle/effect/object resource descriptors with no +0x68 state-table selector and no field-map CNS refs
- descriptor activation/order path grounded: True; count `0x004576e8` order `0x004576e9` slot base `0x00457750`; load rebuild `0x0042349c -> 0x00432323` after save reads: True before selector selection: True; opcode 0x20 self-mutation eliminated: True; current selector active order proven: False
- web tile step: `48` ms from `ORIGINAL_FRAME_MS`; clamp 48..300 ms
- web walk frames: 5 per direction, idle 0, moving starts at 1, frame 48x64
- web source rect selector mapping grounded: True; 8-command cadence grounded: True; cadence commands: 8; model: web character source rects use original party actor +0x28 frame selector indices for idle states and the full 8-command timer=1 walking selector cadence
- frame-quantized interpolation present: True; held input tile chaining: True
- wall-slide fallback present: True; actual movement direction drives facing: True; diagonal follow-up: True; default collision mode: `originalLayer1Flags` (local tile class + layer0 fallback)
- camera deadzone present: True; deadzone tiles: {'x': 6, 'y': 5}
- deferred map query sync: True
- actor-overlap collision mode present: True; default enabled: False; query: `actorCollision=1 enables review-only party blocking`
- actor controller: `0x0043022d`; collision helper: `0x004319f8`
- original party trail ring: 7 slots at `0x00574540`/`0x00574550`/`0x00574552`/`0x00574554`; initial cursors [0, 4, 1]; timing: leader tile mutation precedes cursor advance/history write; follower reads happen after the leader write in the same actor controller pass; web partyTrail slots: 7; cursor model: True; records accepted target: True; bounded: True
- original collision flags: `0x0058d7d0`/`0x0058d7ce`; direction bits {'down': '0x02', 'up': '0x01', 'left': '0x04', 'right': '0x08'}
- directional collision cases: 4; object overlap response: actor-overlap collision response after tile-flag checks; clears latch: True
- map collision loader: `0x0042449c` copies CNS layer0 to `0x00595af0` and CNS layer1 flags to `0x0058d7d0`
- web original layer1 flag collision mode present: False; default uses original layer1 flags: True; tileClass fallback default: False
- web actor-overlap collision mode present: True; default enabled: False; opt-in review switch: True
- web party-trail history present: True; ring slots: 7; actor slots: 3; initial cursors: [0, 4, 1]; cursor model: True; matches original cursors: True; records accepted target: True; call timing match: True; still approximate: False
- input-driven tile mutation grounded: True; original collision flag tests grounded: True
- original actor position model: tile-field-snap-plus-fixed-point-draw-projection; sub-tile interpolation evidence found: False; scope: identified actor controller tile mutations +0xe8/+0xea and draw projection writes +0x1c/+0x20
- browser interpolation classification: browser-visual-overlay-not-original-proof; overlay detected: True; fixed-point preserved: True; offset field: `interpolationOffset`
- web position interpolation still visual approximation: True; web source-rect phase still inferred: False; exact actor sprite-frame phase still inferred: False
- status: `movement-consumer-partially-grounded`

The original per-frame update at 0x00411476 calls the input/action-mask step, active-object callback step, and object script/timer step once for each capped frame. The input step stores a continuous action mask separately from the new-press edge mask, which matches the browser's held-key walking behavior. The draw preparation path maps object tile fields +0xe8/+0xea against save/camera tile globals 0x004576dc/0x004576de, multiplies by 16 pixels, applies small screen biases, and stores fixed-point draw coordinates; the draw dirty-rect helper then consumes those projected +0x1c/+0x20 coordinates. The web runtime uses that actor-controller tile mutation evidence as a 48ms tile step, keeps the original party actor +0x28 idle source rects and full 8-command walking selector cadence for each facing direction, and keeps frame-quantized tile interpolation as a browser visual overlay rather than original sub-tile interpolation proof. The input-driven actor controller at 0x0043022d reads the continuous input mask, writes object tile fields +0xe8/+0xea, and calls the collision helper at 0x004319f8, which checks map collision flag bits from 0x0058d7d0/0x0058d7ce against object footprint fields +0xe6/+0xe7. The map loader at 0x0042449c copies the CNS layer0 word grid to 0x00595af0 and the CNS layer1 word grid directly to 0x0058d7d0, so the original collision flag grid is now mapped back to CNS layer1. The actor controller also maintains a 7-slot tile/direction history ring at 0x00574540/0x00574550/0x00574552/0x00574554 for companion actor following, seeded with per-actor cursors 0,4,1; in the same actor-controller pass it writes the mutated leader tile before follower reads. The browser has a partyTrail cursor-ring model for the same surface and records the accepted movement target into the ring. The actor controller also recomputes party actor draw coordinates from the leader tile or follower trail entry and uses object +0x68 for direction/animation state values 1-8. The object script/timer update at 0x00432ff0 drives +0x28 through +0x62/+0x64 and opcode 0x21 handler 0x004044bd. Party actor descriptors at 0x00442d95 initialize +0x64 through opcode 0x20 handler 0x0040448a, then use opcode 0x18 command bytes 18 a0 64 68 to select state-table frame scripts from object +0x68 for cara_at1/cara_rs1/cara_sm1. Those selected scripts contain opcode 0x21 selector sequences that loop via opcode 0x03. The same 12-row descriptor table is now classified: rows 0..2 are party walking descriptors, while rows 3..11 are battle/effect/object resource descriptors with no +0x68 state-table selector and no field-map CNS refs. Save-load active descriptor materialization is also separated: 0x00432323 rebuilds slot descriptor pointers from count/order after save blocks are read and before selector pointer selection, and later add/remove paths are general-table opcode 0x62/0x63 mutations. The draw helper's +0x28 frame selector, 0x0055abd8 surface-table lookup, source-rect math, and optional +0x30/+0x34 sprite offset path are identified, and the identified actor-controller range does not directly write +0x28, and no original sub-tile interpolation writer has been identified in the actor tile/projection path; current selector 2:0 active order and scripted behavior for non-player descriptors remain unresolved. The browser now keeps the same 8-command timer=1 selector cadence for party actor source rects while a held move chains from one original-frame tile step into the next. The browser uses originalLayer1Flags as the default collision mode for that grid, lets party actors overlap/pass through each other by default, and keeps actorCollision=1 only as a review switch for the old overlap-blocking behavior; tileClassLayer0 remains available as a local-review fallback. Full non-player descriptor behavior still requires more reconstruction.

## Projection Formula

`drawX = ((object[+0xe8] - word[0x004576dc]) << 4) + 0x18; drawY = ((object[+0xea] - word[0x004576de]) << 4) + 0x08; both are then shifted left by 16 before storing`

## Verification

| check | value |
| --- | --- |
| updateFunctionMatchesExpected | True |
| perFrameCallsMatchExpected | True |
| drawPrepCallMatchesExpected | True |
| actionPackerCallMatchesExpected | True |
| inputMaskStoresMatchExpected | True |
| cameraGlobalsMatchExpected | True |
| tileProjectionShiftMatchesExpected | True |
| tileProjectionBiasMatchesExpected | True |
| fixedPointShiftMatchesExpected | True |
| webTileSizeMatchesRuntimeProjection | True |
| webOriginalFrameMsMatchesRuntimeTiming | True |
| webDefaultTileStepMatchesOriginalFrame | True |
| webWalkFrameConstantsMatchSpriteSheet | True |
| webFrameQuantizedInterpolationPresent | True |
| webSourceRectSelectorMappingPresent | True |
| webSourceRectSelectorCadencePresent | True |
| webHeldInputTileChainingPresent | True |
| webWallSlideFallbackPresent | True |
| webDefaultCollisionModeIsOriginalLayer1Flags | True |
| webOriginalLayer1FlagCollisionModePresent | False |
| webActorOverlapCollisionModePresent | True |
| webActorOverlapCollisionDefaultDisabled | True |
| webPartyTrailHistoryPresent | True |
| webPartyTrailRingMatchesOriginalSlotCount | True |
| webPartyTrailCursorModelPresent | True |
| webPartyTrailInitialCursorsMatchOriginal | True |
| webPartyTrailRecordsAcceptedMoveTarget | True |
| webPartyTrailCallTimingMatchesOriginalTileMutation | True |
| actorControllerMatchesExpected | True |
| drawProjectionDetailsMatchExpected | True |
| spriteFrameSelectorDetailsMatchExpected | True |
| descriptorActivationOrderPathMatchesExpected | True |
| inputDrivenTileMutationGrounded | True |
| originalPartyTrailRingGrounded | True |
| originalCollisionFlagTestsGrounded | True |
| collisionFlagGridMappedToCnsLayer1 | True |

## Actor Motion

| area | value |
| --- | --- |
| actor controller | `0x0043022d` |
| current input mask refs | 24 |
| party trail history | 7-slot tile/direction trail ring for companion actor following at `0x00574540`/`0x00574550`/`0x00574552`/`0x00574554` |
| party actor screen projection | party actor draw coordinates are recomputed from tile/trail entries; +0x68 stores direction/animation state; party frame scripts provide the selector phase outside the actor controller |
| collision helper call | `0x0043047b -> 0x004319f8` |
| collision helper | `0x004319f8` |
| collision flag table reads | 4 |
| object overlap response | actor-overlap collision response after tile-flag checks |
| script tile step handler | `0x00409658` |
| sprite frame selector | +0x28 is the draw frame selector consumed by the bounds/source-rect helper; its low 16 bits select the frame and bits 16..23 select the 0x0055abd8 surface table. The helper also applies per-object +0x30/+0x34 sprite offsets when the object flags request it. The object script/timer path drives +0x28 through opcode 0x21 frame-script commands, while the actor controller writes +0x68 direction/animation state but does not write +0x28 in the identified controller range. Party actor descriptors ground the +0x68-to-+0x64 frame-script table and opcode 0x21 +0x28 selector sequences for Ataho/Rinshan/Smashu; the remaining descriptor rows are classified as battle/effect/object resources without +0x68 state-table selectors. |
| sprite frame selector writer | object script commands write +0x28 either as a full selector, low frame index, or high surface-table index at `0x004044bd` |
| sprite frame timer | object script/timer update decrements +0x62 and, when it reaches zero, runs the +0x64 frame script through the generic 0x00440538 handler table; opcode 0x21 writes +0x28 and reloads/advances the frame script at `0x00432ff0` via `0x00402321` opcode 0x21 handler `0x004044bd` |
| party actor frame script table | party actor descriptors initialize +0x64 with opcode 0x20, then use opcode 0x18 command bytes 18 a0 64 68 to select +0x64 = stateTable[object+0x68]; states 1..8 decode to opcode 0x21 +0x28 selector sequences that loop through opcode 0x03; the full 12-row descriptor table is classified so rows 3..11 stay separate as battle/effect/object resource descriptors at `0x00442d95` |
| object descriptor table | the 12-row descriptor table has +0x68 animation state-table selectors only in rows 0..2 for the party walking descriptors; rows 3..11 are battle/effect/object resource descriptors with no +0x68 state-table selector and no field-map CNS refs at `0x00442d95` |
| descriptor activation/order | save load calls 0x00432323 after count/order and slot blocks are read and before selector pointer selection; later add/remove active descriptor mutations are general-table opcode 0x62/0x63 paths, while the current selector 2:0 active order remains unproven because public samples do not cover it from `/home/exedev/hwanse/out/save_selector_opcode20_runtime_materializers.json` |
| map collision loader | `0x0042449c` |
| CNS layer0 destination | `0x00595af0` |
| CNS layer1 collision destination | `0x0058d7d0` |

## Party Actor Frame Scripts

| character | descriptor | surface | state table | idle state 1 selector | moving state 5 loop |
| --- | --- | --- | --- | --- | --- |
| cara_at1.cns | `0x004f867c` | `0x11` | `0x004f8e68` | `0x00110000` | `0x00110004`, `0x00110005`, `0x00110004`, `0x00110000`, `0x00110006`, `0x00110007`, `0x00110006`, `0x00110000` |
| cara_rs1.cns | `0x004e75a0` | `0x14` | `0x004e78ec` | `0x00140000` | `0x00140004`, `0x00140005`, `0x00140004`, `0x00140000`, `0x00140006`, `0x00140007`, `0x00140006`, `0x00140000` |
| cara_sm1.cns | `0x00546004` | `0x17` | `0x0054638c` | `0x00170000` | `0x00170004`, `0x00170005`, `0x00170004`, `0x00170000`, `0x00170006`, `0x00170007`, `0x00170006`, `0x00170000` |

## Object Descriptor Table

| index | descriptor | class | linked CNS | +0x68 state table | valid frame initializers |
| ---: | --- | --- | --- | --- | ---: |
| 0 | `0x004f867c` | party-walking-state-table | `cara_at1.cns` | `0x004f8e68` | 1 |
| 1 | `0x004e75a0` | party-walking-state-table | `cara_rs1.cns` | `0x004e78ec` | 1 |
| 2 | `0x00546004` | party-walking-state-table | `cara_sm1.cns` | `0x0054638c` | 1 |
| 3 | `0x004b218c` | battle/effect-resource | `btl_etc.cns`, `btl_efc.cns` | - | 0 |
| 4 | `0x004d5868` | battle/effect-resource | `btl_at.cns` | - | 1 |
| 5 | `0x004da88c` | battle/effect-resource | `btl_rs.cns` | - | 1 |
| 6 | `0x00524060` | battle/effect-resource | `btl_sm.cns` | - | 1 |
| 7 | `0x004d21c0` | object-resource | `zsa_saru.cns` | - | 1 |
| 8 | `0x0048c094` | object-resource | `zsl_yoi.cns` | - | 1 |
| 9 | `0x0055734c` | object-resource | `zsa_mi.cns` | - | 1 |
| 10 | `0x0052dad8` | object-resource | `zsa_iwa.cns` | - | 1 |
| 11 | `0x004aab00` | object-resource | `zsa_kika.cns` | - | 1 |

## Directional Collision Cases

| direction | input latch | blocked fallback | flag bit | flag grid | scan shape | boundary |
| --- | --- | --- | --- | --- | --- | --- |
| down | `0x05` | `0x01` | `0x02` | `0x0058d7d0` | horizontal footprint width at bottom edge row | mapHeight - footprintHeight == tileY |
| up | `0x06` | `0x02` | `0x01` | `0x0058d7d0` | horizontal footprint width at top/block row | tileY == 0 |
| left | `0x07` | `0x03` | `0x04` | `0x0058d7d0` | left footprint edge sample at bottom row | tileX == 0 |
| right | `0x08` | `0x04` | `0x08` | `0x0058d7ce` | right footprint edge sample through edge-shifted collision table | mapWidth - footprintWidth == tileX |
