Reach the runtime fallback from the per-engine branches too - #87
Merged
Conversation
#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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#85 wired the runtime fallback where the generic workflow ends, but the per-engine branches of
startAutoTranslatenever 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:
.locres. The text is on screen, it just is not in the files — which is what gs-hook is for.gs-hook/testappmodels 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
FallbackCausepicks 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:checkat baseline 802,a11y:checkat 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