Δ9Φ963 · studio doc
A real-time arena battler where nobody is driven. You choose a roster; two AI teams then fight on a stone ring with their own kits, cooldowns and cards, and the fight resolves on its own terms.
Lattice Crypt is a game you play with keys. LYGO Arena is the same world with the hands taken off the wheel: a real-time team battler in which the interesting decision happens before the bell. You pick the team, the challengers are drawn blind from the full roster, and then you watch a fight nobody is steering, including you.
The design bet is that the kits are legible enough to be fun to read. A fighter is not a stat block
with a name: every one of the thirty-three carries its own stat line, three named moves and three draft
cards, derived from the numbers Lattice Crypt already gives it, shot, speed, magic, armour, melee,
brave and faith for the champions, the FOE table for the crypt creatures, the
PETS table for the pack.
Four modes: 1v1, 2v2, 3v3 and 5v5. Team size is the only rule that changes; the arena, the kits and the AI are identical, so a 5v5 is not a different game so much as a louder one. Between the count and the result the only inputs are the watch controls: pause, and 1× / 2× / 4× / 8× speed.
Medians over 300 simulated battles in the balance harness (§6). Every match is expected to land inside two minutes; the ring starts closing at 80 seconds so no two healers can out-sustain the floor and leave a fight unresolved.
Each match builds a fresh map from the Lattice Crypt stone atlas: a symmetric tile field with a realm family chosen by seed, walls mirrored so neither side inherits better ground, and both spawn pockets flood-filled to guarantee that every tile a body can stand on is reachable from both ends. The harness asserts this over 120 seeds, a map that strands a fighter is a bug, not a surprise.
There are five colosseums, and the seed picks both the ground and the realm it is cut from. The layouts are kept whole in the code; in this build every ring is swept bare, no pillars, no walls, no loose stone, while the brains are tuned, so that what a body does is the brain's doing and not the walls'. The five are still built and still named, and cover comes back with one flag:
Every one of them is deliberately nearly empty, 86% open sand, with nothing standing in it at all right now, because the obstacle a body snags on is the obstacle that ruins the watch. What is left is placed to be worth standing next to: cover to break a line of fire, a gap to hold, a lane to run. The harness builds 120 seeds and asserts that both pockets can reach all ten spawn slots in every one of them.
Bodies move in continuous space, not on the grid. They walk with a measured feel for stone: a move is judged by how much of the body would end up inside a wall rather than by a yes/no test, so a body shoved into the border by a knockback can always push its way back out, the bug that once left a kit frozen against the wall for the rest of a match. A body whose cell is stone walks straight to the middle of the nearest open cell, through the collision test, because it is already in the wall. The choice between the direct line and the route around cover is held for half a second: an enemy stepping behind a pillar would otherwise flip that decision every few ticks and leave the body walking on the spot.
Every kit fights like itself, because every kit makes its own decisions. Two layers produce that: a squad brain per team on a slow clock, and a task brain per body on a fast one. The squad brain reads the ground, not the fog: it counts who is alive, whose line is winning the exchange, who is wounded, who shoots from far, who mends, and then it makes one call: who to kill, and why. The body brain turns that call into one word of work, which is what the chip on its health bar shows you while you watch.
The body brain works from a book of twelve tasks:
Style is a doctrine, not a leash. The anchors and the chargers fight what the team called. The killers go for the body that will die soonest, the ladder measures it, and the names hold: assassins take the most-wounded enemy on 98% of their decisions, hunters on 96%, duelists on 80%. The kiters deal with what is on them, the artillery hits whatever is bunched, the medics look after the line. Nobody crosses the arena alone to do it: a target of your own choosing has to be within reach of your own line.
Cards fire on hooks: Second Wind when a body first drops below 30%, Name Recall to rise once more after being cut down, Unwrite Ink to shrug off the first stun, plus auras, leech, thorns, echo rounds and executioner edges that modify every number passing through them.
The brain decides, and for a while the movement underneath it quietly disbelieved it. A body walking to a point that cannot be walked to, a retreat extrapolated from a body already against the wall, or a cover cell picked without a stone test, grinds the border for the rest of the match, which is what a spectator reads as a fighter standing in a corner with no idea what to do. Every goal is now clamped into the ring and snapped out of stone in one place, so no call site has to remember. A body told to hold, and pressed into the wall while it holds, used to swap its shoulder back and forth forever: being stuck now outranks the plan and it walks clear first. Targets are committed for 0.9 seconds instead of being re-chosen every frame, a body does not change its mind about who to fight six times a second, while anything its style explicitly asked for still lands at once, because that is doctrine and the knives live on it. Measured on identical seeds: bodies standing alone against a border fell from 201 samples to 6–47 across runs. The target-change rate, which looked like the same bug, was not: it is one change per body every six seconds, a fighter picking its fight, not dithering.
The numbers below are what the engine builds for an undrafted kit: base health, base damage, attacks per second, reach in world units, flat armour, the three moves, the three cards, and the composite power figure the menu shows while you draft. Power is a draft aid, not a promise, §6 says where it lies.
| Kit | HP | Damage | Rate | Reach | Armour | Three moves | Three cards | Power |
|---|---|---|---|---|---|---|---|---|
| KaelGallant Blade | 700 | 43 | 0.8 | 48 | 9 | Rend Shield Wall Squire's Charge | MIGHT IRON VAMP | 166 |
| ValeAegis Knight | 920 | 30 | 0.72 | 51 | 12 | Cover Bulwark Shield Wall | BULWARK THORN IRON | 197 |
| OrinBlack Sigil | 600 | 49 | 0.86 | 262 | 2 | Unwrite Resonance Flask Chorus Flask | MIGHT FOCUS RES | 131 |
| NiaPath Archer | 620 | 31 | 0.92 | 275 | 3 | Pinning Volley Dash Needle Rain | STRIDE LUCK VOLLEY | 128 |
| LyraSpiral Bard | 560 | 32 | 0.84 | 250 | 3 | Hymn Spiral Chorus Weave Bloom | RES REGEN AURA | 118 |
| ArkosLattice Lancer | 662 | 45 | 0.78 | 61 | 6 | Geodesic True Line Dash | PIERCE MIGHT NIMBLE | 142 |
| D9raWolf Monk | 850 | 44 | 0.88 | 45 | 6 | Shockwave Wolf Fury Squire's Charge | FURY BRUTAL LEECH | 164 |
| SrathShadow Needle | 560 | 33 | 0.9 | 243 | 2 | Doublespeak Needle Rain Dash | VEIL LUCK ECHO | 118 |
| KairosHour Mage | 548 | 38 | 1 | 269 | 2 | Right-Time Borrowed Second Δ9 Seal | HASTE RES CLOCK | 123 |
| JusticaeAccord Knight | 944 | 31 | 0.74 | 54 | 10 | Fair Plate Verdict Bulwark | EXEC BULWARK THORN | 187 |
| SeidonTide Seer | 600 | 35 | 0.8 | 256 | 4 | Undertow Current Geodesic | AURA RES GIFT | 128 |
| SancoraWeave Chemist | 798 | 32 | 0.78 | 256 | 4 | Chorus Flask Weave Bloom Cover | REGEN GIANT AURA | 139 |
| LightfatherArchitect · Δ9 | 680 | 37 | 0.84 | 256 | 6 | Δ9 Seal Provenance Cover | AURA GIFT GIFT | 152 |
| Kit | HP | Damage | Rate | Reach | Armour | Three moves | Three cards | Power |
|---|---|---|---|---|---|---|---|---|
| WraithCrypt Fodder | 420 | 32 | 1.05 | 256 | 2 | Wail Drain Cloud | HASTE NIMBLE LEECH | 105 |
| BruteCrypt Fodder | 1090 | 39 | 0.66 | 48 | 9 | Ground Pound Ward Field | IRON GIANT THORN | 190 |
| ImpCrypt Fodder | 403 | 31 | 0.95 | 237 | 1 | Imp Fire Flicker | STRIDE LUCK NIMBLE | 95 |
| HurlerCrypt Artillery | 859 | 47 | 0.72 | 51 | 4 | Lob Stone Ground Pound | MIGHT BULWARK GIANT | 137 |
| ShadeVeilkin | 471 | 36 | 0.92 | 51 | 3 | Flicker Snatch | VEIL LEECH NIMBLE | 101 |
| ThiefTithe-Thief | 507 | 35 | 1.05 | 51 | 2 | Snatch Flicker | STRIDE VAMP LUCK | 106 |
| DrainThe Unwriting | 700 | 46 | 0.78 | 64 | 4 | Unmake Drain Cloud | LEECH REGEN MIGHT | 128 |
| GateNexus Guardian | 1735 | 46 | 0.6 | 61 | 13 | Gate Slam Ward Field Call the Wards | IRON THORN GIANT | 269 |
| CrownNexus Guardian | 1446 | 52 | 0.62 | 58 | 7 | Regal Volley Lob Stone | MIGHT BULWARK WELL | 209 |
| SmithNexus Guardian | 1020 | 60 | 0.64 | 250 | 10 | Anvil Gate Cannon | MIGHT BRUTAL IRON | 223 |
| Heart of the CryptNexus Guardian | 1900 | 44 | 0.68 | 58 | 8 | Heartbeat Ward Field | GIANT REGEN VAMP | 245 |
| LeviathanNexus Guardian | 1080 | 58 | 0.72 | 243 | 6 | Tide Crush Undertow | MIGHT STRIDE LEECH | 201 |
| Tithe-KingNexus Guardian | 1000 | 45 | 0.98 | 64 | 5 | Tithe Snatch | VAMP EXEC STRIDE | 179 |
| Name-EaterNexus Guardian | 1008 | 53 | 0.76 | 262 | 6 | Nameless Unmake | MIGHT CLOCK LEECH | 196 |
| The LockSuper Boss | 2100 | 53 | 0.66 | 269 | 14 | The Lock Seals Gate Cannon | IRON BULWARK GIANT | 323 |
| Kit | HP | Damage | Rate | Reach | Armour | Three moves | Three cards | Power |
|---|---|---|---|---|---|---|---|---|
| AshmaneWolf | 496 | 37 | 0.96 | 48 | 4 | Pack Bite Roar | STRIDE FURY LEECH | 114 |
| SolstrideLion | 543 | 49 | 0.9 | 51 | 4 | Solstride Roar | LUCK BRUTAL MIGHT | 138 |
| IronhideBear | 1024 | 38 | 0.74 | 54 | 10 | Maul Ward Field | IRON GIANT THORN | 196 |
| TuskwardElephant | 1033 | 35 | 0.68 | 61 | 11 | Stomp Ground Pound | IRON BULWARK WELL | 197 |
| GlassbarbScorpion | 448 | 38 | 1.02 | 51 | 4 | Glassbarb Clamp Flicker | THORN FURY VEIL | 115 |
Feel is not a spreadsheet, but some things are. tools/arena_check.js loads the real
roster and the real engine in Node behind a stub DOM and runs the whole thing headless. It asserts what
a player would notice, and prints what a designer wants to argue about.
NaN in any live body, nobody outside the
arena, both sides actually cast moves, and no kit ships without ever dealing damage.The ladder produced four systemic fixes rather than thirty-three hand tweaks, which is the point of building it:
Where it stands today, honestly: the ladder's last 420-match run prints a field average of 47.5%, the table is a spread, not a score. The tails are real. Eight kits hold 25–37%: the light creatures and the short-reach melee die two fights in three, because they are the body the pack finishes first. The strongest seven sit at 64–75%, and the same fact explains them from the other end , the kits at the top die only 14–27% of the time, so it is no longer only the bosses: a body that fights from eight tiles and never has to walk in owns the top of the table. Two members of the floor were not weak at all, they were broken: the imp and the stone-hurler fought at bolt range carrying a melee punch, so they stood outside their own reach for the whole match. That one was a defect, not a balance problem, and it is fixed. The floor was then lifted a fourth time the only way this table allows, through survivability, never damage: the five kits whose 95% interval sits wholly under 50% while they die 63–88% of the time took 1–2 armour and 8–10% health, armour-led, because a first pass at 14–21% health pushed the harness' own mean 3v3 past its two-minute mark.
A callsign is optional and nothing else is collected. When a match ends and a call is set, the result posts to the shared arcade book (mode, side that won, MVP, session wins) and the browser keeps a queue so a failed post is retried later instead of being lost. The live hall reads the same book and shows only this game's rows. Empty beats a fake board.
Space pause · 1 2 3 4 speed 1× 2× 4× 8× · M mute · R rematch · Esc back to the menu. The donation-gate reminder is held shut for the length of a match and released on the results board or the menu.
Art, atlases, roster lineage and the numeric DNA all come from Lattice Crypt; the arena engine, the kits and the harness are new. Built by Δ9Φ963 with the steward, Justin Helmer. Lightfather. Everything runs in the browser with no build step: three scripts, a stylesheet and one atlas pack.
Files: roster.js (33 kits, 26 cards, the move table) · arena.js
(the engine) · brain.js (the tasking brain) · banter.js (every spoken line) ·
arena-ui.js (menu, HUD, feed, caster, results) · tools/arena_check.js (90
assertions) · tools/arena_ladder.js (the field table).
Two AI teams fighting in silence is a diagram. So every body in the ring now talks, and a
caster calls the fight from the side of the sand. Nothing either of them says is on a timer:
a line only exists because an event produced it. banter.js holds the library, 248
fighter lines across ten kinds (open, taunt, kill, die, hurt, crowd control, save, win, lose,
ace), personal lines for 22 kits, and 56 caster lines that are filled in from the event that
fired them: who killed whom, who was mended, which map just closed.
When a body dies, the caster names it. When somebody is dragged back from the floor by a mender, he says so. When a body is rooted, stunned or slowed by a rival's crowd control, he says that too. The engine emits the event and the caster says the line, which means the commentary cannot drift away from the fight, checked by the harness, which fails if a single caster line appears without an event behind it.
Everything spoken is also written: the feed under the characters carries the banter (0:35 Lyra: Rooted. Eat it.), a bubble rides the head of whoever said it, and the caster's own panel sits under the challengers with his portrait and his last call. The voices come from the browser's own speech engine, filtered to the human-sounding voices only , the joke voices some systems ship make a fight sound like a machine test, with a fixed pitch and rate per kit, so the wolf always sounds like the wolf. Voices can be switched off from the top bar.
One microphone. More lines made the fight noisier, not clearer. director.js
holds the floor: the engine and the banter library offer lines, and the director decides
whether a line is said at all, when, and how long the speaker keeps the microphone. There is one
mouth at a time and the floor is held for the true length of the line, measured from the voice,
not estimated, so nothing can talk over anything. Between two lines there is a beat, 1.15
seconds, or half a second when the bell is waiting. A body may not speak twice inside six
seconds and the booth not inside 3.4. There is a budget: nine lines in any twenty-four seconds
and four in any ten. Over budget a line is dropped, not queued, and anything stale by 2.6
seconds is thrown away rather than said late, a report of a kill that happened four seconds ago
is worse than silence. The booth calls the fight and the bodies answer it; before that rule the
booth out-talked the ring ten to one, and now the two mouths split a match 36 to 42.
The intro is a programme, not a warm-up. The count before a match is fifteen seconds
with nothing else in it. At half a second the caster names the ring; your team is read out by
name after that, then the challengers, then the bell, "Fight! Anybody's guess. Who's gonna
win?", and the room goes quiet for three to five seconds behind it, timed from the end of the
line rather than from the moment it was called for, so the silence is a silence. The announcer
reads at 1.22× because a five-name team takes five seconds to say and the countdown does not
stretch for anybody: if he runs long the bell preempts him. The bodies take their first lines
after the silence, not under it. Everything about the timeline is asserted headlessly before it
is ever watched: that every body is introduced by name before the bell, that the bell is the last
thing said and lands on the clock, that the room is quiet behind it, that no two lines ever
overlap, that a beat separates them, that neither mouth starves the other, and that most offered
lines go unsaid. Watched live at 1×: the count ran 14.98 to 0.19, the three intro lines landed
with gaps of 0.8, 2.5 and 1.8 seconds, and the bell took the panel to ON AIR.
The floor has an address. A spectator can see who is talking without guessing: the
speaker's row is lit, the caster panel carries an ON AIR tag, the line is marked in
the feed, and the bubble over a head lasts exactly as long as the voice that owns it, scaled
with the match speed, so a line at 8× does not hang there after the voice has gone.
Every roster change is measured, not guessed. tools/arena_check.js runs 90
headless battles and asserts what a spectator would notice, 66 checks now, from "no fighter
ships with a dead kit" to "only one mouth talks at a time".
tools/arena_ladder.js 420 runs the whole field through 420 matches and prints a
win rate, a death rate and a damage-per-match line for all 33 kits, each with its own 95%
interval. The floor of the roster, the kits that were losing more than two fights in three ,
has been raised four times off the back of that table.
Ninety battles is not a verdict. That harness is marginal at its own sample: on the build this page describes it passed 66 of 66 on one run, then tripped a different assertion on each of the next two, once a battle that ended with nobody hurt, once a mean 3v3 of 113s against its own 110s mark, which read 62.7s on the following run after a 4% health change. A failed run is a reason to re-run, not a reason to revert. The 420-match table, and a cause that can be named, are what settle a question.
The table knows its own noise. Teams are drawn at random, so the same build measured twice does
not produce the same table: two runs of 420 matches disagreed by up to sixteen points on a single kit (the
bulwark gate read 60% in one run and 76% in the next; the smith 43% and then 64%). The
ladder therefore prints a 95% interval beside every win rate and marks a ~ on each kit whose
interval covers 50%, that kit is average, not weak, and tuning it is tuning a coin toss. Only the tails
are stable between runs: the same handful sits at the top and the same handful at the bottom every time.
A win rate is an outcome, not a behaviour. tools/arena_doctrine.js samples a battle
four times a second and reports what the bodies were actually doing rather than who won: how long each kit
spent fighting with no ally inside 200px, how long it spent closing on a longest-reach enemy whose shot was
loaded, how long the engine's own stuck counter was tripped, how often it shared a target with an ally,
which deaths were isolated, and , read straight off every plan's own reason line , a census of
what the whole field was thinking. On the shipped build it reports the picture this page had been missing:
across 144 controlled duels of the six shortest-reach kits against the six longest, the contact kits win a
third of them and spend a fifth of their health before they land a hit. A third of the deaths in the field
are bodies with no ally within 165px. Of thirteen withdrawals, not one body was still alive six seconds
later. There is no wall-grinding to fix: the stuck counter is tripped for zero seconds in every battle,
because the clamp-and-snap work holds. Three doctrine variants were written against those numbers ,
lead the walk toward where the target will be, wait out the reload window instead of walking into a loaded
gun, and take the middle of the arena away from a kiter so its only retreat is a wall. All three were
reverted. The census proves they fired, and the duel win rate did not move, which is a result and not a
failure: a 48px body in the open against a 269px rifle is losing to reach, and reach is not a thing a
better plan fixes.
A blow that deals nothing has to say so. Play reported the basic attack passing straight through a body with no number on it, and the report was half right and half a screen that was lying: a hit that a ward swallowed whole returned zero and printed nothing at all, so a defended body looked like a phantom. In twenty battles, forty blows were eaten by a ward in complete silence. The same was true of a weak blow fully stopped by armour, and of an aimed shot that slid past a strafing body and expired off the edge of the arena without a word. All three now print what happened , ward, block, miss , and every match counts them: melee swings, aimed shots fired, blows armour stopped, blows a ward ate, and shots that arrived at nothing. Measured over the same twenty battles that is the honest split of the complaint: the damage really was missing in those cases, and the screen was missing the reason. Roughly one aimed shot in six (17%) arrives at nobody because the target moved, which is a miss and is now labelled as one.
The committed step was measured two pixels short. Every kit in the roster owns Close , one committed step of 224px , and the engine would only spend it when the target stood within 240px, a hardcoded sixteen pixels of margin. A rifle holds its band at ninety per cent of its reach, and the longest guns hold at 247px: just outside that window. The one move written for crossing open ground could not be spent on the one enemy it was written for, and the brute , no blink, so Close is its only way in , ran three separate fights of two hundred seconds without ever landing a hit. The check now measures to the reach of the body doing the stepping, which is the quantity that always mattered, and it changes behaviour measurably: over 24 seeded battles, a third more melee swings and seventeen per cent less time spent fighting alone. It does not move the duel win rate against the guns, and the reason is worth writing down rather than hiding: Close is on a thirty-second clock, and a rifle re-anchors its band with every shot. One step per half minute is not a way in. That is a kit-design question, not an engine one.
A match where nobody fought is a match that did not happen. The harness counts battles that end with no damage dealt by either side and nobody down, and it was flagging about one battle in ninety: two lines holding their bands, each waiting on the other, while withdrawal and leashing and holding were each a reasonable order and the sum of them was a fight that never started. A plan is now void after twelve seconds without a blow landing anywhere on the floor, which outranks every other rule below it. The count is zero across the ninety battles since, and it was not zero before.
So every change here is made against a cause that can be named, never against a number: a kit whose reach exceeds its attack (the imp, the hurler), a signature ability that decides maps on its own (the vanguard's revive, now forty seconds and a 30% return), a body so light it dies before it arrives. Two fixes that measured as net-negative were reverted rather than kept for the look of activity: a rule that made light kits break off earlier lost more team fights than it saved (the kite fell from 53% to 33%), and handing three hunter kits a heal-cut made them worse, a wound sends its owner after the enemy mender, and an average body that dives at a protected back line does not come back.