diff --git a/README.md b/README.md index edbffac..e29bc01 100644 --- a/README.md +++ b/README.md @@ -324,16 +324,16 @@ Open the **Workflow** tab and choose **Wolf RPG (WolfDawn)** from the engine sel | Step | Action | |------|--------| -| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 6. | +| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 7. | | **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. | | **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. | | **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` entries; `refs` and `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. | | **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. | | **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 3–4 so vocab and database terms are consistent. Configure speaker handling for low-confidence nameplate guesses. Batch mode is recommended for large `CommonEvent.dat` files. | -| **6 Inject** | Tick translated JSON files from `translated/`, then **Inject selected** or **Inject all**. Each file is reported individually; failures open a dialog with details. Uses pristine originals from `wolf_json/originals/`. | -| **7 Fix wrap** | Paste overflowing in-game text to **search** `translated/` JSON and jump to the database sheet and row (sheet names match Step 4 and `names.json` notes, e.g. `├■街の噂(MOB)`). Edit the line, set wrap width, **Wrap this row** or **Wrap all overflowing rows in this sheet**, save, then **Inject this file** (usually `DataBase.project.json`). Per-sheet widths are remembered in `wolf_json/wrap_profile.json`. **Advanced:** **wolf relayout** for event message boxes (maps/CommonEvent only — not bulletin-style DB UI) and **wolf desc-relayout** for standard 説明 description fields. | -| **8 Package** | Run from loose `Data/` (backs up `Data.wolf` → `.bak`) or repack `Data.wolf`. | -| **9 Saves** | *(Optional)* Update existing `.sav` files for the translated build. | +| **6 Precheck** | Dry-run selected JSON for safety-guard skips; fix those lines before writing binaries. | +| **7 Inject** | Tick translated JSON files from `translated/`, then **Inject selected** or **Inject all**. Each file is reported individually; failures open a dialog with details. Uses pristine originals from `wolf_json/originals/`. | +| **8 Package** | Run from loose `Data/` (backs up `Data.wolf` → `.bak`) or repack `Data.wolf` so you can playtest and see overflow in-game. Optionally rewrite existing `.sav` files so old Japanese saves load in the translated build. | +| **9 Fix wrap** | After packaging, paste overflowing in-game text to **search** `translated/` JSON and jump to the database sheet and row (sheet names match Step 4 and `names.json` notes, e.g. `├■街の噂(MOB)`). Edit the line, set wrap width, **Wrap this row** or **Wrap all overflowing rows in this sheet**, save, then **Inject this file** (usually `DataBase.project.json`) and re-package to verify. Per-sheet widths are remembered in `wolf_json/wrap_profile.json`. **Advanced:** **wolf relayout** for event message boxes (maps/CommonEvent only — not bulletin-style DB UI) and **wolf desc-relayout** for standard 説明 description fields. | ### Recommended order by game layout diff --git a/gui/wolf_workflow_tab.py b/gui/wolf_workflow_tab.py index 6a63023..f68776b 100644 --- a/gui/wolf_workflow_tab.py +++ b/gui/wolf_workflow_tab.py @@ -19,16 +19,17 @@ Mirrors the RPGMaker WorkflowTab, driven by the vendored WolfDawn ``wolf`` CLI lines before writing binaries Step 7 Inject - inject translations into Data/ binaries and refresh wolf_json/; quick-inject picks a few files or all - Step 8 Fix wrap - search translated JSON by in-game text; Relayout + Step 8 Package - run from a loose Data/ folder or repack Data.wolf; + optional save rewrite for existing .sav files + Step 9 Fix wrap - search translated JSON by in-game text; Relayout (names-wrap / cell geometry) or Manual (width + font); - inject the edited file - Step 9 Package - run from a loose Data/ folder, or repack Data.wolf - Step 10 Saves - fix baked strings in existing .sav files (optional) + inject the edited file, then re-package to verify names.json is staged into files/ but is NOT translated in the bulk phases - WolfDawn tags each name safe / refs / verify and Phase 0 translates only safe entries (per-name, not per-category), harvests them into vocab.txt, and Step 7 injects the -result. All text wrapping lives in Step 8. +result. Package (Step 8) makes the build playable; Fix wrap (Step 9) is the +playtest feedback loop. The extracted JSON is staged in ``/wolf_json/`` (a manifest maps each file back to its base binary), then imported into ``files/`` for the shared @@ -620,9 +621,8 @@ class WolfWorkflowTab(QWidget): ("5 Maps/Events", self._build_step5_maps_events), ("6 Precheck", self._build_step6_precheck), ("7 Inject", self._build_step4_inject), - ("8 Fix wrap", self._build_step7_relayout), - ("9 Package", self._build_step5_package), - ("10 Saves", self._build_step6_saves), + ("8 Package", self._build_step5_package), + ("9 Fix wrap", self._build_step7_relayout), ] self._step_labels = [label for label, _ in _tab_defs] @@ -2599,18 +2599,19 @@ class WolfWorkflowTab(QWidget): layout.addWidget(_make_hr()) layout.addWidget(self._desc( - "English text is often longer than Japanese. Step 8 (Fix wrapping): paste " - "overflowing in-game text to find the sheet, wrap, and re-inject." + "Next: Step 8 (Package) so you can playtest, then Step 9 (Fix wrapping) " + "to paste overflowing in-game text, wrap, and re-inject." )) self._refresh_inject_list() def _build_step7_relayout(self, layout: QVBoxLayout): """Search-first fix wrapping: find sheet by in-game text, wrap, re-inject.""" - layout.addWidget(_make_section_label("Step 8 · Fix wrapping")) + layout.addWidget(_make_section_label("Step 9 · Fix wrapping")) layout.addWidget(self._desc( - "Paste overflowing in-game text to find it in translated JSON. " - "Relayout uses cell width + max lines (names.json → wolf names-wrap). " - "Manual uses wrap width + body font; emphasis \\f[N] scales with the body." + "After packaging and playtesting, paste overflowing in-game text to find " + "it in translated JSON. Relayout uses cell width + max lines " + "(names.json → wolf names-wrap). Manual uses wrap width + body font; " + "emphasis \\f[N] scales with the body. Re-inject, then re-package to verify." )) search_row = QHBoxLayout() @@ -3566,9 +3567,8 @@ class WolfWorkflowTab(QWidget): "Maps", "Precheck", "Inject", - "Wrap", "Package", - "Saves", + "Wrap", ) name = short_names[idx] if 0 <= idx < len(short_names) else str(idx) mark = "✓" if done else "" @@ -3901,13 +3901,14 @@ class WolfWorkflowTab(QWidget): self._run_task(task, on_done=_after) - # ── Step 5: Package ──────────────────────────────────────────────────────── + # ── Step 8: Package (+ optional saves) ───────────────────────────────────── def _build_step5_package(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 9 · Package the Translated Game")) + layout.addWidget(_make_section_label("Step 8 · Package the Translated Game")) layout.addWidget(self._desc( - "Choose how the translated build runs. A loose Data/ folder is simplest for " - "playtesting; repacking rebuilds a single Data.wolf archive for distribution." + "Make the injected build playable so you can spot overflow in-game, then " + "fix wrapping in Step 9. A loose Data/ folder is simplest for playtesting; " + "repacking rebuilds a single Data.wolf archive for distribution." )) loose_btn = self._register(_make_btn("Use loose Data/ folder (back up archives)", "#007acc")) @@ -3928,6 +3929,28 @@ class WolfWorkflowTab(QWidget): "archive's encryption where possible (--like the backed-up original)." )) + layout.addWidget(_make_hr()) + layout.addWidget(self._subheading("Update existing saves (optional)")) + layout.addWidget(self._desc( + "WOLF .sav files bake in the game title and some strings at save time. Rewrite " + "them so old Japanese saves load cleanly in the translated build. Only needed " + "for players who already have saves, or to test against one. A timestamped " + "backup is made automatically." + )) + + row = QHBoxLayout() + self.save_edit = QLineEdit() + self.save_edit.setPlaceholderText("Save folder or .sav file…") + row.addWidget(self.save_edit, 1) + browse_btn = self._register(_make_btn("Browse…", "#3a3a3a")) + browse_btn.clicked.connect(self._browse_saves) + row.addWidget(browse_btn) + layout.addLayout(row) + + run_btn = self._register(_make_btn("Update saves", "#007acc")) + run_btn.clicked.connect(self._update_saves) + layout.addWidget(run_btn) + def _package_loose(self): if not self._require_root(): return @@ -3979,30 +4002,6 @@ class WolfWorkflowTab(QWidget): self._run_task(task) - # ── Step 10: Saves ───────────────────────────────────────────────────────── - - def _build_step6_saves(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 10 · Update Existing Saves (optional)")) - layout.addWidget(self._desc( - "WOLF .sav files bake in the game title and some strings at save time. This rewrites " - "them so old Japanese saves load cleanly in the translated build. It is only needed " - "for players who already have saves, or to test against one. A timestamped backup is " - "made automatically." - )) - - row = QHBoxLayout() - self.save_edit = QLineEdit() - self.save_edit.setPlaceholderText("Save folder or .sav file…") - row.addWidget(self.save_edit, 1) - browse_btn = self._register(_make_btn("Browse…", "#3a3a3a")) - browse_btn.clicked.connect(self._browse_saves) - row.addWidget(browse_btn) - layout.addLayout(row) - - run_btn = self._register(_make_btn("Update saves", "#007acc")) - run_btn.clicked.connect(self._update_saves) - layout.addWidget(run_btn) - def _browse_saves(self): start = self.save_edit.text() or (str(Path(self._game_root) / "Save") if self._game_root else str(Path.home())) folder = QFileDialog.getExistingDirectory(self, "Select Save Folder", start)