engine: materialize live ADV glyphs

This commit is contained in:
gamer147
2026-07-30 19:04:22 -04:00
parent c2b72cd4b9
commit 1367563485
15 changed files with 758 additions and 27 deletions

View File

@@ -105,7 +105,8 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
│ ALF asset store, script provider, AGF/LZSS and Windows CUR decoders,
│ and resource facade
│ ├── Age.Engine/Text/ backend-neutral glyph-mask requests/results, deterministic AGE
text compositor/layout, and bounded font/glyph cache primitives
│ compositor/layout, retained live-glyph publication, and bounded
│ font/glyph cache primitives
│ ├── 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

View File

@@ -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 live ADV or History; those paths still use Labels pending their retained-glyph
materialization steps.
core now drives immediate surface strings and live ADV; retained History remains on its Label pending the
next migration step.
The independent Windows reference backend landed alongside, but outside, the neutral engine as
`Age.Engine.Text.Windows.WindowsGdiGlyphMaskRasterizer`. It holds one `CreateICA("DISPLAY")` information
@@ -3014,8 +3014,31 @@ offscreen capture, fill/copy, and preservation behavior apply without a detached
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.
History and the wait indicator remain on their existing presentation paths for the later staged migration.
Live ADV now uses the same exact surface pixels through ordinary retained objects.
`AdvTextLayoutPresentationBinding` is transient engine state: it carries the layout surface (`slot+0x14`),
first object handle, capacity, wait-indicator handle, and reset cursor without changing the persisted
History record layout. The VM passes that binding and its `GfxState` to the host for each `show-text` run,
then writes the returned GDI-measured cursor into the canonical live layout before processing another
literal/dynamic run.
`RetainedAdvTextLayoutPresentation` stores each five-dword record together with the layout origin active
when that run was built. Publication binds `first_handle+index` from the record's source edges to
`run_origin+(left,top)`, so MAMES-style origin changes do not move earlier glyphs. The complete run is
rasterized once into the layout RGBA surface; timed reveal only adds another `GfxState` binding and never
rerasterizes or reuploads the complete line. The configured capacity bounds timed publication. Click or
Skip sets the existing force-complete flag, binds every remaining publishable record, synchronizes that
endpoint, and returns without releasing the subsequent stable input wait.
Because these are normal handles, a partial `0x1f7` removes only the selected glyph. Op `0x20a` walks the
retained records through the current reveal count and recreates missing bindings. HIDEWIN suspension erases
the active page's published range and restore republishes it; layout reset erases the configured interval,
drops transient records, and zeroes the source surface. Whole scene reset/release drops the transient layout
state. Numbered History serialization still owns none of these pixels or records: the saved-frame script
path reconstructs them, and a reconstruction test proves identical handle/source/destination bindings.
Exact runs are omitted from `SnapshotLiveAdvText`, eliminating the detached always-on-top Label while the
unavailable-backend fallback continues to use that snapshot.
#### ADV wait indicator -- ops `0x73` / `0x72` (2026-07-11)

View File

@@ -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 | `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 ` 明朝`/` ゴシック` 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 |
| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. Godot selects the separate `Age.Engine.Text.Windows` backend for immediate `0x204`/`0x205` strings and live ADV when Windows ACP 932 is available; exact masks are cached, composited into layout/numbered RGBA surfaces, and live glyphs become ordinary retained handles. History still uses the existing `Label` path. The fallback loads Windows ` 明朝`/` ゴシック` when available and otherwise chooses the Japanese/default font with FreeType embolden and a Godot outline | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. Immediate and live text now obey retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, and reset. Non-Windows/non-932 runs report why exact selection failed and atomically retain the old Label presentations; they do not claim GDI parity | Add an explicitly non-identical portable backend and face-substitution policy, then migrate History/wait-indicator presentation and remove the remaining 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 |

View File

@@ -612,13 +612,22 @@ primitive, which cannot be made equivalent by choosing another embolden constant
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
bind one normal `GfxState` object per revealed record. Click/Skip publishes the remaining records and keeps
the existing consumed-click boundary. Layout reset erases its range and clears its surface; partial
`0x1f7` erases work per handle naturally; `0x20a` republishes from retained records. Consecutive
literal/dynamic runs advance by returned metrics rather than relying on Label concatenation.
5. **Materialize live ADV glyphs as ordinary retained objects.** Completed 2026-07-30 for the exact Windows
path. The VM passes `GfxState` plus the transient layout binding into presentation and accepts the measured
final cursor in return; the persisted History ABI is unchanged. `RetainedAdvTextLayoutPresentation`
retains each native edge record with the layout origin active for that run, builds the complete line into
the layout surface once, and binds `first_handle + glyph_index` in reveal order. Consecutive
literal/dynamic runs therefore begin at GDI's returned cursor instead of relying on Label concatenation.
Timed reveal stops at capacity, while click/Skip publishes the remaining available handles through the
existing consumed-click branch. Partial `0x1f7` erases ordinary handles; `0x20a` recreates all records
through the current reveal count. HIDEWIN suspension erases that range and restoration republishes it.
Layout reset erases the interval, clears its RGBA surface and transient records, and saved-frame-style
reconstruction deterministically rebuilds the same bindings. Exact runs are excluded from the live Label
snapshot; unavailable-backend runs retain it unchanged. Focused tests cover handle order/capacity,
per-run origins, partial erase/republication, suspension, measured cursor handoff, reset, and
reconstruction. The synthesized Godot VM self-test exercises timed exact reveal and reports
`live-adv-text=retained-glyphs`; real GPU captures of SC0000 pages 1 and 7 confirm ordinary and voiced
dialogue placement/wrapping alongside the immediate speaker-name surface.
6. **Migrate History and the ADV wait indicator through the same retained path.** Op `0x1d1` rasterizes and
binds into its target layout instead of creating a layout-keyed Label. Bind the configured wait atlas at
the op-`0x212` handle so it follows native z-order and source-cell animation. Once SC0000, HISTORY,