An *engine* is the code that plays the game. The TypeScript engine is generally used for scripting and low-throughput scenarios, while the Rust engineenables high-performance computing and massive parallelism.

The engines

Side by side

Here is a full table that combines all the records about the various engine measurements, so they can easily be compared side by side.

QuantityTypeScriptBrowserC++C++ fastScenarioRustRust ClassicSource
Board bytes49 cells; bytes not recordedengine.tsnot recorded49finding-13-fast-engine49 + scanfast-engine.hpp49 + 49 latentscenario.hpp28RS-…e89ea128not recorded
Rules parity partner and scaleC++: 256 seeds / 6,852 moves; Rust: 256 gamesreproducibility, RS-…e89ea128TypeScript: test-gated, counts not retainedfast-search.test.tsTypeScript: 256 seeds / 6,852 moves; scenario: 218,470 moves; C++ fast: 438,020 moves; Rust: 512 center + 256 search-policy gamesreproducibility, finding-02-scenario-benchmark, finding-13-fast-engine, RS-…e89ea128C++: 438,020 moves; whole-game 704 comparisonsfinding-13-fast-engine, finding-15-depth5-exact-estimatorC++: 218,470 movesfinding-02-scenario-benchmarkC++, C++ fast, TypeScript: 36,427 moves / 40,286 wavesRS-…e89ea128TypeScript Classic: one shared transition; 4 cargo testsclassic-engine.test.ts
Leaf paritynot applicablebit-identical vs evaluateHeuristic, all profilesfast-search.test.tsnot applicable225,183 states bit-exactfinding-13-fast-enginenot applicable150,854 statesRS-…e89ea128not applicable
Search paritynot applicabledepth 2/3/4 exact incl. evictionfast-search.test.tsnot applicable306 moves, 9 configsfinding-13-fast-engine428 solver comparisons vs naivefinding-02-scenario-benchmark105 d4s7 + 10 d5s7 rootsRS-…e89ea128not applicable
Single-core moves/s649,471not recorded6,799,1806,511,760not recorded12,838,933not recorded
Leaf ns/evalnot recordednot recorded970.3 at load 14.6finding-13-fast-engine278.8 at load 14.6; 187.5finding-13-fast-engine, RS-…e89ea128not applicable155.6RS-…e89ea128not applicable
d4s7 ms/decisionnot recordednot recorded3,247.8; 2,903.9 at load 26.9RS-…e89ea128, finding-13-fast-engine1,071.5; 910.9RS-…e89ea128, finding-13-fast-enginenot applicable907.6 with 64k table; 1,633.4 no tableRS-…e89ea128not applicable
d5s7 ms/decisionnot recordednot recorded23,992.6; 30,340.2RS-…e89ea128, finding-13-fast-engine7,817.3; 9,379.2RS-…e89ea128, finding-13-fast-enginenot applicable7,787.9 / 7,047.1 / 6,748.4 at 256k / 1M / 4M; 63,325.4 no tableRS-…e89ea128not applicable
Speedup vs its own referencenot applicablenot recorded1.00 (the reference)finding-13-fast-engine3.08x d4s5 decision; 2.88x to 3.23x games and probes; memo 1.581x / 1.634x indicative; M6 5.56x play dutyfinding-13-fast-engine, RS-…bd1697c8, RS-…8f3e9b4ftable 7.6x nodes / 4.9x wallfinding-02-scenario-benchmark1.97x vs C++ fast engine, 19.8x vs TypeScript; frontier 1.2612x d4s7, 1.1352x d5s7 on arm64 12 CPUsRS-…e89ea128, RS-…1b3ed9a5not recorded
Table memory40,000 entries; bytes not recordedsolver.tsnot recorded~15.4 MB / ~51 MB at 60k / 200k, estimatedfinding-13-fast-engine4,648,576 B at 60k; 16,194,304 B at 200kfinding-13-fast-engine, RS-…e89ea128not recorded3,145,728 B at 64k; searcher 2,496 BRS-…e89ea128not applicable
16-thread moves/snot recordednot applicablenot recorded82,364,000not recorded129,483,860not applicable
Latent / hidden-board modeyes (LatentBoardOptions)engine.tsnofast-search.tsnoengine.hppnofast-engine.hppyes (LatentRevealSource)scenario.hppnoRS-…e89ea128yes (explicit inputs)lib.rs
Idle-host measurementnonenonenonenonenonenonenone
no record

The record behind the latency columns notes: “Timing measured on a shared workstation (load average 1.1-1.7); ratios between back-to-back arms are the trustworthy quantity, absolute nanoseconds are not.” (RS-20260824T075451Z-e89ea128).

Nothing in this table qualifies as a clean performance baseline under the benchmark rules. A clean measurement would need exclusive resources, three repeats and a scaling preflight.

Fast-engine speedups over the unoptimised reference — about 3x, not the hoped-for 5–10x

  • fast engine (finding-13)
  • one-entry leaf memo (indicative)
