Implement native text face selection
This commit is contained in:
@@ -19,7 +19,17 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
|
|||||||
| `0x144e0` | `sfx_channel_resource_ids` | `int` | base of ten packed resource ids retained by the sound-effect facade; op 0xb4 loads a channel, op 0xb6 clears it, and numbered-save layouts restore then reopen every positive id |
|
| `0x144e0` | `sfx_channel_resource_ids` | `int` | base of ten packed resource ids retained by the sound-effect facade; op 0xb4 loads a channel, op 0xb6 clears it, and numbered-save layouts restore then reopen every positive id |
|
||||||
| `0x14d54` | `adv_text_layout_ptr_table` | `void*` | text_manager at ctx+0x14940 plus layout pointer table +0x414; ops 0x212/0x213 configure each layout's wait-indicator handle and retained glyph-object range at layout+0x64/+0x68/+0x6c |
|
| `0x14d54` | `adv_text_layout_ptr_table` | `void*` | text_manager at ctx+0x14940 plus layout pointer table +0x414; ops 0x212/0x213 configure each layout's wait-indicator handle and retained glyph-object range at layout+0x64/+0x68/+0x6c |
|
||||||
| `0x14e08` | `gfx_default_object_slot` | `int` | op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero |
|
| `0x14e08` | `gfx_default_object_slot` | `int` | op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero |
|
||||||
|
| `0x14e0c` | `text_primary_font_height` | `int` | primary LOGFONTA.lfHeight at text-manager+0x4cc; stored as a negative pixel height and updated by op 0x75 |
|
||||||
|
| `0x14e10` | `text_primary_font_width` | `int` | primary LOGFONTA.lfWidth at text-manager+0x4d0; op 0x75 stores negative half the requested font size |
|
||||||
|
| `0x14e1c` | `text_primary_font_weight` | `int` | primary LOGFONTA.lfWeight at text-manager+0x4dc; op 0x2bd writes 700 for enabled or zero for disabled |
|
||||||
|
| `0x14e84` | `text_antialias_enabled` | `int` | text-manager+0x544; message:UseAntiFont selects GGO_GRAY4_BITMAP when nonzero and GGO_BITMAP when zero |
|
||||||
|
| `0x14e88` | `text_antialias_version` | `int` | text-manager+0x548 loaded from set:AntiFontVersion; Himegari SYS4INI supplies version 3 |
|
||||||
|
| `0x14e8c` | `text_primary_color_bgr` | `uint` | text-manager+0x54c internal GDI BGR/COLORREF primary glyph color; op 0x76 converts script RGB into it |
|
||||||
|
| `0x14e90` | `text_effect_color_bgr` | `uint` | text-manager+0x550 internal GDI BGR/COLORREF effect glyph color; op 0x77 converts script RGB into it |
|
||||||
|
| `0x14e98` | `text_render_mode` | `int` | text-manager+0x558; op 0x78 selects primary-only, displaced shadow, reduced-coverage repeat, or sampled ellipse outline |
|
||||||
| `0x14ea0` | `text_line_spacing` | `int` | extra pixel leading between text lines; defaults to 6, op 0x8b writes it |
|
| `0x14ea0` | `text_line_spacing` | `int` | extra pixel leading between text lines; defaults to 6, op 0x8b writes it |
|
||||||
|
| `0x14ea4` | `text_effect_offset_x` | `int` | text-manager+0x564; op 0x1a4 horizontal displacement for mode 1 or ellipse radius for mode 3 |
|
||||||
|
| `0x14ea8` | `text_effect_offset_y` | `int` | text-manager+0x568; op 0x1a4 vertical displacement for mode 1 or ellipse radius for mode 3 |
|
||||||
| `0x14f45` | `script_frame_index` | `int` | call-script frame index (0x1e-dword frames) |
|
| `0x14f45` | `script_frame_index` | `int` | call-script frame index (0x1e-dword frames) |
|
||||||
| `0x46188` | `renderer_device_state` | `int` | embedded D3D9 device/render state; op 0x20e passes this base to the target/depth clear worker |
|
| `0x46188` | `renderer_device_state` | `int` | embedded D3D9 device/render state; op 0x20e passes this base to the target/depth clear worker |
|
||||||
| `0x46598` | `d3d9_device` | `void*` | IDirect3DDevice9 pointer at renderer_device_state+0x410; op 0x20e invokes vtable Clear +0xac |
|
| `0x46598` | `d3d9_device` | `void*` | IDirect3DDevice9 pointer at renderer_device_state+0x410; op 0x20e invokes vtable Clear +0xac |
|
||||||
@@ -94,6 +104,7 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
|
|||||||
| `0x6f8b4` | `script_launch_queue_high_water` | `int` | highest consumed cursor retained across FIFO compaction |
|
| `0x6f8b4` | `script_launch_queue_high_water` | `int` | highest consumed cursor retained across FIFO compaction |
|
||||||
| `0x6f8b8` | `script_launch_dispatch_active` | `int` | suppresses immediate dispatch while op 0x143 batches mounted append record-zero ids and while a queued script is active |
|
| `0x6f8b8` | `script_launch_dispatch_active` | `int` | suppresses immediate dispatch while op 0x143 batches mounted append record-zero ids and while a queued script is active |
|
||||||
| `0x9928c` | `save_frame_boundary_index` | `int` | highest script-frame index included by numbered-save layouts 2/3; -1 falls back to cur_ctx_index; op 0x1ad marks current frame and op 0x2 clears after unwinding below it |
|
| `0x9928c` | `save_frame_boundary_index` | `int` | highest script-frame index included by numbered-save layouts 2/3; -1 falls back to cur_ctx_index; op 0x1ad marks current frame and op 0x2 clears after unwinding below it |
|
||||||
|
| `0x99290` | `gfx_draw_mode` | `int` | loaded from set:DrawMode during runtime initialization; Himegari uses mode 1, selecting the cached GetGlyphOutline text path |
|
||||||
| `0x9b24c` | `dispatch_table` | `void*` | opcode->handler table base [0x400]; handler(op) = *(0x9b24c + op*4) |
|
| `0x9b24c` | `dispatch_table` | `void*` | opcode->handler table base [0x400]; handler(op) = *(0x9b24c + op*4) |
|
||||||
| `0x9c658` | `sys4ini_count` | `int` | SYS4INI record count at embedded FileDB+0x40c |
|
| `0x9c658` | `sys4ini_count` | `int` | SYS4INI record count at embedded FileDB+0x40c |
|
||||||
| `0x9c65c` | `archive_name_table` | `void*` | archive-name table base at embedded FileDB+0x410; arc_id*0x100 indexes it |
|
| `0x9c65c` | `archive_name_table` | `void*` | archive-name table base at embedded FileDB+0x410; arc_id*0x100 indexes it |
|
||||||
|
|||||||
@@ -2453,6 +2453,67 @@ Matching evidence: `build/native-adv-text-trace.jsonl` and Godot timeline captur
|
|||||||
non-overlapping name/dialogue bands at y=447–468, 478–500, and 507–530. A manual run progressed 14 pages:
|
non-overlapping name/dialogue bands at y=447–468, 478–500, and 507–530. A manual run progressed 14 pages:
|
||||||
11 clicks completed active reveals and 14 later clicks released 14 distinct waits through `0xe0c`.
|
11 clicks completed active reveals and 14 later clicks released 14 distinct waits through `0xe0c`.
|
||||||
|
|
||||||
|
#### Native font face, weight, antialiasing, and effects (2026-07-28)
|
||||||
|
|
||||||
|
The earlier retained-text work established *where* glyphs appear but deliberately left their final raster
|
||||||
|
appearance to Godot `Label`. Static reconstruction now identifies the complete native style selected by
|
||||||
|
Himegari and the specific reasons the port's text looks only approximate.
|
||||||
|
|
||||||
|
Himegari's decompressed SYS4INI settings trailer supplies `FONT=MS 明朝`, `DRAWMODE=1`,
|
||||||
|
`ENABLEANTIFONT=1`, and `ANTIFONTVERSION=3`. `engine_initialize_subsystems_from_settings@0x415390`
|
||||||
|
passes the face to `adv_text_manager_initialize@0x456800`, stores draw mode 1 at `ctx+0x99290`, applies
|
||||||
|
the enabled `message:UseAntiFont` default of 1 through `text_set_antialias_enabled@0x4137e0`, and stores
|
||||||
|
AntiFont version 3 at text-manager `+0x548`. The ordinary primary `LOGFONTA` begins as:
|
||||||
|
|
||||||
|
- `lfHeight=-24`, `lfWidth=-12`, `lfWeight=0`;
|
||||||
|
- no escapement/orientation, italic, underline, or strikeout;
|
||||||
|
- `DEFAULT_CHARSET`, default output/clip precision, default quality, and default pitch/family;
|
||||||
|
- `lfFaceName="MS 明朝"`.
|
||||||
|
|
||||||
|
Size opcode `0x75` keeps the negative pixel height and a negative half-width in both primary LOGFONT
|
||||||
|
states. It rounds ordinary odd requested heights down by one and special-cases 32/33 to 31, although every
|
||||||
|
Himegari call uses an even size from 8 through 24. Bold opcode `0x2bd` sets `lfWeight` to exactly 700, not
|
||||||
|
an arbitrary stroke expansion. Face opcode `0x1a5` is also effectful:
|
||||||
|
`op_0x1a5_set_font_face@0x42bbf0` calls `text_set_primary_font_face@0x42b1d0`, which copies the requested
|
||||||
|
face and constructs an `@%s` vertical-writing companion before
|
||||||
|
`text_rebuild_primary_font_resources@0x44f7f0` recreates the `CreateFontIndirectA` handles, metrics, and
|
||||||
|
glyph buffers. The 207 corpus calls use only `MS 明朝` (168) and `MS ゴシック` (39).
|
||||||
|
|
||||||
|
With Himegari's DrawMode 1 and AntiFont enabled, `GetGlyphOutlineA` requests
|
||||||
|
`GGO_GRAY4_BITMAP` with an identity `MAT2`. `text_raster_string_cached@0x45b600` caches the returned
|
||||||
|
0..16 coverage bitmap and composites it into the locked 16- or 32-bit destination with ordinary
|
||||||
|
source-over color/alpha interpolation. Layout measurement separately uses `GetTextExtentPoint32A` against
|
||||||
|
the selected GDI font, so face, explicit `lfWidth`, and weight affect advances and wrapping as well as
|
||||||
|
pixels.
|
||||||
|
|
||||||
|
The shipped effect profiles use only modes 1 and 3:
|
||||||
|
|
||||||
|
- mode 1 draws one effect-color copy at `(x+effect_x,y+effect_y)`, then the primary glyph; all 43
|
||||||
|
Himegari sites pair it with `(0,0)`;
|
||||||
|
- mode 3 samples an ellipse around the glyph. `text_compute_outline_ellipse_offset@0x459860` computes
|
||||||
|
`cos(angle)*effect_x` / `sin(angle)*effect_y`; the caller steps by
|
||||||
|
`360 / (sqrt(effect_x²+effect_y²)*8)`, blits the effect-colored glyph at every rounded offset, then
|
||||||
|
draws the primary glyph. All 164 paired sites use `(1,1)`, producing the normal one-pixel outline.
|
||||||
|
|
||||||
|
The ordinary ADV preset is therefore 24-pixel `MS 明朝`, weight 700, white fill, mode-3 `(1,1)` outline
|
||||||
|
in `0x606060`, with 8 pixels of line leading. The small menu-description preset is 16-pixel
|
||||||
|
`MS ゴシック`, weight 0, white fill, mode 1 with zero displacement, and 9 pixels of leading.
|
||||||
|
|
||||||
|
The port now carries opcode `0x1a5` through `AdvTextStyle` into live, retained, History, and surface-text
|
||||||
|
presentation. `godot/Main.cs` recognizes Himegari's two requested families and loads
|
||||||
|
`C:/Windows/Fonts/msmincho.ttc` or `msgothic.ttc` when present. Missing or unknown requested faces fall
|
||||||
|
back to the existing best-effort Japanese font probe, and regular/bold variants are cached per resolved
|
||||||
|
face so a runtime face switch cannot reuse the preceding family's synthetic-bold font. This restores the
|
||||||
|
important Mincho/Gothic distinction on a normal Windows installation without treating the proprietary
|
||||||
|
fonts as game assets.
|
||||||
|
|
||||||
|
The remaining raster behavior is still an approximation: weight 700 uses Godot
|
||||||
|
`VariationEmbolden=1.2`, and every nonzero render mode becomes a symmetric Godot outline. Mode 1 is
|
||||||
|
therefore modeled as an outline rather than its native displaced-copy operation. Exact GDI-compatible
|
||||||
|
advances, grayscale coverage, and mode-3 edge pixels require a dedicated glyph backend (or a deliberately
|
||||||
|
calibrated approximation) behind the existing engine-owned layout/style contract. Portable configurable
|
||||||
|
face substitutions/defaults are intentionally deferred to the broader runtime configuration design.
|
||||||
|
|
||||||
#### ADV wait indicator -- ops `0x73` / `0x72` (2026-07-11)
|
#### ADV wait indicator -- ops `0x73` / `0x72` (2026-07-11)
|
||||||
|
|
||||||
The small bat marker is a configured ADV-layout sprite, not a glyph or part of SO001. `SYSTEM4.BIN`
|
The small bat marker is a configured ADV-layout sprite, not a glyph or part of SO001. `SYSTEM4.BIN`
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Port status (2026-07-24): after the blocking host releases this wait, the VM que
|
|||||||
- **summary:** (pixels) - set the primary text font height and rebuild its native rasterization state.
|
- **summary:** (pixels) - set the primary text font height and rebuild its native rasterization state.
|
||||||
- **grounding:** source=investigation, confidence=high
|
- **grounding:** source=investigation, confidence=high
|
||||||
- **depended on by:** 0x205
|
- **depended on by:** 0x205
|
||||||
- **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.
|
- **evidence:** Ghidra /v2: op_0x75_set_font_size@0x41ea10 calls text_set_primary_font_size@0x415bd0 on ctx+0x14940. The worker writes the negated pixel height to both primary LOGFONT states, sets lfWidth to negative half the requested size, applies AGE's odd/32/33-pixel height adjustment, and rebuilds both GDI font resources plus glyph buffers. All shipped Himegari sizes are even and avoid the adjustment; HISTORY.BIN selects 22 pixels while active and restores 24 on exit.
|
||||||
|
|
||||||
### 0x76 `set-text-color` (set-text-color, argc 1)
|
### 0x76 `set-text-color` (set-text-color, argc 1)
|
||||||
- **summary:** (rgb) - set the current primary text color.
|
- **summary:** (rgb) - set the current primary text color.
|
||||||
@@ -60,7 +60,9 @@ Port status (2026-07-24): after the blocking host releases this wait, the VM que
|
|||||||
### 0x78 `set-text-render-mode` (set-text-render-mode, argc 1)
|
### 0x78 `set-text-render-mode` (set-text-render-mode, argc 1)
|
||||||
- **summary:** (mode) - select the current text raster/effect mode.
|
- **summary:** (mode) - select the current text raster/effect mode.
|
||||||
- **grounding:** source=investigation, confidence=high
|
- **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.
|
- **evidence:** Ghidra /v2: op_0x78_set_text_render_mode@0x41eb10 writes operand 1 to text-manager+0x558 and rebuilds font state. Himegari's DrawMode=1 path reaches text_raster_string_cached@0x45b600: mode 1 blits effect color at (+x,+y); mode 2 repeats a quarter-coverage glyph bitmap in primary color; mode 3 samples text_compute_outline_ellipse_offset@0x459860 around 360 degrees before the primary blit. The corpus uses only mode 1 (43 sites) and mode 3 (165 sites).
|
||||||
|
|
||||||
|
Mode 0 draws only the primary glyph. Mode 1 draws one effect-colored copy displaced by the configured x/y offset, then the primary glyph. Mode 2 adds a reduced-coverage primary-color bitmap pass after the ordinary glyph and is unused by Himegari scripts. Mode 3 repeatedly draws the effect glyph around an ellipse whose radii are the configured x/y extents, then draws the primary glyph; Himegari's (1,1) profile produces its one-pixel outline.
|
||||||
|
|
||||||
### 0x79 `set-adv-text-reset-cursor` (set-adv-text-reset-cursor, argc 3)
|
### 0x79 `set-adv-text-reset-cursor` (set-adv-text-reset-cursor, argc 3)
|
||||||
- **summary:** (layout_slot)(x)(y) - configure the cursor coordinates restored by a later reset of the selected ADV text layout. Slot 0 selects the current layout. This does not move the live cursor immediately; opcode 0x7a does that.
|
- **summary:** (layout_slot)(x)(y) - configure the cursor coordinates restored by a later reset of the selected ADV text layout. Slot 0 selects the current layout. This does not move the live cursor immediately; opcode 0x7a does that.
|
||||||
@@ -101,7 +103,14 @@ Port status (2026-07-24): after the blocking host releases this wait, the VM que
|
|||||||
### 0x1a4 `set-text-effect-offset` (set-text-effect-offset, argc 2)
|
### 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.
|
- **summary:** (x)(y) - set the horizontal and vertical extent/offset used by the current text effect mode.
|
||||||
- **grounding:** source=investigation, confidence=high
|
- **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).
|
- **evidence:** Ghidra /v2: op_0x1a4_set_text_effect_offset@0x41f440 writes operands 1/2 to text-manager+0x564/+0x568. Mode 1 uses them as a single displaced effect-color shadow. Mode 3 treats them as ellipse radii and samples x=cos(angle)*offset_x, y=sin(angle)*offset_y around the glyph before the primary draw. The Himegari corpus pairs mode 1 with (0,0) at all 43 sites and mode 3 with (1,1) at all 164 paired sites.
|
||||||
|
|
||||||
|
### 0x1a5 `set-font` (set-font, argc 1)
|
||||||
|
- **summary:** (face) - select the primary GDI font face, derive its vertical-writing companion, and rebuild the native font/glyph state.
|
||||||
|
- **grounding:** source=investigation, confidence=high
|
||||||
|
- **evidence:** Ghidra /v2: op_0x1a5_set_font_face@0x42bbf0 resolves operand 1 and calls text_set_primary_font_face@0x42b1d0 on ctx+0x14940. The worker copies the face to manager+0x4e8, formats @%s into the vertical LOGFONT state at +0x18e74, rebuilds CreateFontIndirectA handles/metrics/glyph buffers through text_rebuild_primary_font_resources@0x44f7f0, and maintains both names in its font-family caches. SYS4INI seeds FONT=MS 明朝.
|
||||||
|
|
||||||
|
The requested CP932 face is copied into the primary LOGFONT lfFaceName and AGE also constructs an @-prefixed vertical face. Himegari uses only the full-width Windows family names MS 明朝 (168 sites) and MS ゴシック (39 sites). This is live raster state, not a declaration/no-op.
|
||||||
|
|
||||||
### 0x1b5 `set-message-glyph-delay` (u0041B5F0, argc 1)
|
### 0x1b5 `set-message-glyph-delay` (u0041B5F0, argc 1)
|
||||||
- **summary:** (milliseconds) - set the per-glyph ADV text reveal delay; zero makes retained UI text publish immediately.
|
- **summary:** (milliseconds) - set the per-glyph ADV text reveal delay; zero makes retained UI text publish immediately.
|
||||||
@@ -173,7 +182,7 @@ Port status (2026-07-24): after the blocking host releases this wait, the VM que
|
|||||||
### 0x2bd `set-font-bold` (set-font-bold, argc 1)
|
### 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.
|
- **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
|
- **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.
|
- **evidence:** Ghidra /v2: op_0x2bd_set_font_bold@0x4251c0 writes enabled?700:0 to LOGFONT weight at text-manager+0x4dc and calls text_rebuild_primary_font_resources@0x44f7f0. It immediately follows set-font throughout the UI corpus; HISTORY.BIN selects the Mincho face with bold enabled.
|
||||||
|
|
||||||
## audio
|
## audio
|
||||||
|
|
||||||
@@ -1339,10 +1348,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
|
|||||||
- **summary:** —
|
- **summary:** —
|
||||||
- **grounding:** source=kelebek, confidence=med
|
- **grounding:** source=kelebek, confidence=med
|
||||||
|
|
||||||
### 0x1a5 `set-font` (set-font, argc 1)
|
|
||||||
- **summary:** —
|
|
||||||
- **grounding:** source=kelebek, confidence=med
|
|
||||||
|
|
||||||
### 0x1a7 `comment` (comment, argc 1)
|
### 0x1a7 `comment` (comment, argc 1)
|
||||||
- **summary:** —
|
- **summary:** —
|
||||||
- **grounding:** source=kelebek, confidence=med
|
- **grounding:** source=kelebek, confidence=med
|
||||||
|
|||||||
@@ -3329,6 +3329,20 @@ shared-profile registration.
|
|||||||
`G[0x3ebe]` (21 references across CALCREVISE, CHMENU, DRAWTIP, GAMECLEAR, GAMESTART, IMPROVE, and
|
`G[0x3ebe]` (21 references across CALCREVISE, CHMENU, DRAWTIP, GAMECLEAR, GAMESTART, IMPROVE, and
|
||||||
TUNE).
|
TUNE).
|
||||||
|
|
||||||
|
## ADV requested-face selection implemented (2026-07-28)
|
||||||
|
|
||||||
|
Opcode `0x1a5` is no longer a presentation no-op. The VM retains its requested face in
|
||||||
|
`AdvTextStyle`, so live text, retained backlog records, History replay, and surface strings all preserve
|
||||||
|
the active family. Godot resolves Himegari's two authored requests (`MS 明朝` and `MS ゴシック`) to
|
||||||
|
the corresponding Windows TTC files and caches regular/bold variants independently. If a requested face
|
||||||
|
is unavailable or unknown, presentation falls back to the existing best-effort Japanese font rather than
|
||||||
|
dropping the text or inventing a bundled font dependency.
|
||||||
|
|
||||||
|
Configurable defaults and per-face substitutions remain deferred to the general runtime configuration
|
||||||
|
design, especially for Linux/macOS where the proprietary Microsoft faces are not normally installed.
|
||||||
|
Focused regressions cover face changes flowing into both live and retained styles; the engine tests and
|
||||||
|
Godot C# build pass. User visual acceptance passed on 2026-07-28.
|
||||||
|
|
||||||
## Persistence native-format reconnaissance complete (2026-07-24)
|
## Persistence native-format reconnaissance complete (2026-07-24)
|
||||||
|
|
||||||
The deferred save/profile ownership question now has a compatibility-mode answer. The remaining native
|
The deferred save/profile ownership question now has a compatibility-mode answer. The remaining native
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ or replaced before claiming portable exports.
|
|||||||
| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 passes the complete 213-payload installed video/audio corpus gate plus audible LOGO/OP/CHAPTER playback | Add target-specific native builds and export packaging |
|
| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 passes the complete 213-payload installed video/audio corpus gate plus audible LOGO/OP/CHAPTER playback | Add target-specific native builds and export packaging |
|
||||||
| Movie integration | Each surface owns a unique playback-instance id; `MovieRuntime` owns `IMovieDecoder` from an injected factory; video-only streams use monotonic pacing while audio-bearing streams use the Godot output clock | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state; managed code is no longer Windows-annotated, while only the win-x64 native bundle exists today | Add Linux/macOS native builds and smoke gates |
|
| Movie integration | Each surface owns a unique playback-instance id; `MovieRuntime` owns `IMovieDecoder` from an injected factory; video-only streams use monotonic pacing while audio-bearing streams use the Godot output clock | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state; managed code is no longer Windows-annotated, while only the win-x64 native bundle exists today | Add Linux/macOS native builds and smoke gates |
|
||||||
| Movie audio | ABI v2 returns timestamped stereo float PCM; bounded managed buffering feeds a per-playback Godot `AudioStreamGenerator` and routes native movie flags to engine buses | All 29 installed audio-bearing streams decode with signal; synchronized LOGO/OP/CHAPTER playback is audibly accepted | Treat absent, distorted, or unsynchronized audio from an audio-bearing movie as a runtime bug |
|
| Movie audio | ABI v2 returns timestamped stereo float PCM; bounded managed buffering feeds a per-playback Godot `AudioStreamGenerator` and routes native movie flags to engine buses | All 29 installed audio-bearing streams decode with signal; synchronized LOGO/OP/CHAPTER playback is audibly accepted | Treat absent, distorted, or unsynchronized audio from an audio-bearing movie as a runtime bug |
|
||||||
| ADV font discovery | `godot/Main.cs` probes `C:/Windows/Fonts` for Japanese fonts | Harmless fallback today, but appearance depends on host fonts | Bundle/configure a redistributable font or add platform-specific discovery |
|
| ADV font discovery/raster fidelity | Opcode `0x1a5` now reaches presentation; `godot/Main.cs` loads Windows `MS 明朝`/`MS ゴシック` from their known TTC files when available, otherwise uses the existing Japanese-font/default fallback; Godot still supplies shaping and outline synthesis | A normal Windows install now preserves Himegari's Mincho/Gothic distinction and face-specific bold caches; Linux/macOS normally lack these proprietary fonts, while advances, wrapping, boldness, and outline coverage still depend on the substitute/backend | Add configurable default and requested-face substitutions as part of the broader runtime configuration design; for portable distribution bundle licensed metric-compatible faces or define profile substitutions, then decide whether exact AGE grayscale/effect rasterization warrants a custom glyph backend |
|
||||||
| Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules |
|
| Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules |
|
||||||
| Save/profile storage | Managed `DirectoryNativeDatStore` under Godot `user://SAVE`; native S3SD/S4SD/S3RT files and 24-bit BMP thumbnails | No Win32 path API at runtime; port saves remain isolated from the original installation | Validate replace/flush, case, permissions, and interrupted-write behavior on each export target |
|
| Save/profile storage | Managed `DirectoryNativeDatStore` under Godot `user://SAVE`; native S3SD/S4SD/S3RT files and 24-bit BMP thumbnails | No Win32 path API at runtime; port saves remain isolated from the original installation | Validate replace/flush, case, permissions, and interrupted-write behavior on each export target |
|
||||||
| Install/repository discovery | `engine/Age.Engine/Sys4/Paths.cs` finds `age-reimpl` above `AppContext.BaseDirectory` and assumes the current workspace sibling layout | Suitable for development, not packaged exports on any OS | Replace runtime discovery with a user-selected game root/profile; retain repository paths only for developer tools/tests |
|
| Install/repository discovery | `engine/Age.Engine/Sys4/Paths.cs` finds `age-reimpl` above `AppContext.BaseDirectory` and assumes the current workspace sibling layout | Suitable for development, not packaged exports on any OS | Replace runtime discovery with a user-selected game root/profile; retain repository paths only for developer tools/tests |
|
||||||
|
|||||||
@@ -9,6 +9,32 @@ using Xunit;
|
|||||||
|
|
||||||
public class AdvTextOpsTests
|
public class AdvTextOpsTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SetFontFaceFlowsIntoLiveAndRetainedTextStyles()
|
||||||
|
{
|
||||||
|
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||||
|
static Operand I(long value) => new(0, value);
|
||||||
|
static Operand S(int index) => new(2, index);
|
||||||
|
var script = ScriptAssembler.Assemble(table, "ADV_FONT_FACE",
|
||||||
|
new List<(int, Operand[])>
|
||||||
|
{
|
||||||
|
(0x1a5, new[] { S(0) }),
|
||||||
|
(0x6e, new[] { I(0), S(2) }),
|
||||||
|
(0x1a5, new[] { S(1) }),
|
||||||
|
(0x6e, new[] { I(0), S(3) }),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
}, new[] { "MS 明朝", "MS ゴシック", "mincho", "gothic" });
|
||||||
|
var host = new RecordingHost();
|
||||||
|
var vm = new VirtualMachine(script, table, host);
|
||||||
|
|
||||||
|
vm.Run();
|
||||||
|
|
||||||
|
Assert.Equal(new[] { "MS 明朝", "MS ゴシック" },
|
||||||
|
host.LiveTextRuns.Select(run => run.Run.Style.FontFace));
|
||||||
|
Assert.Equal(new[] { "MS 明朝", "MS ゴシック" },
|
||||||
|
vm.TextHistory.Records.Select(record => record.Style.FontFace));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TextCursorAndDrawStringReachHostWithLocalStringPointer()
|
public void TextCursorAndDrawStringReachHostWithLocalStringPointer()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class HistoryPresentationOpsTests
|
|||||||
history.DefineLayout(1, 640, 160, 80, 430);
|
history.DefineLayout(1, 640, 160, 80, 430);
|
||||||
history.AppendMetadata(9, 2, AdvTextStyle.Default);
|
history.AppendMetadata(9, 2, AdvTextStyle.Default);
|
||||||
history.AppendText(0, 10, "retained dialogue", new AdvTextStyle(24, 8, false,
|
history.AppendText(0, 10, "retained dialogue", new AdvTextStyle(24, 8, false,
|
||||||
0xffffff, 0x606060, 3, 1, 1, 9));
|
0xffffff, 0x606060, 3, 1, 1, 9, "MS 明朝"));
|
||||||
history.ResetLayout(1);
|
history.ResetLayout(1);
|
||||||
history.AppendText(0, 11, "next group", AdvTextStyle.Default);
|
history.AppendText(0, 11, "next group", AdvTextStyle.Default);
|
||||||
history.SetRecordingEnabled(false);
|
history.SetRecordingEnabled(false);
|
||||||
@@ -42,9 +42,9 @@ public class HistoryPresentationOpsTests
|
|||||||
Assert.Equal((4, 0, "retained dialogue"),
|
Assert.Equal((4, 0, "retained dialogue"),
|
||||||
(render.LayoutSlot, render.FirstRecordIndex, render.Text));
|
(render.LayoutSlot, render.FirstRecordIndex, render.Text));
|
||||||
Assert.Equal(new AdvTextLayoutSnapshot(4, 600, 150, 65, 150, 45, 42, 600, 150), render.Layout);
|
Assert.Equal(new AdvTextLayoutSnapshot(4, 600, 150, 65, 150, 45, 42, 600, 150), render.Layout);
|
||||||
Assert.Equal((24, 0xffffffL, 0x606060L, 8),
|
Assert.Equal((24, 0xffffffL, 0x606060L, 8, "MS 明朝"),
|
||||||
(render.Style.PrimaryFontSize, render.Style.TextColor, render.Style.EffectColor,
|
(render.Style.PrimaryFontSize, render.Style.TextColor, render.Style.EffectColor,
|
||||||
render.Style.LineSpacing));
|
render.Style.LineSpacing, render.Style.FontFace));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ public readonly record struct AdvTextStyle(
|
|||||||
int RenderMode,
|
int RenderMode,
|
||||||
int EffectOffsetX,
|
int EffectOffsetX,
|
||||||
int EffectOffsetY,
|
int EffectOffsetY,
|
||||||
int LineSpacing)
|
int LineSpacing,
|
||||||
|
string FontFace)
|
||||||
{
|
{
|
||||||
public static AdvTextStyle Default => new(0, 0, false, 0, 0, 0, 0, 0, 6);
|
public static AdvTextStyle Default => new(0, 0, false, 0, 0, 0, 0, 0, 6, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>A stable snapshot of the layout state associated with a retained record.</summary>
|
/// <summary>A stable snapshot of the layout state associated with a retained record.</summary>
|
||||||
|
|||||||
@@ -2269,6 +2269,7 @@ public sealed class VirtualMachine
|
|||||||
case "end-text-line":
|
case "end-text-line":
|
||||||
TextHistory.EndLine((int)Read(a[0]), _advTextStyle); return pc + 1;
|
TextHistory.EndLine((int)Read(a[0]), _advTextStyle); return pc + 1;
|
||||||
case "set-font":
|
case "set-font":
|
||||||
|
_advTextStyle = _advTextStyle with { FontFace = ReadStr(a[0]) }; return pc + 1;
|
||||||
case "comment": case "display-furigana": case "dev_ukn":
|
case "comment": case "display-furigana": case "dev_ukn":
|
||||||
return pc + 1;
|
return pc + 1;
|
||||||
case "create-texture": // 0x1f8 (slot)(w)(h) — allocate a blank surface at the slot
|
case "create-texture": // 0x1f8 (slot)(w)(h) — allocate a blank surface at the slot
|
||||||
|
|||||||
@@ -34,8 +34,13 @@ public partial class Main : Godot.Control
|
|||||||
private readonly System.Collections.Generic.List<Label> _advTextLabels = new();
|
private readonly System.Collections.Generic.List<Label> _advTextLabels = new();
|
||||||
private readonly System.Collections.Generic.List<Label> _surfaceTextLabels = new();
|
private readonly System.Collections.Generic.List<Label> _surfaceTextLabels = new();
|
||||||
private readonly System.Collections.Generic.Dictionary<int, Label> _historyTextLabels = new();
|
private readonly System.Collections.Generic.Dictionary<int, Label> _historyTextLabels = new();
|
||||||
private Font? _presentationRegularFont;
|
private Font? _presentationFallbackFont;
|
||||||
private FontVariation? _presentationBoldFont;
|
private readonly Dictionary<string, Font> _presentationFaceFonts =
|
||||||
|
new(System.StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly Dictionary<string, FontVariation> _presentationBoldFonts =
|
||||||
|
new(System.StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _unavailablePresentationFaces =
|
||||||
|
new(System.StringComparer.OrdinalIgnoreCase);
|
||||||
private Label _status = null!;
|
private Label _status = null!;
|
||||||
private Label _locatorHud = null!;
|
private Label _locatorHud = null!;
|
||||||
private AudioStreamPlayer _bgm = null!; // looping background music
|
private AudioStreamPlayer _bgm = null!; // looping background music
|
||||||
@@ -146,6 +151,7 @@ public partial class Main : Godot.Control
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ff = new FontFile { Data = System.IO.File.ReadAllBytes(fp) };
|
var ff = new FontFile { Data = System.IO.File.ReadAllBytes(fp) };
|
||||||
|
_presentationFallbackFont = ff;
|
||||||
_text.AddThemeFontOverride("font", ff);
|
_text.AddThemeFontOverride("font", ff);
|
||||||
_speaker.AddThemeFontOverride("font", ff);
|
_speaker.AddThemeFontOverride("font", ff);
|
||||||
_status.AddThemeFontOverride("font", ff);
|
_status.AddThemeFontOverride("font", ff);
|
||||||
@@ -1367,17 +1373,21 @@ public partial class Main : Godot.Control
|
|||||||
private void ApplyAdvTextStyle(Label label, AdvTextStyle style)
|
private void ApplyAdvTextStyle(Label label, AdvTextStyle style)
|
||||||
{
|
{
|
||||||
int fontSize = style.PrimaryFontSize > 0 ? style.PrimaryFontSize : 24;
|
int fontSize = style.PrimaryFontSize > 0 ? style.PrimaryFontSize : 24;
|
||||||
_presentationRegularFont ??= _text.GetThemeFont("font");
|
Font regularFont = ResolvePresentationFont(style.FontFace, out string fontKey);
|
||||||
if (style.Bold)
|
if (style.Bold)
|
||||||
{
|
{
|
||||||
_presentationBoldFont ??= new FontVariation
|
if (!_presentationBoldFonts.TryGetValue(fontKey, out FontVariation? boldFont))
|
||||||
{
|
{
|
||||||
BaseFont = _presentationRegularFont,
|
boldFont = new FontVariation
|
||||||
VariationEmbolden = 1.2f,
|
{
|
||||||
};
|
BaseFont = regularFont,
|
||||||
label.AddThemeFontOverride("font", _presentationBoldFont);
|
VariationEmbolden = 1.2f,
|
||||||
|
};
|
||||||
|
_presentationBoldFonts.Add(fontKey, boldFont);
|
||||||
|
}
|
||||||
|
label.AddThemeFontOverride("font", boldFont);
|
||||||
}
|
}
|
||||||
else label.AddThemeFontOverride("font", _presentationRegularFont);
|
else label.AddThemeFontOverride("font", regularFont);
|
||||||
label.AddThemeFontSizeOverride("font_size", fontSize);
|
label.AddThemeFontSizeOverride("font_size", fontSize);
|
||||||
label.AddThemeColorOverride("font_color", RgbColor(style.TextColor, Colors.White));
|
label.AddThemeColorOverride("font_color", RgbColor(style.TextColor, Colors.White));
|
||||||
label.AddThemeColorOverride("font_outline_color", RgbColor(style.EffectColor, new Color(0.38f, 0.38f, 0.38f)));
|
label.AddThemeColorOverride("font_outline_color", RgbColor(style.EffectColor, new Color(0.38f, 0.38f, 0.38f)));
|
||||||
@@ -1387,6 +1397,57 @@ public partial class Main : Godot.Control
|
|||||||
label.AddThemeConstantOverride("outline_size", outline);
|
label.AddThemeConstantOverride("outline_size", outline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Font ResolvePresentationFont(string requestedFace, out string fontKey)
|
||||||
|
{
|
||||||
|
_presentationFallbackFont ??= _text.GetThemeFont("font");
|
||||||
|
string face = requestedFace?.Trim() ?? "";
|
||||||
|
if (face.Length == 0)
|
||||||
|
{
|
||||||
|
fontKey = "";
|
||||||
|
return _presentationFallbackFont;
|
||||||
|
}
|
||||||
|
if (_presentationFaceFonts.TryGetValue(face, out Font? cached))
|
||||||
|
{
|
||||||
|
fontKey = face;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
if (!_unavailablePresentationFaces.Contains(face))
|
||||||
|
{
|
||||||
|
foreach (string path in PresentationFontPaths(face))
|
||||||
|
{
|
||||||
|
if (!System.IO.File.Exists(path)) continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var loaded = new FontFile { Data = System.IO.File.ReadAllBytes(path) };
|
||||||
|
_presentationFaceFonts.Add(face, loaded);
|
||||||
|
fontKey = face;
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Try the next known file before falling back to the presentation default.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_unavailablePresentationFaces.Add(face);
|
||||||
|
}
|
||||||
|
fontKey = "";
|
||||||
|
return _presentationFallbackFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> PresentationFontPaths(string face)
|
||||||
|
{
|
||||||
|
if (face.Equals("MS 明朝", System.StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| face.Equals("MS Mincho", System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
yield return "C:/Windows/Fonts/msmincho.ttc";
|
||||||
|
}
|
||||||
|
else if (face.Equals("MS ゴシック", System.StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| face.Equals("MS Gothic", System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
yield return "C:/Windows/Fonts/msgothic.ttc";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Color RgbColor(long rgb, Color fallback)
|
private static Color RgbColor(long rgb, Color fallback)
|
||||||
{
|
{
|
||||||
if ((rgb & 0x00ff_ffff) == 0) return fallback;
|
if ((rgb & 0x00ff_ffff) == 0) return fallback;
|
||||||
|
|||||||
@@ -63,11 +63,61 @@ name = "gfx_default_object_slot"
|
|||||||
type = "int"
|
type = "int"
|
||||||
note = "op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero"
|
note = "op 0x80 selected slot; op 0x1d9 substitutes it when its explicit object-slot operand is zero"
|
||||||
[[field]]
|
[[field]]
|
||||||
|
offset = 0x14e0c
|
||||||
|
name = "text_primary_font_height"
|
||||||
|
type = "int"
|
||||||
|
note = "primary LOGFONTA.lfHeight at text-manager+0x4cc; stored as a negative pixel height and updated by op 0x75"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e10
|
||||||
|
name = "text_primary_font_width"
|
||||||
|
type = "int"
|
||||||
|
note = "primary LOGFONTA.lfWidth at text-manager+0x4d0; op 0x75 stores negative half the requested font size"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e1c
|
||||||
|
name = "text_primary_font_weight"
|
||||||
|
type = "int"
|
||||||
|
note = "primary LOGFONTA.lfWeight at text-manager+0x4dc; op 0x2bd writes 700 for enabled or zero for disabled"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e84
|
||||||
|
name = "text_antialias_enabled"
|
||||||
|
type = "int"
|
||||||
|
note = "text-manager+0x544; message:UseAntiFont selects GGO_GRAY4_BITMAP when nonzero and GGO_BITMAP when zero"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e88
|
||||||
|
name = "text_antialias_version"
|
||||||
|
type = "int"
|
||||||
|
note = "text-manager+0x548 loaded from set:AntiFontVersion; Himegari SYS4INI supplies version 3"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e8c
|
||||||
|
name = "text_primary_color_bgr"
|
||||||
|
type = "uint"
|
||||||
|
note = "text-manager+0x54c internal GDI BGR/COLORREF primary glyph color; op 0x76 converts script RGB into it"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e90
|
||||||
|
name = "text_effect_color_bgr"
|
||||||
|
type = "uint"
|
||||||
|
note = "text-manager+0x550 internal GDI BGR/COLORREF effect glyph color; op 0x77 converts script RGB into it"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14e98
|
||||||
|
name = "text_render_mode"
|
||||||
|
type = "int"
|
||||||
|
note = "text-manager+0x558; op 0x78 selects primary-only, displaced shadow, reduced-coverage repeat, or sampled ellipse outline"
|
||||||
|
[[field]]
|
||||||
offset = 0x14ea0
|
offset = 0x14ea0
|
||||||
name = "text_line_spacing"
|
name = "text_line_spacing"
|
||||||
type = "int"
|
type = "int"
|
||||||
note = "extra pixel leading between text lines; defaults to 6, op 0x8b writes it"
|
note = "extra pixel leading between text lines; defaults to 6, op 0x8b writes it"
|
||||||
[[field]]
|
[[field]]
|
||||||
|
offset = 0x14ea4
|
||||||
|
name = "text_effect_offset_x"
|
||||||
|
type = "int"
|
||||||
|
note = "text-manager+0x564; op 0x1a4 horizontal displacement for mode 1 or ellipse radius for mode 3"
|
||||||
|
[[field]]
|
||||||
|
offset = 0x14ea8
|
||||||
|
name = "text_effect_offset_y"
|
||||||
|
type = "int"
|
||||||
|
note = "text-manager+0x568; op 0x1a4 vertical displacement for mode 1 or ellipse radius for mode 3"
|
||||||
|
[[field]]
|
||||||
offset = 0x14f45
|
offset = 0x14f45
|
||||||
name = "script_frame_index"
|
name = "script_frame_index"
|
||||||
type = "int"
|
type = "int"
|
||||||
@@ -448,6 +498,11 @@ name = "save_frame_boundary_index"
|
|||||||
type = "int"
|
type = "int"
|
||||||
note = "highest script-frame index included by numbered-save layouts 2/3; -1 falls back to cur_ctx_index; op 0x1ad marks current frame and op 0x2 clears after unwinding below it"
|
note = "highest script-frame index included by numbered-save layouts 2/3; -1 falls back to cur_ctx_index; op 0x1ad marks current frame and op 0x2 clears after unwinding below it"
|
||||||
[[field]]
|
[[field]]
|
||||||
|
offset = 0x99290
|
||||||
|
name = "gfx_draw_mode"
|
||||||
|
type = "int"
|
||||||
|
note = "loaded from set:DrawMode during runtime initialization; Himegari uses mode 1, selecting the cached GetGlyphOutline text path"
|
||||||
|
[[field]]
|
||||||
offset = 0x9b24c
|
offset = 0x9b24c
|
||||||
name = "dispatch_table"
|
name = "dispatch_table"
|
||||||
type = "void*"
|
type = "void*"
|
||||||
|
|||||||
@@ -1116,7 +1116,7 @@ noop_headless = false
|
|||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
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."
|
evidence = "Ghidra /v2: op_0x75_set_font_size@0x41ea10 calls text_set_primary_font_size@0x415bd0 on ctx+0x14940. The worker writes the negated pixel height to both primary LOGFONT states, sets lfWidth to negative half the requested size, applies AGE's odd/32/33-pixel height adjustment, and rebuilds both GDI font resources plus glyph buffers. All shipped Himegari sizes are even and avoid the adjustment; HISTORY.BIN selects 22 pixels while active and restores 24 on exit."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
@@ -1175,11 +1175,12 @@ abi_source = "kelebek+decode-validated"
|
|||||||
name = "set-text-render-mode"
|
name = "set-text-render-mode"
|
||||||
category = "adv"
|
category = "adv"
|
||||||
summary = "(mode) - select the current text raster/effect mode."
|
summary = "(mode) - select the current text raster/effect mode."
|
||||||
|
details = "Mode 0 draws only the primary glyph. Mode 1 draws one effect-colored copy displaced by the configured x/y offset, then the primary glyph. Mode 2 adds a reduced-coverage primary-color bitmap pass after the ordinary glyph and is unused by Himegari scripts. Mode 3 repeatedly draws the effect glyph around an ellipse whose radii are the configured x/y extents, then draws the primary glyph; Himegari's (1,1) profile produces its one-pixel outline."
|
||||||
noop_headless = false
|
noop_headless = false
|
||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
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."
|
evidence = "Ghidra /v2: op_0x78_set_text_render_mode@0x41eb10 writes operand 1 to text-manager+0x558 and rebuilds font state. Himegari's DrawMode=1 path reaches text_raster_string_cached@0x45b600: mode 1 blits effect color at (+x,+y); mode 2 repeats a quarter-coverage glyph bitmap in primary color; mode 3 samples text_compute_outline_ellipse_offset@0x459860 around 360 degrees before the primary blit. The corpus uses only mode 1 (43 sites) and mode 3 (165 sites)."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
@@ -3577,7 +3578,7 @@ noop_headless = false
|
|||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
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)."
|
evidence = "Ghidra /v2: op_0x1a4_set_text_effect_offset@0x41f440 writes operands 1/2 to text-manager+0x564/+0x568. Mode 1 uses them as a single displaced effect-color shadow. Mode 3 treats them as ellipse radii and samples x=cos(angle)*offset_x, y=sin(angle)*offset_y around the glyph before the primary draw. The Himegari corpus pairs mode 1 with (0,0) at all 43 sites and mode 3 with (1,1) at all 164 paired sites."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
@@ -3597,17 +3598,18 @@ abi_source = "kelebek+decode-validated"
|
|||||||
|
|
||||||
[opcode.semantics]
|
[opcode.semantics]
|
||||||
name = "set-font"
|
name = "set-font"
|
||||||
category = "unknown"
|
category = "adv"
|
||||||
summary = ""
|
summary = "(face) - select the primary GDI font face, derive its vertical-writing companion, and rebuild the native font/glyph state."
|
||||||
|
details = "The requested CP932 face is copied into the primary LOGFONT lfFaceName and AGE also constructs an @-prefixed vertical face. Himegari uses only the full-width Windows family names MS 明朝 (168 sites) and MS ゴシック (39 sites). This is live raster state, not a declaration/no-op."
|
||||||
noop_headless = false
|
noop_headless = false
|
||||||
source = "kelebek"
|
source = "investigation"
|
||||||
confidence = "med"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
evidence = ""
|
evidence = "Ghidra /v2: op_0x1a5_set_font_face@0x42bbf0 resolves operand 1 and calls text_set_primary_font_face@0x42b1d0 on ctx+0x14940. The worker copies the face to manager+0x4e8, formats @%s into the vertical LOGFONT state at +0x18e74, rebuilds CreateFontIndirectA handles/metrics/glyph buffers through text_rebuild_primary_font_resources@0x44f7f0, and maintains both names in its font-family caches. SYS4INI seeds FONT=MS 明朝."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
role = ""
|
role = "CP932 GDI face name"
|
||||||
observed_types = ["string"]
|
observed_types = ["string"]
|
||||||
|
|
||||||
[[opcode]]
|
[[opcode]]
|
||||||
@@ -6765,7 +6767,7 @@ noop_headless = false
|
|||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
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."
|
evidence = "Ghidra /v2: op_0x2bd_set_font_bold@0x4251c0 writes enabled?700:0 to LOGFONT weight at text-manager+0x4dc and calls text_rebuild_primary_font_resources@0x44f7f0. It immediately follows set-font throughout the UI corpus; HISTORY.BIN selects the Mincho face with bold enabled."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user