Implement ADV skip lifecycle

This commit is contained in:
gamer147
2026-07-20 16:41:44 -04:00
parent 6aede1e525
commit 7d438a7574
9 changed files with 176 additions and 34 deletions

View File

@@ -33,12 +33,14 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
| `0x51b80` | `gfx_animation_service_flags` | `uint` | retained_gfx_manager+0xb56c service flags; bit 1 suppresses op 0x243 force-complete/clock-reset request |
| `0x52bd4` | `surfaces` | `void*` | surface array base [~1000 slots]; create/set-texture (0x1f8/0x1f9) allocate |
| `0x53d14` | `cur_ctx_index` | `uint` | current script-context index (curCtx); indexes 0x78-byte coroutine/frame records |
| `0x53d24` | `save_load_stack_restore_active` | `int` | set while a deserialized numbered-save script stack is being reconstructed; op 0xae restores saved frame PCs/contexts and clears it at the terminal saved context |
| `0x53d28` | `frame_codebase` | `void*` | current frame codebase (PC = codebase + off*4) |
| `0x53d2c` | `frame_pc` | `int` | current frame PC column (op = *(0x53d2c + curCtx*0x78)) |
| `0x53d60` | `ctx_record_base` | `void*` | 0x78-byte context-record array base (coroutine/script contexts) |
| `0x53d64` | `frame_script_resource_id` | `uint` | raw packed SYS4/AAI resource id for this 0x78-byte script frame; persisted ReadTextDB script key |
| `0x53d88` | `frame_instruction_word_count` | `int` | current decoded instruction length in dwords for each 0x78-byte script frame; interpreter advances PC by this value * 4 |
| `0x550fc` | `message_skip_display_enabled` | `int` | persistent all-message Skip state returned by op 0x19a for the ADV control-strip active overlay |
| `0x55100` | `adv_skip_service_enabled` | `int` | ADV presentation lifecycle gate: op 0x19b clears it while preserving the persistent Skip toggle; op 0x19c sets it before recomputing active message/read skip |
| `0x55104` | `auto_message_enabled` | `int` | ADV Auto mode flag; op 0x1b6 reads, op 0x1b7 writes, adv_input_service_poll consumes |
| `0x55110` | `text_history_recording_suppressed` | `uint` | high bit suppresses ADV retained-history index/text/metadata/voice recording; op 0x1bb writes 0x80000000 or zero, and HISTORY.BIN brackets itself with disable/enable |
| `0x55120` | `anti_tamper_a` | `int` | anti-tamper checksum operand |

View File