Fast-engine speedups over the frozen C++ reference, from the headline table of finding-13 and the one-entry leaf-memo record. Every bar is a ratio measured back to back on the shared machine; the memo bars are flagged indicative by their own record.
Notes and sources

Engineering evidence, CHECK tier: no score, strength or ranking claim is made anywhere. finding-13 section 0 headline table, all ratios measured back-to-back or interleaved in one process on a machine shared at load 14-57 — ratios are the trustworthy quantity; absolute times are inflated roughly 3x at load 50 relative to load 15, and an earlier SEQUENTIAL (non-interleaved) measurement of the same stack reported an illusory 5.51x that vanished under interleaving (recorded as the load-drift lesson). Behind every bar: identical selected columns (306 gated search moves plus 438,020 gated engine moves), identical logical work at all nine (depth, strata) configurations, identical completed depth, bit-identical leaf values over 225,183 states, and whole-game identity on the 64-game cohort (704 field comparisons, finding-15 section 1). The leaf-memo bars are a separate record (RS-20260822T074305Z-bd1697c8, metrics.timingIndicative): one-entry memo, interleaved plain/memo under load ~31, 12 roots x 3 reps (d4s5) and 6 roots x 2 reps (d4s7), flagged indicative by the record itself; the memo is deliberately NOT shipped (finding-13 section 8.4 — a cache-semantics change needing its own declaration). Not a clean performance baseline under docs/benchmarks.md; nothing here may enter research/benchmarks/profiles-v1.json.

Spec: web/content/figures/engine-speedup.json · 2 source records · kind bar

Cross-engine parity

Two implementations mean two chances to be wrong. If the fast engine dropped a chain wave one move earlier, or awarded the rise bonus in a different order, every score from the C++ experiments would mean something slightly different from every score from the TypeScript ones, and nobody would notice by comparing means. So the engines are compared move by move instead.

Pick a seed. The seed fixes the sequence of discs the game will deal, in both engines, through the same arithmetic. Play a complete game in each engine, choosing columns from a shared random stream rather than from a policy, so both engines make exactly the same moves and any difference in the result is a difference in the rules. After every move each engine writes one record: the points that move scored, the running score, the level, the moves left before the next rise, whether the game ended, whether the board was cleared, whether the level advanced, every chain wave, and the full 49-cell board as a string. Compare the two streams line by line, byte for byte. Any difference at all is a failure.

The same method sits behind every “bit-identical” claim on this site: the fast engine's leaf, search and trajectory gates, the scenario engine's parity run, and the Rust engine's three gate binaries all replay the C++ engine and stop at the first differing byte. The method has a limit the fidelity audit states plainly: two engines can agree exactly on the wrong rules, and the 70,000-point board-clear branches have zero cross-engine coverage in the parity sweep (docs/exploratory/audit-01-engine-fidelity.md).

The method behind every bit-identical claim on this site. One seed fixes the disc tape (here the first eight discs of seed 0x2d700000, the first seed of the parity sweep), both engines play the same column each move, and after every move each writes one record: points, score, level, moves left, game over, the waves and the 49-cell board. The records must match byte for byte, and the first difference stops the run. The TypeScript and C++ engines agreed on 256 seeded games and 6,852 moves (docs/reproducibility.md); the scenario engine replayed 218,470 moves (docs/exploratory/finding-02-scenario-benchmark.md), the fast engine 438,020 (docs/exploratory/finding-13-fast-engine.md) and the Rust engine 36,427 (RS-20260824T075451Z-e89ea128) against the C++ engine with zero mismatches.

Efficiency work

The approach directories that build, speed up or check an engine rather than play the game. Each keeps its own page with its records and gates.

Agent contextEngine source entry points, gates and open questions

Source entry points

Gates

The tests and gate binaries that must pass before an engine's output counts, grouped by the file or command that runs them.

src/core/typescript/engine.test.ts

  • scoring constants match the original game
  • gravity preserves the order of discs in every column
  • line counts stop at gaps and include covered discs
  • a wave clears all matching discs simultaneously
  • two hits in one wave fully reveal a solid disc
  • gravity, cracks, reveals, and scoring compose across chain waves
  • exact gray-disc outcomes retain their full probability mass
  • the Hardcore game starts above a solid row and cracks it on a 1
  • clearing the board awards the original screen-clear bonus
  • every fifth move raises a solid row and awards the level bonus
  • a level-up explosion continues the fifth move's chain depth
  • a rising row ends the game instead of discarding an occupied top cell
  • the exact move model includes all seven next discs
  • streamed move outcomes preserve exact probability and expected score
  • seeded games remain settled and gravity-packed through game over

src/core/typescript/engine-latent.test.ts

  • a reveal uses the covered cell's predetermined latent value
  • a reveal without a latent value is an error, not a draw
  • latent values follow their covered cell through gravity
  • a row rise shifts latent values up and draws the new row from the source
  • scripted latent games are exactly reproducible
  • moves without a latent board keep random reveals and report no latent state

