Benchmarks.

Per-cell history for causl-ts — the default pure-TypeScript engine — against Jotai, Redux Toolkit, and MobX across the canonical scenario taxonomy in causljs/causl-bench. Each card shows one (library × scenario × scale) cell — median wall-time as the solid line, p95 as the translucent band, run-by-run, with a regression verdict computed from the latest two runs. The causl-wasm Rust production engine Enterprise is benchmarked separately under a two-engine comparison; that data and its analysis ship with the Enterprise wasm-performance guide.

Live history

Per-cell median + p95 + verdict.

The page first tries ./history.json; if the nightly publish hasn't landed yet it falls back to ./history.sample.json, a checked-in multi-run stub so the dashboard is never empty. The “source” cell in the meta strip below tells you which one is showing.

Profile artifacts pending CI restoration

Each cell carries a profile link that points at ./profiles/latest/<library-scenario-scale>/. Those directories are populated by the nightly perf-snapshot workflow, which is currently parked in .github/workflows-disabled/perf-snapshot.yml until GitHub Actions is re-enabled. The links remain useful as discoverable placeholders so the dashboard surface doesn't change shape when CI is restored.

New sample field: boundaryCrossings (Phase L1) Enterprise

Each causl-wasm Enterprise sample now carries a boundaryCrossings integer counting how many times the scenario crossed the JS↔WASM bridge during the timed region. causl-ts samples carry 0 (no bridge). The per-cell history charts will gain a boundary-crossings sparkline in a follow-up; today the value is queryable in history.json directly. The full boundary-tax analysis is an Enterprise topic — see the wasm-performance guide.

Charting library — vanilla SVG (decision log)

D3 (~70 KB minified) and Plotly (~3.5 MB minified) were both evaluated as upgrades over the inline-SVG MVP. We stayed on vanilla SVG and brought the visible upgrades — cubic-Hermite (Catmull-Rom α=0.5) line smoothing, D3-style "nice" axis ticks at 1/2/5 × 10k, per-point hover tooltips covering every run — directly into dashboard.js.

The trade we declined: a lazy-loaded D3 bundle would have added ~70 KB of parse/compile cost to a page whose only interactive surface is a small per-cell sparkline grid. Lighthouse Performance budgets penalise any third-party JS that touches the main thread post-paint, and Plotly is two orders of magnitude over budget. The visible upgrades the issue wanted were ~80 lines of inline math each, not a framework dependency. The dashboard ships with zero external runtime dependencies; the <noscript> block below points no-JS readers at the raw history JSON.

Engine comparison: causl-ts vs causl-ts-wasm-engine Enterprise

Side-by-side per-scenario perf envelope under two engine implementations. The upstream causl-ts series is the default pure-TypeScript engine; the causl-ts-wasm-engine Enterprise series is the fork wired to the consolidated WASM bridge from causljs/causl-wasm. Toggle the source per bench run with CAUSL_TS_SOURCE=upstream|wasm-engine in causljs/causl-bench. The JS↔WASM crossings column is the star metric — it counts how many times each scenario rang the WASM bridge on this run. causl-ts is always 0 (no boundary); causl-ts-wasm-engine should be > 0 on every cell. The wall-time delta between engines is dominated by boundary cost per crossing × number of crossings.

Scenario causl-ts causl-ts-wasm-engine Enterprise JS↔WASM crossings
+ payload bytes min/median/max
wall (ms) CPU user (ms) CPU sys (ms) heap peak (B) hot path cold path
Loading dual-engine.json from causljs/causl-bench

Library limitations

Why some (scenario × library) cells are absent.

Some cells in the comparison table are absent because of structural limits in the comparator library, not because the scenario was selected to disadvantage it. Where a comparator can run a cell, its result is published as-is; where it cannot, the structural reason is stated and the cell renders as (skipped — …) with a reason string after #1293. The fairness-audit discipline (docs/bench/2026-05-12-fairness-audit.md) classifies every skip into one of the three buckets below.

Class 1 · Stack overflow at chain depth

Recursive read-path overflows V8 at linear-chain × 10000.

Affected cells: linear-chain × 10000 for jotai, mobx, redux-toolkit. Causl reports a real median here post-#956.

Each comparator's read path evaluates the chain via mutual recursion through its track/get entry point, so the V8 call stack overflows at depth 10 000. The harnesses throw a typed RecursiveEvalStackOverflowError up-front (see packages/bench/src/libraries/_expansion-stub.ts) so the doomed setup never runs and the tolerant runner records a clean skip with the per-cell reason string.

Causl's Phase-D driver is iterative end-to-end after #956: the registration walk and the commit-time fix-point both run through an explicit StackFrame[] rather than recursive calls. SPEC §5.1 Amendment 3 pins this as an implementation-detail trail with no contract change — every §3 atomicity and §5.1 phase invariant is preserved byte-identically; only the depth ceiling moves. The asymmetry is structural to the JS engine shape, not the workload, and tracked under #721 part 3 (mobx investigation) / #798 / #922 / #926.

Class 2 · No public API expressible

Scenario's acceptance gate cannot be expressed in the comparator's public surface.

Affected cells: commit-firehose-1000-subs, multi-fetch-race-N10, and op-wasm-boundary-1k — each skipped for jotai, mobx, redux-toolkit.

These scenarios require commit-metadata semantics (subscribeReads vs subscribeCommits split — SPEC §5.1 Phase F), staleness-aware async (a commit-seq surface so a late fetch result can detect that a newer write has landed — see SPEC.async.md), or the WASM-FFI boundary contract (SPEC §17.6) Enterprise. None of the three comparator libraries have an equivalent public-API surface, so the cell's acceptance gate is not architecturally expressible.

The harness throws a typed ExpansionScenarioNotImplementedError with a reason string identifying the public-API gap — deferred explicitly under #843 rather than implementing a cell that runs but cannot answer the question the scenario was constructed to ask (the “loud silence” failure mode).

Class 3 · Expansion not implemented

Catch-all for typed skips that fall outside Classes 1 and 2.

Any cell where the harness throws ExpansionScenarioNotImplementedError for a per-cell reason that does not reduce to a structural stack overflow or to a public-API gap. None are currently active in the published comparison table — every entry today falls into Class 1 or Class 2 — but the bucket exists so future expansion scenarios with library-specific gaps have a documented home rather than appearing as a silent drop. The per-cell reason string in comparison_table.md always names the gap; this class is the residual category.

What we measure

Three axes, picked because they fail in the field.

Microbenchmarks lie about real workloads, so each axis is anchored to a scenario that already exists as a runnable demo in the repository. No synthetic loops without a corresponding adopter shape.

Cascade latency

Time-to-coherent-state.

Spreadsheet-diamond and linear-chain scenarios from packages/bench/scenarios/ commit a single root edit and measure wall-clock time until every dependent has settled to its glitch-free value. Reported as median + p95 across runs, library by library.

Bundle size

Full-import @causl/core.

Measured against the SPEC §14.2 budget — the 15–30 KB band with an 18 KB working target — using the same size-limit configuration that already gates every PR. Surfaced via bundle-table.ts and will land on this page in a follow-up.

Memory ceiling

Bounded enumerator under load.

The BFS frontier / traces / races caps are swept across cap settings, plotting states_visited versus wall-clock so adopters can pick a cap without guessing.

For the full methodology behind these numbers — the SPEC §17.5 capability-cost residual band and how to run the bench harness against your own workload — read the Performance usage guide. Opting into the WebAssembly backend Enterprise and its benchmark story are covered in the Enterprise wasm-performance guide.