Task 5 of 6
The red half is not a sweep — half the lattice has not been offered a move. The black
half is the same kernel with its parity test flipped, and the ordering that matters is in the
chaining: black(red(s)). The black cells read the lattice the red
half produced, so their ΔE accounts for the reds that just moved. That is not an
optimisation, it is the correctness argument: a black cell's four neighbours are all red, and
the reds have finished.
Give the two halves different seeds too — 2k and 2k + 1 — or every
black cell would draw the same number its red neighbours just used.
Then run it. Same starting lattice as task 3, same temperature, same thirty sweeps, and
the energy that climbed from −0.01 to +1.30 now falls to about
−1.84, sweep after sweep — 26 of the 29 steps go down, and the three that do not
tick back up by less than 0.004. That wobble is the temperature doing its job:
T = 1.5 is cold, not zero, so a handful of uphill moves are accepted every sweep
and the energy is allowed to breathe. Nothing about the physics changed and nothing got
slower: the same 16,384 spins are offered the same moves. All that changed is which of
them are allowed to move at the same time.
black(red(s)).1 moves, everything else passes throughawait black(await red(s, T, 2k), T, 2k + 1) — the black half reads what the red half wroteCopy the red kernel and change one digit:
const parity = (x + y) % 2;
if (parity !== 1) return spin;
Still a number, never a boolean — the WebGL backend rejects const isBlack = …
exactly as it rejects isRed.
const afterRed = await red(s, temperature, k * 2);
s = await black(afterRed, temperature, k * 2 + 1); // NOT black(s, …)
Or in one line, await black(await red(s, T, 2 * k), T, 2 * k + 1). The inner
await is not decoration: an un-awaited kernel call hands the black half a
Promise instead of a lattice.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.