Follow-up to the mdb removal in the previous commit. Three of the six
extensions #64 listed as deliberate local additions are NWN2 formats,
not NWN:EE ones, and belong in an NWN:EE tool no more than mdb did.
Checked against xoreos (src/aurora/types.h), which covers every Aurora
game and so distinguishes the games' tables:
gr2 4003, sits in the NWN2 block (MDB2 4000, MDA2 4001, SPT2 4002,
GR2 4003, PWC 4008). Granny is NWN2; NWN:EE does not use it.
wlk xoreos numbers it 20004 and xml 20003, both above
xml kFileTypeMAXArchive in the section headed "Entries for files not
found in archives with numerical type IDs / Found in NWN2's ZIP
files". Neither has an archive restype in any Aurora game, so
Crucible's 0x0BCC and 0x0BCD were invented outright, in a band
Aurora does use elsewhere (3022 FSM, 3023 ART).
NWN:EE covers those jobs with formats already in the table: wok, pwk
and dwk for walkmeshes, and EE's own UI XML loads from ui/ovr rather
than from a HAK by restype, which is consistent with upstream
neverwinter.nim registering no xml number.
The remaining three local additions stay. lyt 3000, vis 3001 and mdx
3008 are real Aurora archive types that NWN1 ships in its own
data/*.bif; xoreos gives the same numbers. Upstream simply does not
list them.
No asset in the corpus uses gr2, wlk or xml, so nothing stops building.
Also drops them from AssetExtensions, and records the rule in a comment
on the table plus a test, so the next NWN2 format gets caught.
Refs ShadowsOverWestgate/sow-tools#64
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five entries in the restype table disagreed with upstream
neverwinter.nim's restype.nim, so HAKs written from those source
extensions were labelled with a type the game reads as something else:
gff 0x07F7 (2039, upstream bte) -> 0x07F5 (2037)
ltr 0x0813 (2067, upstream bak) -> 0x07F4 (2036)
jpg 0x081C (2076, upstream tml) -> 0x0821 (2081)
dfa -> dft (0x07FD/2045 was already right,
only the name was a typo)
mdb 0x0816 (2070, upstream xbc) -> removed
Upstream registers no restype for mdb, so there is no correct number to
give it; keeping it squatting on xbc silently mislabels the resource.
It is dropped from AssetExtensions too, which turns .mdb into a loud
"unsupported extension" build error instead of a corrupt HAK entry. No
asset in the current corpus uses any of these paths.
Root cause of the miss: the init() consistency guard only panicked when
a number was missing from the reverse map. Its `if canonicalExt == ext
{ continue }` branch did nothing when the two maps disagreed, so a
mismatch was never caught. The guard now panics on disagreement and
also checks the reverse direction.
Adds a conformance test pinning the shared numbers against upstream,
including the four numbers upstream reserves for other extensions, and
a test that the two maps are mutual inverses.
Refs ShadowsOverWestgate/sow-tools#64
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What
Two correctness fixes surfaced in review of the direct-depot HAK artifact work.
### Reject resref+type collisions in `chunksFromManifest`
A manifest can name two distinct source paths that collapse to the same
`resref+type` (e.g. `creature/foo.tga` and `placeable/foo.tga` — resref is the
lowercase basename minus extension). The ERF writer keys resources on
`Name:Type`, so the second silently shadowed the first: an asset would vanish
from the packed HAK with no error.
`chunksFromManifest` now runs `ensureUniqueChunkResources` per chunk and fails
the build on a duplicate. This guards **both** build paths — the legacy
`--source-manifest` flow and the direct content-addressed flow
(`chunksFromSourceManifest` → `chunksFromManifest`).
### Document erf post-write hash coupling
`writeResourceData` streams the source into the output while hashing, so
size/SHA mismatches are only detected *after* the bytes are written. A non-nil
return therefore means the writer holds partial, unverified output and the
caller must discard it. Added a comment making that contract explicit;
`writeHAKArchive` already honours it (writes to a temp file, removes on any
Write error, never renames a bad archive into place).
## Tests
- `TestChunksFromManifestRejectsResrefCollision`: collision → error, distinct
resrefs → clean. Asserts only error presence/absence — silent asset loss is
the contract, not any specific wording.
- `go vet ./internal/erf/ ./internal/pipeline/` clean.
- `go test ./internal/erf/ ./internal/pipeline/` green.
## Follow-up
A new crucible release must be cut after this merges so the guard ships in the
binary `sow-assets-manifest` pins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #8
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.com>