@@ -1240,6 +1240,23 @@ the missing native governor and remains fast without teleporting between blockin
op `0x88` state reaches the host before the following cadence yields. Validation is engine 168/168,
zero-warning Godot build, and threaded `SELFTEST OK`.
**ADV entry/exit lifecycle.** The script comments `savemesskip` and `loadmesskip` describe a temporary
suspension boundary, not a second saved preference. `op_0x19b_suspend_adv_skip_service@0x416560` clears
active `ctx+0x13dc`, run-state bit `0x08000000`, and `ctx+0x55100` (`adv_skip_service_enabled`) while
deliberately preserving `ctx+0x550fc`, the all-message Skip toggle returned by op `0x19a`.
`op_0x19c_resume_adv_skip_service@0x4165a0` sets the lifecycle gate again and reconstructs active
fast-forward from persistent all-message Skip or `adv_read_skip_state`; the separate startup guard at
`ctx+0x6f86c` can suppress that reactivation. SC0000 brackets ADV teardown/setup with this pair, as do
branch transitions and `CALLBACK_LOAD.BIN`.
The port therefore keeps persistent Skip separate from currently active fast-forward. Opcode `0x19b`
deactivates the host service without changing the control-strip toggle, and `0x19c` recomputes the host
service from the persistent toggle plus the host's live read-skip channel. This also makes op `0x1c7`
correctly report inactive during the suspended interval. Focused regressions cover preservation across the
pair and read-skip-only reactivation. Implementing the `message:ReadTextSkip` preference itself remains
deferred with ops `0x1ca`/`0x1cb` and the profile-owned `ReadTextDB`; the lifecycle implementation does not
invent a storage backend.
### Opcode `0x1ad` marks the numbered-save resume-frame boundary (2026-07-20)
Opcode `0x1ad` is a zero-operand persistence marker, not an input reset or modal-UI synchronization call.
@@ -1266,6 +1283,26 @@ real implementation belongs in the future unified save architecture, where the V
`ExecFrame` chain and remember which frame is the resume boundary. This is the same architectural deferral as
the already-deferred profile/read-state work, not a reason to invent a seed or offset-specific shortcut.
### Opcode `0xae` continues numbered-save stack restoration (2026-07-20)
Opcode `0xae` is the load-side rendezvous paired with serialized script-frame state. Its handler,
`op_0xae_continue_save_load_stack_restore@0x416790`, normally returns after recording its one-dword length.
It only becomes effectful while `ctx+0x53d24` (`save_load_stack_restore_active`) is set by
`save_data_deserialize_and_begin_restore@0x40fd10`. That deserializer restores the selected save layout,
loads `CALLBACK_LOAD.BIN` or the saved entry script, and resets the current context so ordinary opcode
`0xae` sites can rebuild the saved stack.
On an active restore, the handler reads `set:SaveVersion1`/`set:SaveVersion2`, selects the matching saved
frame layout, replaces the current frame PC with its saved resume or call target, and advances through the
serialized contexts. At the saved terminal context it clears the restore flag and reinstates the saved
context/return state. The corpus placement supports that control-flow role: 305 calls overwhelmingly follow
coroutine-resume or call boundaries, including SC0000's main-loop resume sequence.
The opcode remains an effectful port gap. It is a no-op during every currently reachable port execution,
but its actual branch cannot be implemented until numbered saves serialize and restore the active
`ExecFrame` chain. Counting an unconditional no-op as coverage would conceal that dependency, so it stays
grouped with `0x1ad` rather than receiving a placeholder VM case.
### ADV read-message Skip and shared `RT.DAT` history (2026-07-18)
Read-message Skip is backed by an engine-owned `ReadTextDB`, not a VM-global flag and not ordinary numbered

View File

@@ -251,6 +251,11 @@ This also names the whole call graph statically (build/callscript-names.json).
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0xa3_handler@0x420060 formats operand 1, queries value_dispatch_lookup@0x419290, writes the matched or operand-2 default PC into the current frame, and clears the command type. Corpus pairs it with 0xa1/0xa2 in 12 generic switch sequences.
### 0xae `continue-save-load-stack-restore` (u00415130, argc 0)
- **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.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0xae_continue_save_load_stack_restore@0x416790 first tests ctx+0x53d24 (set by save_data_deserialize_and_begin_restore@0x40fd10). When clear it returns. When set, it selects the serialized frame layout through set:SaveVersion1/2, restores the current PC from that layout's saved return/call target, advances through contexts with FUN_0040f2d0, and clears the restore flag on reaching the saved terminal context. Its 305 corpus sites overwhelmingly follow coroutine-resume/call boundaries, which provide the rendezvous points used while reconstructing the stack.
### 0xc8 `sleep` (sleep, argc 1)
- **summary:** Pause the current script for <duration> milliseconds while retained presentation continues.
- **grounding:** source=investigation, confidence=high
@@ -681,6 +686,16 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x19a_get_message_skip@0x4271b0 writes ctx+0x550fc to operand 1. The shared ADV redraw routine uses it to select SO001's active Message-skip icon at x=728.
### 0x19b `suspend-adv-skip-service` (u00414E80, argc 0)
- **summary:** () - suspend active ADV fast-forward while preserving the user's persistent all-message Skip toggle.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x19b_suspend_adv_skip_service@0x416560 clears run-state bit 0x08000000, ctx+0x13dc active message skip, ctx+0x55100 ADV skip-service enable, and the click-cancel phase, but deliberately leaves ctx+0x550fc persistent all-message Skip unchanged. Corpus comments call it `savemesskip`; ADV-end/branch and CALLBACK_LOAD paths use it before leaving ADV presentation.
### 0x19c `resume-adv-skip-service` (u00414EC0, argc 0)
- **summary:** () - enable the ADV skip service and recompute active fast-forward from persistent all-message Skip or the live read-skip channel.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x19c_resume_adv_skip_service@0x4165a0 sets ctx+0x55100, clears the click-cancel phase, and normally activates run-state bit 0x08000000 when ctx+0x550fc persistent all-message Skip or ctx+0x6dbd4 adv_read_skip_state is nonzero. Otherwise it clears ctx+0x13dc and the run-state bit. A separate startup fast-forward guard at ctx+0x6f86c suppresses reactivation while active. Corpus comments call it `loadmesskip`; ADV setup, branch, and CALLBACK_LOAD paths pair it with op 0x19b.
### 0x1b6 `get-auto-message` (u00414F60, argc 1)
- **summary:** (out) - return whether automatic message advance is enabled.
- **grounding:** source=investigation, confidence=high
@@ -895,10 +910,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=med
### 0xae `u00415130` (u00415130, argc 0)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0xb7 `u0041D0E0` (u0041D0E0, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
@@ -1031,14 +1042,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=med
### 0x19b `u00414E80` (u00414E80, argc 0)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x19c `u00414EC0` (u00414EC0, argc 0)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x19d `u0041C680` (u0041C680, argc 2)
- **summary:** —
- **grounding:** source=kelebek, confidence=low

