Version 3.4.0: Edit tab (edit list, large subtitle, replacements with effects and presets), Sounds tab only browses, ackit fx (pedalboard + own reverb) and replace --fx, first Linux build, catalog without a mod package

This commit is contained in:
2026-10-28 13:54:12 +01:00
parent a706dae419
commit 6de470174e
36 changed files with 2872 additions and 278 deletions

View File

@@ -167,8 +167,84 @@
dist/UnheardCity-3.3.0-windows-amd64.zip (86 MB) smoke test PASS with PATH = C:\Windows only (21,015 sounds,
9,377 subtitles, 7,020 menu texts, ITA), GUI start/quit PASS.
- DONE by the user in game: replaced voice lines, edited subtitles and replaced footsteps (after the fill fix).
- OPEN: channel order for >2 channels unverified; 19 speaker labels to check by listening; the Linux build was
never built or run (no Linux here).
- Mission 4 (2026-10-27, plan: `docs/history/mission4_dubbing_plan.md`): dubbing every character's lines with
the game's voice effects, inside Unheard City (Dubbing tab), engine in ackit, effects from pedalboard.
Stage 0 (evidence, read-only) IN PROGRESS on the Linux container: HIRC effect survey (effect slots of sounds,
containers, buses; effect objects) + signal features of every speech recording, then listening per group.
2026-10-27 first results (scratch scripts, INT; not yet in docs/check_docs): v56 layouts from wwiser (node
base params: u8 override, u8 count, [u8 bypass, count x {u8 index, u32 fx id, u8 shareset, u8 rendered}],
u32 bus, u32 parent; Sound: after the source + u8 source bits; bus: u8 isEnv at +33, ducks 17 B, then fx).
Effect objects (type 0x12/0x13): RoomVerb 84 + Matrix Reverb 6 + Delay/Meter/ML1/EQ all in Init (env
reverbs = run time); McDSP FutzBox share-set 4272216A in 1,125 speech banks, on 464 actor-mixer slots, run
time with RTPC curves -> 2,858 speech rows (2,358 WAD + 500 in-memory) are clean in the files (wide band;
record dry). Rendered (baked) slots: 33 speech rows (car radio 25, radio scanner 3, museum thug-to-Penguin 4,
Steel HQ 1); their fx id is 0 (type lost). Every speech row goes through the ML1 limiter on the top bus.
Most baked effects are studio-made (no metadata): signal features vs each speaker's widest-band lines AND vs
all speakers (a speaker whose every line is processed has no dry baseline: Mr. Freeze was missed by the
speaker-relative test alone) + a "dark" flag (< 3 kHz, Clayface) give 77 groups of >= 6 lines (169 in all,
3,273 recordings; filters: Freeze, Strange/Mayor PA, Joker PRED, Two-Face court, radio scanner, Penguin,
Zsasz, copter, Oracle, tapes; echo: Penguin exhibit, Riddler cameo, Strange TV; multichannel layouts).
Not measured yet: pitch/formant shift, distortion, modulation (tremolo/flanger), noise beds; every flagged
line is listed in `flagged_lines.csv` so small groups are not lost.
Stage 0 DONE (2026-10-27): the user listened to the 77 groups (results + scope change in the plan, sections
9-10): manual controls for every effect heard, no automatic profiles/FX.csv.
- Mission 4 stages 1-2 DONE (2026-10-27, Linux container): `ackit/fx.py` (pedalboard 0.9.26 + numpy; 10 blocks:
pitch/formant/drift, octave layers, robot, glitch, voice band EQ (Butterworth via FFT: pedalboard's cuts are
first order and cost 3.7 dB in the pass band when stacked), distortion/GSM, noise, echo, reverb + reverse
swells, channel layout in WAVE mask order; seeded; `BLOCKS` drives the UI), CLI `ackit fx-template` +
`replace --fx`; Dubbing tab (`app/dubbing.py`, replaced the same day by the Edit tab, below: speech list with character/category/dub-state filters, a second
EditorPanel with the effects rack, takes + settings per sound key in user data `dubs/`, "use these effects for
the selected lines"); EditorPanel renders effects in the background (cached) between trim and loudness, apply
meta records the effects; group-box indicators styled like checkboxes. 170 tests (19 fx, 2 dubbing GUI, 1 CLI);
`tools/e2e_features.py --lang INT` 10/10 incl. new check 8 (5-ch dubbed line: decoded from the game file, C dry,
SL echo 0.160 s later; reverted byte-identical); game original after, `verify --catalog --quick` OK;
check_docs all match; smoke test renders effects; first Linux build (`dist/UnheardCity-3.3.0-linux-x86_64.tar.gz`,
124 MB) smoke + GUI start PASS with PATH=/usr/bin:/bin. User guide: Dubbing section + docs/img/dubbing.png
(`tools/screenshots.py --only dubbing`; the other images were not re-rendered on Linux).
NEXT: user tries the tab and tunes the effect defaults by ear; Windows build + in-game listening of a dubbed
line (needs the Windows PC); version bump (3.4.0) when released. Not done: hint per line that the game adds an
effect at run time (FutzBox lines; data known from stage 0, would need the HIRC survey in the catalog build).
- 2026-10-27 UI revision (user: subtitle box too small, Sounds and Dubbing redundant, effects panel "raw",
reverb quality): browsing and editing split. Sounds = browser + compact preview (app/preview.py: details,
subtitle text, original/installed waveforms, Edit / Add to edit list; Enter = Edit); Dubbing renamed Edit and
made universal (app/edit.py: edit list of the sounds sent from the browser, Previous/Next, Alt+Left/Right,
takes + settings in user data `edits/`, old `dubs/` moved there once); editor relaid out for the Edit tab
(SCRIPT = ScriptBox, large 3-line text + original + English; LISTEN rows with play buttons; YOUR TAKE; action
bar pinned below the scroll area); effects panel app/effects.py (looks, per-block preset buttons, sliders with
readable values, log for Hz/ms/s, double-click reset, wheel only when focused); fx: own reverb (generated room
response, 4 bands, early reflections; RT60 measured back within 15 %) instead of Freeverb, PRESETS + LOOKS.
`MainWindow.editor` = the Edit tab's editor. 175 tests (with the real game); check_docs match; smoke + Linux
package PASS; docs/img main/browse/editor re-rendered on Linux (dubbing.png removed); e2e_editor uses a
temporary edit store.
- 2026-10-27 second UI round (user: preview player buttons scattered; no scrolling in the Edit tab; generic
terms, not dubbing ones): preview = details | player (Play original, Play installed, Stop together above the
waveforms) | Edit, Add to edit list. Effects panel = preset list + effect chain (QListWidget: tick = on, click
= settings) + one settings page (QStackedWidget); moving a slider turns its effect on. Editor compacted
(2-line subtitle, room counter in the button row, 44 px waveforms that grow with the window, tighter
spacing, edit list 250 px): no scroll bars at 1400 x 900 and 1280 x 800 (GUI test). Wording: replacement
(not take), effects (not voice effects), Subtitle (not script), Band-pass EQ; code too (EditStore
has/load/save_replacement, item field `source`, folder `edits/replacements`; earlier `take`/`takes` data
moved on load, tested; SubtitlePanel/SubtitleEdit). Theme: list check boxes styled like the others.
- 2026-10-27 version 3.4.0 (minor: Edit tab, effects, `ackit fx-template` / `replace --fx`): every Markdown
document checked (links, images, named files; REPORT.md rewritten for mission 4; check_docs gained effect
claims: presets, effect names, the pedalboard pin, the no-scroll window sizes); generated files removed
(listening WAVs, 3.3.0 builds, scratch survey data). `dist/UnheardCity-3.4.0-linux-x86_64.tar.gz` (124 MB)
smoke + GUI start PASS with PATH=/usr/bin:/bin; tested by the user.
Catalog: `FEChar_Batman_AKV8_SF.upk` was a mod the user once installed, not a game file (absent from the clean
Linux install); rebuilt from that install (`tools/build_catalog.py`, no sound or subtitle differs): its 124 bank
copies left BANK_locations.csv.gz (every sound in it is also in original packages: same 15,355 sounds, 93,702
copies in 1,498 files), docs figures updated.
Reverb (user: "no mix slider"): the level was "Amount" and never fully wet (dry 1 - wet/2) -> "Mix" (key `mix`,
2nd slider), equal-power dry/wet (0 = dry only, 1 = reverb only); `wet` of older settings is read as `mix`
(fx.RENAMED); presets scaled x0.73 to sound as before. Effect chain: the selected row is grey, so its red tick
shows. Final check: 177 tests (with the game), `ackit verify` PASS, `verify --catalog` OK, e2e_features INT
10/10, check_docs all match, 22 Markdown files without broken links, package smoke + GUI start PASS. Channel order
of the 3-5 ch WEMs = fmt channel mask order (0x37 = FL FR C SL SR, 0x33 quad): the centre holds the dry voice,
PA surrounds echo it 160-173 ms later (Harley/HQ 121 ms, Freeze rears 73 ms); Riddler/Ivy/Ra's 5 ch are
decorrelated per channel. Baked reverb is rare (blind RT60 < 0.2 s for 90 %). Listening samples:
`external/fx_samples/INDEX.md` (git-ignored; the WAVs were deleted with version 3.4.0, the user's notes kept). NEXT: user listens and names the groups; then the survey goes
into ackit/bnk.py + a tool, docs/formats/effects.md with check_docs claims, and the other 4 voice languages.
- OPEN: 19 speaker labels to check by listening.
## Environment facts
- Game: Steam AppID 200260 "Batman: Arkham City GOTY", `C:\Program Files (x86)\Steam\steamapps\common\Batman Arkham
@@ -187,6 +263,14 @@
their third-party downloads were moved from `tools/`, `repositories/` to `external/` (git-ignored).
- Ghidra project BAC: `.text` Ghidra address = runtime + 0xC00 (docs use runtime addresses).
- Linux dev machine (2026-10-27): podman container (Debian 13, user `claude`, HOME=/home/claude, sudo) on an
Arch host. The game is the host's Steam Flatpak install, mounted read-write at
`/home/gora/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Batman Arkham City GOTY`
(only that folder: no libraryfolders.vdf, no Proton prefix). Steam discovery looks under HOME, so pass
`--game DIR` (CLI) / `ACKIT_TEST_GAME=DIR` (tests) and `--lang` (no UserEngine.ini; `Game.language()` -> INT).
Python 3.14.8 built from source into /usr (`/usr/bin/python3.14`, the host's path too); `.venv` regenerated
with it (`.venv/bin/python -m pytest -q`; GUI tests need `QT_QPA_PLATFORM=offscreen`). No in-game checks here.
## Where to look
- Findings, failed hypotheses, in-game experiment log E0-E14: `docs/reverse-engineering/findings.md`.
- Formats: `docs/formats/`. Decoder addresses: `docs/reverse-engineering/decoder.md`.