engine: add exact Windows GDI glyph backend
This commit is contained in:
@@ -106,9 +106,11 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
|
||||
│ and resource facade
|
||||
│ ├── Age.Engine/Text/ backend-neutral glyph-mask requests/results, deterministic AGE
|
||||
│ text compositor/layout, and bounded font/glyph cache primitives
|
||||
│ └── Age.Engine/Persistence/ native S3SD/S4SD + S3RT codecs, shared payload/ReadTextDB,
|
||||
│ ├── Age.Engine/Persistence/ native S3SD/S4SD + S3RT codecs, shared payload/ReadTextDB,
|
||||
│ layout-3 numbered state/history/gfx, DAT/STH pair + BMP codec,
|
||||
│ and profile-owned state
|
||||
│ └── Age.Engine.Text.Windows/ optional exact Windows GDI gray-4 reference backend; kept
|
||||
│ outside the platform-neutral VM/runtime core
|
||||
├── native/ authored native runtime boundaries
|
||||
│ └── age_movie_ffmpeg/ project-owned FFmpeg C ABI, immutable Windows dependency manifest,
|
||||
│ and bootstrap/build scripts (outputs stay under disposable build/)
|
||||
|
||||
@@ -2985,6 +2985,21 @@ callbacks for future native font handles; `CachedGlyphMaskRasterizer` applies th
|
||||
core is not yet connected to ops `0x204`/`0x205`, live ADV, History, or Godot, so the Label backend and
|
||||
visible behavior remain unchanged until the later integration steps.
|
||||
|
||||
The independent Windows reference backend landed alongside, but outside, the neutral engine as
|
||||
`Age.Engine.Text.Windows.WindowsGdiGlyphMaskRasterizer`. It holds one `CreateICA("DISPLAY")` information
|
||||
context and selects bounded, safely disposed `CreateFontIndirectA` objects carrying the requested negative
|
||||
height/width, weight, `DEFAULT_CHARSET`, and raw CP932 face bytes. Each request preserves the original one-
|
||||
or two-byte CP932 code for `GetGlyphOutlineA(GGO_GRAY4_BITMAP)` and `GetTextExtentPoint32A`; returned masks
|
||||
flow through the same normalized `GlyphMask` contract without another pixel conversion.
|
||||
|
||||
This exact ANSI reference requires Windows system ACP 932. `TryGetAvailability` reports the active-platform/
|
||||
code-page incompatibility and the constructor refuses to masquerade as exact when unavailable.
|
||||
`GlyphRasterizerBackendInfo` identifies the backend as `windows-gdi-gray4`, native-CP932, and pixel-exact.
|
||||
On the reference installation, three test requests—24px regular Mincho `あ`, 24px weight-700 Mincho `姫`,
|
||||
and 16px weight-700 Gothic `ア`—match an independently created Unicode GDI font/DC call byte-for-byte in
|
||||
coverage and exactly in black box, aligned stride, glyph origin, cell extent, and advance. The backend is
|
||||
not wired into Godot yet, so this gate validates mask provenance without changing visible rendering.
|
||||
|
||||
#### 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`
|
||||
|
||||
@@ -33,7 +33,7 @@ or replaced before 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 passes the complete 213-payload installed video/audio corpus gate plus audible LOGO/OP/CHAPTER playback and real TEST.AGF green-mask decode | 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. 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, 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 |
|
||||
| ADV font discovery/raster fidelity | Live presentation still uses `godot/Main.cs` to load Windows `MS 明朝`/`MS ゴシック` when available and otherwise choose the existing Japanese-font/default fallback; its `Label` path substitutes FreeType embolden and a Godot outline. The new `Age.Engine.Text` core is OS-neutral: requests select native-CP932 or portable-Unicode policy, mask results normalize to 0..16 coverage/GDI-style metrics, and managed code owns deterministic compositing/layout plus bounded caches | Visible behavior is unchanged. On the reference Windows install, regular advances and SC0000 placement agree, but provisional bold remains visibly lighter/softer. Synthetic masks now test the common compositor independently of any OS font; no authored runtime code calls GDI | Next add the independent Windows GDI adapter outside `Age.Engine`, then integrate immediate surface pixels. Keep explicit portable face substitution/raster policy and do not claim cross-backend pixel equivalence |
|
||||
| ADV font discovery/raster fidelity | Live presentation still uses `godot/Main.cs` to load Windows `MS 明朝`/`MS ゴシック` when available and otherwise choose the existing Japanese-font/default fallback; its `Label` path substitutes FreeType embolden and a Godot outline. `Age.Engine.Text` remains OS-neutral. The separate optional `Age.Engine.Text.Windows` reference library now owns the authored GDI calls: `CreateICA`, bounded `CreateFontIndirectA` handles, `GetTextExtentPoint32A`, and `GetGlyphOutlineA(GGO_GRAY4_BITMAP)` | Visible behavior is unchanged because the reference backend is not wired into Godot. It explicitly requires Windows ACP 932 and reports unavailable elsewhere. Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle; synthetic masks continue to test the common compositor on every platform | Integrate immediate surface pixels against this exact Windows reference, then add an explicitly non-identical portable backend/face-substitution policy. Keep the GDI project optional and out of non-Windows deliverables rather than hiding failed exact selection |
|
||||
| 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 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. `Paths.cs` remains the development/test locator for generated opcode data, diagnostics, and CLI conveniences | Installed game data no longer depends on the workspace sibling layout, enabling an executable beside `AGE.EXE`, a terminal launch from the install, or a launcher-supplied absolute profile root. A packaged export still needs its generated runtime metadata bundled independently of repository discovery | Add export packaging for the opcode/profile artifacts, let the future profile launcher pass `--game-root`, and run executable-directory/CWD plus case/permission smoke gates on Linux and macOS |
|
||||
|
||||
@@ -591,11 +591,16 @@ primitive, which cannot be made equivalent by choosing another embolden constant
|
||||
synthetic-mask tests, independent of OS fonts, pin coverage rounding, bearings/clipping, mode-1 displacement,
|
||||
mode-2 quarter coverage, mode-3 sampling/overlap, wrapping, punctuation, vertical overflow, cursor results,
|
||||
and cache eviction.
|
||||
3. **Add the independent Windows GDI reference backend.** Keep the P/Invoke adapter outside the
|
||||
platform-neutral `Age.Engine` project so the VM continues to call no OS APIs. Reproduce the decoded display
|
||||
IC, LOGFONT, measurement, and `GGO_GRAY4_BITMAP` request, and compare masks/`GLYPHMETRICS`/advances directly
|
||||
on Windows. Backend selection must be explicit and diagnostic; an unavailable exact backend falls back
|
||||
rather than silently claiming parity.
|
||||
3. **Add the independent Windows GDI reference backend.** Completed 2026-07-30 as the separate
|
||||
`Age.Engine.Text.Windows` library; the VM and platform-neutral `Age.Engine` still call no OS APIs.
|
||||
`WindowsGdiGlyphMaskRasterizer` owns a `CreateICA("DISPLAY")` information context, bounded/safely disposed
|
||||
`CreateFontIndirectA` handles, CP932 `GetTextExtentPoint32A`, identity `MAT2`, and
|
||||
`GetGlyphOutlineA(GGO_GRAY4_BITMAP)`. It accepts only explicit native-CP932 requests and reports backend id,
|
||||
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.
|
||||
4. **Move immediate surface strings first.** Make ops `0x204`/`0x205` rasterize directly into the numbered
|
||||
RGBA surface instead of appending `SurfaceTextDraw` metadata. Remove the separate surface-label projection
|
||||
only after GPU and software paths prove that ordinary handle order, object alpha/tint, affine transforms,
|
||||
|
||||
Reference in New Issue
Block a user