src/core/typescript/classic-engine.test.ts

  • Classic TypeScript matches the shared native conformance transition
  • Classic uses a decreasing 30-drop clock and 7,000-point rise bonus
  • Classic incoming discs contain seven numbers and one gray outcome
  • Classic accepts latent values for dropped gray discs
  • an explicit Classic tape is independently replayed to completion

web/lib/play/fast-search.test.ts

  • fast leaf is bit-identical to evaluateHeuristic for every profile
  • fast leaf sees every chance outcome identically, not just root boards
  • fast move generator streams the reference outcomes in order with exact probabilities
  • fast move generator settles a caller-supplied unsettled board like the reference
  • fast move generator ignores illegal columns and terminal states like the reference
  • completed depth-2 searches match the reference exactly
  • completed depth-3 searches match the reference exactly, per profile
  • a depth-4 search matches the reference exactly, including cache eviction
  • work-limited searches abort at the same point and report the same partial result
  • depth-complete callbacks fire with identical intermediate results
  • timing: fast search against reference on the same decisions (informational)

make test-native; make parity

  • native-suite --gradient-check
  • native-suite --ntuple-self-test
  • native-suite --ntuple-search-self-test
  • fair-depth4 --self-test
  • approaches/baselines-diagnostics/native-parity/main.ts over seeds 0x2d700000 + 0..255

build/fast-engine (finding-13 section 10)

  • gate-leaf
  • gate-search
  • gate-trajectory
  • fast-engine-memo/gate.cpp
  • fast-reveal-sampling/gate.cpp

build/scenario

  • scenario-parity --seeds 4096

approaches/fair-expectimax/rust-engine (target/release)

  • gate_trajectory against rust-engine/cpp/trace.cpp and rust-engine/ts/trace.ts
  • gate_leaf against rust-engine/cpp/leaf_trace.cpp
  • gate_search against rust-engine/cpp/search_trace.cpp

src/core/rust/classic-engine (cargo test)

  • clock_and_bonus_match_classic
  • gray_drop_keeps_its_hidden_value_through_gravity
  • level_boundary_uses_seven_thousand_and_a_decreasing_clock
  • shared_typescript_conformance_transition

Open questions

  1. Gitignored run artifacts: benchmark.json, bench.log and gates.log for the Rust run, the memo gates.log, the M6 equivalence and timing JSON, and the arm64 machine profile MACH-20260825T045112Z-2d4c14b8 are all under runs/ and absent from this checkout. README figures that exist only there cannot be checked against their source here.
  2. "14.1x in a clean run" (RS-20260824T075451Z-e89ea128 summary): no run record, artifact reference or limitation describes that clean run. It is not printed on this site until the owner confirms what it refers to.
  3. Which C++ fast arm the Rust benchmark compared against: rust-engine/build.sh compiles search_bench.cpp against the variant-search driver; whether that arm carried the one-entry leaf memo is not stated in the record. The 1.18x and 1.11x search ratios should be read as "vs the C++ fast search as built by rust-engine/build.sh".
  4. Machine and load for audit-02 section M5's 4,193.6 ns leaf figure are not stated in that section.
  5. The M6 README's artifact path runs/RUN-20260823T215500Z-sol/fastm6/ matches neither run ID (RUN-20260824T005500Z-4cde9171 and RUN-20260823T215500Z-fc74e0b4 exist); the result record RS-20260824T010000Z-8f3e9b4f is the citation.
  6. The Rust Classic engine's intended consumer: infra/README.md mentions a multi-hour Rust policy game in the submission context and the contribution record says "for future research use", but nothing in the tree calls the crate today.
  7. Hashes: src/core/native/engine.hpp still hashes to the value pinned in research/benchmarks/baselines-v1.json (b6dcde5f…3090). src/core/typescript/engine.ts has grown to 874 lines since audit-01, so audit-01's TypeScript hash is quoted only as "at the time of the audit".
  8. The policy-layer move loop (audit-01 M3): finding-13's anchor gate and the Rust engine exercise playMoveSampled indirectly through search-parity gates, but no gate compares playMoveSampled to engine.hpp::playMove directly. This site keeps audit-01's wording.
  9. Browser solver environment: no Node, V8 or browser version is recorded for the browser port anywhere; even an informational figure would lack a machine profile.
  10. The KF linear-Q Rust transfer and pruned-search pilot experiments (EX-20260902-…-4328a730, EX-20260902-…-bf465b1d) reference the Rust engine run and were outside the engines audit's scope; they may add Rust-side policies or leaf variants.
  11. "98 reads" and "about 70 bit operations" per wave appear only in README captions (fast-engine and rust-engine); finding-13 records only "on the order of 1,300 board reads per wave" for the reference.