*palcus.itp files are Toolset-generated snapshots: category skeleton plus
one descriptor per custom blueprint. Treat them as derived data. Module
source now commits only descriptor-free skeletons; build strips any stale
descriptors and re-inserts descriptors computed from the blueprints in the
source tree (name/STRREF, resref, and CR/faction for creatures), so the
DM Creator lists always match what actually ships in the .mod. Compare
applies the same projection. Extract skips *palcus.itp entirely, so local
Toolset palette churn can no longer leak into module source.
Blueprints with PaletteID 255 (hidden) or no PaletteID are omitted, per
engine convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`crucible module build` runs the full pipeline (module + HAK producer +
topdata). A module-only project consumes prebuilt HAKs and has no
`paths.assets`, so the HAK stage crashed on the unset assets dir: the
music-credits walk hit `filepath.WalkDir("")` (`lstat : no such file or
directory`) and asset collection hit `filepath.Rel(root, "")`.
Guard both on an unset `AssetsDir()`: an absent assets tree simply means
no authored credits and no asset files to collect, so the HAK stage
no-ops cleanly while topdata generated-2DA assets still build. Honors
spec Decision 3 (canonical `crucible module build`, no `build-module`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #17
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.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>
When that flag is used, sow-tools:
- collects only the assets referenced by that manifest subset
- skips full-project chunk planning for the per-archive run
- reconstructs just the requested archive chunks from the manifest
Root cause:
build-haks --archive ... was still collecting content requirements for
the entire asset inventory before chunk filtering.
So even when building sow_over_01, it demanded downloaded LFS objects
from unrelated groups like appr.
sow-tools no longer relies on git lfs ls-files --json for planning. It
now parses the checked-out LFS pointer files directly, so split sizing
and content_hash use the real LFS oid and size.
In build.go, build-haks now does conservative chunk reuse instead of
wiping all generated HAKs up front. It plans chunks as before, computes
a deterministic per-chunk content_hash, reuses an existing .hak only
when the prior manifest entry and on-disk archive still match, rebuilds
only changed chunks, and deletes only stale generated HAKs that fall out
of the new plan. I added coverage for unchanged reuse and selective
rebuilds in pipeline_test.go, and go test ./... passes in sow-tools.
In sow-tools, extract now behaves like a real sync by default: it
overwrites changed extracted files, removes stale extracted files that
no longer exist in the built archives, and normalizes extracted resource
filenames to lowercase. I also made validation warn on uppercase
resource filenames so mixed-case names like I_ELVENCHAIN are surfaced
instead of quietly lingering. The extract command output now includes
overwritten and removed counts too.