Status & Theory · Hackenbush Lab

What this engine does — and what it doesn’t

An honest account of the combinatorial game theory behind Hackenbush Lab: the parts that are exact and tested, the parts that are heuristic or approximate, and the parts that are genuinely out of reach. The book to read alongside it is Winning Ways for Your Mathematical Plays (Berlekamp, Conway & Guy).

← back to the lab

Read this first: it is not a general solver

This is a practical tool for small and medium Red-Blue-Green Hackenbush positions, not a complete combinatorial-game engine. It computes exact answers within a budget by recursively unfolding game trees. That recursion is exponential in the worst case: green edges (playable by either side) and stable “no-fall” edges multiply the branching, and around a dozen live edges a single tangled component can already become slow — or, on adversarial inputs, take effectively forever. The heaviest curated positions (the Boss tier) solve off the main thread in a Web Worker, behind a Solve button with a live state counter and a Cancel button — but every other analysis, including each edit in Create and each move in Play, still runs synchronously on the main thread, so a hard position you build yourself can still freeze the page. Finite values are reduced to canonical form (domination plus reversible-option bypass), so what is displayed is the game’s simplest name; some theoretical refinements (deep transfinite arithmetic, a full thermal solver, misère quotients) remain deliberately not implemented. The sections below say exactly where the lines are.

Start with the video

The clearest visual introduction to this whole world is Owen Maitzen’s HACKENBUSH video. It walks from cutting colored edges all the way up to the surreal numbers, and it is the best way to build intuition before poking at the lab.

►
HACKENBUSH: a window to a new world of math
Owen Maitzen · youtube.com/watch?v=ZYj4NkeGPdM

Here is how the ideas the video shows line up with what this engine actually computes:

In the videoIn this engine
Blue-Red Hackenbush; who wins a position✓ fully — and it adds Green (impartial) edges the video doesn’t cover
Giving a position a numeric value✓ exact dyadic / surreal value for cold positions
The “simplest number” rule & the number tree; dyadic fractions (½, ¾…)✓ with a day-by-day construction path
Adding two positions together✓ disjunctive sum + component decomposition
An infinite blue stalk = ω✓ for the infinite shapes it recognizes (ω, ω±n, ω·n, ω², grass)
That any infinite Hackenbush position yields a surreal number (⅓ from an infinite zig-zag, ε, …)× not general. The engine pattern-matches a fixed family of infinite shapes from edge topology; it does not evaluate an arbitrary infinite Blue-Red path to its exact surreal value

So the engine is a faithful calculator for the finite story the video tells (and adds impartial Green/Nim on top), but the video’s grand finale — that every surreal number falls out of infinite Hackenbush — is exactly the part this engine only approximates, for a handful of recognized patterns.

The game

Hackenbush is played on a graph of colored edges standing on the ground (a baseline). On your turn you cut one edge of a color you control; any edges left disconnected from the ground then fall away.

The player who cannot move loses (normal play). The whole point of the theory is to assign each position a value that says who wins, by how much, and how the position behaves when added to others.

The theory we use

Every position is read as a partizan game G = { Blue options | Red options } and analyzed recursively. The concepts below are the ones the engine actually computes; each is annotated with where it sits in the literature.

Game values, outcomes & order

Positions are compared with Conway’s order relations (G ≥ H defined recursively), which yields the four outcome classes (Left wins / Right wins / first player wins / second player wins). Winning Ways, Ch. 1–2; On Numbers and Games, Conway.

Surreal numbers (cold positions)

When a position is a genuine number — every Blue option strictly below every Red option, and all options themselves numbers — the engine extracts the exact dyadic/surreal value via the simplicity (“simplest number”) rule, and can show the day-by-day construction and birthday. Crucially, it does not pretend every position is a number: hot and fuzzy positions are shown as their raw short-game forms instead. Winning Ways, Ch. 2 (“Whose Game?”).

Disjunctive sums & components

Independent grounded subgraphs are solved separately and combined as a disjunctive sum, which keeps many real positions tractable. This decomposition is transfinite-aware (it carries infinite components through the sum). Winning Ways, Ch. 2.

Nimbers & Sprague–Grundy

Green “bamboo” stalks are impartial: the engine computes their nim-values with the mex rule and recognizes *n symbolically. Winning Ways, Ch. 3 (“Nim”); Sprague–Grundy.

Infinitesimals

The named infinitesimals that show up next to numbers are recognized for display: star *, up/down ↑ ↓, ↑*, double-up/down ⇑ ⇓, and tiny(n)/miny(n). Winning Ways, Ch. 3–4 (“Uppitiness”).

Switches, temperature & thermography

Hot positions of the form {a | b} with a > b are detected as switches; for the cases it recognizes directly the engine reports the mean value and temperature and draws a thermograph (a frozen vertical wall for cold numbers, a V for switches). This is the engine’s weakest area — see the partial list. Winning Ways, Ch. 6 (“Heating, Cooling…”).

Transfinite values (infinite edges)

Edges flagged infinite stand for unbounded stalks. From the edge topology the engine pattern-matches ordinal values ω, -ω, ω±n, ω·n, ω², and combines parallel infinite green “grass” into ω*[n] by a stalk-count rule. This is heuristic pattern-matching, not general ordinal arithmetic — it is deliberately guarded by negative regression tests so it does not over-fire (e.g. two bridged ω’s must not collapse to plain ω). ONAG (ordinals as games); transfinite Hackenbush.

Loopy games

Edges flagged loop invoke loopy values: on, off, over, under, dud, with real arithmetic for sums, comparison, outcome and display. This covers the five named values and their shifts, not the whole of Conway’s loopy theory. Winning Ways, Ch. 11 (“Survival in the Lost World”).

Canonical position hashing

To avoid re-solving isomorphic subpositions, the memo keys positions by a Weisfeiler–Lehman color-refinement hash (~17× state reduction on symmetric positions). Note: this de-duplicates graph-isomorphic positions; it is not the same as reducing a game to canonical form by domination and reversibility (see below).

What’s solid regression-tested

These behave correctly end-to-end on the tested range:

Also solid (tooling rather than theory): auto-play / AI vs AI, multi-edge curving, floating-vertex cues, component thumbnails, light/dark theme, and shareable-position URLs.

Partial & approximate use with care

These work in common cases but have documented gaps or fall back to weaker answers:

Planned doable, not yet built

Hard / out of scope research-level

Why it’s not a general engine

Three honest constraints, in order of how often they bite:

  1. Exponential search. Exact analysis unfolds the game tree. Green edges double the moves available at each node, and stable edges keep more structure alive to branch on. The README puts it plainly: pure cold blue/red positions stay practical longest; green and stable edges enlarge the space sharply; and “around a dozen live edges, exact recursive search can already become expensive unless the position splits into independent grounded components.” Some inputs simply do not finish in reasonable time.
  2. It can still block the page. Boss-tier positions were moved into a Web Worker, so those solve without freezing and can be cancelled. Everything else — each edit you make in Create, each move in Play, Auto mode, the self-test — is still synchronous JavaScript on the UI thread. A hard position you build yourself shows an in-board shimmer, but the tab can stutter or freeze until it resolves, and there is no timeout that guarantees a fast bail.
  3. Some theory is heuristic or skipped. Transfinite values are pattern-matched from topology, not derived by general ordinal arithmetic (and are fenced in by negative tests precisely because a naive matcher over-fires). Canonical reduction by domination and reversibility is applied to every finite short game, but loopy and transfinite values are pattern-matched and left unreduced. The thermal machinery has never actually been exercised: no position anywhere in this app reaches a positive temperature (see the testing section), so “handles simple switches” is untested rather than proven. For anything outside the recognized patterns, the engine returns an honest general game form (or, for a few mixed-infinite shapes, an explicitly approximate result) rather than a false clean value.

The design choice throughout is honesty over false precision: when the engine cannot prove a clean value it says so — showing a raw short-game form, an approximate tag, or a fuzzy outcome — instead of guessing a tidy number. That is also why it is best described as a workbench for small and medium exact studies, not a general-purpose Hackenbush solver.

How we check it (and where coverage is thin)

An in-app regression harness (the Run Self-Test button) currently runs 88 cases across 14 categories, asserting on the analyzer’s computed value fields. Coverage is uneven — deliberately heaviest where bugs have bitten, lightest exactly where the theory is weakest:

CategoryCasesWhat it guards
Gallery18curated named positions keep their solved values
Loopy14on/off/over/under/dud arithmetic; incl. 4 fixes for ω being dropped in mixed sums
Numbers9surreal/dyadic extraction
SignExpansion7L/R sign strings
Thermograph7wall/temperature plotting
Structural6normalization & pruning despite visual defects
Transfinite5ω family pattern matching
Nimbers · AtomicWeight · ShareableURL4 eachmex values · uptimal weights · URL round-trips
Regression · Construction3 eachnegative guards on the transfinite matcher · day-by-day paths
Hot · Composites2 eachswitches/temperature & basic sums — the least-guarded, and the weakest, areas

Two caveats worth stating. These in-browser checks are separate from the npm test suite, which adds 137 Node tests (165 with HB_SLOW=1, which re-solves every curated position instead of the cheap subset) covering the engine primitives, canonical form, the Fusion Principle, misère play, and locking every curated position's winner against a golden file. Most importantly, Hot and Composites have only two cases each, and one Hot check passes on a loose condition — so the thermal/switch behavior is the part you should trust least.

And it did not survive checking. Measuring every position in the app found that not one has a positive temperature: all seven “Hot Games”, and 3,398 randomly generated mixed boards, come out at temperature 0. Several positions had been labelled “±1”, “switch” or “hot” purely because their value printed with braces — but { X | X } is tepid, not a switch, and the partisan options here keep resolving to infinitesimals. Those labels have been corrected to state the winner instead, and a test now refuses any entry that advertises heat the solver cannot find. Whether Blue-Red-Green Hackenbush can be hot at these sizes is left open; what is certain is that nothing in this library is.

References