5 — The simulation pipeline
Written: 2026-08-15 · Established by: reading src/sim/simulate.ts, src/sim/pool.ts,
src/sim/partition.ts, src/sim/field.ts, src/sim/mesher.ts, src/sim/rest-material.ts and
src/workers/tri-dexel.worker.ts at commit 93a9a67.
This is where Ranger's time goes, and it is the only subsystem with a hard resolution limit on what it may claim. Ranger models the stock as a tri-dexel field — three perpendicular sets of rays, each ray holding the intervals along it where material remains — and cuts that field by sweeping the tool's swept volume through it.
simulate() in src/sim/simulate.ts is the orchestrator. It never touches a worker directly: it
takes a SimulationRunner, which is either the worker pool or the single-threaded inline-runner
the tests drive.
Four phases, left to right. The sweep is the long pole and carries the first 90% of the progress bar; the comparison and the mesh share the last tenth.
The two numbers that shape everything above
JOB_BUDGET_BYTES is 24 MB and MIN_JOBS is 64. A job's field costs rays × capacity × 32
bytes whether or not the rays need the capacity, so the budget is what keeps the concurrent peak
survivable — eight workers give a 192 MB ceiling on working memory. The floor of 64 jobs exists
because jobs are wildly uneven: a slice of the grid the toolpath never visits costs almost nothing
once the kernel's region cull has rejected every move, while a slice through the middle of the part
carries the whole file. MIN_JOBS is deliberately not a multiple of the pool size, so a scaling
measurement compares the same partition run two ways rather than two different partitions.
SWEEP_BATCH_SEGMENTS is 4096, and it is two granularities at once. WebAssembly cannot yield, so
the batch boundary is both where progress is reported and the only place a cancellation can take
effect.
The comparison happens on the main thread, before the mesh
That ordering is load-bearing. The verdict is computed from the assembled field, not from the mesh, because ADR-0024 states its rules in lattice terms and the mesh is a lossy reconstruction of the lattice. Doing it on the main thread also means the nominal body is rasterised onto the grid this run derived, with no opportunity for a caller to supply a lattice of its own.
Every verdict states the pitch it was computed at, and declines below its own resolution rather
than reporting with a caveat. That is the same ADR, and it is the reason rest-material.ts carries a
DeclinedRegions type at all.
Resume, and what it is allowed to skip
A run can start from a SimCheckpoint — a snapshot of the field at an operation boundary.
longestUnchangedPrefix finds how much of the operation list is untouched, checkpointUsable agrees
the prefix matches, and simulate adds one further test the diagram cannot show: reach is a property
of the whole program, so a checkpoint is refused if any operation past its own prefix pushed the
deepest commanded point lower than the checkpoint assumed.
Where the honest failures are surfaced
overflow counts rays that ran out of interval capacity — the ceiling on how many separate walls
one ray may thread. Crossing it is a visible defect, so the count is carried forward across a resume
and deliberately double-counts rather than risking an under-report.
MeshTooLargeError carries a suggested coarser pitch instead of quietly decimating the mesh.