From 9a6bc38f4037037182b8c46662083f41526304b5 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 18 Jul 2026 23:16:50 -0400 Subject: [PATCH] Document ADV History retained-record model --- docs/engine-ctx-reference.md | 1 + docs/engine-re.md | 73 ++++-- docs/opcode-reference.md | 198 ++++++++-------- docs/phase-a-slice-plan.md | 30 ++- tools/age_opcodes_himegari.py | 3 - vm-map/engine-ctx.toml | 5 + vm-map/opcodes.toml | 414 +++++++++++++++++----------------- 7 files changed, 408 insertions(+), 316 deletions(-) diff --git a/docs/engine-ctx-reference.md b/docs/engine-ctx-reference.md index c29592b..7010bad 100644 --- a/docs/engine-ctx-reference.md +++ b/docs/engine-ctx-reference.md @@ -29,6 +29,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch- | `0x53d88` | `cmd_type_table` | `int` | per-object cmd-type column base (write *(0x53d88 + curCtx*0x78)) | | `0x550fc` | `message_skip_display_enabled` | `int` | persistent all-message Skip state returned by op 0x19a for the ADV control-strip active overlay | | `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 | | `0x55124` | `anti_tamper_b` | `int` | anti-tamper checksum operand | | `0x5512c` | `anti_tamper_fp` | `int` | anti-tamper (import fn ptr / result) | diff --git a/docs/engine-re.md b/docs/engine-re.md index 8a3cae8..8cd81b6 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -1220,7 +1220,7 @@ dword flag. `adv_refresh_read_skip_state@0x406cd0` and ops `0x6e/0x71/0x72` comb The write side records completion, rather than merely displaying text. Ordinary click/wheel advance and Auto expiry queue `{script_id, message_index, message_count}` through `read_text_db_queue_message@0x469340`; an op-`0x72` wait already being passed by Skip queues the same tuple -directly. Opcode `0x71` remains the structural T1 label/anchor, but it is not a pure runtime no-op: +directly. Opcode `0x71` resets a text layout at the structural sites targeted by T1; it is not a pure runtime no-op: `op_0x71_handler@0x41e540` snapshots the current code position and calls `read_text_db_commit_pending@0x46ae20`, which grows or creates the per-script flag array and sets the queued indices to one. This queued/commit seam lets the port reproduce native read eligibility without scene @@ -1251,7 +1251,7 @@ History remains a distinct retained-text subsystem rather than another variation | Control | Native action | Current port boundary | Relative cost | |---|---|---|---| -| History (`x=684`) | Cancel the ADV hotspot wait and run `HISTORY.BIN` over the text manager's retained record stream | Hotspot, nested `call-script`, and generic callback/input services work; retained text-history records, history query/render opcodes, and several UI/font operations do not | High | +| History (`x=684`) | Cancel the ADV hotspot wait and run `HISTORY.BIN` over the text manager's retained record stream | Input/callback infrastructure works; a bounded retained-history model, its read/write opcodes, and supporting text/presentation ops remain | Medium-high, bounded | | Auto (`x=706`) | Toggle the Auto service | Implemented, including timed wait completion | Done | | Message Skip (`x=728`) | Enable persistent all-message fast-forward | Implemented; pacing discrepancies remain a later fidelity adjustment | Done | | Read-message Skip (`x=750`) | Toggle `message:ReadTextSkip`; gate advancement through shared ReadTextDB state | Native persistence and queue/commit/query flow investigated; service not implemented | Medium-high, bounded | @@ -1263,18 +1263,63 @@ cursor selection (`0x86/0x87`), mouse callback registration/dispatch (`0xcc/0xcd those services plus real op-`0x199` frame redirection; the existing retained renderer supplies the visual state while the script saves translations, hides the ADV chrome, permits view/pan input, and restores state. -`HISTORY.BIN` is a substantially larger subsystem. Current static coverage is 47/78 distinct opcodes -correctly handled (781/854 instructions), leaving 31 effectful opcode kinds across 73 instructions. The -largest repeated gaps are local literal-array initialization (`0x64`, 15 sites) and the still-unreversed -menu/action registration op `0xa2` (9 sites); the five core history navigation/render/metadata/voice opcodes -are semantically reversed but still need a port-side retained-history service. AGE does not build the backlog -from `RT.DAT`. -It retains a session text-manager stream of 0x48-byte records plus an 8-byte logical-message index. Op -`0x1d0` navigates message groups, `0x1d1` rerenders stored records with their font/color/layout metadata, -`0x1d3/0x1d4` recover typed metadata and voice pairs, and `0x1bd` replays a selected stored voice. History -also needs the generic callback/input layer used by Hide Window, local literal-array copies, and remaining -menu/text-surface operations. ReadTextDB can share the point where a message completes, but it cannot serve -as the backlog data model because it stores only read flags, not text, styling, names, or voice metadata. +`HISTORY.BIN` remains a larger subsystem, but the formerly ambiguous gaps are now bounded. Static runtime +coverage is still 47/78 distinct opcodes (781/854 instructions): this investigation refined semantics, not +the C# handler count. The 15 op-`0x64` sites decode count-prefixed inline integer arrays used for row +rectangles and coordinates. Ops `0xa1/0xa2/0xa3` are a generic value switch (`begin`, `add case`, `dispatch`), +not a History-specific menu/input service; HISTORY maps already-produced action values to local branches. +Op `0x12e` scans those rectangle/offset arrays for pointer hover. The Hide Window callback/input layer is +therefore already sufficient. + +#### Retained History record model and lifetime (2026-07-18) + +The native text manager at `ctx+0x14940` owns two vectors: + +- manager `+0xd24/+0xd28`: 0x48-byte retained records; +- manager `+0xd34/+0xd38`: 8-byte logical entries `{layout_slot, first_record_index}`. + +A normal record holds the layout slot at `+0x00`, geometry at `+0x04..+0x10`, value/metadata fields at +`+0x14/+0x18`, font/color/baseline state at `+0x1c..+0x24`, flags at `+0x28`, and an inline-or-heap string +object at `+0x2c` (length `+0x40`, capacity `+0x44`). Flag bit 0 begins a logical group; bit 1 is filtered by +History's navigation mode; `0x20000000` denotes typed metadata and `0x40000000` denotes a voice pair. + +The write path is part of ordinary ADV execution: + +- op `0x70` defines a text layout and op `0x71` resets one. Unless recording is suppressed, each appends + `{layout_slot,current_record_count}` and arms the next record's group-start bit. The op-`0x71` operand is a + layout slot—not a T1 anchor id—although T1 entries structurally target these reset sites. Its ReadTextDB + snapshot/commit work remains a second responsibility. +- op `0x6e`'s glyph builder appends normal text chunks with the active geometry/font/color state. +- voice op `0xc4` appends a `0x40000000` record containing its stored pair. +- op `0x1d2` appends a `0x20000000` record with operand 1 as the value and operand 2 as the metadata type. + It was previously misclassified as a safe statement marker; 17,323 corpus uses make this a foundational + correction. +- op `0x1bb(0)` writes suppression bit `0x80000000` at `ctx+0x55110`; op `0x1bb(1)` clears it. HISTORY uses + that pair at entry/exit so its own UI text is not added to the backlog. +- op `0x85` clears both vectors. Its 286 corpus uses are two sites in each of 143 ordinary ADV scripts, + generally bounding History to the current ADV block rather than an unbounded profile log. + +The read side is the previously identified op-`0x1d0..0x1d4` family. Op `0x1d0` returns a logical entry's +layout slot and first record index; op `0x1d1` renders records until the next group boundary; op `0x1d3` +finds typed metadata; op `0x1d4` finds the voice pair; op `0x1bd` replays its voice id. + +History is independent of `RT.DAT`, but native full save fidelity does serialize the live backlog. +`text_history_serialize@0x451d00` writes the index and packed records/strings after context/numbered-save +serialization, and `text_history_deserialize@0x456130` restores them on the matching load path. This is a +future numbered-save integration seam, not a reason to choose a shared-profile backend now: an in-memory +History button can be complete first, while save/load restoration stays deferred with the wider storage +architecture decision. + +The remaining supporting gaps are ordinary presentation operations: primary/ruby font sizes (`0x75/0x197`), +font weight (`0x2bd`), colors/effect mode/offset (`0x76/0x77/0x78/0x1a4`), layout origin (`0x198`), surface +rectangle fill (`0x20b`), message-window alpha (`0x131`), and retained-object presentation (`0x222`). The +`0xd3/0xd4/0xd5` callback-sequence family drives the smooth scrollbar interpolation and is the main +remaining scheduler detail; omitting it would affect motion fidelity, not the backlog data model. + +The port currently retains only the current `_advText` plus a diagnostic `(offset,text)` capture list. +History should therefore begin with an engine/session-owned `AdvTextHistory` service—records, logical index, +suppression, and clear lifetime—fed by the native write opcodes. The host should render a requested group; +it should not own the canonical backlog. No persistence format is required for that first slice. The original dependency order was **Hide Window first** to establish reusable callback/coroutine input, then Read-message Skip, then History after both the input layer and message-completion seam exist. Hide Window is diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index eac92ed..641472c 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -5,6 +5,16 @@ ## adv +### 0x70 `define-adv-text-layout` (define-adv-text-layout, argc 5) +- **summary:** (layout_slot)(width)(height)(x)(y) - configure an ADV text layout/surface and, when history recording is enabled, append its logical history index and arm the next retained record as a group start. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x70_handler@0x41e4e0 forwards the five operands and ctx+0x55110 to adv_text_layout_define@0x4550e0. The worker stores width/height/origin, creates or resizes surface slot layout+0x14, appends {layout_slot,current_record_count} to the 8-byte history index unless suppression bit31 is set, and arms manager+0xd40[slot] so the next 0x48-byte record receives group-start flag bit0. + +### 0x71 `reset-adv-text-layout` (reset-adv-text-layout, argc 1) +- **summary:** (layout_slot) - clear/reset an ADV text layout, append a retained-history boundary when recording is enabled, snapshot the current code/text position, and commit pending ReadTextDB records. T1 entries target these structural reset sites. +- **grounding:** source=investigation, confidence=high +- **evidence:** Corpus: T1 targets op-0x71 records, but the operand is a layout slot (SC0000 uses 1; HISTORY computes 2..6), not an anchor id. Ghidra /v2: op_0x71_handler@0x41e540 calls adv_text_layout_reset@0x455210 with ctx+0x55110; the worker clears the selected layout and appends {slot,current_record_count}/arms group-start unless suppressed. The handler also snapshots (frame_pc-frame_codebase)/4 and text state and calls read_text_db_commit_pending@0x46ae20. + ### 0x72 `wait-for-input` (wait-for-input, argc 1) - **summary:** (layout_slot) - arm the ADV input wait after text reveal completes; activate the configured wait indicator and, while Auto is enabled, arm the appropriate Auto-message timer. - **grounding:** source=investigation, confidence=high @@ -15,21 +25,71 @@ - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: op_0x73_configure_wait_indicator@0x41e900 passes operands 1..9 to adv_text_configure_wait_indicator@0x44ff60 and operand 10 to adv_indicator_set_frame_period@0x44d060. The worker writes surface/source rect, layout-relative destination, enabled=1, and terminal/column values to the selected layout at +0x3c..+0x60. SYSTEM4 executes `set-texture 0x337c 0xc 0xff00` (raw id 0x337c = SO000.AGF, a 390x27 strip of thirteen 30x27 bat frames), then `0x73 1 385 140 12 0 0 30 27 12 48`; layout 1 begins at y=430, placing the 30x27 marker at screen (385,570), matching the original. Op 0x72 activates this descriptor only after reveal completion. +### 0x75 `set-font-size` (set-font-size, argc 1) +- **summary:** (pixels) - set the primary text font height and rebuild its native rasterization state. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x75_handler@0x41ea10 calls text_set_primary_font_size@0x415bd0 on ctx+0x14940. The worker writes the negated pixel height to both primary LOGFONT states, derives width, and rebuilds font resources. HISTORY.BIN selects 22 pixels while active and restores 24 on exit. + +### 0x76 `set-text-color` (set-text-color, argc 1) +- **summary:** (rgb) - set the current primary text color. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x76_handler@0x41ea50 byte-swaps operand RGB into text-manager+0x54c and rebuilds text raster state. Retained 0x48-byte history records copy this field to record+0x24; HISTORY.BIN uses white. + +### 0x77 `set-text-effect-color` (set-text-effect-color, argc 1) +- **summary:** (rgb) - set the secondary outline/shadow color used by the current text raster mode. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x77_handler@0x41eab0 byte-swaps operand RGB into text-manager+0x550 and rebuilds text raster state. The rasterizer consumes it beside the primary color and retained history copies it to record+0x1c; HISTORY.BIN uses dark 0x333e52 while active. + +### 0x78 `set-text-render-mode` (set-text-render-mode, argc 1) +- **summary:** (mode) - select the current text raster/effect mode. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x78_handler@0x41eb10 writes operand 1 to text-manager+0x558 and rebuilds font state. Direct draw and text_history_render_records branch on this field (including a distinct mode-3 offset path). HISTORY.BIN selects mode 3 and restores mode 3 with the normal ADV font preset. + ### 0x7a `set-adv-text-cursor` (set-adv-text-cursor, argc 3) - **summary:** (layout_slot)(x)(y) - set the cursor in the selected ADV text layout's last 20-byte record. Slot 0 selects the current layout. - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: op_0x7a_handler@0x41eba0 fetches operands 3,2,1 and calls adv_text_set_cursor@0x4530f0 on text manager ctx+0x14940. Slot 0 resolves manager+0x4c8; manager+0x414[slot] selects the layout; text_layout_set_cursor@0x452530 writes x/y to +4/+8 of its last 0x14-byte record. SC0000 0x9d3 computes slot 1, x=75, y=47 before voiced show-text. -### 0x1d0 `step-text-history` (u0041BA80, argc 3) -- **summary:** (out_group)(out_record)(delta) - move through retained ADV text-history groups and return the resolved group/record indices, or -1 at a boundary. +### 0x85 `clear-text-history` (clear-text-history, argc 0) +- **summary:** Clear both the retained ADV text-record vector and its logical layout/message index. - **grounding:** source=investigation, confidence=high -- **evidence:** Ghidra /v2: op_0x1d0_step_text_history@0x427360 calls text_history_step_group@0x4537c0 with operand 3 and mode mask 2, then writes two outputs. The helper walks the text manager's 8-byte history index vector over its 0x48-byte retained text records, skipping masked records and returning -1 outputs at a boundary. HISTORY.BIN uses negative deltas to count and page backward and positive deltas to page forward. +- **evidence:** Ghidra /v2: op_0x85_handler@0x4163d0 calls text_history_clear@0x455d70 on the text manager. The worker destroys the 0x48-byte record vector and clears the 8-byte logical index vector. Corpus: two sites in each of 143 normal ADV scripts bracket the retained backlog lifetime. + +### 0x197 `set-ruby-font-size` (set-ruby-font-size, argc 1) +- **summary:** (pixels) - set the secondary/ruby text font height and rebuild its native rasterization state. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x197_handler@0x41f3b0 calls the parallel secondary-font size worker at 0x415c40 on ctx+0x14940, which updates the second LOGFONT pair and rebuilds it. UI font preset sequences consistently set primary size through 0x75 and the smaller ruby size through 0x197; HISTORY.BIN uses 22/8. + +### 0x198 `set-adv-text-layout-origin` (set-adv-text-layout-origin, argc 3) +- **summary:** (layout_slot)(x)(y) - set the selected ADV text layout's presentation origin; slot 0 selects the current layout. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x198_handler@0x41f3f0 passes all three operands to adv_text_layout_set_origin@0x44ff30 on ctx+0x14940. The worker resolves slot 0 to current and writes x/y to layout+0x0c/+0x10. HISTORY.BIN positions its dynamically selected backlog layout with this opcode. + +### 0x1a4 `set-text-effect-offset` (set-text-effect-offset, argc 2) +- **summary:** (x)(y) - set the horizontal and vertical extent/offset used by the current text effect mode. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x1a4_handler@0x41f440 writes operands 1/2 to text-manager+0x564/+0x568. Native raster/bounds paths expand or shift text by these values when an effect mode is active; mode 3 subtracts both. HISTORY.BIN uses (1,1). + +### 0x1bb `set-text-history-recording` (set-text-history-recording, argc 1) +- **summary:** (enabled) - enable or suppress retained ADV text-history recording. Zero suppresses recording; one enables it. +- **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. + +### 0x1d0 `step-text-history` (step-text-history, argc 3) +- **summary:** (out_layout_slot)(out_record_index)(delta) - move through retained ADV text-history groups and return the resolved layout slot and first record index, or -1 outputs at a boundary. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x1d0_step_text_history@0x427360 calls text_history_step_group@0x4537c0 with operand 3 and mode mask 2, then writes the index entry's first field (layout slot) and second field (record index). The helper walks the 8-byte logical index over 0x48-byte retained records, skips record-flag bit1 for mode mask 2, and returns -1 outputs at a boundary. HISTORY.BIN uses negative deltas to count/page backward and positive deltas to page forward. ### 0x1d1 `render-text-history` (u0041BAE0, argc 5) - **summary:** (layout_slot)(record_index)(flags)(color_a)(color_b) - render retained ADV text records into a selected text layout/surface. - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: op_0x1d1_render_text_history@0x41f950 forwards all five operands plus the text manager at ctx+0x14508 to text_history_render_records@0x4526c0. That helper walks 0x48-byte retained text records, applies record flags/colors/font state, measures strings, and rasterizes/binds them to the chosen layout. HISTORY.BIN uses it to draw each visible backlog line. +### 0x1d2 `append-text-history-metadata` (append-text-history-metadata, argc 2) +- **summary:** (value)(metadata_type) - append a typed metadata record to the current retained ADV message group when history recording is enabled. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x1d2_handler@0x41f9c0 tests ctx+0x55110 and, when recording is enabled, calls text_history_append_typed_metadata@0x455f00. That appends a 0x48-byte record with flag 0x20000000, operand 1 at +0x14, and operand 2 at +0x18, consuming the pending group-start flag if set. The corpus has 17,323 uses; HISTORY.BIN later queries metadata types 1 and 2 through op 0x1d3. This opcode is effectful, not a statement marker. + ### 0x1d3 `find-text-history-value` (u0041BB90, argc 5) - **summary:** (out_found)(out_value)(direction)(record_index)(value_type) - find typed metadata within one retained ADV message group. - **grounding:** source=investigation, confidence=high @@ -45,6 +105,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). +### 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 +- **evidence:** Ghidra /v2: op_0x2bd_handler@0x4251c0 writes enabled?700:0 to LOGFONT weight at text-manager+0x4dc and calls the font rebuild worker. It immediately follows set-font throughout the UI corpus; HISTORY.BIN selects the Mincho face with bold enabled. + ## audio ### 0xb4 `sfx-load` (play-sound-effect, argc 2) @@ -84,6 +149,11 @@ ## compute +### 0x64 `copy-inline-int-array` (copy-inline-int-array, argc 2) +- **summary:** (destination)(inline_blob_offset) - decode the count-prefixed integer literal blob at codebase + offset*4 and copy its values to consecutive VM integer cells beginning at destination. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x64_handler@0x426b00 resolves operand 1 as a writable VM address, reads a count dword followed by encoded values from frame_codebase + operand2*4, reverses the engine rotate/XOR encoding with anti_tamper_a, and writes consecutive dwords. HISTORY.BIN uses 15 blobs to initialize its rectangle, coordinate, and lookup arrays. + ### 0x135 `bit-set` (bit-set, argc 2) - **summary:** (value)(bit_index) - set the indexed bit in the destination integer. - **grounding:** source=investigation, confidence=high @@ -134,6 +204,21 @@ This also names the whole call graph statically (build/callscript-names.json). - **grounding:** source=investigation, confidence=high - **evidence:** native-RE (Ghidra): handler FUN_0041fba0 (= ctx[0x26c93+0x8f]) sets [frame PC @+0x53d2c] = [frame codebase @+0x53d28] + operand*4 and pushes ((pc-base)>>2)+3 onto the per-frame return stack ([ctx+0x552e8]/[ctx+0x55248]). Target is a code OFFSET within the current script (matches header table T3 tag 0x8F = local call targets), confirming it is a local JSR, not a script load. +### 0xa1 `begin-value-switch` (begin-value-switch, argc 0) +- **summary:** Clear and initialize the current value-to-PC dispatch table used by the following case and switch-jump opcodes. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0xa1_handler@0x42bd60 resets the engine's open-addressing value-dispatch table with capacity 0xfff. Corpus has 12 canonical sequences of 0xa1, one or more 0xa2 cases, then 0xa3 dispatch. + +### 0xa2 `add-value-switch-case` (add-value-switch-case, argc 2) +- **summary:** (case_value)(target_pc) - format the VM value as a dispatch key and add its branch target to the current switch table. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0xa2_handler@0x42d310 formats operand 1 through vm_value_format_string@0x418860 and inserts operand 2 into value_dispatch_insert_case@0x42cf70. HISTORY.BIN maps action values 0,3,4,5,6 to callback branches; it is generic switch/case dispatch, not menu registration. + +### 0xa3 `value-switch-jump` (value-switch-jump, argc 2) +- **summary:** (selector)(default_target) - jump to the target registered for selector, or to the supplied default target when no case matches. +- **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. + ### 0xc8 `sleep` (sleep, argc 1) - **summary:** Pause the current script for milliseconds while retained presentation continues. - **grounding:** source=investigation, confidence=high @@ -173,6 +258,11 @@ SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit ## draw +### 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 +- **evidence:** Ghidra /v2: op_0x131_handler@0x4295e0 calls the settings getter with `message:MesWinAlpha` and writes the result. HISTORY.BIN and the shared ADV redraw path compute (16-value)<<4 for the control-strip alpha. + ### 0x1a2 `gfx-cmd-register` (gfx-cmd-register, argc 1) - **summary:** 0x1a2 (value) — gfx cmd-type 3. Handler gfx_op_0x1a2_descriptor_register@0x42d360 builds a key from operand 1's lvalue descriptor and inserts its value into an open-addressing descriptor hash (vm_lvalue_descriptor_hash_insert@0x42cf70). This structure is separate from op 0x215's retained gfx-object map; op 0x215 does not query this hash. NOT save/scene. - **grounding:** source=investigation, confidence=high @@ -228,6 +318,11 @@ SC0000 label_1235a reaches this when 0x1c7/0x1cc are zero. Native run-state bit - **grounding:** source=inference, confidence=med - **evidence:** SC0000 label_12649: set-texture(resId,slot) then 0x208(slot)->w,h feeds w/2 horizontal-center + foot-anchor subtraction into draw-texture dst; stubbing yields 0x0 sizes / off-center draws +### 0x20b `fill-surface-rect` (fill-surface-rect, argc 7) +- **summary:** (surface_slot)(x)(y)(width)(height)(alpha)(rgb) - fill a clipped rectangle on a graphics surface with the supplied color and alpha. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x20b_handler@0x422d50 converts x/y/width/height to an exclusive rectangle, clamps alpha to 255, repacks RGB to native ARGB, and calls the surface manager's rectangle-fill path at 0x4790e0. HISTORY.BIN clears each 400x30 name strip on temporary surface 0xc1 before draw-string. + ### 0x20c `present-frame` (present-frame, argc 0) - **summary:** Present the composited frame; label_1235a uses this on the read/message-skip branch to expose the completed foreground endpoint immediately. - **grounding:** source=investigation, confidence=high @@ -298,6 +393,11 @@ Native handler gfx_op_0x20c_present_frame -> gfx_render_frame @0x4820b0. This is - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra 0x47ecc0 calls matrix builder 0x48afb1 for target obj+0x1ac. Consumer 0x472f00 uses delay obj+0x44, duration obj+0x58, current obj+0x16c, target obj+0x1ac, shared start obj+0x34, and frame-time ctx+0xb550. +### 0x222 `present-gfx-object-range` (present-gfx-object-range, argc 2) +- **summary:** (first_handle)(count) - flush/present retained graphics objects in the selected handle range and clear their pending update flags. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x222_handler@0x4235e0 calls gfx_present_object_range@0x482230. The worker enters the graphics service, walks the retained-object map, processes flagged objects whose handles fall in [first,first+count), clears pending flags, and finalizes the render batch. HISTORY.BIN uses (0,60000) after rebuilding its retained presentation. + ### 0x223 `queue-surface-alpha-transition` (queue-surface-alpha-transition, argc 8) - **summary:** (command_key)(target_slot)(range_a_start)(range_a_count)(range_b_start)(range_b_count)(delay_ms)(duration_ms) — queue a type-0 timed alpha transition command in the separate ctx+0x414 command map. This is render-target/surface presentation state, not an object affine matrix. The render frame composites the two handle ranges into target_slot and ramps alpha 0->1 after delay over duration. - **grounding:** source=investigation, confidence=high @@ -472,6 +572,11 @@ 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_0x10a_set_cursor_virtual@0x421590 maps virtual coordinates through the active VirtualFullScreen geometry and calls SetCursorPos. SC0000 alternates the cursor by one vertical pixel after state-changing ADV button clicks so the hover state re-enters cleanly. +### 0x12e `find-hit-rectangle` (find-hit-rectangle, argc 8) +- **summary:** (index_inout)(reference_rect)(pointer_x)(pointer_y)(rect_array)(x_offsets)(y_offsets)(count) - scan forward through encoded rectangles and return the next hit index, or -1. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x12e_find_hit_rectangle@0x428ff0 resolves the reference rectangle and three array operands as VM pointers, decodes rectangle/offset values with anti_tamper_a, scans candidates after the incoming index, applies per-entry x/y offsets to the pointer coordinates, performs rectangle overlap containment tests, and writes the matched array index or -1 to operand 1. HISTORY.BIN uses it for mouse hover selection over its literal-initialized row rectangles. + ### 0x19a `get-message-skip` (u00414E50, argc 1) - **summary:** (out) - return the current all-message skip state set by op 0x88. - **grounding:** source=investigation, confidence=high @@ -524,10 +629,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **summary:** zero-arg; call->0x1bf->stmt-end — end-of-call-statement marker - **grounding:** source=inference, confidence=med, noop_headless=True -### 0x1d2 `stmt-desc?` (u0041BB40, argc 2) -- **summary:** 2 imm; immediately after stmt-begin 0x1f4 — statement descriptor? -- **grounding:** source=harness, confidence=med, noop_headless=True - ### 0x1d5 `cond-block` (u00415700, argc 0) - **summary:** zero-arg; ALWAYS follows jcc — marks conditional body entry - **grounding:** source=inference, confidence=high, noop_headless=True @@ -553,13 +654,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **grounding:** source=harness, confidence=low, noop_headless=True - **evidence:** SC0000 offset 0x0 = op 0x259 (argc 0); 0x258's summary names it 'script-entry 0x259'; VM treats it as no-op (default stub) across all 279 CLEAN A0 scenes -## structural - -### 0x71 `label-def` (u0041A7B0, argc 1) -- **summary:** (anchor_id) - define a T1 label/anchor, snapshot the current code position, and commit pending read-message records to the shared ReadTextDB. -- **grounding:** source=investigation, confidence=high -- **evidence:** Corpus: count exactly matches each script's T1 table and T1 entries target these records, preserving the structural label role. Ghidra /v2: op_0x71_handler@0x41e540 records (frame_pc-frame_codebase)/4 at the current frame's +0x20 field, snapshots text state, and calls read_text_db_commit_pending@0x46ae20. That drains queued {script_resource_id,message_index,message_count} records into ReadTextDB. script_frame_load_resource@0x40e980 stores the raw packed SYS4/AAI resource id at frame+0x04 (EngineCtx+0x53d64), establishing the persistence key. Therefore 0x71 is not a pure runtime no-op when persistent read history is modeled. - ## unknown ### 0x1 `u004149C0` (u004149C0, argc 0) @@ -674,10 +768,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 -### 0x64 `copy-local-array` (copy-local-array, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=med - ### 0x6c `copy-to-global` (copy-to-global, argc 2) - **summary:** — - **grounding:** source=kelebek, confidence=med @@ -690,26 +780,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 -### 0x70 `u0041A750` (u0041A750, argc 5) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0x75 `u0041AC30` (u0041AC30, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0x76 `u0041AC60` (u0041AC60, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0x77 `u0041ACB0` (u0041ACB0, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0x78 `u0041AD00` (u0041AD00, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x79 `u0041AD30` (u0041AD30, argc 3) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -722,10 +792,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 -### 0x85 `u00414CF0` (u00414CF0, argc 0) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x8b `u0041B3D0` (u0041B3D0, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -738,18 +804,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 -### 0xa1 `u00427C00` (u00427C00, argc 0) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0xa2 `u00427FD0` (u00427FD0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0xa3 `u004244D0` (u004244D0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0xae `u00415130` (u00415130, argc 0) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -826,10 +880,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 -### 0x12e `u0041E940` (u0041E940, argc 8) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x12f `u0041ECB0` (u0041ECB0, argc 4) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -838,10 +888,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 -### 0x131 `u00415F70` (u00415F70, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x132 `u0041EF00` (u0041EF00, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -910,14 +956,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 -### 0x197 `u0041B510` (u0041B510, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0x198 `u0041B540` (u0041B540, argc 3) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x19b `u00414E80` (u00414E80, argc 0) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -946,10 +984,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 -### 0x1a4 `u0041B580` (u0041B580, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x1a5 `set-font` (set-font, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=med @@ -1018,10 +1052,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 -### 0x1bb `u0041B7B0` (u0041B7B0, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x1c1 `u0041B820` (u0041B820, argc 3) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -1058,10 +1088,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 -### 0x20b `u00420D50` (u00420D50, argc 7) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x20d `u00420E10` (u00420E10, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -1075,10 +1101,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 -### 0x222 `u004216C0` (u004216C0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x22a `u00421A90` (u00421A90, argc 3) - **summary:** — - **grounding:** source=kelebek, confidence=low @@ -1135,10 +1157,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 -### 0x2bd `u00423100` (u00423100, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x2bf `u00423180` (u00423180, argc 3) - **summary:** — - **grounding:** source=kelebek, confidence=low diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index 46d9829..f5a2c34 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -1564,8 +1564,8 @@ x=728 pacing check before starting Read-message Skip. Native RE confirms that this service cannot be implemented as a second all-message toggle. AGE resolves the current script code offset through its per-frame message-offset table, then tests a dword flag in an engine-owned `ReadTextDB` keyed by the raw packed script resource id and per-script message index. Click, -Auto, and active-Skip completion queue `{script_resource_id,index,count}`; opcode `0x71`, while still the -structural T1 label/anchor, +Auto, and active-Skip completion queue `{script_resource_id,index,count}`; opcode `0x71`, whose layout-reset +sites are also the structural T1 targets, commits those pending records and therefore is not a pure runtime no-op. The database is profile-wide rather than slot-local. AGE loads and writes `RT.DAT` beside shared @@ -1654,3 +1654,29 @@ architecture selects. With Auto, all-message Skip, and Hide Window implemented and Read-message Skip consciously deferred, History is the sole remaining unimplemented ADV control-strip action to investigate as a current slice. + +### ADV History retained-record investigation (2026-07-18) + +Native RE now bounds History as an in-memory engine service rather than an unknown menu subsystem. The text +manager retains 0x48-byte text/metadata/voice records plus 8-byte `{layout_slot,first_record_index}` logical +entries. Ops `0x70/0x71` establish group boundaries, `0x6e` writes styled text, `0xc4` writes voice pairs, +and op `0x1d2` writes typed metadata. That last opcode was incorrectly classified as a safe marker despite +17,323 corpus uses and is now corrected. Op `0x1bb` suppresses recursive recording while HISTORY runs; op +`0x85` clears both vectors at ordinary ADV block boundaries. + +The other large-looking gaps are generic: `0x64` copies encoded inline literal arrays; `0xa1/0xa2/0xa3` +implement switch/case dispatch; `0x12e` performs rectangle-array hover tests. The existing Hide Window input +callbacks already supply HISTORY's actions. Font/color/layout/surface/presentation opcodes are individually +bounded, while `0xd3/0xd4/0xd5` remains the smooth scrollbar callback/interpolation detail. + +Native context saves can serialize/deserialize the live backlog, but that path is distinct from `RT.DAT`. +The first implementation should deliberately stay in memory: add an engine/session-owned history service, +wire the native writers and `0x1d0..0x1d4` readers, then let the host render requested groups. Save/load +restoration remains a named integration seam for the later unified storage architecture rather than a new +backend decision in this slice. Full layout, lifecycle, and opcode evidence lives in +[`engine-re.md`](engine-re.md#retained-history-record-model-and-lifetime-2026-07-18). + +**Recommended next implementation slice:** land the history record/index model and its ordinary ADV write +path first, with focused synthetic tests for grouping, metadata, voice pairs, suppression, and clear. Then +add generic literal-array/switch support and the HISTORY read/render path. This orders the work around the +data that must exist before clicking History can display anything. diff --git a/tools/age_opcodes_himegari.py b/tools/age_opcodes_himegari.py index d11ddc8..e070e4a 100644 --- a/tools/age_opcodes_himegari.py +++ b/tools/age_opcodes_himegari.py @@ -4,7 +4,6 @@ from __future__ import annotations INFERRED: dict[int, dict] = { - 0x71: dict(name='label-def', category='structural', noop=False, confidence='high', source='investigation', summary='(anchor_id) - define a T1 label/anchor, snapshot the current code position, and commit pending read-message records to the shared ReadTextDB.'), 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) + gfx cmd-type 5. 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.'), 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.'), @@ -40,9 +39,7 @@ INFERRED: dict[int, dict] = { 0x1bf: dict(name='call-end', category='marker', noop=True, confidence='med', source='inference', summary='zero-arg; call->0x1bf->stmt-end — end-of-call-statement marker'), 0x1ca: dict(name='set-read-message-skip', category='input', noop=False, confidence='high', source='investigation', summary='(enabled) - set the engine setting `message:ReadTextSkip`, which skips only previously read text.'), 0x1cb: dict(name='get-read-message-skip', category='input', noop=False, confidence='high', source='investigation', summary='(out) - read the engine setting `message:ReadTextSkip`.'), - 0x1d0: dict(name='step-text-history', category='adv', noop=False, confidence='high', source='investigation', summary='(out_group)(out_record)(delta) - move through retained ADV text-history groups and return the resolved group/record indices, or -1 at a boundary.'), 0x1d1: dict(name='render-text-history', category='adv', noop=False, confidence='high', source='investigation', summary='(layout_slot)(record_index)(flags)(color_a)(color_b) - render retained ADV text records into a selected text layout/surface.'), - 0x1d2: dict(name='stmt-desc?', category='marker', noop=True, confidence='med', source='harness', summary='2 imm; immediately after stmt-begin 0x1f4 — statement descriptor?'), 0x1d3: dict(name='find-text-history-value', category='adv', noop=False, confidence='high', source='investigation', summary='(out_found)(out_value)(direction)(record_index)(value_type) - find typed metadata within one retained ADV message group.'), 0x1d4: dict(name='find-text-history-pair', category='adv', noop=False, confidence='high', source='investigation', summary='(out_a)(out_b)(direction)(record_index) - find paired metadata within one retained ADV message group.'), 0x1d5: dict(name='cond-block', category='marker', noop=True, confidence='high', source='inference', summary='zero-arg; ALWAYS follows jcc — marks conditional body entry'), diff --git a/vm-map/engine-ctx.toml b/vm-map/engine-ctx.toml index 53e93e2..93734a6 100644 --- a/vm-map/engine-ctx.toml +++ b/vm-map/engine-ctx.toml @@ -123,6 +123,11 @@ name = "auto_message_enabled" type = "int" note = "ADV Auto mode flag; op 0x1b6 reads, op 0x1b7 writes, adv_input_service_poll consumes" [[field]] +offset = 0x55110 +name = "text_history_recording_suppressed" +type = "uint" +note = "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" +[[field]] offset = 0x55120 name = "anti_tamper_a" type = "int" diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index f2ceba5..8b64770 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -847,28 +847,28 @@ observed_types = ["g-int", "l-int", "l-ptr"] [[opcode]] op = 0x64 -label = "copy-local-array" +label = "copy-inline-int-array" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "copy-local-array" -category = "unknown" -summary = "" +name = "copy-inline-int-array" +category = "compute" +summary = "(destination)(inline_blob_offset) - decode the count-prefixed integer literal blob at codebase + offset*4 and copy its values to consecutive VM integer cells beginning at destination." noop_headless = false -source = "kelebek" -confidence = "med" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x64_handler@0x426b00 resolves operand 1 as a writable VM address, reads a count dword followed by encoded values from frame_codebase + operand2*4, reverses the engine rotate/XOR encoding with anti_tamper_a, and writes consecutive dwords. HISTORY.BIN uses 15 blobs to initialize its rectangle, coordinate, and lookup arrays." [[opcode.semantics.args]] i = 1 -role = "" +role = "destination first integer" observed_types = ["g-int", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "inline blob dword offset" observed_types = ["imm"] [[opcode]] @@ -946,64 +946,64 @@ observed_types = ["imm"] [[opcode]] op = 0x70 -label = "u0041A750" +label = "define-adv-text-layout" argc = 5 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041A750" -category = "unknown" -summary = "" -noop_headless = false -source = "kelebek" -confidence = "low" -depends_on = [] -evidence = "" - -[[opcode.semantics.args]] -i = 1 -role = "" -observed_types = ["imm"] - -[[opcode.semantics.args]] -i = 2 -role = "" -observed_types = ["imm"] - -[[opcode.semantics.args]] -i = 3 -role = "" -observed_types = ["imm"] - -[[opcode.semantics.args]] -i = 4 -role = "" -observed_types = ["imm"] - -[[opcode.semantics.args]] -i = 5 -role = "" -observed_types = ["imm"] - -[[opcode]] -op = 0x71 -label = "u0041A7B0" -argc = 1 -abi_source = "kelebek+decode-validated" - -[opcode.semantics] -name = "label-def" -category = "structural" -summary = "(anchor_id) - define a T1 label/anchor, snapshot the current code position, and commit pending read-message records to the shared ReadTextDB." +name = "define-adv-text-layout" +category = "adv" +summary = "(layout_slot)(width)(height)(x)(y) - configure an ADV text layout/surface and, when history recording is enabled, append its logical history index and arm the next retained record as a group start." noop_headless = false source = "investigation" confidence = "high" depends_on = [] -evidence = "Corpus: count exactly matches each script's T1 table and T1 entries target these records, preserving the structural label role. Ghidra /v2: op_0x71_handler@0x41e540 records (frame_pc-frame_codebase)/4 at the current frame's +0x20 field, snapshots text state, and calls read_text_db_commit_pending@0x46ae20. That drains queued {script_resource_id,message_index,message_count} records into ReadTextDB. script_frame_load_resource@0x40e980 stores the raw packed SYS4/AAI resource id at frame+0x04 (EngineCtx+0x53d64), establishing the persistence key. Therefore 0x71 is not a pure runtime no-op when persistent read history is modeled." +evidence = "Ghidra /v2: op_0x70_handler@0x41e4e0 forwards the five operands and ctx+0x55110 to adv_text_layout_define@0x4550e0. The worker stores width/height/origin, creates or resizes surface slot layout+0x14, appends {layout_slot,current_record_count} to the 8-byte history index unless suppression bit31 is set, and arms manager+0xd40[slot] so the next 0x48-byte record receives group-start flag bit0." [[opcode.semantics.args]] i = 1 -role = "label/anchor id" +role = "layout slot (0 = current)" +observed_types = ["imm"] + +[[opcode.semantics.args]] +i = 2 +role = "width" +observed_types = ["imm"] + +[[opcode.semantics.args]] +i = 3 +role = "height" +observed_types = ["imm"] + +[[opcode.semantics.args]] +i = 4 +role = "origin x" +observed_types = ["imm"] + +[[opcode.semantics.args]] +i = 5 +role = "origin y" +observed_types = ["imm"] + +[[opcode]] +op = 0x71 +label = "reset-adv-text-layout" +argc = 1 +abi_source = "kelebek+decode-validated" + +[opcode.semantics] +name = "reset-adv-text-layout" +category = "adv" +summary = "(layout_slot) - clear/reset an ADV text layout, append a retained-history boundary when recording is enabled, snapshot the current code/text position, and commit pending ReadTextDB records. T1 entries target these structural reset sites." +noop_headless = false +source = "investigation" +confidence = "high" +depends_on = [] +evidence = "Corpus: T1 targets op-0x71 records, but the operand is a layout slot (SC0000 uses 1; HISTORY computes 2..6), not an anchor id. Ghidra /v2: op_0x71_handler@0x41e540 calls adv_text_layout_reset@0x455210 with ctx+0x55110; the worker clears the selected layout and appends {slot,current_record_count}/arms group-start unless suppressed. The handler also snapshots (frame_pc-frame_codebase)/4 and text state and calls read_text_db_commit_pending@0x46ae20." + +[[opcode.semantics.args]] +i = 1 +role = "layout slot (0 = current)" observed_types = ["imm", "l-int"] [[opcode]] @@ -1095,19 +1095,19 @@ observed_types = ["imm"] [[opcode]] op = 0x75 -label = "u0041AC30" +label = "set-font-size" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041AC30" -category = "unknown" -summary = "" +name = "set-font-size" +category = "adv" +summary = "(pixels) - set the primary text font height and rebuild its native rasterization state." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x75_handler@0x41ea10 calls text_set_primary_font_size@0x415bd0 on ctx+0x14940. The worker writes the negated pixel height to both primary LOGFONT states, derives width, and rebuilds font resources. HISTORY.BIN selects 22 pixels while active and restores 24 on exit." [[opcode.semantics.args]] i = 1 @@ -1116,19 +1116,19 @@ observed_types = ["imm"] [[opcode]] op = 0x76 -label = "u0041AC60" +label = "set-text-color" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041AC60" -category = "unknown" -summary = "" +name = "set-text-color" +category = "adv" +summary = "(rgb) - set the current primary text color." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x76_handler@0x41ea50 byte-swaps operand RGB into text-manager+0x54c and rebuilds text raster state. Retained 0x48-byte history records copy this field to record+0x24; HISTORY.BIN uses white." [[opcode.semantics.args]] i = 1 @@ -1137,19 +1137,19 @@ observed_types = ["imm"] [[opcode]] op = 0x77 -label = "u0041ACB0" +label = "set-text-effect-color" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041ACB0" -category = "unknown" -summary = "" +name = "set-text-effect-color" +category = "adv" +summary = "(rgb) - set the secondary outline/shadow color used by the current text raster mode." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x77_handler@0x41eab0 byte-swaps operand RGB into text-manager+0x550 and rebuilds text raster state. The rasterizer consumes it beside the primary color and retained history copies it to record+0x1c; HISTORY.BIN uses dark 0x333e52 while active." [[opcode.semantics.args]] i = 1 @@ -1158,19 +1158,19 @@ observed_types = ["imm"] [[opcode]] op = 0x78 -label = "u0041AD00" +label = "set-text-render-mode" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041AD00" -category = "unknown" -summary = "" +name = "set-text-render-mode" +category = "adv" +summary = "(mode) - select the current text raster/effect mode." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x78_handler@0x41eb10 writes operand 1 to text-manager+0x558 and rebuilds font state. Direct draw and text_history_render_records branch on this field (including a distinct mode-3 offset path). HISTORY.BIN selects mode 3 and restores mode 3 with the normal ADV font preset." [[opcode.semantics.args]] i = 1 @@ -1325,19 +1325,19 @@ observed_types = ["imm"] [[opcode]] op = 0x85 -label = "u00414CF0" +label = "clear-text-history" argc = 0 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00414CF0" -category = "unknown" -summary = "" +name = "clear-text-history" +category = "adv" +summary = "Clear both the retained ADV text-record vector and its logical layout/message index." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x85_handler@0x4163d0 calls text_history_clear@0x455d70 on the text manager. The worker destroys the 0x48-byte record vector and clears the 8-byte logical index vector. Corpus: two sites in each of 143 normal ADV scripts bracket the retained backlog lifetime." [[opcode]] op = 0x86 @@ -1639,70 +1639,70 @@ observed_types = ["imm"] [[opcode]] op = 0xa1 -label = "u00427C00" +label = "begin-value-switch" argc = 0 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00427C00" -category = "unknown" -summary = "" +name = "begin-value-switch" +category = "control" +summary = "Clear and initialize the current value-to-PC dispatch table used by the following case and switch-jump opcodes." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0xa1_handler@0x42bd60 resets the engine's open-addressing value-dispatch table with capacity 0xfff. Corpus has 12 canonical sequences of 0xa1, one or more 0xa2 cases, then 0xa3 dispatch." [[opcode]] op = 0xa2 -label = "u00427FD0" +label = "add-value-switch-case" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00427FD0" -category = "unknown" -summary = "" +name = "add-value-switch-case" +category = "control" +summary = "(case_value)(target_pc) - format the VM value as a dispatch key and add its branch target to the current switch table." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0xa2_handler@0x42d310 formats operand 1 through vm_value_format_string@0x418860 and inserts operand 2 into value_dispatch_insert_case@0x42cf70. HISTORY.BIN maps action values 0,3,4,5,6 to callback branches; it is generic switch/case dispatch, not menu registration." [[opcode.semantics.args]] i = 1 -role = "" +role = "case value" observed_types = ["imm", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "target PC dword offset" observed_types = ["imm"] [[opcode]] op = 0xa3 -label = "u004244D0" +label = "value-switch-jump" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u004244D0" -category = "unknown" -summary = "" +name = "value-switch-jump" +category = "control" +summary = "(selector)(default_target) - jump to the target registered for selector, or to the supplied default target when no case matches." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +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." [[opcode.semantics.args]] i = 1 -role = "" +role = "selector value" observed_types = ["g-int", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "default target PC dword offset" observed_types = ["imm"] [[opcode]] @@ -2519,58 +2519,58 @@ observed_types = ["imm", "g-int", "l-int", "l-ptr"] [[opcode]] op = 0x12e -label = "u0041E940" +label = "find-hit-rectangle" argc = 8 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041E940" -category = "unknown" -summary = "" +name = "find-hit-rectangle" +category = "input" +summary = "(index_inout)(reference_rect)(pointer_x)(pointer_y)(rect_array)(x_offsets)(y_offsets)(count) - scan forward through encoded rectangles and return the next hit index, or -1." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x12e_find_hit_rectangle@0x428ff0 resolves the reference rectangle and three array operands as VM pointers, decodes rectangle/offset values with anti_tamper_a, scans candidates after the incoming index, applies per-entry x/y offsets to the pointer coordinates, performs rectangle overlap containment tests, and writes the matched array index or -1 to operand 1. HISTORY.BIN uses it for mouse hover selection over its literal-initialized row rectangles." [[opcode.semantics.args]] i = 1 -role = "" +role = "previous/matched index inout" observed_types = ["l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "reference rectangle pointer" observed_types = ["l-int"] [[opcode.semantics.args]] i = 3 -role = "" +role = "pointer x" observed_types = ["l-int"] [[opcode.semantics.args]] i = 4 -role = "" +role = "pointer y" observed_types = ["l-int"] [[opcode.semantics.args]] i = 5 -role = "" +role = "candidate rectangle array" observed_types = ["l-int"] [[opcode.semantics.args]] i = 6 -role = "" +role = "per-entry x offset array" observed_types = ["l-int"] [[opcode.semantics.args]] i = 7 -role = "" +role = "per-entry y offset array" observed_types = ["l-int"] [[opcode.semantics.args]] i = 8 -role = "" +role = "candidate count/end index" observed_types = ["imm", "g-int", "l-int"] [[opcode]] @@ -2632,23 +2632,23 @@ observed_types = ["l-int"] [[opcode]] op = 0x131 -label = "u00415F70" +label = "get-message-window-alpha" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00415F70" -category = "unknown" -summary = "" +name = "get-message-window-alpha" +category = "draw" +summary = "(out) - read the configured `message:MesWinAlpha` value used to alpha-modulate the ADV chrome." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x131_handler@0x4295e0 calls the settings getter with `message:MesWinAlpha` and writes the result. HISTORY.BIN and the shared ADV redraw path compute (16-value)<<4 for the control-strip alpha." [[opcode.semantics.args]] i = 1 -role = "" +role = "alpha setting out" observed_types = ["g-int", "l-int"] [[opcode]] @@ -3198,19 +3198,19 @@ observed_types = ["string"] [[opcode]] op = 0x197 -label = "u0041B510" +label = "set-ruby-font-size" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041B510" -category = "unknown" -summary = "" +name = "set-ruby-font-size" +category = "adv" +summary = "(pixels) - set the secondary/ruby text font height and rebuild its native rasterization state." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x197_handler@0x41f3b0 calls the parallel secondary-font size worker at 0x415c40 on ctx+0x14940, which updates the second LOGFONT pair and rebuilds it. UI font preset sequences consistently set primary size through 0x75 and the smaller ruby size through 0x197; HISTORY.BIN uses 22/8." [[opcode.semantics.args]] i = 1 @@ -3219,33 +3219,33 @@ observed_types = ["imm"] [[opcode]] op = 0x198 -label = "u0041B540" +label = "set-adv-text-layout-origin" argc = 3 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041B540" -category = "unknown" -summary = "" +name = "set-adv-text-layout-origin" +category = "adv" +summary = "(layout_slot)(x)(y) - set the selected ADV text layout's presentation origin; slot 0 selects the current layout." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x198_handler@0x41f3f0 passes all three operands to adv_text_layout_set_origin@0x44ff30 on ctx+0x14940. The worker resolves slot 0 to current and writes x/y to layout+0x0c/+0x10. HISTORY.BIN positions its dynamically selected backlog layout with this opcode." [[opcode.semantics.args]] i = 1 -role = "" +role = "layout slot" observed_types = ["imm", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "origin x" observed_types = ["imm", "l-int"] [[opcode.semantics.args]] i = 3 -role = "" +role = "origin y" observed_types = ["imm", "l-int"] [[opcode]] @@ -3500,19 +3500,19 @@ observed_types = ["g-int", "l-ptr"] [[opcode]] op = 0x1a4 -label = "u0041B580" +label = "set-text-effect-offset" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041B580" -category = "unknown" -summary = "" +name = "set-text-effect-offset" +category = "adv" +summary = "(x)(y) - set the horizontal and vertical extent/offset used by the current text effect mode." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x1a4_handler@0x41f440 writes operands 1/2 to text-manager+0x564/+0x568. Native raster/bounds paths expand or shift text by these values when an effect mode is active; mode 3 subtracts both. HISTORY.BIN uses (1,1)." [[opcode.semantics.args]] i = 1 @@ -4012,23 +4012,23 @@ observed_types = ["imm"] [[opcode]] op = 0x1bb -label = "u0041B7B0" +label = "set-text-history-recording" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041B7B0" -category = "unknown" -summary = "" +name = "set-text-history-recording" +category = "adv" +summary = "(enabled) - enable or suppress retained ADV text-history recording. Zero suppresses recording; one enables it." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +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." [[opcode.semantics.args]] i = 1 -role = "" +role = "enabled (0 or 1)" observed_types = ["imm"] [[opcode]] @@ -4269,28 +4269,28 @@ observed_types = ["imm", "g-int"] [[opcode]] op = 0x1d0 -label = "u0041BA80" +label = "step-text-history" argc = 3 abi_source = "kelebek+decode-validated" [opcode.semantics] name = "step-text-history" category = "adv" -summary = "(out_group)(out_record)(delta) - move through retained ADV text-history groups and return the resolved group/record indices, or -1 at a boundary." +summary = "(out_layout_slot)(out_record_index)(delta) - move through retained ADV text-history groups and return the resolved layout slot and first record index, or -1 outputs at a boundary." noop_headless = false source = "investigation" confidence = "high" depends_on = [] -evidence = "Ghidra /v2: op_0x1d0_step_text_history@0x427360 calls text_history_step_group@0x4537c0 with operand 3 and mode mask 2, then writes two outputs. The helper walks the text manager's 8-byte history index vector over its 0x48-byte retained text records, skipping masked records and returning -1 outputs at a boundary. HISTORY.BIN uses negative deltas to count and page backward and positive deltas to page forward." +evidence = "Ghidra /v2: op_0x1d0_step_text_history@0x427360 calls text_history_step_group@0x4537c0 with operand 3 and mode mask 2, then writes the index entry's first field (layout slot) and second field (record index). The helper walks the 8-byte logical index over 0x48-byte retained records, skips record-flag bit1 for mode mask 2, and returns -1 outputs at a boundary. HISTORY.BIN uses negative deltas to count/page backward and positive deltas to page forward." [[opcode.semantics.args]] i = 1 -role = "history group out" +role = "layout slot out" observed_types = ["l-int"] [[opcode.semantics.args]] i = 2 -role = "history record out" +role = "first history record index out" observed_types = ["l-int"] [[opcode.semantics.args]] @@ -4341,28 +4341,28 @@ observed_types = ["imm"] [[opcode]] op = 0x1d2 -label = "u0041BB40" +label = "append-text-history-metadata" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "stmt-desc?" -category = "marker" -summary = "2 imm; immediately after stmt-begin 0x1f4 — statement descriptor?" -noop_headless = true -source = "harness" -confidence = "med" +name = "append-text-history-metadata" +category = "adv" +summary = "(value)(metadata_type) - append a typed metadata record to the current retained ADV message group when history recording is enabled." +noop_headless = false +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x1d2_handler@0x41f9c0 tests ctx+0x55110 and, when recording is enabled, calls text_history_append_typed_metadata@0x455f00. That appends a 0x48-byte record with flag 0x20000000, operand 1 at +0x14, and operand 2 at +0x18, consuming the pending group-start flag if set. The corpus has 17,323 uses; HISTORY.BIN later queries metadata types 1 and 2 through op 0x1d3. This opcode is effectful, not a statement marker." [[opcode.semantics.args]] i = 1 -role = "" +role = "metadata value" observed_types = ["imm"] [[opcode.semantics.args]] i = 2 -role = "" +role = "metadata type" observed_types = ["imm"] [[opcode]] @@ -5058,19 +5058,19 @@ observed_types = ["imm"] [[opcode]] op = 0x20b -label = "u00420D50" +label = "fill-surface-rect" argc = 7 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00420D50" -category = "unknown" -summary = "" +name = "fill-surface-rect" +category = "draw" +summary = "(surface_slot)(x)(y)(width)(height)(alpha)(rgb) - fill a clipped rectangle on a graphics surface with the supplied color and alpha." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x20b_handler@0x422d50 converts x/y/width/height to an exclusive rectangle, clamps alpha to 255, repacks RGB to native ARGB, and calls the surface manager's rectangle-fill path at 0x4790e0. HISTORY.BIN clears each 400x30 name strip on temporary surface 0xc1 before draw-string." [[opcode.semantics.args]] i = 1 @@ -5654,19 +5654,19 @@ observed_types = ["imm"] [[opcode]] op = 0x222 -label = "u004216C0" +label = "present-gfx-object-range" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u004216C0" -category = "unknown" -summary = "" +name = "present-gfx-object-range" +category = "draw" +summary = "(first_handle)(count) - flush/present retained graphics objects in the selected handle range and clear their pending update flags." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x222_handler@0x4235e0 calls gfx_present_object_range@0x482230. The worker enters the graphics service, walks the retained-object map, processes flagged objects whose handles fall in [first,first+count), clears pending flags, and finalizes the render batch. HISTORY.BIN uses (0,60000) after rebuilding its retained presentation." [[opcode.semantics.args]] i = 1 @@ -6666,19 +6666,19 @@ evidence = "SC0000 offset 0x0 = op 0x259 (argc 0); 0x258's summary names it 'scr [[opcode]] op = 0x2bd -label = "u00423100" +label = "set-font-bold" argc = 1 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00423100" -category = "unknown" -summary = "" +name = "set-font-bold" +category = "adv" +summary = "(enabled) - set the current primary text font weight to 700 when enabled or 0 when disabled, then rebuild the native font state." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x2bd_handler@0x4251c0 writes enabled?700:0 to LOGFONT weight at text-manager+0x4dc and calls the font rebuild worker. It immediately follows set-font throughout the UI corpus; HISTORY.BIN selects the Mincho face with bold enabled." [[opcode.semantics.args]] i = 1