Document ADV system menu input path

This commit is contained in:
gamer147
2026-07-21 11:01:50 -04:00
parent c6b4985984
commit 1f8b36937b
7 changed files with 77 additions and 17 deletions

View File

@@ -21,6 +21,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
| `0x4d7c` | `shared_profile_state` | `void*` | embedded shared SAVE.DAT state object; owns profile integer/settings tables and container timing metadata |
| `0x5190` | `shared_profile_int_table` | `int` | open-addressing 12-byte string-key to 32-bit value table; op 0x1a2 stores, 0x1a3 loads, shared SAVE.DAT serializes it |
| `0x14d54` | `gfx_obj_ptr_table` | `void*` | per-object pointer table (ops 0x212/0x213 write obj+0x64/0x68/0x6c) |
| `0x14e08` | `gfx_default_object_slot` | `int` | op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero |
| `0x14ea0` | `text_line_spacing` | `int` | extra pixel leading between text lines; defaults to 6, op 0x8b writes it |
| `0x14f45` | `script_frame_index` | `int` | call-script frame index (0x1e-dword frames) |
| `0x46188` | `renderer_device_state` | `int` | embedded D3D9 device/render state; op 0x20e passes this base to the target/depth clear worker |

View File

@@ -2311,6 +2311,40 @@ enabled. That transient channel is kept separate from persistent op-`0x88` Skip,
cannot clear the user's toggle; both channels share the existing text completion, wait advance, voice
deferral, and skip cadence. This needs no profile storage, boot seed, or game-specific conditional.
### ADV right-click/X system-menu path (2026-07-21)
Right-click during ADV is not a frontend-owned menu shortcut. It is the same script-owned keyed-hotspot
mechanism already partially modeled for SC0000. SYSTEM4 maps physical right mouse and keyboard X to logical
action 7. Each of the 136 SC-family scripts registers a 1x1 dummy rectangle whose activation callback is
the local branch that cancels the ADV hotspot wait, calls raw script id `0x1f` (`MENU.BIN`), then rebuilds
the parent ADV controls and redraws the retained page. Opcode `0x97` binds action 7 to that rectangle.
Native `adv_input_service_poll@0x411230` polls the configured logical-action mask first, then calls
`input_hotspot_poll_bound_action_callback@0x403fb0`. The helper scans armed records in registration order;
for each nonnegative op-`0x97` action index it tests `mask & (1 << action)` and returns the record's ordinary
activation callback PC. This is the missing port seam. `HotspotRegistry.BindKey` already retains the action
on the matching record, but no code consumes `Entry.InputBit`; Godot currently routes only pointer-left
activation and action-4/5 page advance. Right mouse therefore reaches `InputBindings` as action 7 but never
queues the MENU callback.
The first implementation slice is narrow and engine-generic: expose bound-action activation on the armed
hotspot registry, route pressed keyboard/mouse/joystick logical-action masks through it before ordinary page
advance, wake the existing callback service, and reuse the same consume/rearm behavior as pointer activation.
No MENU-specific branch belongs in Godot or the VM.
The reached script path is promising but should be validated incrementally. `MENU.BIN` is 48/49 opcodes
handled and its only static gap is op `0x80`; `INFO.BIN`, which selects character/enemy/voice/affinity/item
information pages, is 30/30 handled. The large `CHMENU` and five `INFO*` detail screens are roughly
91.595% handled and may expose secondary visual/data gaps after the shell opens. SAVE and CONFIG are only
about 80% handled and remain separate storage/audio-settings work, not prerequisites for opening or closing
the system menu.
The sole `MENU.BIN` gap is now decoded: `op_0x80_set_default_gfx_object_slot@0x41ed40` stores operand 1 at
EngineCtx `+0x14e08`; `op_0x1d9_handler@0x420a30` substitutes that selected slot only when its explicit
object-slot operand is zero. MENU-family scripts select slots 7/8/9 on entry and restore slot 1 during
teardown. This selector should join the input bridge implementation for state correctness, though the
surveyed MENU/INFO scripts do not themselves call op `0x1d9`, so it is not the cause of the missing launch.
---
## Native walls backlog (targets for this loop)

View File

