Bounded semantic events, full-state checkpoints, hash-bound transport, and presentation-only replay for native Wang solves.

Type
Data and rendering contract
Status
Current implementation
Updated

Native solver event trace and offline replay

The reference and optimized native solvers can emit an opt-in sequence of semantic search events. The sequence records what the selected solver path actually observed; it is not a reconstructed lesson, a profiler sample, or a claim about a different engine. Ordinary solve calls retain their original ABI and allocate no provenance storage.

The reference solver component owns and explains the observed reference animation. This contract remains the authority for events, checkpoints, completeness, identity, and replay.

Native boundary and ownership

wang/solver_trace.h defines separate wang_solve_serial_traced() and wang_solve_optimized_traced() entry points. They call the same exact solver core and mechanism policies as the ordinary entry points. The caller supplies WangSolveTraceOptions and a destroyed WangTracedSolveResult; the combined result owns both the ordinary solve result and the trace allocations.

The recorder allocates the initial dense state, bounded event array, and any bounded checkpoint rows before search. An event capacity of at least two reserves both the root and terminal result. Checkpoints are disabled as 0/0; otherwise interval and capacity must both be positive. Validation or allocation failure transfers nothing. The combined destructor releases every owned array, resets the structure, accepts null, and is idempotent.

No trace option or field is added to WangSolverOptions or WangSolveResult. The ordinary reference and optimized calls therefore preserve their status, dense witness, metrics, and ownership contracts without allocating trace state.

Disabled-path profile control

A deterministic Cachegrind control compared the same metrics-enabled 4-by-4 backtracking smoke against merge base 8616e31, using GCC 14.2, portable -O2, and trace-disabled ordinary entry points. Search work and every public metric are exactly equal. The reference executable moved from 487,161 to 490,563 instructions (+0.70%) and from 101,091 to 101,706 data references (+0.61%); optimized moved from 388,660 to 390,553 instructions (+0.49%) and from 116,401 to 116,766 data references (+0.31%). Cache-miss rates and hotspot order remain unchanged. The small dispatch cost is below one percent on this control and creates no allocation or new search work; it is recorded rather than presented as an exact zero-overhead claim.

This is one instruction-profile control, not a timing benchmark or a universal performance bound. The full Cachegrind gate also covers the traced entry points and the complete native test suite.

Event semantics

The immutable root state is the dense domain array after root restrictions and before arc propagation. Ordered events then carry only stable semantic facts:

Kind Meaning
root Establish the full replay base in the initial phase
domain_reduction Replace one exact old domain with a strict subset, caused by decision or propagation
propagation Mark the end of an initial or search propagation interval
decision Record the chosen cell and singleton candidate before its delta
conflict Identify an observed empty active-cell domain
backtrack Restore the ordered delta stack to an exact change mark
result Publish the terminal sat or unsat status

Every event has a complete-run sequence number, DFS depth, and change mark; fields that do not apply are null in JSON. Domains remain canonical 23-bit tile masks. The event order is deterministic for a fixed solver path and input, but reference and optimized traces are not required to be byte-equal to one another.

When capacity is exhausted, the recorder freezes the contiguous prefix and retains the reserved terminal result. Its sequence number reveals the omitted event count and truncated is true. The state after the gap is intentionally unknown: replay returns the last reconstructable prefix state for the terminal frame and never invents the missing deltas.

Checkpoints and independent replay

Each checkpoint contains the full dense state and change mark immediately after its configured event interval. Checkpoint capacity is independent from event capacity; checkpoints_truncated reports exhaustion. They provide bounded random-access anchors and, more importantly, independent replay assertions.

The immutable Python model and the isolated renderer each validate the event grammar and replay the same semantic deltas without calling either solver. They reject widening or no-op reductions, an incorrect old domain, invalid cell indices, impossible rollback marks, mismatched checkpoint state, a misplaced terminal event, and a complete SAT state that disagrees with the hash-bound solution. This is validation of recorded consistency, not a second proof that the source decision is correct; native and Python tiling checkers retain that responsibility.

Closed transport and identity

wang-solver-trace-v1 is a closed square, row-major document with semantics: observed. It records the solver name, terminal status, formula, region and optional solution digests, layout, capacity metadata, full initial domains, events, and checkpoints.

wang-explain-manifest-v3 binds that trace to the existing versioned formula, tileset, region, reduction-provenance, and optional solution artifacts through their exact schemas, basenames, and SHA-256 digests. The manifest is installed atomically only after all content-addressed artifacts. A SAT trace requires a solution reference; an UNSAT trace forbids one.

The committed example under tests/fixtures/pipeline_sat_solver_trace/ comes from the reference solver on tests/instances/pipeline_sat.cm13. It is a complete 2,896-event run with an event capacity of 4,096 and 22 checkpoints at interval 128. It records 451 dense positions, 444 of them active.

Reproduction and presentation

Build the shared library and export one observed run:

make shared
uv run python tools/export_solver_trace.py \
  tests/instances/pipeline_sat.cm13 \
  build/solver-trace/manifest.json \
  --solver reference \
  --event-capacity 4096 \
  --checkpoint-interval 128 \
  --checkpoint-capacity 32

The isolated renderer consumes only the manifest and its JSON artifacts:

cd renderer
uv run --locked python wang_trace_render.py \
  ../build/solver-trace/manifest.json \
  ../build/solver-trace/rendered

wang_trace.py owns strict loading and one semantic replay. wang_trace_render.py selects events and composes RGB frames. wang_animation.py writes those same frames as deterministic atomic PNGs, one contact sheet, and a presentation-only GIF. The renderer imports neither Z3 nor native code, and successful raster output is never treated as correctness evidence.

Z3 uses a different boundary. Its versioned summaries are explicitly labeled encoding-order, fix version, seed and thread count, and record the project-defined constraint order, returned result/model, and stable project-owned counts. They do not expose or infer Z3’s internal search order.

The observed-run dossier index lists the versioned SAT and UNSAT cases that reuse this trace and rendering chain in an opt-in LaTeX/PDF report. It adds no second replay path and does not turn UNSAT diagnostics into a proof certificate.