Commit Graph

4 Commits

Author SHA1 Message Date
Matthew Stanley fe6bd20a0e launcher: config-driven Localization dropdown + 2-column Settings
Adds a general, opt-in language selector to the RmlUi launcher, driven entirely
by game.toml [localization]:

  [localization]
  default = "en"
  languages = [ {code="en", label="English"}, {code="off", label="Default"} ]

- config_loader: parse [localization].default + languages[] into RuntimeConfig;
  add UserSettings.language with settings.toml [localization] parse + write.
- launcher: a "LOCALIZATION" panel in the Settings view (cycle toggle, matching
  the Renderer/Screen-model idiom) shown only when the game declares languages.
  The chosen code persists to settings.toml and rides UserSettings.
- text_xlate: new text_xlate_set_language() re-applies the language after the
  launcher (init runs before it); "off"/"jp"/"" disable APPLY (native game).
- main: seed/read-back the language through the launcher and re-apply on every
  boot path (so a settings.toml choice sticks even when the launcher is skipped).
- Settings view laid out in two columns (VIDEO | LOCALIZATION+AUDIO+CONTROLLER)
  with SYSTEM full-width below, so it uses the horizontal space and fits without
  scrolling; #root gains overflow-y:auto as a safety net.

Only games that declare [localization] get the menu (Tsumu today); byte-identical
for every other title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:19:08 -07:00
Matthew Stanley d78e7a784f text_xlate: [[vram_patch]] pre-rendered-strip layer + GP0-ring builder attribution
Two interlocking pieces from the Tsumu HUD/menu-label investigation:

- GP0 ring builder attribution (gpu.h/gpu.c/debug_server.c): op-0x80
  entries now carry a bounded guest-stack unwind (bld[6]) + copy-time
  $sp (csp); gpu_frame_dump emits func/ra per entry and csp+bld[] for
  copies. This cracked the HUD compose: whole label strips are
  MoveImaged from the glyph sheet (src 834/852,352 -> HUD cache y=32).

- [[vram_patch]] (text_xlate.cpp/.h + gpu.c hook): third translation
  layer for text that exists only as pre-rendered pixels in an
  uploaded texture asset (Tsumu HUD labels, menus, prompts live in
  the glyph-sheet image; the 144-glyph font has no katakana at all).
  On every GP0 0xA0 upload completion the configured rects are
  verified halfword-for-halfword against expected JP pixels and only
  then rewritten with the target-language pixels via the renderer
  facade (software/GL/supersample coherent). Re-applies on every
  re-upload; verify-fail leaves foreign assets untouched. Debug:
  xlate stats gains vram_patches/vram_applied; new "vpatch"
  force-apply subcommand for config iteration without rebuild.

Verified on-screen: HUD "STAGE: 1-1 / BATTERY: 100", pause menu
(CONTINUE/RESTART/GIVE UP/CHANGE CONTROLS, TARGET BATT), title-menu
strips. docs/STRING_TRANSLATION.md Appendix Z documents the model,
mechanism, authoring recipe, and remaining strip backlog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:41:42 -07:00
Matthew Stanley f00631eaab text_xlate: per-glyph label RAM source-patch layer
Adds a third translation path to the framework for UI text drawn
glyph-by-glyph as sprites from a fixed-stride, space-padded EXE table
(Tsumu's 59-slot level/stage-name array at 0x80070300..0x800708FE).
Such labels carry no string pointer and no terminator, so the existing
dispatch/arg-scan message hook can never reach them.

Instead, [[glyph_label]] TOML entries drive a load-time RAM source patch:
once the EXE label region is resident, each confirmed slot is verified
byte-for-byte against its expected JP source bytes, then overwritten with
the target-language text (fullwidth-Latin Shift-JIS, little-endian)
re-padded with fullwidth spaces to exactly the slot width. The game's own
per-glyph routine then draws English. Verify-before-patch means a slot is
never touched unless the exact JP label is present; writing exactly
`width` bytes (== the table stride) means a patch never spills into the
adjacent slot.

- label_transcode_le(): UTF-8 -> fixed-width LE fullwidth-SJIS, truncated
  to whole cells, fw-space padded; never exceeds width.
- glyph_labels_patch_locked(): idempotent verify-and-patch scan, run from
  the dispatch hook while any label is unpatched (throttled; a single
  relaxed load once g_glyph_pending hits 0).
- Loaded/gated exactly like message entries (language column, PSX_LANG,
  apply-armed). "xlate glyph" debug subcommand reports per-slot status.

Verified (build-dbg, headless): all 59 slots patched (glyph_patched=59,
pending=0), RAM byte-exact English at TOWER/SANDBOX/JELLYFISH/LOOP THE
WARP, game alive at 26M dispatches. Framework code only; no regen, works
in Release. Table + user visual sign-off in TsumuRecomp/translations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:51:40 -07:00
Matthew Stanley 46d43555e6 runtime: on-the-fly string translation mechanism (capture + apply)
Implements the framework from docs/STRING_TRANSLATION.md: an always-on
capture inventory + a draw-time apply hook, both hung off the psx_dispatch
chokepoint via fntrace_record (no generated-code edits, no BIOS regen,
works in Release).

- text_xlate.{h,cpp}: EncodingProfile abstraction (shift_jis profile:
  SJIS record reader with NUL/0xFFFF terminators + halfwidth-kana + control
  framing; UTF-8 -> fullwidth-SJIS transcoder so English renders with the
  font's existing fullwidth Latin, zero glyph injection). Capture inventory
  (FNV-1a64 keyed) queryable over TCP; apply repoints the string-arg reg at
  guest-stack scratch (KV-gated: only known records are swapped, stray
  pointers untouched; own scratch is skipped to avoid recapture).
- fntrace.c: call text_xlate_on_dispatch() at the dispatch chokepoint.
- config_loader: [localization].language (+ [runtime].language alias);
  env PSX_LANG overrides. Loads translations/*.toml per project.
- main.cpp: text_xlate_init() at game-config load.
- debug_server: "xlate" command (stats|dump|todo|reload) — observability
  over TCP, no log files.

Verified: builds (Release), Tsumu boots headless, capture live
(distinct records accumulating), apply disabled until a table is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 11:42:07 -07:00