Version 3.2.2: in-memory replacements fill their slots exactly (padded ones played as silence)

This commit is contained in:
2026-10-11 00:10:44 +02:00
parent cac52e7597
commit b6b96307ca
16 changed files with 223 additions and 52 deletions

View File

@@ -65,8 +65,11 @@ tables): high risk for little gain, not done.
- **Model:** one catalog row per Wwise media ID (archive `BANK`, target = media ID), not per bank: the same
media is stored in up to dozens of bank copies (banks are cooked into every package that uses them), and the
game may load any of them. A replacement is written into **every** copy (`BANK_locations.csv.gz`).
- **In place:** each copy keeps its size (zero-padded, like WAD speech); DIDX/HIRC offsets and the package
layout never change, so nothing else in the engine data has to be rewritten.
- **In place:** each copy keeps its size; DIDX/HIRC offsets and the package layout never change, so nothing
else in the engine data has to be rewritten. Unlike WAD speech, an in-memory copy is never zero-padded:
the replacement fills its slot exactly with a label list chunk (the game plays a padded in-memory WEM as
silence; `docs/formats/banks.md`). Shrinking the declared sizes (DIDX and every Sound object's in-memory
size) would also work, but needs every HIRC source of the media in every bank copy located and rewritten.
- **Packages:** ackit reads the LZO packages itself (pure Python, ~47 MB/s, block-level access) and writes
only the changed 128 KiB blocks, stored as a single LZO literal run (valid LZO1X without matches), then
fixes the chunk table. No compressor and no external tool are needed; untouched blocks stay byte-identical.

View File

@@ -111,10 +111,16 @@ and the game ties the recordings together itself:
- Catalog archive `BANK` (one row per media ID) + `data/catalog/BANK_locations.csv.gz` (every copy, and the
heads and whole copies of WAD speech recordings); built by `tools/build_catalog.py`, checked by `ackit verify
--catalog`.
- A replacement is written **in place into every copy** (no larger than the smallest copy, zero-padded), so
DIDX/HIRC offsets and sizes and the package layout never change. Loose banks are patched directly; in
packages only the touched blocks are re-encoded (`upk.md`). A sound left in one package only would let the
game load the original from another one.
- Replacing a speech line that has prefetch heads or whole copies rewrites them (the first bytes of the new,
zero-padded line; for a whole copy, all of it).
- A replacement is written **in place into every copy** (no larger than the smallest copy), so DIDX/HIRC
offsets and sizes and the package layout never change. Loose banks are patched directly; in packages only
the touched blocks are re-encoded (`upk.md`). A sound left in one package only would let the game load the
original from another one.
- **Each copy is filled exactly**, never zero-padded: every in-memory WEM the game ships ends exactly at its
slot's end, and the game plays a replacement followed by zero padding there as **silence** (findings 24:
footsteps replaced that way were silent in game; filled, they play). A smaller replacement is grown to the
slot size with a label list chunk before `data` (`LIST{adtl, labl}`, the form the game's own WEMs use for
markers; `ackit/wem.py: fill`), so the encoder leaves at least 26 spare bytes, an even number.
- Replacing a speech line that has prefetch heads or whole copies rewrites them: a head gets the first bytes
of the new, zero-padded line (the start of the streamed WEM as the WAD stores it); a whole copy is filled
like an in-memory sound.
- Design and revert rules: `docs/decisions.md` (4b). The same WEM rules apply (`wem.md`).

View File

@@ -125,6 +125,15 @@ Conclusion: approach A works end-to-end in-game. Use >= 30 s probes for the intr
catalog had the same `<Bank>_<n>` name as an SFX WAD sound of the same bank (`CHR_RasBossFight_1`,
`SEQ_DemonTrials_1`); they now take `<Bank>_<MEDIA>`, and a line's own name wins over another recording's
position alias (`CHR_RadioScanner#1` in FRA). Every name of every catalog resolves to its own sound.
24. (2026-10-10, gameplay test by the user) **Padded in-memory replacements play as silence.** All 568 Batman
footsteps were replaced; in game the original steps were heard and the replaced cape-open sound was silent.
Memory scan while walking: no original footstep WEM loaded, 137 replacements loaded; debugger: the
replacements' Vorbis setups are created and parsed OK (ret 0). Every untouched in-memory WEM fills its slot
exactly (RIFF size = DIDX size, 500 / 500 sampled), while the replacements were zero-padded. Filled with a
`LIST{adtl, labl}` chunk before `data` (55 in-memory originals carry cue+LIST chunks), every step plays the
replacement. ackit now fills every in-memory copy and every whole bank copy of a WAD recording
(`wem.fill`); WAD entries and prefetch heads keep zero padding (streamed playback is unaffected). The
remaining sound after each step is Batman's gear and cape foley (`CHR_BatmanFoleyBelt`, `..Cape`).
## Old path -> new path
`scripts/harness.py` -> `ackit verify --ingame` (ackit/verify/ingame.py); `scripts/validate_builder.py` ->