Get upstream nwn_nwsync_write running on this machine and capture reference output for one real hak.
Part of #54. This is the conformance oracle for the whole effort, and right now it is a sentence in sow-platform#94 rather than a working binary. Nothing can be proven correct until it exists.
Done means:
nwn_nwsync_write runs locally. It is Nim, from niv/neverwinter.nim. This machine is NixOS — prefer nixpkgs or a transient nix shell; if it is not packaged, record what it actually took.
One real .hak from the corpus is on disk, pulled from the depot.
Upstream has been run over that single hak, and its output — the blob tree and the NSYM manifest plus sidecar — is stored somewhere durable enough for a byte comparison later.
The manifest is inspected against the format written down in #94, and any discrepancy between that spec and what the binary actually produces is reported. The spec was read from source, not from output; this is where it gets checked.
Record in the answer: how the binary was obtained, where the reference output lives, which hak was used and its digest.
## Question
Get upstream `nwn_nwsync_write` running on this machine and capture reference output for one real hak.
Part of #54. This is the conformance oracle for the whole effort, and right now it is a sentence in [sow-platform#94](https://git.westgate.pw/ShadowsOverWestgate/sow-platform/issues/94) rather than a working binary. Nothing can be proven correct until it exists.
Done means:
- `nwn_nwsync_write` runs locally. It is Nim, from `niv/neverwinter.nim`. This machine is NixOS — prefer nixpkgs or a transient `nix shell`; if it is not packaged, record what it actually took.
- One real `.hak` from the corpus is on disk, pulled from the depot.
- Upstream has been run over that single hak, and its output — the blob tree and the NSYM manifest plus sidecar — is stored somewhere durable enough for a byte comparison later.
- The manifest is inspected against the format written down in #94, and any discrepancy between that spec and what the binary actually produces is reported. The spec was read from source, not from output; this is where it gets checked.
Record in the answer: how the binary was obtained, where the reference output lives, which hak was used and its digest.
Verification sweep — still valid, and now the highest-value open ticket on this map
Nothing here is stale. Confirmed against the repo: no upstream binary, no reference output, no conformance harness. grep -r nwn_nwsync_write finds one source comment and nothing executable.
PR #71 raises the stakes rather than lowering them. The format shipped in internal/nwsync was read from upstream source (nwsync.nim, compressedbuf.nim, libupdate.nim), and the 12 unit tests check the code against that same reading — as the PR body says, a shared misreading passes them. #59 is the only check that can catch one.
Two additions to "done means", both cheap now that emit exists:
Run crucible nwsync emit over the same hak and diff its NSYM against upstream's byte for byte, and its blobs after decompression. That is the whole point of the single-hak split and it is now runnable.
Feed specs to upstream reversed when comparing — #62 established module_haks is earliest-wins for us against upstream's last-added-wins.
## Verification sweep — still valid, and now the highest-value open ticket on this map
Nothing here is stale. Confirmed against the repo: no upstream binary, no reference output, no conformance harness. `grep -r nwn_nwsync_write` finds one source comment and nothing executable.
PR #71 raises the stakes rather than lowering them. The format shipped in `internal/nwsync` was read from upstream **source** (`nwsync.nim`, `compressedbuf.nim`, `libupdate.nim`), and the 12 unit tests check the code against that same reading — as the PR body says, a shared misreading passes them. #59 is the only check that can catch one.
Two additions to "done means", both cheap now that emit exists:
- Run `crucible nwsync emit` over the same hak and diff its NSYM against upstream's **byte for byte**, and its blobs after decompression. That is the whole point of the single-hak split and it is now runnable.
- Feed specs to upstream **reversed** when comparing — #62 established `module_haks` is earliest-wins for us against upstream's last-added-wins.
archvillainette
self-assigned this 2026-07-30 17:33:46 +00:00
It runs unpatched on NixOS — the interpreter resolves — but dlopens sqlite at startup and dies with could not load: libsqlite3.so(|.0). Fix is one env var, no patchelf, no FHS wrapper:
Version: neverwinter 2.1.2 (/07a475, nim 2.2.4) — the same tree the format was read from.
Where the reference output lives
/home/vicky/nwsync-ref/ on the dev machine (not in any repo — it is 300 MB of bulk):
bin/ — the unpacked upstream 2.1.2 toolset plus the crucible-nwsync built from a131b25
hak/sow_vfxs_01.hak — the input
out-upstream/ — upstream's repository: data/, manifests/cc577365edc6bda2ce7b0f7f1896482fe3478572 + .json, and latest
out-crucible/ — crucible nwsync emit --out over the same hak
Which hak
sow_vfxs_01.hak from the current haks release v0.1.9, sha256 521ab5de8e4a8f5569ff189fbf3a04c4f693e239afbf709c932ec2fbfa9f7831, 249,842,292 bytes — the smallest of the 11 published haks, so the loop is cheap to repeat. 2,474 resources, 2,296 distinct blobs.
The comparison — it passes
Manifest byte-identical.cmp clean against upstream's manifest, and Crucible's own name for it is the same SHA-1, cc577365edc6bda2ce7b0f7f1896482fe3478572. The entry sort, the mapping demotion of repeated SHA-1s, and the header all reproduce exactly.
2,296 blobs, same names, same paths, data/sha1/<h0h1>/<h2h3>/<sha1>; diff of the two file lists is empty.
Blob headers byte-identical — all 20 bytes of NWCompressedBuffer framing before the zstd frame match on every blob, so magic, version, algorithm, uncompressed size, zstd header version and dictionary are all right.
Payloads identical after decompression on all 2,296, and sha1(uncompressed) == blob name on all 2,296.
Compressed bytes differ as predicted: 58,377,749 on disk upstream vs 59,315,391 for Crucible, +1.6% from Go's zstd against Nim's libzstd. The blob names are unaffected, which is the whole point.
Crucible writes no latest and no .origin; upstream writes latest. Correct — we serve an explicit hash.
Reproduce with crucible-nwsync emit --out out-crucible hak/sow_vfxs_01.hak and nwn_nwsync_write out-upstream hak/sow_vfxs_01.hak, then diff. Emit takes 1.8 s against upstream's ~90 s.
Discrepancies found, none fatal
on_disk_bytes means something different upstream. Upstream's sidecar claims 61,359,113 while its own blob tree is 58,377,749; Crucible's claim equals its real tree exactly. The gap is ~5%, and 178 of the 2,474 entries are duplicate SHA-1s (7.2%) — upstream is almost certainly summing per entry, counting a shared blob once per resref, not per stored file. #53 already calls this a display statistic, so nothing to change, but the merged sidecar will be closer to upstream if it keeps counting per entry.
module_name differs. Upstream leaves it empty for a bare hak spec; Crucible writes the artifact name (sow_vfxs_01). Sidecar-only, outside the hashed manifest — but the merged manifest's module_name is the module's, so the per-artifact one is ours to define. Fine as is; noting it so the next byte-diff does not read it as drift.
created_with / emitter_version differ by design (PR #71).
Flag order is positional-hostile.emit hak/x.hak --out DIR fails with "exactly one artifact is required" — Go's flag stops at the first non-flag argument. Cost me a run. Worth fixing in #60's CLI reshape, where the positionals change anyway.
So the format spec in sow-platform#94 is confirmed correct as read, on one real hak, at the byte level. What is not yet proven: multi-artifact assemble against upstream's multi-spec run (upstream's shadowing is last-added-wins, ours is earliest-wins, so specs feed reversed — per #62), and anything about the upload path (#60).
## Resolved — upstream runs, reference output captured, and Crucible matches it byte for byte
### How the binary was obtained
Not packaged in nixpkgs (`nix search nixpkgs neverwinter` finds nothing). Upstream ships prebuilt Linux binaries, so no Nim toolchain was needed:
```
curl -L https://github.com/niv/neverwinter.nim/releases/download/2.1.2/neverwinter-x86_64-linux-gnu.zip
```
It runs unpatched on NixOS — the interpreter resolves — but `dlopen`s sqlite at startup and dies with `could not load: libsqlite3.so(|.0)`. Fix is one env var, no patchelf, no FHS wrapper:
```
LD_LIBRARY_PATH=$(nix build --no-link --print-out-paths nixpkgs#sqlite.out)/lib ./nwn_nwsync_write ...
```
Version: `neverwinter 2.1.2 (/07a475, nim 2.2.4)` — the same tree the format was read from.
### Where the reference output lives
`/home/vicky/nwsync-ref/` on the dev machine (not in any repo — it is 300 MB of bulk):
- `bin/` — the unpacked upstream 2.1.2 toolset plus the `crucible-nwsync` built from `a131b25`
- `hak/sow_vfxs_01.hak` — the input
- `out-upstream/` — upstream's repository: `data/`, `manifests/cc577365edc6bda2ce7b0f7f1896482fe3478572` + `.json`, and `latest`
- `out-crucible/` — `crucible nwsync emit --out` over the same hak
### Which hak
`sow_vfxs_01.hak` from the `current` haks release `v0.1.9`, sha256 `521ab5de8e4a8f5569ff189fbf3a04c4f693e239afbf709c932ec2fbfa9f7831`, 249,842,292 bytes — the smallest of the 11 published haks, so the loop is cheap to repeat. 2,474 resources, 2,296 distinct blobs.
### The comparison — it passes
- **Manifest byte-identical.** `cmp` clean against upstream's manifest, and Crucible's own name for it is the same SHA-1, `cc577365edc6bda2ce7b0f7f1896482fe3478572`. The entry sort, the mapping demotion of repeated SHA-1s, and the header all reproduce exactly.
- **2,296 blobs, same names, same paths**, `data/sha1/<h0h1>/<h2h3>/<sha1>`; `diff` of the two file lists is empty.
- **Blob headers byte-identical** — all 20 bytes of NWCompressedBuffer framing before the zstd frame match on every blob, so magic, version, algorithm, uncompressed size, zstd header version and dictionary are all right.
- **Payloads identical after decompression** on all 2,296, and `sha1(uncompressed) == blob name` on all 2,296.
- Compressed bytes differ as predicted: 58,377,749 on disk upstream vs 59,315,391 for Crucible, +1.6% from Go's zstd against Nim's libzstd. The blob names are unaffected, which is the whole point.
- Crucible writes no `latest` and no `.origin`; upstream writes `latest`. Correct — we serve an explicit hash.
Reproduce with `crucible-nwsync emit --out out-crucible hak/sow_vfxs_01.hak` and `nwn_nwsync_write out-upstream hak/sow_vfxs_01.hak`, then diff. Emit takes 1.8 s against upstream's ~90 s.
### Discrepancies found, none fatal
1. **`on_disk_bytes` means something different upstream.** Upstream's sidecar claims 61,359,113 while its own blob tree is 58,377,749; Crucible's claim equals its real tree exactly. The gap is ~5%, and 178 of the 2,474 entries are duplicate SHA-1s (7.2%) — upstream is almost certainly summing per **entry**, counting a shared blob once per resref, not per stored file. #53 already calls this a display statistic, so nothing to change, but the merged sidecar will be closer to upstream if it keeps counting per entry.
2. **`module_name` differs.** Upstream leaves it empty for a bare hak spec; Crucible writes the artifact name (`sow_vfxs_01`). Sidecar-only, outside the hashed manifest — but the merged manifest's `module_name` is the module's, so the per-artifact one is ours to define. Fine as is; noting it so the next byte-diff does not read it as drift.
3. **`created_with` / `emitter_version`** differ by design (PR #71).
4. **Flag order is positional-hostile.** `emit hak/x.hak --out DIR` fails with "exactly one artifact is required" — Go's `flag` stops at the first non-flag argument. Cost me a run. Worth fixing in #60's CLI reshape, where the positionals change anyway.
So the format spec in sow-platform#94 is confirmed correct as read, on one real hak, at the byte level. What is **not** yet proven: multi-artifact `assemble` against upstream's multi-spec run (upstream's shadowing is last-added-wins, ours is earliest-wins, so specs feed reversed — per #62), and anything about the upload path (#60).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Question
Get upstream
nwn_nwsync_writerunning on this machine and capture reference output for one real hak.Part of #54. This is the conformance oracle for the whole effort, and right now it is a sentence in sow-platform#94 rather than a working binary. Nothing can be proven correct until it exists.
Done means:
nwn_nwsync_writeruns locally. It is Nim, fromniv/neverwinter.nim. This machine is NixOS — prefer nixpkgs or a transientnix shell; if it is not packaged, record what it actually took..hakfrom the corpus is on disk, pulled from the depot.Record in the answer: how the binary was obtained, where the reference output lives, which hak was used and its digest.
Verification sweep — still valid, and now the highest-value open ticket on this map
Nothing here is stale. Confirmed against the repo: no upstream binary, no reference output, no conformance harness.
grep -r nwn_nwsync_writefinds one source comment and nothing executable.PR #71 raises the stakes rather than lowering them. The format shipped in
internal/nwsyncwas read from upstream source (nwsync.nim,compressedbuf.nim,libupdate.nim), and the 12 unit tests check the code against that same reading — as the PR body says, a shared misreading passes them. #59 is the only check that can catch one.Two additions to "done means", both cheap now that emit exists:
crucible nwsync emitover the same hak and diff its NSYM against upstream's byte for byte, and its blobs after decompression. That is the whole point of the single-hak split and it is now runnable.module_haksis earliest-wins for us against upstream's last-added-wins.Resolved — upstream runs, reference output captured, and Crucible matches it byte for byte
How the binary was obtained
Not packaged in nixpkgs (
nix search nixpkgs neverwinterfinds nothing). Upstream ships prebuilt Linux binaries, so no Nim toolchain was needed:It runs unpatched on NixOS — the interpreter resolves — but
dlopens sqlite at startup and dies withcould not load: libsqlite3.so(|.0). Fix is one env var, no patchelf, no FHS wrapper:Version:
neverwinter 2.1.2 (/07a475, nim 2.2.4)— the same tree the format was read from.Where the reference output lives
/home/vicky/nwsync-ref/on the dev machine (not in any repo — it is 300 MB of bulk):bin/— the unpacked upstream 2.1.2 toolset plus thecrucible-nwsyncbuilt froma131b25hak/sow_vfxs_01.hak— the inputout-upstream/— upstream's repository:data/,manifests/cc577365edc6bda2ce7b0f7f1896482fe3478572+.json, andlatestout-crucible/—crucible nwsync emit --outover the same hakWhich hak
sow_vfxs_01.hakfrom thecurrenthaks releasev0.1.9, sha256521ab5de8e4a8f5569ff189fbf3a04c4f693e239afbf709c932ec2fbfa9f7831, 249,842,292 bytes — the smallest of the 11 published haks, so the loop is cheap to repeat. 2,474 resources, 2,296 distinct blobs.The comparison — it passes
cmpclean against upstream's manifest, and Crucible's own name for it is the same SHA-1,cc577365edc6bda2ce7b0f7f1896482fe3478572. The entry sort, the mapping demotion of repeated SHA-1s, and the header all reproduce exactly.data/sha1/<h0h1>/<h2h3>/<sha1>;diffof the two file lists is empty.sha1(uncompressed) == blob nameon all 2,296.latestand no.origin; upstream writeslatest. Correct — we serve an explicit hash.Reproduce with
crucible-nwsync emit --out out-crucible hak/sow_vfxs_01.hakandnwn_nwsync_write out-upstream hak/sow_vfxs_01.hak, then diff. Emit takes 1.8 s against upstream's ~90 s.Discrepancies found, none fatal
on_disk_bytesmeans something different upstream. Upstream's sidecar claims 61,359,113 while its own blob tree is 58,377,749; Crucible's claim equals its real tree exactly. The gap is ~5%, and 178 of the 2,474 entries are duplicate SHA-1s (7.2%) — upstream is almost certainly summing per entry, counting a shared blob once per resref, not per stored file. #53 already calls this a display statistic, so nothing to change, but the merged sidecar will be closer to upstream if it keeps counting per entry.module_namediffers. Upstream leaves it empty for a bare hak spec; Crucible writes the artifact name (sow_vfxs_01). Sidecar-only, outside the hashed manifest — but the merged manifest'smodule_nameis the module's, so the per-artifact one is ours to define. Fine as is; noting it so the next byte-diff does not read it as drift.created_with/emitter_versiondiffer by design (PR #71).emit hak/x.hak --out DIRfails with "exactly one artifact is required" — Go'sflagstops at the first non-flag argument. Cost me a run. Worth fixing in #60's CLI reshape, where the positionals change anyway.So the format spec in sow-platform#94 is confirmed correct as read, on one real hak, at the byte level. What is not yet proven: multi-artifact
assembleagainst upstream's multi-spec run (upstream's shadowing is last-added-wins, ours is earliest-wins, so specs feed reversed — per #62), and anything about the upload path (#60).