engine: render immediate text into surfaces
This commit is contained in:
@@ -115,8 +115,9 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
|
||||
│ └── age_movie_ffmpeg/ project-owned FFmpeg C ABI, immutable Windows dependency manifest,
|
||||
│ and bootstrap/build scripts (outputs stay under disposable build/)
|
||||
├── tools/frida/ runtime-capture + engine-dump scripts (see tools/frida/README.md)
|
||||
└── godot/ DELIVERABLE — the Godot/C# ADV front-end (references Age.Engine),
|
||||
including the TITLE-only F4 debug scene launcher
|
||||
└── godot/ DELIVERABLE — the Godot/C# ADV front-end (references Age.Engine
|
||||
plus the optional exact Windows text adapter), including the
|
||||
TITLE-only F4 debug scene launcher
|
||||
```
|
||||
|
||||
The disposable `build/page-map-<SCENE>.jsonl` files are produced by normal Godot runs and map runtime ADV
|
||||
|
||||
@@ -2961,14 +2961,14 @@ pixels differ. The screenshot is useful evidence that the difference is visible,
|
||||
behavioral source.
|
||||
|
||||
The fidelity correction is consequently a decoded glyph-mask backend, not screenshot-driven embolden
|
||||
calibration. A Windows reference implementation can call the same GDI APIs with the decoded `LOGFONTA`
|
||||
and reproduce AGE's integer compositor. A portable backend must expose the same mask/metrics/compositing
|
||||
contract while explicitly defining its font-substitution and rasterizer policy; FreeType output should
|
||||
be treated as that backend's result, not claimed to be GDI-equivalent. Godot's one-pixel line-box
|
||||
compensation remains independently supported by the matching viewport placement. Portable configurable
|
||||
face substitutions/defaults remain deferred to the broader runtime configuration design. Implementation
|
||||
is explicitly backlogged until gameplay settles; the scoped architecture and acceptance gates live in
|
||||
`docs/remake-architecture-and-roadmap.md` under “AGE-exact glyph-mask text renderer.”
|
||||
calibration. The Windows implementation calls the same GDI APIs with the decoded `LOGFONTA` and reproduces
|
||||
AGE's integer compositor. A portable backend must expose the same mask/metrics/compositing contract while
|
||||
explicitly defining its font-substitution and rasterizer policy; FreeType output should be treated as that
|
||||
backend's result, not claimed to be GDI-equivalent. Godot's one-pixel line-box compensation remains
|
||||
independently supported by the matching viewport placement. Portable configurable face
|
||||
substitutions/defaults remain deferred to the broader runtime configuration design. The scoped architecture
|
||||
and acceptance gates live in `docs/remake-architecture-and-roadmap.md` under “AGE-exact glyph-mask text
|
||||
renderer.”
|
||||
|
||||
The platform-neutral half of that correction landed on 2026-07-30 under `Age.Engine.Text`.
|
||||
`GlyphRasterRequest` carries face, positive pixel height, native requested width, weight, Unicode scalar,
|
||||
@@ -2982,8 +2982,8 @@ and returns the five-dword edge records plus final cursor, observed overflow, wr
|
||||
state. It preserves native horizontal precedence: vertical-only overflow stops before compositing, while a
|
||||
simultaneous horizontal overflow follows the wrap/kinsoku path. A reusable bounded LRU provides eviction
|
||||
callbacks for future native font handles; `CachedGlyphMaskRasterizer` applies the same bound to masks. This
|
||||
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.
|
||||
core is not yet connected to live ADV or History; those paths still use Labels pending their retained-glyph
|
||||
materialization 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
|
||||
@@ -2998,7 +2998,24 @@ code-page incompatibility and the constructor refuses to masquerade as exact whe
|
||||
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.
|
||||
now selected by Godot for immediate surface strings when `TryGetAvailability` confirms Windows ACP 932.
|
||||
|
||||
`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,
|
||||
negative half-width, and weight 700 only for bold), then advances from GDI's returned cell metrics while
|
||||
the shared compositor writes into a cloned `RgbaImage`. `GodotAdvHost.DrawStringToSurface` publishes that
|
||||
snapshot for ops `0x204` and `0x205`; successful exact draws add no `SurfaceTextDraw` metadata. Text on a
|
||||
bound surface is therefore ordinary texture data in both software and GPU retained renderers: later handles
|
||||
can occlude it, and the owning object's alpha/tint, animated fade, affine transform, source rectangle,
|
||||
offscreen capture, fill/copy, and preservation behavior apply without a detached overlay. A bounded
|
||||
2,048-mask cache sits above the already bounded GDI font cache.
|
||||
|
||||
If the exact backend is unavailable, string conversion fails, or the target surface cannot be resolved,
|
||||
the host records one explicit diagnostic and retains the old metadata/Label projection for that draw. The
|
||||
fallback is constructed before surface publication, so it cannot mix a partial new raster with the Label.
|
||||
Live ADV, History, and the wait indicator remain on their existing presentation paths for the later staged
|
||||
migration.
|
||||
|
||||
#### ADV wait indicator -- ops `0x73` / `0x72` (2026-07-11)
|
||||
|
||||
|
||||
@@ -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. `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 |
|
||||
| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. Godot now selects the separate `Age.Engine.Text.Windows` backend for immediate `0x204`/`0x205` strings when Windows ACP 932 is available; exact masks are cached and composited into numbered RGBA surfaces. Live ADV and History still use the existing `Label` path, which loads Windows `MS 明朝`/`MS ゴシック` when available and otherwise chooses the Japanese/default fallback with FreeType embolden and a Godot outline | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. Immediate surface text now obeys retained ordering, alpha/tint/fade, transforms, clipping, capture, and surface mutation in both render paths. Non-Windows/non-932 runs report why exact selection failed and atomically retain the old surface-label projection; they do not claim GDI parity | Add an explicitly non-identical portable backend and face-substitution policy, then migrate live ADV/History and remove Label compensations. Keep the GDI project 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 |
|
||||
|
||||
@@ -601,11 +601,17 @@ primitive, which cannot be made equivalent by choosing another embolden constant
|
||||
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,
|
||||
source clipping, offscreen `0x222` captures, fills/copies, and backbuffer preservation all consume the same
|
||||
pixels. This is the smallest integration slice and directly fixes the reported surface-fade defect.
|
||||
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
|
||||
a cloned numbered `RgbaImage` snapshot. Successful draws publish no `SurfaceTextDraw`, so both retained
|
||||
renderers consume the same pixels in ordinary handle order and naturally apply object alpha/tint, affine
|
||||
transforms, source clipping, capture, fill/copy, and backbuffer preservation. Synthetic integration tests
|
||||
pin later-handle occlusion, source clipping, scale/translation, static alpha/tint, animated fade, capture,
|
||||
copy, and clear. The Godot self-test additionally exercises the GDI backend, 2,048-entry mask-cache bound,
|
||||
dynamic GPU upload, and surface mutation path. Backend absence, an unrepresentable glyph, or an unresolved
|
||||
surface falls back atomically to the prior metadata/Label projection with an explicit diagnostic; that
|
||||
projection remains only for this temporary unavailable-backend case until the portable backend lands.
|
||||
5. **Materialize live ADV glyphs as ordinary retained objects.** Extend the transient layout-presentation
|
||||
binding with its source surface, first handle, and capacity without changing the persisted History record
|
||||
ABI. Build the complete line before reveal, update the canonical live cursor from measured metrics, then
|
||||
|
||||
Reference in New Issue
Block a user