Halberd Slash, Mind Lash, Shield Bash and the holy heal Flash of Light were all coming out as smoke. The rule that picks how a spell looks was matching fragments of its name, and the three letters in "ash" appear in all four.

Sixty four spells were affected, and nothing anywhere reported a problem. A rule that matches the wrong thing does not crash, it paints the spell the wrong colour forever, and with 618 spells in the game it was never going to be caught by playing.
Two pages exist because of that class of bug. The spell lab casts one spell properly, inside the real game, so you can look at it. The bench casts one spell four times at once, at every quality setting, so you can compare them. Both are open. Open them alongside this and the rest of the post describes what you are looking at.
What you see when you open the spell lab
A flat grass arena, a caster standing in the middle, six friendly units in a ring around him and one enemy target dummy ten tiles to the east. A panel lists every spell that caster genuinely knows. Click one, click the ground, and the spell goes off exactly as it would in a match, because it is a match: the page starts the same game the play page starts, on a small flat arena instead of a generated map, with the fog lifted and the enemy given nothing to do.
The three things it puts on the field are each there for a reason. The caster carries its real spell list rather than a curated one. The ring of six friendlies exists so an area buff or a heal has something to land on, and each is told to hold position so the game's own targeting cannot make them wander off and break the ring. The dummy has its health topped up and its damage zeroed every couple of seconds, and it respawns if something enormous kills it outright.
That last detail is not only about keeping a target alive. A dead dummy means the enemy player owns nothing at all, and the game would declare victory in the middle of a preview.
Because the game underneath is real, the page implements no selection, no spell targeting and no click-to-cast conversion of its own. It fires the same event the interface fires, the next click on the world is caught by the same handler the shipped game uses, and the same call queues the cast. The page itself is two canvases and an inspector panel. Everything a spell preview could get subtly wrong is therefore not the page's code to get wrong.
What you see when you open the bench
Four panes across the screen, the same spell going off in all four at the same instant, each labelled with its quality setting and its measured cost. Low, medium, high and top, all four, because the top setting is a real one with signature effects, ground fissures and soft particles, and leaving it out would hide the top end of the exact thing the page compares.
The obvious way to build that is to boot four games. It does not work, because the quality setting is global to the page, so four games in one tab would fight over one value. Four full simulations is also far more than a visual comparison needs. So the bench owns one plain three.js scene and loads the drawing side directly.
Four things could make a side by side lie, and each is closed off deliberately.
Different settings. Each pane builds its own effects from a pinned set of values, so no pane reads the global one.
Different framing. All four sit at the same place in the world and are shown or hidden per pane rather than spread out along a line. Every pane therefore gets an identical camera, ground and lighting. The quality setting is the only variable.
Different dice rolls. A cast pushes the same random seed into all four on the same frame. Without that you are comparing four rolls of the dice and calling the difference quality.
A different code path. The panes never call the renderer directly. They emit the same description of an impact the game emits, into their own private event channel, and let the real code handle it, so the entry point, the conversions and the arguments are the ones a live match uses. That rule was added after a hand-maintained second entry point quietly dropped the list of chain targets, which made chain lightning impossible to judge on the bench without saying so. The effects behind it are covered in porting shader-grade spell effects.
That last one generalises further than the other three. A preview tool has exactly one job, which is to be trustworthy about the shipped thing, and the moment it makes its own call into the system it becomes a second caller with different arguments. So find the seam the real thing already goes through, the event, the action, the queued message, the public function, and push through that instead. The test is whether you could delete the preview's call and still have the real game exercise the same line. If not, the preview will drift, and it will drift silently, because nothing on the far side of it has any users.
Measuring each pane honestly
The cost printed under each pane is measured rather than estimated, and the recipe is short enough to lift into anything.
renderer.info.autoReset = false; // once, at setup
renderer.setScissorTest(true);
for (let i = 0; i < panes.length; i++) {
for (let j = 0; j < panes.length; j++) panes[j].root.visible = i === j;
const x = Math.floor(i * paneWidth);
renderer.setViewport(x, 0, Math.floor(paneWidth), height);
renderer.setScissor(x, 0, Math.floor(paneWidth), height);
renderer.info.reset();
renderer.render(scene, camera);
stats[i].drawCalls = renderer.info.render.calls;
stats[i].triangles = renderer.info.render.triangles;
}
renderer.setScissorTest(false);
for (const pane of panes) pane.root.visible = true;The automatic reset is on by default, and it clears the counters at the start of every render. Turning it off and resetting by hand is what makes the number survive a pane that needs more than one pass. The visibility toggle in the inner loop is what makes the figure mean anything at all: without it, every pane's number includes the other three. And the whole thing draws the shared scenery four times over, which is the right trade for a page whose entire purpose is measurement.
The overlay that turned it from a preview into a check
The bench could answer "what does this setting buy?" long before it could answer "is this the right effect?", because nothing on screen said where the damage actually landed.
So there is now a reference overlay, drawn thin and dashed in the visual language of a measuring grid so that it can never be mistaken for the effect itself. It shows the exact perimeter the game damages, produced by the same code the in-game ground markings use, plus the curved flight path for the seven spells that have one. There is one per pane, because the panes are shown and hidden, and a single shared overlay would vanish from three panes out of four.
Playback runs at a tenth speed, or freezes entirely. Everything the bench schedules, the stagger between volleys, the automatic recast, the wind-up, runs on a clock that is slowed along with everything else rather than on real time. Mixing the two collapsed the volley stagger once, and kept a frozen scene casting into itself.

What the bench actually found
Six classes of bug in two days, and not one of them was a crash. Every one of these drew something, at a plausible frame rate, and looked like a spell.
| What was wrong | Spells affected |
|---|---|
| Name fragments matched inside longer words, so the school was wrong | 64 |
| Single-target melee casts drew a wide area blast | 72 |
| Bolts fell back to the default projectile and threw rocks | 20 |
| Two different effects ran for the same cast at the top two settings | all |
| Hero ultimates keyed on the hero, naming no subject at all | 225 |
| Cones and lines fell back to a circle at the top two settings | directional spells |
The hero ultimates are the clearest case for the page existing. Every one was keyed on the hero rather than the spell, so Great Plague, Starfall, Void Cataclysm and Storm of Sotek all resolved to the same generic purple. The fix reads the spell's display name when its identifier yields nothing, and a test now pins both halves, including the deliberate refusals. A spell called Hail of Stars must not come out as frost: the word "hail" reaches exactly one spell in the game and that spell rains starfire, so it is describing a trajectory rather than a school. Faction identity itself comes from the letterform work in inventing a magic alphabet, and you can see all nine alphabets side by side in the alphabet lab.
What each page is honest about
The two pages have opposite shapes, and knowing which one to open is most of the value.
The bench runs no game at all, which is what makes it cheap. The curved flight paths are a diagram rather than real projectiles, the chain arcs fall back to a ring when there are no real units to bounce between, and the reference rings prove that an effect matches the area it claims. That is a check on the drawing against the data, and it is the right tool for asking whether a quality setting is worth what it costs.
The main lab is the opposite: a real game, real mana, real cooldowns, one caster at a time on flat grass against a dummy that cannot fight back. That is the right tool for asking whether a spell behaves, and the wrong one for asking what it costs.
Between them they cover the two questions that actually get asked about a spell. The general form is in why every system gets its own lab, and it holds here: a lab proves a system works in a lab, so build the one whose limits you can name.





