From 9a643e19df831c4c2cdc5868c9b776e2147ad7d6 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 2 Aug 2026 14:45:06 -0400 Subject: [PATCH] Document patch-wrapper font realization --- docs/engine-re.md | 49 ++++++++++++++++++++++--- docs/phase-a-slice-plan.md | 17 +++++++++ docs/platform-portability.md | 2 +- docs/remake-architecture-and-roadmap.md | 13 ++++++- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/engine-re.md b/docs/engine-re.md index a2259f2..ab079d1 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -2904,9 +2904,11 @@ Thus the record is a layout/publication cell, not a guaranteed ink bounding box. native record and derives a separate transient vertical publication crop from mask origin/height and effect offsets; its horizontal crop remains the measured cell to avoid exposing the next glyph during timed reveal. -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. +At the script/AGE request boundary, 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. This describes AGE's logical `LOGFONTA`; the reference launcher's realized font +is a separate boundary qualified below. 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 @@ -2939,7 +2941,8 @@ same viewport-relative top (`y=478`) and bright horizontal bounds (`x=101..410`, placement and effective advance. Native nevertheless has 1,656 bright neutral pixels versus the port's 1,145 (+45%), 1,296 near-white pixels versus 713 (+82%), a 23px rather than 22px bright box, and roughly twice the gray edge population. The port is materially underweight and softer despite requesting the -correct Mincho face. +authored Mincho face. The live qualification below shows that the wrapper-launched reference realizes that +request as Gothic, so this is an input-family mismatch rather than a Mincho weight mismatch. The discrepancy is not an unknown value to tune. The native and port pipelines are behaviorally different: @@ -2972,8 +2975,9 @@ antialiasing, light hinting, automatic subpixel positioning, embedded bitmaps di default `FontFile` policy. Bold is the unrelated `FontVariation.VariationEmbolden=0.53` operation plus one pixel of artificial glyph spacing; mode 3 is Godot `Label`'s `outline_size=1`. Thus the family, nominal size, placement, and total width can agree while stems, serifs, white-core coverage, and edge -pixels differ. The screenshot is useful evidence that the difference is visible, but it is not the -behavioral source. +pixels differ. This explains why the statically reconstructed Mincho path differs from Godot, but the live +qualification below supersedes it as the explanation for the user's native comparison: the patch-wrapper +reference selects Gothic before rasterization. The fidelity correction is consequently a decoded glyph-mask backend, not screenshot-driven embolden calibration. The Windows implementation calls the same GDI APIs with the decoded `LOGFONTA` and reproduces @@ -3014,6 +3018,39 @@ and 16px weight-700 Gothic `ア`—match an independently created Unicode GDI fo coverage and exactly in black box, aligned stride, glyph origin, cell extent, and advance. The backend is now selected by Godot for immediate surface strings when `TryGetAvailability` confirms Windows ACP 932. +**Live-output qualification (2026-07-30).** A same-line comparison at `SC0000@0x869` +(`その地には、魔王と呼ばれる者がいた。`) first disproved the stronger claim that matching the +reconstructed request already proves visible native parity. The native capture has 2,391 pixels at +luminance 240+ inside the text crop; the forced `windows-gdi-gray4` port capture has 1,347, with nearly the +same bounds. The user's port capture and a fresh forced-GDI capture have identical threshold counts at every +measured level. Forced portable is substantially narrower, while forced-GDI GPU and software captures are +effectively identical, ruling out portable substitution and retained GPU sampling. An independent +offscreen `CreateICA`/24px `MS 明朝`/width -12/weight-700 Gray-4 request plus the decoded compositor +reproduces the port capture with luminance MSE 2.68. + +A focused live Frida capture then established the missing boundary. At all 16 intercepted per-glyph raster +entries for `SC0000@0x869`, the manager still carries height `-24`, width `-12`, weight `700`, AntiFont 1, +version 3, and the UTF-8 bytes of the authored `MS 明朝` face. The `HFONT` actually selected in AGE's +display IC is different: `GetObjectA` reports face `MS Gothic`, `SHIFTJIS_CHARSET` 128, height `-24`, width +`-12`, and weight `700`; `GetTextMetricsA` reports height 24, ascent 21, descent 3, average width 13, and +maximum width 48. The intercepted `GetGlyphOutlineA` format is 5 (`GGO_GRAY4_BITMAP`) with identity `MAT2` +and 25-pixel Japanese cell increments. + +Thirteen uncached glyph masks from that line were saved at the call boundary. Replaying their CP932 codes +through an independent `MS Gothic` request matches all 13 in black box, origin, cell increment, stride, and +every 0..16 coverage byte. Both `MS Gothic` and `MS ゴシック` produce 13/13; `MS Mincho` and +`MS 明朝` produce 0/13. This is the visible flourish/stroke-weight cause. It is not a compositor, +coverage scaling, GPU, or weight problem. + +The distinction comes from the reference launch path, not the script preset. `AGE Patch.exe` loads +`jprun.dll`; that DLL imports/detours `CreateFontIndirectA` (and related font APIs) and contains repeated +hard-coded `MS Gothic` family strings. The comparison screenshots therefore show the patch-wrapper +realization of AGE's Mincho request as bold MS Gothic, not true unwrapped native rendering. +`windows-gdi-gray4` remains the correct native-default path because it reproduces AGE's authored Mincho +request directly. A future launch/profile parameter may optionally override the realized face to reproduce +wrapper-specific environments, but that is compatibility configuration rather than a native-fidelity +correction and is not current work. + `ImmediateSurfaceTextRenderer` builds the complete CP932 request list before touching the destination, making an unsupported character an atomic fallback rather than a partially rasterized string. It applies the decoded LOGFONT rebuild rules (default 24px Mincho, odd heights rounded down, 32/33 mapped to 31, diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index c2abcf8..760f1d9 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -3683,6 +3683,23 @@ remain cell-bounded so timed reveal cannot expose a neighboring glyph. Synthetic regressions pass, the Godot build is clean, and forced exact and portable self-tests both report `SELFTEST OK`. +**Live native-face follow-up (2026-07-30; investigation only):** the user-reported delicate strokes and +exposed Mincho flourishes are a family mismatch at the reference launch boundary. Their port capture is +pixel-count identical to fresh forced `windows-gdi-gray4` Mincho output; forced portable is much narrower, +forced-GDI GPU/software agree, and the port matches an independent offscreen weight-700 MS Mincho Gray-4 +request plus the decoded compositor. The native image still has 2,391 near-white text pixels versus 1,347 +in the port at `SC0000@0x869`. + +A focused live capture resolved the mismatch. AGE's manager carries the authored Mincho face bytes and +`-24/-12/700`, but `GetObjectA` on its selected `HFONT` reports `MS Gothic`, Japanese charset 128, with +24/21/3 height/ascent/descent. Thirteen uncached `GetGlyphOutlineA(GGO_GRAY4_BITMAP)` masks match an +independent bold MS Gothic request exactly in metrics and every coverage byte (13/13); Mincho matches 0/13. +The shipped `AGE Patch.exe` loads `jprun.dll`, which detours `CreateFontIndirectA` and contains hard-coded +`MS Gothic` family strings. This identifies the screenshot difference as a wrapper artifact rather than a +true-native mismatch: the port's authored Mincho request remains the correct default. No +runtime/configuration code changed. An optional future launch parameter could override the realized family +for wrapper-specific compatibility, but no text correction is required for this finding. + ## Persistence native-format reconnaissance complete (2026-07-24) The deferred save/profile ownership question now has a compatibility-mode answer. The remaining native diff --git a/docs/platform-portability.md b/docs/platform-portability.md index e295608..0820e90 100644 --- a/docs/platform-portability.md +++ b/docs/platform-portability.md @@ -49,7 +49,7 @@ claiming portable exports. | AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED; `0x24d` movie masks) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 and Linux-x64 both pass the complete 213-payload installed video/audio corpus gate. The Linux library bundle is present in a validated Godot export; Windows also passes audible LOGO/OP/CHAPTER playback and real TEST.AGF green-mask decode | Add macOS native builds, distributable source-offer packaging, and real Linux/macOS window/audio movie 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. Opcode `0x24d` redirects decoded green bytes through the platform-neutral retained rasterizer and exact managed packed-alpha mask helper | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state. Ordinary and mask movies share VFS/FFmpeg ownership; the mask result is a backend-neutral dynamic RGBA surface consumed by either Godot renderer. Managed code is no longer Windows-annotated, and target-aware builds stage either the win-x64 or Linux-x64 native bundle | Add macOS native builds and Linux/macOS in-engine 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 | -| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. On compatible ACP-932 Windows, `auto` selects the separate exact `Age.Engine.Text.Windows` GDI gray-4 backend. Otherwise Godot TextServer supplies Unicode masks and metrics from ordered system-font substitutions in `godot/config/himegari-text-rendering.json`; this policy is explicitly not GDI pixel-exact. Immediate `0x204`/`0x205`, live ADV, retained History, and the wait atlas all enter ordinary RGBA surfaces/retained handles | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. Forced exact and forced portable self-tests cover immediate, live, and History rasterization, bounded caching, complete-line materialization, retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, reset, and teardown. Gameplay `Label` pools and overlay projection are gone | Produce and smoke-test Linux/macOS exports, confirm installed family availability/fallback selection there, and keep the GDI project out of non-Windows deliverables | +| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. On compatible ACP-932 Windows, `auto` selects the separate exact-to-request `Age.Engine.Text.Windows` GDI gray-4 backend. Otherwise Godot TextServer supplies Unicode masks and metrics from ordered system-font substitutions in `godot/config/himegari-text-rendering.json`; this policy is explicitly not GDI pixel-exact. Immediate `0x204`/`0x205`, live ADV, retained History, and the wait atlas all enter ordinary RGBA surfaces/retained handles | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. A live `SC0000@0x869` trace proves that the `AGE Patch.exe`/`jprun.dll` wrapper overrides authored Mincho to MS Gothic: 13 captured masks match Gothic byte-for-byte and Mincho 0/13. This is wrapper behavior, not the true-native default. Forced exact and forced portable self-tests cover immediate, live, and History rasterization, bounded caching, complete-line materialization, retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, reset, and teardown. Gameplay `Label` pools and overlay projection are gone | Optionally expose realized-face overrides through future launch profiles when wrapper compatibility is desired; independently produce and smoke-test Linux/macOS exports, confirm installed family availability/fallback selection there, and keep the GDI project out of non-Windows deliverables | | 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/settings storage | `Sys4PersistencePaths` models AGE's independent `SAVEPATH` and `REGFILEPATH` resolutions. Godot replaces Himegari's related profile directory with `user://`, yielding `user://SAVE` for native S3SD/S4SD/S3RT files and thumbnails plus `user://SYS4REG.INI` for the BOM-less CP932 options file; the preserving writer changes only its nine `[sound]` keys | Save payloads and engine options are isolated together without changing either native format. Native/drop-in resolution remains available through `USEAPPDATAFOLDER` plus both SYS4INI paths. A single-root override is rejected when `SAVEPATH` is not beneath `REGFILEPATH`, preventing cross-profile guesses | Expose explicit profile/native selection through the future launcher and allow independent overrides for profiles whose two native paths are unrelated. Validate CP932 availability, replace/flush, case, permissions, and interrupted-write behavior on each export target | | Game-install, runtime metadata, and repository discovery | `GameRootSelection` accepts `--game-root`, then probes the executable directory and current working directory for `SYS4INI.BIN`; on Unix the frontend prefers inherited shell `PWD` because Godot may change the process directory during project startup. Godot injects the selected root into its catalog and loose-first ALF store. `Himegari.csproj` embeds generated `build/opcodes.json`, and the frontend loads that assembly resource rather than `Paths.OpcodesJson`. `Paths.cs` remains a development/test locator; its page-map use is compiled only for `TOOLS` builds | Installed game data and packaged VM metadata no longer depend on the workspace sibling layout. The Linux export passed an explicit absolute game root outside the repository and read the installed archives. Exported automatic page maps use `user://diagnostics/page-maps`; editor/development runs preserve `build/page-map-.jsonl` | Let the future profile launcher pass `--game-root`, and run executable-directory/CWD plus case/permission smoke gates on Linux and macOS | diff --git a/docs/remake-architecture-and-roadmap.md b/docs/remake-architecture-and-roadmap.md index 0b4943f..6246492 100644 --- a/docs/remake-architecture-and-roadmap.md +++ b/docs/remake-architecture-and-roadmap.md @@ -599,8 +599,14 @@ primitive, which cannot be made equivalent by choosing another embolden constant policy, exactness, and call-chain detail through `GlyphRasterizerBackendInfo`. Availability requires Windows system ACP 932; another OS/ACP reports a diagnostic reason instead of silently claiming parity. Three representative regular/bold Mincho and bold Gothic requests byte-match an independent Unicode GDI oracle - for coverage, stride, `GLYPHMETRICS`, extent, and advance. This reference backend remains opt-in and is not - yet connected to live presentation; the existing Label path therefore remains the current fallback. + for coverage, stride, `GLYPHMETRICS`, extent, and advance. A later live capture resolved the apparent + lighter-weight discrepancy: the `AGE Patch.exe`/`jprun.dll` reference launch realizes AGE's authored + 24px bold Mincho request as `MS Gothic`. Thirteen intercepted masks match an independent Gothic request + byte-for-byte and Mincho 0/13. That result is specific to the wrapper and does not supersede the true + native Mincho target; a realized-face override is optional launch-profile compatibility, not an + acceptance requirement for the exact backend. + This reference backend remains opt-in and is not yet connected to live presentation; the existing Label path + therefore remains the current fallback. 4. **Move immediate surface strings first.** Completed 2026-07-30 for the exact Windows path. Ops `0x204`/`0x205` now convert the complete Unicode string to explicit CP932 glyph identities before changing any pixels, derive AGE's rebuilt negative height/half-width and weight, and composite the exact masks into @@ -657,6 +663,9 @@ primitive, which cannot be made equivalent by choosing another embolden constant neighbors, clipping, alpha, and RGB integer rounding. - On Windows, representative CP932 glyph masks, `GLYPHMETRICS`, and advances match a direct invocation of the decoded GDI request; tests compare returned data, not screenshot histograms. +- If wrapper compatibility is requested, a launch profile may override the realized face ahead of the + raster request. Himegari's `AGE Patch.exe`/`jprun.dll` path realizes 24px authored Mincho as MS Gothic, + but the true-native acceptance target remains the authored Mincho request. - Surface-string tests prove text is occluded by later handles and inherits the bound object's alpha, tint, affine transform, source clipping, offscreen capture, and transition behavior in both render backends. - Retained-layout tests prove per-glyph handle order, capacity bounds, partial/full erase, reset/republication,