Skip to content

Reach the runtime fallback from the per-engine branches too - #87

Merged
rouges78 merged 1 commit into
mainfrom
claude/engine-runtime-fallback
Aug 21, 2026
Merged

Reach the runtime fallback from the per-engine branches too#87
rouges78 merged 1 commit into
mainfrom
claude/engine-runtime-fallback

Conversation

@rouges78

Copy link
Copy Markdown
Owner

#85 wired the runtime fallback where the generic workflow ends, but the per-engine branches of startAutoTranslate never get there — each returns on its own. A Ren'Py or Unreal game that failed simply stopped, and the runtime path built in #85 stayed out of reach for exactly the games most likely to need it.

Seven hook points

Five run failures — the catch blocks of Danganronpa/Spike, Hendrix, Ren'Py, Visionaire, and Tyrano/NW.js/Electron.

Two structural dead ends, which until now told the user to go find the OCR page themselves:

  • Unreal with no .locres. The text is on screen, it just is not in the files — which is what gs-hook is for.
  • RPG Maker classic (RPG_RT 2000/2003) — the textbook case. gs-hook's GDI source was tuned on exactly how RPG_RT composes a frame, and gs-hook/testapp models that back buffer glyph by glyph. The old message said "this engine exposes no extractable strings, use live OCR"; the engine it describes is the one the runtime path was designed around.

Godot is deliberately not hooked. It does not fail — it routes to a working dedicated translator. Offering a fallback there would be noise.

The two kinds of failure cannot share a message

This is the part worth reviewing. "Questo gioco non si lascia tradurre nei file" is true when the engine exposes no text, and a lie when Ollama just went down — and the catch blocks cannot tell the two apart from the exception alone.

So FallbackCause picks between that line and one that offers the runtime path without declaring the static path impossible: "La traduzione sui file non è riuscita. Se vuoi, avvia il gioco e prova la traduzione a schermo."

It only changes what we claim, never what we do. With the game already running the action and the message are identical either way, and a test pins that so the distinction cannot quietly grow into two behaviours.

Verification

895 tests (4 new), tsc --noEmit, eslint 0 errors (4 pre-existing warnings in the touched component), i18n:check at baseline 802, a11y:check at baseline 78. The new string is translated across all eleven locales rather than copied.

Each of the seven insertions is anchored on a unique existing line and asserted unique before applying — no fuzzy matching in a 4300-line component.

Still open

The fallback has not been seen firing on a real game end to end. Every link below it was verified with real injection, but this path needs an installed title that fails its engine branch. That is the next thing worth doing, and it needs a game more than it needs code.

🤖 Generated with Claude Code

#85 wired the fallback where the generic workflow ends, but the
per-engine branches of startAutoTranslate never get there: each returns
on its own. A Ren'Py or Unreal game that failed simply stopped, and the
runtime path built in #85 stayed out of reach for exactly the games most
likely to need it.

Seven hook points. Five are run failures — the catch blocks of
Danganronpa/Spike, Hendrix, Ren'Py, Visionaire and Tyrano/NW.js/Electron.
Two are structural dead ends that until now told the user to go find the
OCR page themselves:

- Unreal with no .locres. The text is on screen, it just is not in the
  files — which is what gs-hook is for.
- RPG Maker classic (RPG_RT 2000/2003), the textbook case: gs-hook's GDI
  source was tuned on exactly how RPG_RT composes a frame, and the test
  app in gs-hook/testapp models that back buffer.

Godot is deliberately not hooked: it does not fail, it routes to a
working dedicated translator.

The two kinds of failure cannot share a message. "This game resists
file-based translation" is true when the engine exposes no text and a
lie when Ollama just went down, so a FallbackCause now picks between
that line and one that offers runtime without declaring the static path
impossible. It only changes what we claim, never what we do — with the
game already running the action is identical, and a test pins that.

