Game line names across languages; RSubtitle; editor in three columns

This commit is contained in:
2026-10-10 02:14:06 +02:00
parent f29af40b37
commit ea3e44000a
40 changed files with 54018 additions and 53559 deletions

View File

@@ -60,8 +60,12 @@ Two separate fields, because Wwise names describe a *scene*, not the speaker of
alternates speakers between `NAME_1`, `NAME_2`, ...):
- **character** - the verified speaker, only from the curated lists in `data/catalog/source/<lang>/*.txt`
(one file per character, listening-based). Mapped by sound name, so labels made on the Italian files apply to
every language (the five language WADs have identical names and variation counts). Display names:
(one file per character, listening-based). The lists name ITA sounds by their position in the ITA WAD
(wemscan names); positions differ between languages, so each label is moved to the game's line name of that
sound (`characters.csv`, empty `language`) and then applies to the same line in every language. Labels of
sounds no event plays stay ITA-only (`language` = ITA). Checked against the speaker field of the game's
dialogue objects: 955 / 955 labelled lines agree (spelling aside: TwoFace, Mysterious_stranger = Azrael).
Display names:
Calendar -> Calendar Man, Croc -> Killer Croc, Freeze -> Mr. Freeze, Ivy -> Poison Ivy, Madhatter -> Mad
Hatter, Quinn -> Harley Quinn, Ras -> Ra's al Ghul, Strange -> Hugo Strange, Talia -> Talia al Ghul,
Twoface -> Two-Face, Vicky -> Vicki Vale, Zsazz -> Victor Zsasz, Unknown -> Unknown (speaker checked but not

View File

@@ -103,6 +103,10 @@ copy per voice language) is filtered the same way, so the sound list never mixes
- Shipped CSVs per archive (names, categories, characters, original size + checksum) make loading instant and
enable change detection; any other build is scanned at runtime (header reads only, cached).
- Verified speakers come only from the user's lists; name-based character inference is kept in a separate field.
- Speech sounds are named after the game's line (speech event), not after their position in the WAD entry:
the WEMs of an entry are stored in media-ID order and every language has its own media IDs, so position i
is a different line in each language (75 % of the multi-variation lines). Line names make names, speaker
labels, mod packages and subtitles agree across languages.
- Category scheme: `docs/categories.md`.
## 6. Repository cleanup choices

View File

