Implement ADV text publication service

This commit is contained in:
gamer147
2026-07-20 10:18:10 -04:00
parent e0aae05f32
commit 0fb608c35e
14 changed files with 204 additions and 25 deletions

View File

@@ -39,6 +39,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
| `0x552e8` | `ret_stack_b` | `void*` | per-frame return stack (companion) |
| `0x5f304` | `sleep_timer` | `int` | sleep timer object (op 0xc8; +8 active, +0x14 start-ms, +0x18 duration) |
| `0x5f3ac` | `timed_callback_timer` | `int` | elapsed timer object armed by op 0xd5 and sampled by the timed callback scheduler |
| `0x5f64c` | `adv_wait_indicator_timer` | `int` | elapsed timer started by op 0x1ce and ADV wait setup to select the animated input-wait indicator frame |
| `0x5f690` | `timed_callback_abort_pc` | `int` | optional local fallback PC supplied to op 0xd5; -1 disables abort redirection |
| `0x5f694` | `timed_callback_entries_vector` | `int` | MSVC vector object base for 16-byte schedule entries; begin/end/capacity pointers follow |
| `0x5f698` | `timed_callback_entries_begin` | `void*` | begin pointer for 16-byte entries {deadline_ms, field_100, primary_pc, catchup_pc} |
@@ -48,8 +49,12 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
| `0x5f6a8` | `timed_callback_cursor` | `int` | next schedule entry consumed by timed_callback_sequence_tick; op 0xd3 resets to zero |
| `0x5f6b4` | `timed_callback_script_resource_id` | `uint` | active frame script resource retained by op 0xd5 for local callback dispatch safety |
| `0x5f6b8` | `timed_callback_script_resource_guard` | `uint` | companion current-script identity checked before redirecting to a scheduled callback |
| `0x5f72c` | `adv_wait_indicator_frame` | `int` | current animated input-wait indicator frame, reset to zero when the service is armed |
| `0x5f730` | `adv_wait_indicator_terminal_frame` | `int` | terminal frame returned by the indicator layout during ADV wait setup |
| `0x5f734` | `adv_wait_indicator_enabled_value` | `int` | raw enable operand last written by op 0x1ce; nonzero arms the ADV wait-indicator service |
| `0x6da78` | `message_skip_activation_guard` | `int` | set when op 0x88 enables Skip; suppresses the enabling click from immediately becoming a hotspot activation |
| `0x6da80` | `message_skip_cancel_click_phase` | `int` | press/release phase used only when set:CancelMesSkipOnClick is nonzero |
| `0x6da84` | `adv_wait_indicator_layout_slot` | `int` | ADV text layout whose wait indicator is armed or erased by input-wait setup and op 0x1ce |
| `0x6da88` | `coroutine_yield_a` | `void*` | op 0x7b yield-state save (op1 -> +ctxidx*4) |
| `0x6db28` | `coroutine_yield_b` | `void*` | op 0x7b yield-state save (op2 -> +ctxidx*4) |
| `0x6dbc8` | `coroutine_runstate` | `int` | op 0x7c resume gate (run-state bit 0x2000000) |

View File

@@ -1725,6 +1725,46 @@ saved.
---
### ADV text publication and wait-indicator service -- opcodes `0x1ce` / `0x20a` (2026-07-20)
The earlier working label "sprite-animation service" was too broad. These operations belong specifically
to ADV text publication and the animated input-wait indicator (the bouncing marker shown after a page
finishes typing). They do not expose a generic retained-object animation channel.
`op_0x1ce_set_adv_wait_indicator_enabled@0x41f8c0` stores its operand at
`EngineCtx.adv_wait_indicator_enabled_value` (`ctx+0x5f734`). A nonzero value sets run-state bit
`0x40000000`, resets `adv_wait_indicator_frame` (`ctx+0x5f72c`) to zero, and starts
`adv_wait_indicator_timer` (`ctx+0x5f64c`). Zero erases the indicator belonging to
`adv_wait_indicator_layout_slot` (`ctx+0x6da84`) with the worker's frame `-2` mode and clears the run bit.
Every Himegari corpus use is `0x1ce(0)`, normally at entry to a modal script; `HISTORY.BIN@0x3` is the
concrete route relevant here.
`op_0x20a_publish_adv_text_layout@0x422ce0` first calls
`adv_text_publish_layout@0x450c80`. Slot zero selects the text manager's current layout; a nonzero operand
selects that indexed layout. The worker erases the layout's old retained-object range, walks its 20-byte
text records, and rebinds their draw objects. When run-state bit `0x40000000` is active, the opcode also
calls `adv_text_publish_wait_indicator_frame@0x453120` with the current indicator frame. The latter worker
uses frame `-1` to return/capture the terminal frame, `-2` to erase the indicator object, and a
nonnegative frame to choose and bind the corresponding atlas cell. All corpus uses are `0x20a(1)`;
`HISTORY.BIN@0x13ab` reaches it through the shared ADV redraw callback.
Opcode `0x72` owns the normal implicit start of the same service: it records the waiting layout, captures
the indicator's terminal frame, resets the active frame to zero, starts the timer, and raises run-state bit
`0x40000000`. Completing the input wait erases the object and clears the bit. In the original interpreter,
return from a nested modal script flows through that shared redraw/wait path and re-arms the parent marker.
The port's host blocks inside the enclosing wait while servicing the nested HISTORY callback, so it restores
the previously enabled parent marker at the callback-return boundary. This is lifecycle equivalence, not a
script-specific seed or global-state workaround.
The port exposes the two explicit operations as host services: `0x1ce` controls marker eligibility and
`0x20a` requests retained ADV publication. Godot's existing indicator clock/configuration remains the
animation implementation. The real SC0000-to-HISTORY regression proves HISTORY disables the marker on
entry, executes layout-slot-1 publication on return, clears transient History rows, and leaves the enclosing
ADV page wait in place. The expanded 63-field `EngineCtx` and semantic function annotations are applied to
the saved `/v2` image.
---
## Native walls backlog (targets for this loop)
- ~~**call-script dispatch**~~ — **SOLVED** (above): `call-script <id>` = raw SYS4INI file index.

