Final-review findings. A 300-frame yarc run emits ~2,578 events/frame, so
the default 1M ring holds ~390 frames while both worked examples showed
--frames 1800: the ring wrapped and threw away most of the run while
trace_probe reported success and trace_dump printed nothing unusual. The
loss was always recoverable from the data — the oldest surviving record's
seq IS the number evicted — so trace_dump and trace_diff now say so on
stderr, and the examples carry a measured VJ_TRACE_RING with a note that
the event rate is ROM-dependent.
vjtrace_dump() checked no fwrite and not fclose, so a truncated dump
returned success — the opposite of the invariant vjtrace_snapshot()
enforces 200 lines below it. It now mirrors that: any short write removes
the partial file and returns -1, so a VJTR file that exists is complete.
A --watch whose ADDR+LEN wrapped past $FFFFFFFF produced lo > hi, a watch
that could never fire, and a run that reported success. Rejected now.
The vj-debug skill's menu_step_probe build line predated that probe
gaining trace_probe_attach and no longer linked; it does now.
Co-Authored-By: Claude Fable 5 <[email protected]>
Task 6 review found three Critical issues in the analyzer CLIs added
in 93c7f26; fixes all three:
- trace_diff.c: load_filtered() trusted the VJTR header's hdr.count
for a malloc(count * sizeof(vjtrace_ev)) with no overflow check. A
crafted header claiming ~2^59 records wrapped that size_t multiply
into a tiny allocation, and the read loop then wrote real trailing
records past its end (reviewer reproduced SIGABRT/exit 133 instead
of exit 2). Now derives the real record count from the file's
actual length (ftell to end, minus header size, divided by
ev_size), requires it to match hdr.count exactly before allocating
anything sized by it, plus a defense-in-depth SIZE_MAX guard on the
multiply itself. trace_dump.c was already unaffected (streams
record-by-record).
- trace_memdiff.c / vjtrace.h: REGSGPU/REGSDSP slot 33 was labeled
CTRL per a literal reading of the task-6 brief's ambiguity
resolution, but the writer (GPUGetFlags()/DSPGetFlags() in
vjtrace_snapshot()) and vjtrace.h's own format comment both say
it's the flags register, not G_CTRL/D_CTRL (a different register at
a different address, not captured by this format at all). Relabeled
to FLAGS in trace_memdiff.c; tightened vjtrace.h's REGSDSP wording
to match REGSGPU's already-clear "+ GPU_FLAGS" and added an
explicit note on the CTRL/FLAGS distinction.
- field_diff.c: the row loop skipped any row where EITHER side's line
was blank, not just a trailing blank on both -- silently dropping
the comparison when one file has a blank line and the other has
real data at that position (reviewer reproduced two genuinely
different CSVs reported identical, exit 0). Now: blank on both
sides is tolerated (never desyncs alignment, since both cursors
always advance together); blank on exactly one side is compared as
an all-empty-fields row against the real row, surfacing as an
ordinary per-column mismatch with exit 1.
Re-verified the full malformed-input matrix (bad magic, truncated
header, mid-record/mid-section truncation, crafted huge count,
blank-line-desync CSV) on all four tools plus real-data behavior on
t1/t2 rings, f1/f2 CSVs, and the snapshot pairs from the original
report -- all unchanged from before the fix except the three bugs
themselves. c89-lint clean. Fix report appended to
.superpowers/sdd/2026-08-11-vjtrace-flight-recorder/task-6-report.md.
Co-Authored-By: Claude Fable 5 <[email protected]>
Task 6 of the vjtrace flight-recorder plan: four standalone C99 CLI
tools in test/tools/ that read the formats Tasks 1/4/5 already write
(VJTR event ring, VJSN state snapshot, per-field CSV), with no harness
dependency -- only src/core/vjtrace.h for the shared vjtrace_ev struct
and event enum.
- trace_dump: text/JSONL dump of a VJTR ring, filterable by
--type/--who/--frame; small register-symbol table for the addr
field verified against docs/jtrm-register-map.md (corrects the
brief's example: B_CMD is $F02238, not $F0223C -- that's B_COUNT).
- trace_diff: structural diff of two VJTR rings on (type, who, addr)
in filtered stream order, default filter excludes per-object
OP_OBJECT noise, --types overrides; reports first divergence with
+/-5 records of context from each side.
- field_diff: row/column diff of two trace_probe --field-csv files;
refuses to align mismatched headers rather than guessing.
- trace_memdiff: byte-run diff of two VJSN snapshots with gap<=4
coalescing, MAINRAM region annotation from the vjag_memory.c
address-map comment, and named per-register lines for
REGS68K/REGSGPU/REGSDSP instead of raw offsets.
All four: exit 0 no-difference/success, 1 difference found, 2 usage
or malformed/truncated input (verified: bad magic, truncated headers,
mid-stream/mid-section truncation, ragged CSV rows, header mismatch --
all exit 2 with a clear message, no crash). c89-lint clean on all four
(the repo's pre-commit hook checks all staged .c files, including
test/tools/, not just src/). Verification details in
.superpowers/sdd/2026-08-11-vjtrace-flight-recorder/task-6-report.md.
Co-Authored-By: Claude Fable 5 <[email protected]>