View File

@@ -2104,3 +2104,31 @@ important than any one opcode's count; persistence-bound `0x1a2`, `0x1ad`, and `
Validation: all 211 engine tests pass; opcode and EngineCtx tests/lints, vm0 RECOVER, the zero-warning
Godot build, threaded self-test, and `git diff --check` are clean.
### Slice A2b ADV skip lifecycle implemented; adjacent persistence gaps classified (2026-07-20)
Native RE resolves script `savemesskip`/`loadmesskip` opcodes `0x19b`/`0x19c` as an ADV presentation
lifecycle pair. Suspend clears active fast-forward and the service gate while preserving the persistent
all-message Skip toggle. Resume re-enables the gate and recomputes active fast-forward from that toggle or
the live read-skip channel. The VM now models persistent and active skip state separately, and focused tests
prove both persistent-toggle preservation and read-skip-only reactivation.
The same investigation classified the two adjacent one-call gaps without papering them over. Opcode `0x1ca`
is the already-known `message:ReadTextSkip` settings write and remains deferred with `0x1cb`, `RT.DAT`, and
the profile-owned read-state decision. Opcode `0xae` is normally inert but becomes the serialized save-stack
restoration rendezvous: it restores frame PCs and walks saved contexts while a flag set by the save-data
deserializer is active. Because the port has no numbered-save `ExecFrame` backend, an unconditional no-op
would be a false implementation; it remains grouped with `0x1ad`.
The canonical opcode source and expanded 78-field EngineCtx source are regenerated. `/v2` names/comments
the three handlers and save deserializer, applies `adv_skip_service_enabled` and
`save_load_stack_restore_active`, and is saved. SC0000 is now **124/129 distinct opcodes handled (96.1%)**
and **16,041/16,257 instructions handled or safe-noop (98.7%)**. Its five remaining gaps total 216
instructions: `0x1a2`×206, `0x1ad`×6, `0x1cb`×2, `0x1ca`×1, and `0xae`×1.
**Next:** keep all five remaining gaps behind the unified profile/read-state/numbered-save ownership work.
Choose the next implementation slice from a different scene or runtime subsystem rather than claiming
normal-path no-ops for `0xae` or a transient-only `ReadTextSkip` setting.
Validation: 213 engine tests pass; opcode and EngineCtx generators/tests/lints, SC0000 coverage, vm0
RECOVER, the zero-warning Godot build, threaded self-test, and `git diff --check` are clean.