View File

@@ -80,6 +80,11 @@
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x1bb_handler@0x41f650 writes 0x80000000 to ctx+0x55110 for operand 0 and zero for operand 1, rejecting other values. Text/layout/metadata/voice paths test or propagate that high bit before appending retained history. HISTORY.BIN disables recording at entry and reenables it at exit so the backlog UI does not record itself.
### 0x1ce `set-adv-wait-indicator-enabled` (u0041B9F0, argc 1)
- **summary:** (enabled) - explicitly start or stop the animated ADV input-wait indicator service.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra op_0x1ce_set_adv_wait_indicator_enabled@0x41f8c0 writes enabled to ctx+0x5f734. A nonzero value sets run bit 0x40000000, resets ctx+0x5f72c to frame zero, and starts the ctx+0x5f64c elapsed timer. Zero erases the indicator through adv_text_publish_wait_indicator_frame@0x453120 using ctx+0x6da84 and frame -2 when necessary, then clears the run bit. The corpus uses 0x1ce(0) on modal/menu entry, including HISTORY.BIN@0x3.
### 0x1d0 `step-text-history` (step-text-history, argc 3)
- **summary:** (out_layout_slot)(out_record_index)(delta) - resolve a cumulative delta from the latest retained ADV boundary and return its layout slot and first record index, or -1 outputs at a boundary.
- **grounding:** source=investigation, confidence=high
@@ -110,6 +115,11 @@
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x204_handler@0x422a60 resolves operand 4 as a string, fetches surface/x/y, then calls draw_string_to_surface@0x450150 on text manager ctx+0x14940. The worker validates and locks gfx-manager surface table +0xa590[slot], chooses text_raster_string_uncached@0x459d90 or cached/effect path @0x45b600, rasterizes GDI GetGlyphOutlineA bitmaps through text_blit_glyph_bitmap@0x458c80 using font/color state +0x4d0/+0x458, then unlocks. SC0000 0x9b2 draws the speaker name into 400x30 surface 0xd at (1,1); following 0x1fb binds it to retained object 0xe678 at (74,444).
### 0x20a `publish-adv-text-layout` (u00420CE0, argc 1)
- **summary:** (layout_slot) - republish one ADV text layout and, while active, its current wait-indicator frame.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra op_0x20a_publish_adv_text_layout@0x422ce0 passes the requested slot to adv_text_publish_layout@0x450c80, which rebinds the layout's retained text records. If ADV run-state bit 0x40000000 is active, it also calls adv_text_publish_wait_indicator_frame@0x453120 with ctx+0x5f72c. Slot zero resolves the text manager's current layout; the corpus always uses slot one, including HISTORY.BIN@0x13ab in its shared ADV redraw callback.
### 0x2bd `set-font-bold` (set-font-bold, argc 1)
- **summary:** (enabled) - set the current primary text font weight to 700 when enabled or 0 when disabled, then rebuild the native font state.
- **grounding:** source=investigation, confidence=high
@@ -1068,10 +1078,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
### 0x1ce `u0041B9F0` (u0041B9F0, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x1cf `u0041DA10` (u0041DA10, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
@@ -1092,10 +1098,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
### 0x20a `u00420CE0` (u00420CE0, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x20d `u00420E10` (u00420E10, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low

View File

@@ -1926,3 +1926,29 @@ slice. Keep `0x1cb` deferred until the global save/profile storage boundary is c
Validation: all 205 engine tests pass, including four focused History presentation cases; opcode and
EngineCtx tests/lints are clean, vm0 RECOVER passes, the Godot build has zero warnings, and threaded
`SELFTEST OK`.
### ADV History text publication / wait-indicator service implemented (2026-07-20)
Native RE corrects the provisional "sprite-animation service" description: `0x1ce` and `0x20a` are the
explicit control points for ADV text publication and its animated input-wait indicator. `0x1ce(enabled)`
starts or stops the indicator's run-state/timer/frame service; Himegari only calls it with zero when entering
modal scripts, including HISTORY. `0x20a(layout_slot)` republishes the selected retained text layout and,
when the service is active, binds its current indicator frame; HISTORY's shared redraw callback uses slot 1.
Normal ADV wait opcode `0x72` implicitly arms the same service.
The VM now forwards both operations through generic host methods. Godot gates its existing animated marker
on the explicit enabled state and requests recomposition for layout publication. Because the port keeps the
parent wait blocked while a nested raw-input callback executes, it restores a previously enabled parent
marker when that callback returns, matching the native path that re-enters the shared redraw/wait routine.
No script offsets, boot seeds, or persistence assumptions were introduced.
A focused synthetic regression verifies the two service calls, and the real SC0000-to-HISTORY regression
requires marker disable plus layout-slot-1 publication while preserving the same enclosing ADV wait and
cleaning the modal rows. The canonical opcode and EngineCtx sources are regenerated; the `/v2` handlers and
workers are named/commented, the expanded 63-field `EngineCtx` is applied, and the image is saved. HISTORY
is now 77/78 distinct opcodes and 853/854 instructions handled or safe-noop. The only remaining instruction
is the deliberately deferred `0x1cb` Read-message Skip setting getter, pending a global profile/save backend
decision.
Validation: all 206 engine tests pass, opcode and EngineCtx tests/lints are clean, vm0 RECOVER passes, the
Godot build has zero warnings, and threaded `SELFTEST OK`.