Document numeric HUD opcode contracts

This commit is contained in:
gamer147
2026-07-21 15:40:46 -04:00
parent 8c0e19ceb6
commit fe1531097a
5 changed files with 112 additions and 46 deletions

View File

@@ -2467,6 +2467,38 @@ Focused tests cover stability, overflow, and zero count. A real-script regressio
SYSTEM4-to-SC0000 state into CHMENU and stops after `0x1db8`, proving selected slot 2 survives the first
release roster sort without a fallback. No startup seed or menu-specific roster injection is involved.
### Numeric glyph styles/draws and half-byte string length — `0x13a` / `0x23b` / `0x1a6` (2026-07-21)
The missing values in the first working `DEBUGMAP` field HUD are not missing globals or a failed texture
load. They are generated by a dedicated decimal-glyph subsystem whose two opcodes were still skipped by
the port:
- `op_0x13a_register_numeric_glyph_style@0x421ab0` takes
`(style_index, surface_slot, atlas_x, atlas_y, digit_width, digit_height)`. It accepts style indices
`0..10` and stores the remaining five operands in the 20-byte record at
`EngineCtx+0x55180+style_index*0x14`; an invalid index raises the standard script error. The corpus has
74 registrations in 24 scripts, including eight in `DRAWCHP.BIN`.
- `op_0x23b_draw_decimal_glyphs@0x424190` takes
`(base_handle, style_index, value, x, y, digit_capacity, flags)`. It first erases the destination handle
range, splits the signed integer through division/remainder by ten, and binds one retained object per
displayed digit using horizontally adjacent cells from the selected style record. Flag bit `0x1`
includes leading zeroes, bit `0x2` centers the used digits, and bit `0x4` left-aligns them; without an
alignment flag the value is right-aligned in the requested capacity. An invalid or unregistered style
raises the script error. The corpus has 147 draws in the same 24 scripts; `DRAWCHP.BIN` has 22 and uses
them for the field HUD's turn/control/mana/level/HP/SP/FS values.
This path creates ordinary retained graphics objects, so the existing atlas decode and compositor are
already the correct backend. The missing engine model is the 11 style records plus the two VM dispatches;
it is not an immediate `GodotAdvHost.DrawTexture` raster operation.
The absent unit and weapon names are a separate layout-compute gap. `DRAWCHP.BIN` does populate both
strings and calls `draw-string` at `0x9c6`/`0x9f5`, but first centers each one with opcode `0x1a6`.
`op_0x1a6_half_byte_strlen@0x427020` resolves the NUL-terminated engine byte string and writes
`strlen(bytes) >> 1`. The script multiplies that result by 21 and subtracts it from x=257 on a 263-pixel
scratch surface. With the opcode skipped, x remains 257 and Godot correctly clips nearly all of the text.
An implementation must preserve the original encoded byte count (or reproduce it with CP932 encoding),
not use the .NET UTF-16 character count.
---
## Native walls backlog (targets for this loop)

View File

