Builds sow-tools#53. Format spec followed is the resolution comment of sow-platform#94, checked line by line against niv/neverwinter.nim at HEAD (`nwsync.nim`, `compressedbuf.nim`, `nwsync/private/libupdate.nim`). ## What lands `crucible nwsync emit <artifact> --out DIR` — explodes one `.hak`/`.erf`, or one loose file such as the TLK, into NWSync blobs plus a NSYM v3 manifest covering only that artifact, with the same `.json` sidecar upstream writes. Blob path `data/sha1/<h0h1>/<h2h3>/<sha1>`, body in NWCompressedBuffer framing (magic `NSYC`, version 3, algorithm 2, uncompressed size, zstd header version 1, dictionary 0, raw zstd frame). The sha1 that names a blob is over the uncompressed bytes. `crucible nwsync assemble --order NAMES --entries DIR --out DIR [--group-id N]` — merges the per-artifact manifests into one, reading no bulk data at all. Merge rule is resref shadowing, not concatenation: a resref in more than one artifact resolves to the earliest artifact in `--order`, which is how the game resolves it. `--group-id` stays caller-supplied (1 current, 2 testing; 0 is absent, matching upstream omitting a zero integer meta field). Rules taken from upstream and not re-invented: `nss`/`ndb`/`gic` always skipped; an unresolvable restype is a hard error, not a skip; a resource over 15 MB fails closed; no `latest` file and no `.origin` file, ever. A `.mod` is refused outright — a persistent world publishes no module contents, so the module contributes no bytes. ## Two deliberate departures - **Emitter version is its own field, not the build revision.** `emitter_version` is a constant bumped only when emitted bytes change. Keying the refuse-to-merge check on `created_with` would invalidate every published index on every unrelated crucible commit and force a re-emit of the whole 15 GB corpus — the opposite of "nothing downstream ever needs the hak again". - **`SOURCE_DATE_EPOCH` pins the sidecar timestamp.** The manifest itself was already deterministic; the sidecar's `created` was not, against the determinism rule in `docs/consumer-contract.md`. ## Not in this PR, and why - **Direct upload.** Only the local `--out` sink exists, which is the conformance path. The upload sink and the mid-hak-failure question are sow-tools#60, and the consumer wiring is #65. - **The conformance run against upstream.** sow-tools#59 owns getting `nwn_nwsync_write` running and capturing reference output. The format here was read from upstream source rather than from its output, so the byte-for-byte manifest comparison and the after-decompression blob comparison still have to happen — that is what #59 is for. The tests in this PR check the layout against the spec, so a shared misreading would pass them. - **The `artifacts/haks/sha256/<a>/<b>/<sha256>.nsym` location.** emit writes `<out>/<name>.nsym`; where a publisher puts it is the publisher's business (#65). - **The acceptance gate** — a real client syncing from an assembled manifest — is unchanged and still open. ## Checks `make check` green (vet, unit tests, shellcheck, yamllint, workflow contract), `make smoke` green with the new builder, `nix build .#crucible` produces `crucible-nwsync`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #71 Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
4.8 KiB
Crucible command surface
internal/dispatch.Registry is the single source of truth for Crucible's
builders, visible commands, descriptions, help, and hidden compatibility
aliases.
Visible commands
| Builder | Command | Purpose |
|---|---|---|
hak |
build |
Build configured HAK archives and their manifest. |
hak |
manifest |
Apply a generated HAK list to module source. |
module |
build |
Build the module and configured project outputs. |
module |
extract |
Extract built archives into configured source trees. |
module |
validate |
Validate project layout, config, and source inventory. |
module |
compare |
Compare source content with built archives. |
module |
manifest |
Apply a generated HAK list to module source. |
topdata |
validate |
Validate topdata source and canonical JSON compatibility. |
topdata |
build |
Compile topdata and refresh the configured package. |
topdata |
package |
Package cached outputs into HAK and TLK files. |
topdata |
compare |
Compare current output with a fresh native build. |
topdata |
convert |
Convert between 2DA and native JSON/module formats. |
wiki |
build |
Render wiki page drafts from compiled topdata. |
wiki |
deploy |
Deploy generated wiki pages to NodeBB. |
assets |
compile |
Compile ASCII .mdl models to binary in place. |
assets |
convert |
Convert textures to/from NWN DDS (flips vertically). |
assets |
upscale |
Upscale textures through an installed backend. |
assets |
check-mdl |
Report uncompiled ASCII .mdl and model-name mismatches. |
assets |
fix-mdl |
Lowercase .mdl names and rewrite model identity to match. |
assets |
check-dupes |
Report runtime-name (basename) collisions across dirs. |
assets |
clean-dupes |
Delete clean-tree files whose basename collides with primary. |
depot |
status |
Report referenced-vs-present drift against a backend. |
depot |
push |
Upload referenced-but-absent blobs from a local depot. |
depot |
verify |
Existence sweep plus sampled download re-hash. |
depot |
get |
Fetch one blob with sha re-verify. |
depot |
pull |
Incremental verified pull of every referenced blob. |
nwsync |
emit |
Explode one artifact into NWSync blobs plus its own NSYM manifest. |
nwsync |
assemble |
Merge per-artifact NSYM manifests into one merged manifest. |
depot status and depot get pick their backend either with --out DIR, a
depot tree on disk, or with --target bunny|cdn, a remote backend. The two
flags are mutually exclusive.
nwsync emit runs where an artifact is born (a .hak/.erf, or a loose file
such as the TLK); nwsync assemble runs at module release and reads only the
small per-artifact manifests. --order lists artifact names highest priority
first: a resref in more than one artifact resolves to the earliest one, the way
the game resolves it. --group-id is per channel — 1 is current, 2 is testing,
and 0 leaves the field out of the sidecar.
Hidden compatibility aliases
Existing scripts may continue using these names indefinitely. They are accepted but omitted from routine help and the interactive menu:
| Builder | Hidden alias | Implementation |
|---|---|---|
hak |
build-haks |
build-haks |
hak |
apply-hak-manifest |
apply-hak-manifest |
module |
build-module |
module-only build-module |
module |
apply-hak-manifest |
apply-hak-manifest |
topdata |
validate-topdata |
validate-topdata |
topdata |
build-topdata |
build-topdata |
topdata |
build-top-package |
build-top-package |
topdata |
compare-topdata |
compare-topdata |
topdata |
convert-topdata |
convert-topdata |
depot |
--target local |
--out $DEPOT_DIR on status/get |
wiki |
build-wiki |
build-wiki |
wiki |
deploy-wiki |
deploy-wiki |
module build-module deliberately retains its narrower module-only behavior;
it is not redirected to the broader module build.
Global commands
crucible help | -h usage + builder list
crucible version | -V build version
crucible list name<TAB>binary<TAB>summary
crucible config [args] inspect and validate effective configuration
crucible changelog [args] generate a release changelog
HAK build options
crucible hak build
[--hak <hak-name> ...]
[--archive <archive-name> ...]
[--source-manifest <path>]
[--content-addressed-root <path>]
[--plan-only]
[--quiet|--verbose|--debug]
When a source manifest contains asset_sources, pass
--content-addressed-root <directory>. Crucible resolves each declared SHA-256
under sha256/<first-two>/<next-two>/<full-sha256> and verifies streamed bytes
while writing selected HAK archives. Authored .bmu files are ordinary assets
and require no conversion pipeline.