@@ -395,6 +395,11 @@ SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit
The handler uses an alpha step of 1 and timer interval=argument when argument <=64. Above 64 it uses step=16 and interval=argument/16. The main loop polls that timer, advances by skipped intervals plus the current interval, and does not resume the script until the target endpoint has been presented. ROOM uses (1,2,10) for button/character entry and exit fades and (1,2,30) for the final fade before returning to TITLE. The port captures retained-frame snapshots whenever op 0x20c presents to a selected offscreen render target, then reproduces this blocking alpha lifecycle in the interactive host.
### 0x80 `set-default-gfx-object-slot` (u0041AF00, argc 1)
- **summary:** (slot) - select the retained graphics-object slot used when op 0x1d9 receives explicit slot zero.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x80_set_default_gfx_object_slot@0x41ed40 writes operand 1 to EngineCtx+0x14e08. op_0x1d9_handler@0x420a30 reads that field only when its operand-1 object slot is zero, otherwise it indexes the explicit slot; both paths write object+0x70. MENU-family scripts select working slots 7/8/9 on entry and restore slot 1 during teardown.
### 0x131 `get-message-window-alpha` (get-message-window-alpha, argc 1)
- **summary:** (out) - read the configured `message:MesWinAlpha` value used to alpha-modulate the ADV chrome.
- **grounding:** source=investigation, confidence=high
@@ -700,10 +705,10 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **evidence:** Ghidra /v2: op_0x94_arm_hotspot_wait@0x4166d0 sets ctx+0xc6e4=1 and calls input_hotspot_arm_cursor_tracking@0x404230 with 10000. SC0000 executes it immediately after the five control-strip and three keyed registrations.
### 0x97 `bind-hotspot-key` (u0041C150, argc 5)
- **summary:** (x)(y)(w)(h)(input_bit) - find the already-registered rectangle with identical bounds and bind an input/key bit number to it.
- **summary:** (x)(y)(w)(h)(logical_action) - find the already-registered rectangle with identical bounds and bind a logical input action to its activation callback.
- **grounding:** source=investigation, confidence=high, noop_headless=True
- **depends on:** 0x90
- **evidence:** Ghidra /v2: op_0x97_bind_hotspot_key@0x41ff30 builds the same inclusive rect as op 0x90 and calls input_hotspot_bind_key_bit@0x403f50. That worker searches registered rects for exact equality and stores operand 5 in the record's key-bit array. SC0000 binds bits 0, 8, and 7 to its three 1x1 keyed records.
- **evidence:** Ghidra /v2: op_0x97_bind_hotspot_key@0x41ff30 builds the same inclusive rect as op 0x90 and calls input_hotspot_bind_key_bit@0x403f50. That worker searches registered rects for exact equality and stores operand 5 in the record's logical-action field. adv_input_service_poll@0x411230 passes the configured live action mask to input_hotspot_poll_bound_action_callback@0x403fb0, which returns the first matching record's activation PC. All 136 SC-family scripts bind action 7 to the callback that calls MENU.BIN; SYSTEM4 maps both right mouse and X to action 7.
### 0xcc `register-mouse-callback` (mouse_callback, argc 2)
- **summary:** (poll_interval_ms)(target_pc) - register a timed per-frame mouse callback in the current script.
@@ -969,10 +974,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x80 `u0041AF00` (u0041AF00, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x8c `jmp` (jmp, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=med

View File

@@ -2206,3 +2206,21 @@ persistent-toggle survival across a physical press/release.
**Next:** manually hold LeftCtrl through text reveal and multiple waits in SC0000, then confirm release
returns to normal cadence and that the on-screen Skip toggle remains active if enabled during the test.
### Slice B1 ADV right-click system-menu path investigated (2026-07-21)
The native and corpus paths agree end to end. SYSTEM4 maps right mouse and X to logical action 7; every one
of the 136 SC-family scripts binds action 7 through op `0x97` to a dummy hotspot whose activation branch
calls `MENU.BIN`. Native `adv_input_service_poll` resolves these bindings against the live logical-action
mask before ordinary page advance. The port already stores the binding in `HotspotRegistry.Entry.InputBit`,
but nothing reads it, so action 7 stops after registration even though the physical input layer is correct.
`MENU.BIN` is 48/49 handled, and its INFO selector is 30/30. The only MENU gap, op `0x80`, is a default
retained graphics-object-slot selector consumed by op `0x1d9`; it is straightforward state but not the
launch blocker. Larger character/info detail screens are about 91.595% handled and should be treated as
follow-up discrepancies reached through manual use. SAVE/CONFIG remain separate, storage-sensitive work.
**Next:** implement one generic bound-action hotspot activation API, route pressed keyboard/mouse/controller
action masks through it before page advance, and implement op `0x80`'s engine-owned selector. Add a focused
synthetic bound-action test plus an SC0000 action-7 regression that proves the real `MENU.BIN` frame is
entered and the parent ADV wait is restored after return.

View File

@@ -9,6 +9,7 @@ INFERRED: dict[int, dict] = {
0x6c: dict(name='zero-int-range', category='compute', noop=False, confidence='high', source='investigation', summary='(destination)(count) — write logical integer zero to count consecutive VM cells beginning at the resolved destination.'),
0x7b: dict(name='coroutine-save-yield-handlers', category='control', noop=False, confidence='high', source='investigation', summary='(handler1_pc)(handler2_pc) — scene-coroutine: save the two per-frame yield/resume handler PCs. Native writes op1→ctx[0x6da88+idx*4], op2→ctx[0x6db28+idx*4] (idx=ctx[0x53d14] script-context index); its generic handler prologue records the 5-dword instruction length. SC0000 0x79: `0x7b label_3c9 label_41e` registers the ADV per-frame render→poll→yield handlers. Part of the scene-coroutine framework (see engine-re.md §Scene-coroutine framework); pairs with 0x7c (resume) + 0x140 (loop iterator).'),
0x7c: dict(name='coroutine-resume', category='control', noop=False, confidence='high', source='investigation', summary='() — scene-coroutine RESUME point. Native requires run-state bit 0x2000000 (ctx[0x6dbc8]) set — THROWS (__CxxThrowException) if unset, so it is only ever reached on a scheduler-driven re-entry, NEVER on a cold first pass (cold flow jmps over it). Restores PC=ctx[0x53d28]+ctx[0x6dbcc]*4, clears the run-bit (ctx+0xa0ce4 &= ~0x2000000), resets input/line state. SC0000 0x443 (falls into the main loop label_444). See engine-re.md §Scene-coroutine framework.'),
0x80: dict(name='set-default-gfx-object-slot', category='draw', noop=False, confidence='high', source='investigation', summary='(slot) - select the retained graphics-object slot used when op 0x1d9 receives explicit slot zero.'),
0x86: dict(name='set-cursor-resource', category='input', noop=False, confidence='high', source='investigation', summary='(resource_id) - load an indexed cursor asset and install it as the active custom cursor.'),
0x87: dict(name='clear-cursor-resource', category='input', noop=False, confidence='high', source='investigation', summary='Clear the active custom cursor and refresh the OS cursor when the game window is active.'),
0x88: dict(name='set-message-skip', category='input', noop=False, confidence='high', source='investigation', summary='(enabled) - set persistent all-message Skip state. Nonzero makes the interpreter inject ADV fast-forward input every tick; zero stops injection and clears the transient skip run-state bit.'),
@@ -16,7 +17,7 @@ INFERRED: dict[int, dict] = {
0x90: dict(name='register-hotspot-callbacks', category='input', noop=True, confidence='high', source='investigation', summary="(x)(y)(w)(h)(on_enter_pc)(on_leave_pc)(on_activate_pc) - append an inclusive cursor rectangle and three callback PCs to the current script frame's native input registry. This opcode only registers; the ADV input service dispatches callbacks later."),
0x93: dict(name='cancel-hotspot-wait', category='input', noop=True, confidence='high', source='investigation', summary="Reset the current frame's hotspot registry/input wait and clear native run-state bit 0x00800000. Used before opening History, Menu, or HIDEWIN flows."),
0x94: dict(name='arm-hotspot-wait', category='input', noop=True, confidence='high', source='investigation', summary='Arm native hotspot input processing after the script has registered its rectangles.'),
0x97: dict(name='bind-hotspot-key', category='input', noop=True, confidence='high', source='investigation', summary='(x)(y)(w)(h)(input_bit) - find the already-registered rectangle with identical bounds and bind an input/key bit number to it.'),
0x97: dict(name='bind-hotspot-key', category='input', noop=True, confidence='high', source='investigation', summary='(x)(y)(w)(h)(logical_action) - find the already-registered rectangle with identical bounds and bind a logical input action to its activation callback.'),
0xae: dict(name='continue-save-load-stack-restore', category='control', noop=False, confidence='high', source='investigation', summary='() - during serialized save restoration, replace the current frame PC with its saved resume/call target and advance through the saved script-context stack; otherwise a no-op.'),
0xb4: dict(name='sfx-load', category='audio', noop=False, confidence='high', source='investigation', summary="(packed_raw_resource_id)(channel) — synchronously open the universal SYS4INI/AAI catalog entry and replace the channel's decoded sound buffer without starting playback. A zero high byte is a raw SYS4INI index; a nonzero high byte selects an append catalog and uses the low 24-bit index. Native manager supports channels 0..12; SC0000 uses 0..9."),
0xb5: dict(name='sfx-start', category='audio', noop=False, confidence='high', source='investigation', summary='(channel) — start the already-loaded channel once (logical loop=false). DirectSound publishes synchronously through Play(0,0,DSBPLAY_LOOPING); the low-level flag loops only the streaming ring, while decoder EOF stops logical playback.'),

View File

@@ -73,6 +73,11 @@ name = "gfx_obj_ptr_table"
type = "void*"
note = "per-object pointer table (ops 0x212/0x213 write obj+0x64/0x68/0x6c)"
[[field]]
offset = 0x14e08
name = "gfx_default_object_slot"
type = "int"
note = "op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero"
[[field]]
offset = 0x14ea0
name = "text_line_spacing"
type = "int"

View File

@@ -1312,18 +1312,18 @@ argc = 1
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u0041AF00"
category = "unknown"
summary = ""
name = "set-default-gfx-object-slot"
category = "draw"
summary = "(slot) - select the retained graphics-object slot used when op 0x1d9 receives explicit slot zero."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra /v2: op_0x80_set_default_gfx_object_slot@0x41ed40 writes operand 1 to EngineCtx+0x14e08. op_0x1d9_handler@0x420a30 reads that field only when its operand-1 object slot is zero, otherwise it indexes the explicit slot; both paths write object+0x70. MENU-family scripts select working slots 7/8/9 on entry and restore slot 1 during teardown."
[[opcode.semantics.args]]
i = 1
role = ""
role = "default retained graphics-object slot"
observed_types = ["imm"]
[[opcode]]
@@ -1577,12 +1577,12 @@ abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "bind-hotspot-key"
category = "input"
summary = "(x)(y)(w)(h)(input_bit) - find the already-registered rectangle with identical bounds and bind an input/key bit number to it."
summary = "(x)(y)(w)(h)(logical_action) - find the already-registered rectangle with identical bounds and bind a logical input action to its activation callback."
noop_headless = true
source = "investigation"
confidence = "high"
depends_on = [0x90]
evidence = "Ghidra /v2: op_0x97_bind_hotspot_key@0x41ff30 builds the same inclusive rect as op 0x90 and calls input_hotspot_bind_key_bit@0x403f50. That worker searches registered rects for exact equality and stores operand 5 in the record's key-bit array. SC0000 binds bits 0, 8, and 7 to its three 1x1 keyed records."
evidence = "Ghidra /v2: op_0x97_bind_hotspot_key@0x41ff30 builds the same inclusive rect as op 0x90 and calls input_hotspot_bind_key_bit@0x403f50. That worker searches registered rects for exact equality and stores operand 5 in the record's logical-action field. adv_input_service_poll@0x411230 passes the configured live action mask to input_hotspot_poll_bound_action_callback@0x403fb0, which returns the first matching record's activation PC. All 136 SC-family scripts bind action 7 to the callback that calls MENU.BIN; SYSTEM4 maps both right mouse and X to action 7."
[[opcode.semantics.args]]
i = 1
@@ -1606,7 +1606,7 @@ observed_types = ["imm"]
[[opcode.semantics.args]]
i = 5
role = "input/key bit index"
role = "logical action index"
observed_types = ["imm"]
[[opcode]]