@@ -237,6 +237,13 @@ Implemented with domain-preserving addressed-array access, native signed 32-bit
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c. The C# VM implements ordinal equality through the shared string resolver, covering literal, global, local, global-string-pointer, and local-string-pointer operands; the traced natural-boot regression proves the reached GAMESTART call no longer falls back.
### 0x1a6 `half-byte-string-length` (halve-strlen, argc 2)
- **summary:** Write half the resolved string's byte length, using integer truncation.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x1a6_half_byte_strlen@0x427020 resolves operand 2, scans to NUL, and vm_operand_write(1, strlen_bytes >> 1). DRAWCHP sites 0x9b3/0x9e2 multiply the result by 21 and subtract it from x=257 before draw-string.
Native applies strlen to the NUL-terminated engine byte string and shifts the byte count right by one. DRAWCHP uses this Shift-JIS-oriented width proxy to center unit and weapon names before rasterizing them into a scratch surface.
### 0x1b0 `copy-dwords` (copy-dwords, argc 3)
- **summary:** (source)(destination)(count) - copy count consecutive 32-bit cells from source to destination.
- **grounding:** source=investigation, confidence=high
@@ -412,6 +419,14 @@ The handler uses an alpha step of 1 and timer interval=argument when argument <=
- **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.
### 0x13a `register-numeric-glyph-style` (u0041F3A0, argc 6)
- **summary:** Register one of 11 decimal-glyph atlas styles as (surface slot, source x/y, digit width/height).
- **grounding:** source=investigation, confidence=high
- **depended on by:** 0x23b
- **evidence:** Ghidra /v2: op_0x13a_register_numeric_glyph_style@0x421ab0 writes operands 2..6 to the selected five-dword record at EngineCtx+0x55180 after enforcing style_index<11. Corpus: 74 sites in 24 scripts; DRAWCHP has eight registrations immediately before its stylized HUD-number draws.
The five-dword definition is stored at EngineCtx+0x55180+style_index*0x14. Opcode 0x23b consumes it to turn an integer into retained draw objects, one atlas cell per decimal digit. An index outside [0,10] raises the engine's script error.
### 0x1f6 `clear-retained-gfx-objects` (clear-retained-gfx-objects, argc 0)
- **summary:** Clear the complete retained gfx-object registry while preserving allocated surface resources. Subsequent object queries return absent until draw/geometry operations recreate records.
- **grounding:** source=investigation, confidence=high
@@ -650,6 +665,14 @@ The handler requires an existing destination texture, allocates/reuses a 0x478-b
- **grounding:** source=investigation, confidence=high
- **evidence:** Native /v2 worker and gfx_object_apply_transform_channels decompile. The consumer advances target_frame cells over duration after delay, preserves the existing source-rect dimensions, and commits the endpoint.
### 0x23b `draw-decimal-glyphs` (u00422460, argc 7)
- **summary:** Draw an integer as decimal glyph objects from a style registered by opcode 0x13a.
- **grounding:** source=investigation, confidence=high
- **depends on:** 0x13a
- **evidence:** Ghidra /v2: op_0x23b_draw_decimal_glyphs@0x424190 reads the five-dword style record at EngineCtx+0x55180, erases the destination handle range, and calls gfx_object_bind_draw once per displayed digit. Corpus: 147 sites in 24 scripts; DRAWCHP contains 22 calls for the field HUD, including turn/control/mana/level/HP/SP/FS values visible as blank in the DEBUGMAP discrepancy.
First erase digit_capacity objects beginning at base_handle. Then split value by signed division/modulo 10 and bind at most digit_capacity retained objects using adjacent digit-width cells from the registered atlas. Flags bit 0 zero-pads, bit 1 centers the used digits, and bit 2 left-aligns them; with no alignment bit the value is right-aligned in the capacity. Invalid or unregistered style indices raise the engine's script error.
### 0x23d `release-transient-surfaces` (release-transient-surfaces, argc 0)
- **summary:** Stop movie bindings and release transient gfx surface slots 42 through 999 inclusive, preserving system-owned slots 0 through 41.
- **grounding:** source=investigation, confidence=high
@@ -1075,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
### 0x13a `u0041F3A0` (u0041F3A0, argc 6)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x13f `check-bit` (check-bit, argc 3)
- **summary:** —
- **grounding:** source=kelebek, confidence=med
@@ -1143,10 +1162,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
### 0x1a6 `halve-strlen` (halve-strlen, argc 2)
- **summary:** —
- **grounding:** source=kelebek, confidence=med
### 0x1a7 `comment` (comment, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=med
@@ -1219,10 +1234,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
### 0x23b `u00422460` (u00422460, argc 7)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x23c `u004162B0` (u004162B0, argc 0)
- **summary:** —
- **grounding:** source=kelebek, confidence=low

View File

@@ -434,6 +434,23 @@ that value during synchronous movie-graph initialization and retains it per movi
slots return -1; unavailable timing metadata warns and returns -1 rather than exposing native's undefined
failure output.
The next visual comparison found all field-HUD numbers absent while their labels, bars, and source artwork
were present, plus apparently blank unit/weapon text. Native and script-side tracing resolves this into two
bounded opcode gaps rather than missing game state:
- `0x13a` registers one of 11 `(surface, atlas x/y, digit width/height)` styles, and `0x23b` expands a
decimal value into retained per-digit objects with zero-pad/center/left/right layout flags. `DRAWCHP.BIN`
contains eight style registrations and 22 numeric draws covering the visible turn/control/mana/level and
HP/SP/FS fields. Both opcodes are currently unimplemented, which precisely explains the blank values.
- `DRAWCHP.BIN` already reads and submits the unit and weapon strings to `0x204`, but its two preceding
`0x1a6` centering calls are unimplemented. Native `0x1a6` returns `strlen(CP932_bytes) >> 1`; skipping it
places the strings at x=257 on a 263-pixel scratch surface, so the existing compositor clips them.
The exact native contracts and addresses live in `docs/engine-re.md`; opcode-source metadata is in
`vm-map/opcodes.toml`. No runtime implementation was made in this investigation. The next field slice is
therefore to implement the shared 11-style numeric renderer and the encoded-byte-length calculation, then
recheck the same `DEBUGMAP` HUD before pursuing any state seeding.
## Later Phase B breadth
Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks: