Mistboard’s Jungle engine has no concept of a den race. It scores a position by material plus a per-piece bonus for advancing toward the enemy den, and never asks whether the runner heading for its den arrives first. It finds out when the search reaches the den, about twelve plies out.

That looked like a clear gap, filed as an issue after a game where the bot shuffled a rat back and forth while an enemy rat walked in. I spent a day trying to close it. Three routes, all null or negative. This is what the measurements said and what I would keep from them.

What the engine already does

Worth separating, because it framed everything after. The engine understands the win condition completely: reaching the den is scored as mate, ordered first among candidate moves, and searched in quiescence. What it lacks is foresight, not comprehension.

It wins anyway, because Jungle is material-dominated and the evaluation reads material exactly. At the moment of den entry the median winner is 20 points ahead, about one cat. Only 4.7% of games are won by a side that was behind.

What it plays without the concept

Game 10 of the study is four moves long and settles the question better than any table below.

Jungle’s middle is three ranks of water. The d-file is the only land bridge across it. Tigers jump water vertically, never horizontally, so a tiger standing in that corridor has almost nowhere to go.

Jungle position: Red's tiger alone on the d-file land bridge, water either side, Blue's elephant directly above it Move 20, Blue to play. The red squares are the move just made: Red’s tiger took a wolf on d6 and now stands in the corridor. Blue’s elephant is one square above it and outranks it. On move 20 Red’s tiger on d5 had two legal moves. Forward or back, both still in the corridor. It went forward and took the wolf, which cost Blue 40 points and parked the tiger next to Blue’s elephant.

Blue’s elephant outranks a tiger, so this looks like a gift. Blue declined to take it immediately, which is the move I was asked to explain. The engine’s reason is that the tiger could not escape: with the elephant on d7 the tiger’s only move was d5, and from d5 its only move was d4, walked down a one-lane corridor by a piece that beats it. Grabbing it costs a tempo to collect something already collected. Blue played a free improving move and took the tiger two moves later, on d5 instead of d6. Both lines reach identical material.

The four moves that decided the game:

41  Blue  elephant d7 -> d6    steps into the corridor after the tiger
42  Red   lion     c3 -> c7    jumps into the square the elephant just left
43  Blue  elephant d6 -> d5    takes the tiger, up a piece
    Red's lion is now three moves from a den Blue cannot defend

The tiger was bait. Not a sacrifice: it was nearly trapped anyway, and taking the wolf scored +10 against +6 for almost every alternative, a difference of four points rather than a plan. What it does on its way off the board is pull the one piece that outranks a lion down the d-file. At move 20 the lion jump scores -195, because c7 sits next to Blue’s elephant on d7. Two moves later the same jump wins, because the elephant left.

Blue ends the sequence 13 points ahead and loses.

Read the plan back and it sounds like the engine understood a den race. It did not. There is no den-race term anywhere in that evaluation, before or after this project. It searched five million nodes and the line came out. That is the whole result: the behaviour the issue wanted to install was already there, produced by depth rather than knowledge.

Base rate: 4.7% of games, and measure it at shipping strength

172 self-play games at the production budget of five million nodes a move.

terminal share
repetition draw 52.9%
den entry 26.2%
no-progress draw 20.9%
genuine race (winner level or behind ten plies out) 4.7%

The same harness at 200k nodes reports 22% races. Weaker play inflates the number roughly fivefold. The cheap measurement was available, and it would have been wrong by 5x in the direction that justifies more work.

Headroom: a 12x deeper search buys two plies

For each race I binary-searched the loser’s game for the earliest ply at which each budget recognises the forced loss, then took the gap.

deficit races
0 plies 4
2 plies 3
4 plies 1

Sixty million nodes against five million, and the deeper search sees the loss a median of two plies earlier. Before that horizon the two track closely, diverging only in the last ply or two.

This is the number that should have ended the project. If twelve times the search recovers two plies, the information is not sitting there waiting for a cleverer evaluation to surface it.

Route A: a search extension, free and paid

Probed without building it. A harness policy grants one side a larger node budget whenever a piece is within R squares of an enemy den, which is a strict upper bound on any real extension.

First finding, before any Elo: the trigger is not sparse.

radius plies where it fires
2 23%
3 43%
4 72%

Jungle’s board is small and dens sit on the back ranks. At R=4, the radius needed to see a race early, the extension is just “search twelve times more everywhere”, so the surgical framing was wrong from the start.

At R=2, 300 colour-swapped pairs per arm:

arm Elo nodes vs control
12x depth near dens, granted free +45 3.3x
budget-neutral, 3.7M base + 10M near dens -15 1.04x
budget-neutral, 1.1M base + 20M near dens -12 0.98x

Same idea, same code, opposite sign. The engine’s five million nodes are fixed by how long a player will wait, so a real extension cannot conjure depth: it takes it from the rest of the search. The free arm measured the extra compute. Nothing about dens.

I ran the budget-neutral arm because +45 looked too good. Stopping there would have bought a week of work for a feature worth slightly less than zero.

Both arms cost an afternoon, because neither required touching the engine. The extension was a harness policy that varies the node budget on a board condition. For any proposal of the form “make it smarter about X”, granting it free perfect X first is the cheapest way to find out whether X matters at all.

Route B: the term itself

Built it anyway, because the extension failed for a reason that does not apply to an evaluation term. An extension costs nodes; a term costs almost nothing per leaf.

Here is the evaluation as it stood. One loop over the board, and every piece contributes independently:

for idx in 0..N {
    let v = VAL[role_of(code) as usize];
    let friendly = color_of(code) == me;
    s += if friendly { 2 * v } else { -2 * v };

    // Advancement toward the relevant den: the enemy's for our pieces, ours for theirs.
    let target = if friendly { enemy_den } else { own_den };
    let dist = manhattan(idx as u8, target);
    let advance = if dist <= 1 { 400 } else { (16 - dist) * 3 };
    s += if friendly { advance } else { -advance };
}

That advancement term is why the evaluation is blind to a race, and the reason is the symmetry. My runner three squares from your den scores +39. Your runner three squares from mine scores -39. Both sides sprinting produces a number near zero, and the position reads quiet at exactly the moment it is most decided.

So the term cannot be another per-piece bonus. It has to be one verdict per side, computed once, comparing the two runners against each other:

for side in [me, 1 - me] {
    // Nearest enemy runner to this side's den, and this side's nearest defender
    // to its own trap ring.
    let (mut att, mut def) = (99, 99);
    for idx in 0..N { /* ...scan once... */ }

    if att > RACE_RADIUS { continue; }   // no race here: contribute nothing

    // The side to move gets one free step in the race for its own den.
    let margin = att - def + i32::from(side == me);
    let pen = if margin <= 0 { RACE_LOST }
              else if margin == 1 { RACE_TIGHT }
              else { 0 };
    score += if side == me { -pen } else { pen };
}

The defender is measured to the trap ring, not to the den. That is the right interception zone because a den’s three neighbours are its three traps, and a piece standing on a trap can be captured by any defender regardless of rank. An elephant walking into your den has to cross a square where your rat can take it.

It changes the chosen move in 17% of the positions it was designed for, and costs about 17% wall clock at equal nodes. Over 300 colour-swapped pairs per weight, before charging that cost:

weight Elo
10 -3
25 +9

Null, in the generous direction.

What I would keep

Hold the budget constant, or you are measuring the budget. The free version of an experiment is the convenient one to build and it hands you a confident positive number. Whatever the change would have to take from, take it, and see whether the result survives.

That trap caught me twice in one day. A contempt sweep the same afternoon showed decisive games rising from 32% to 50%, which is what higher contempt produces by construction, since both sides in a self-play sweep get the setting. Only the two values facing each other could say whether it cost anything. It did not, so that one shipped.

The evaluation gap was real and correctly diagnosed. It just had nothing behind it. Two constants found in the same corpus, a no-capture draw clock that was ending games early and a draw contempt near zero, took Jungle from a quarter of games ending in a win to about half, and neither teaches the engine anything. The issue asked for understanding. Understanding was never the constraint.

The games are public, and so is the position above. The analysis board runs the same engine, so if you think Blue should have taken the tiger you can make it play the line out.