@@ -13,6 +13,13 @@ the UE3 packages (`upk.md`), holding the subtitle text of all 11 languages. acki
- Strings are stored as cp1252 (positive FString length) or UTF-16 (negative length): RUS, POL, JPN, KOR
and some INT/ESN/FRA/DEU strings are UTF-16.
## Scripted subtitles (`RSubtitle`)
41 more subtitles (radio and encryption-card lines) are `RSubtitle` objects in `Startup.upk`: the same layout,
with the per-language array named `ArraySubtitles` and a float (duration) at `0x2C` instead of a speech
reference. `RSubtitleLookup` (`Startup.upk`, `Lookups`) is an array of `{int key @0, object @4}`, key =
FNV-1 of the speech line name without `Speech_` (e.g. `CH_ZERO_CH0_ENCRYPTION_CARD2_01`), which links each
`RSubtitle` exactly to its line. ackit reads and writes both kinds the same way.
## Object layout (compact tagged properties)
The export data is a list of properties `u16 type, u16 field offset, value`, ended by `u16 0`. Types seen:
`0x10` object reference (i32 export/import index), `0x0D` FString, `0x09` struct/array (name i32+i32 = struct

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

@@ -89,6 +89,17 @@ Conclusion: approach A works end-to-end in-game. Use >= 30 s probes for the intr
prefixed with markers and 12 subtitles patched in place (21 files). All 12 markers are found as UTF-16 in the
game's memory after ~45 s (the localization files are read at startup), no crash, revert byte-identical:
PASS. Subtitles are not loaded on the title screen (no dialogue object in Frontend*/Startup/BmGame).
20. (2026-10-10) **Variation positions differ between languages.** A WAD entry stores its WEMs in media-ID order
and each language has its own media IDs, so for 7,010 of 9,366 lines (75 %) the same game line is at a
different position in the ITA and INT entries (e.g. Speech_CH_ZERO_CH0_fight_intro_01 = ITA :0, INT :2).
Position names (NAME_i, wemscan) therefore mean different lines per language. Speech sounds are now named
after their speech event (same in every language); the curated ITA lists are mapped through it. Independent
check: the speaker field of the dialogue objects agrees with the mapped labels for 955 / 955 lines.
21. (2026-10-10) In-memory speech banks are localized (language list + one bank per language in one AkBank
export, checked on 215 banks against the WAD languages of their streamed lines); 41 more subtitles are
RSubtitle objects linked through RSubtitleLookup (key = FNV-1 of the line name without `Speech_`). Subtitle
coverage by category is uneven in the game itself: efforts 0 %, combat barks 11 %, cinematics 10 % (cutscene
lines are mostly not reached by speech events and have no dialogue object), story/predator/open world ~90 %+.
## Old path -> new path
`scripts/harness.py` -> `ackit verify --ingame` (ackit/verify/ingame.py); `scripts/validate_builder.py` ->

View File

@@ -26,6 +26,12 @@ game is set to (Steam > game properties > Language) and applies to everything yo
- **Subtitles** and **Menu text**: the texts of that language.
Changing it reloads the lists; the next start is again in the game's language.
## Names
Voice lines carry the game's own line names (e.g. `Speech_TAPE_JOKER_1_04`), the same in every language and the
same as in the Subtitles tab. Inside a WAD entry the game stores the variations of a line in a different order
in each language, so the position-based name is kept only as a second name (`Speech_TAPE_JOKER_1#4` = the 4th
WEM of that entry in this language; search finds both).
## Find a sound
- Type in the **search box** (Ctrl+F): words can be parts of the name, a category, a character, your own
notes or tags, or an ID like `b49cdf48`. All words must match.
@@ -44,7 +50,9 @@ Changing it reloads the lists; the next start is again in the game's language.
![Browsing by category](img/browse.png)
## Listen
Select a row and press **Space** (or double-click) to play the original. In the editor below:
The editor under the list shows the selected sound in three columns side by side (drag the dividers to
resize them): **Listen**, **Replace** and **Subtitle**. Select a row and press **Space** (or double-click) to
play the original. In **Listen**:
- **Original** / **Installed** play the game's original and what is currently in the game. Click on a
waveform to play from that point. **Loop**, **Volume** and **Stop** (Esc) work for everything.
- **A/B** switches between the original and your replacement at the same position, to compare.
@@ -54,8 +62,8 @@ Select a row and press **Space** (or double-click) to play the original. In the
1. **Load file…** (Ctrl+O): WAV, FLAC, OGG, OPUS, MP3 or AIFF. (M4A/WMA work only if ffmpeg is installed.)
Or choose a microphone in the list and press **Record**, then **Stop recording**. Listen with
**Replacement**; record again as often as you like.
2. Adjust: **Keep from / to** trims (or right-drag on the replacement waveform), **Trim silence**, **Gain**,
**Normalize**, **Match original loudness** (recommended for voice lines).
2. Adjust: **Keep … to** trims (or right-drag on the replacement waveform), **Trim silence**, **Gain**,
**Normalize**, **Match loudness** (to the original; recommended for voice lines).
3. **Apply to game** (Ctrl+S). The audio is converted to the original's format and written into the game files.
The row turns bold and shows *Modified*.
@@ -65,7 +73,7 @@ you are asked to shorten it. Music (`Music_…`) can be any length. A warning ap
than the original, since the game may cut it off.
## Undo
- **Revert to original** in the editor puts that sound back exactly.
- **Revert** in the editor puts that sound back exactly.
- Right-click rows → **Revert selected**; toolbar **Revert all** puts every original back.
- Originals are kept in your user data folder (`%APPDATA%\UnheardCity` on Windows,
`~/.local/share/unheardcity` on Linux), so reverting works after updating or reinstalling the app too.