895 tests, tsc, eslint (0 errors), i18n and a11y gates at baseline. The
new string is translated across all eleven locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rouges78
rouges78 merged commit 45171c3 into main Aug 21, 2026
7 checks passed
@rouges78
rouges78 deleted the claude/engine-runtime-fallback branch August 21, 2026 14:08
rouges78 added a commit that referenced this pull request Aug 21, 2026
First real-game test of the runtime chain, on Father's Day (UE, Steam).

The transport half works: the DLL connects to the Rust server inside a
shipping title, and the game stays healthy — SendMessageTimeout answers,
103s CPU, 608 MB. That confirms the overlapped-I/O fix from #85 on
something other than the test app, which matters, because that bug froze
the process instantly.

The other half does not. Zero requests reach the server, because the
Unreal L1 source refuses to hook FText::ToString when its byte pattern is
ambiguous — four matches here — and declining is the right call: a wrong
hook in a shipping game means a crash. The GDI sources stay active but
see nothing, since UE draws through Slate/Direct3D. The missing link is
symbol resolution, not IPC.

The trap: "connesso a GameStringer via IPC" reads like success and is
only half the chain. The number that decides is how many requests reach
the server; at zero the problem is upstream of the IPC, and staring at
the pipe will never find it.

Carries the corollary for #87 — hooking the fallback to the "Unreal
without .locres" dead end is right in principle, but while that pattern
stays ambiguous the runtime path has no text to translate on UE. RPG_RT
and GDI games are different; that source works and has been seen working.

Second entry, on the Paks folder, from falling into the log's own trap in
a variant it did not cover: every UE game has at least two, and the first
one found is usually Engine/Programs/CrashReportClient/Content/Paks. I
measured that one and concluded REANIMAL shipped no localization — 45 MB
of crash reporter against 15.6 GB of game; TerraTech Legion, 46 against
5.5 GB. The control that catches it is The Skin Stapler, which must come
back positive at 1679 entries; while a method says otherwise, the method
is what is broken.

Docs only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rouges78 added a commit that referenced this pull request Aug 21, 2026
Installing an RPG Maker classic to test the complete flow turned up why
that flow could never have run.

GameStringer had a branch introducing itself as "RPG Maker classico
(RPG_RT 2000/2003)", with a message for the user and, since #87, the hook
into the runtime fallback. That branch was unreachable for exactly those
games. Three layers were blind the same way: engine_detector::is_rpg_maker
looked for www/data/System.json, Game.rpgproject and rgss*.dll with no
check for RPG_RT; RpgMakerVersion had no variant for 2000/2003, so
detection fell to Unknown and errored; and in the frontend that error
landed in a catch commented "detect fallito → prosegui col workflow
file-based normale", so the branch was skipped silently.

Now there is a RpgMakerVersion::RT variant, detection on the data files,
and a depth-limited search. For RT, find_data_files returns Ok(vec![]):
zero data files is not an error, it is the fact that routes these games to
runtime translation. Returning Err would fail detection and leave the
branch as unreachable as before.

Never look for the executable — plenty of RPG_RT games rename RPG_RT.exe
to the game's title, while .ldb and .lmt are never touched. And the folder
depth has to be searched: Steam installs Yume Nikki under
common/Yume Nikki/yumenikki/. find_executables_in_folder had the same gap
and now looks one level down when the root is empty, which matters because
its failure mode is worse than "not found" — callers fall back to a name
that does not exist and hunt a process that never will.

That makes four independent places where the same lesson had to be
learned: Unreal's Paks folders, the RPG Maker detector, the engine
detector, and the executable search.

Verified end to end. With Yume Nikki running, pressing "STRING IT!" took
the whole path: the app showed "RPG Maker · RT — 0 file, 0 stringhe" and
gs-hook came up with GDI sources, the preloaded dictionary and an IPC
connection. Three independent confirmations — the log written at the
instant of the click, RPG_RT.exe being 32-bit so the dual-arch selection
chose the x86 DLL, and a "GameStringer Overlay" window, which
ensure_overlay_window creates only inside the injection's success branch.
First time the path from the button to the game has been walked whole.

Four tests cover root and subfolder detection, zero-not-error extraction,
and a non-RPG-Maker folder. Full Rust suite: 1560 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant