Δ9Φ963 · studio doc

LYGO Arena

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.

Play the arena Live hall Lattice Crypt Hub

1. What this is

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.

2. Modes and the shape of a match

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.

26s1v1 median
34s2v2 median
41s3v3 median
50s5v5 median

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.

3. The arena

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.

4. The brain

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.

Fighting as a group

Answering a charge

Getting there

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.

5. The roster: 33 real kits

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.

Champions: 13

KitHPDamageRateReachArmourThree movesThree cardsPower
KaelGallant Blade700430.8489Rend Shield Wall Squire's ChargeMIGHT IRON VAMP166
ValeAegis Knight920300.725112Cover Bulwark Shield WallBULWARK THORN IRON197
OrinBlack Sigil600490.862622Unwrite Resonance Flask Chorus FlaskMIGHT FOCUS RES131
NiaPath Archer620310.922753Pinning Volley Dash Needle RainSTRIDE LUCK VOLLEY128
LyraSpiral Bard560320.842503Hymn Spiral Chorus Weave BloomRES REGEN AURA118
ArkosLattice Lancer662450.78616Geodesic True Line DashPIERCE MIGHT NIMBLE142
D9raWolf Monk850440.88456Shockwave Wolf Fury Squire's ChargeFURY BRUTAL LEECH164
SrathShadow Needle560330.92432Doublespeak Needle Rain DashVEIL LUCK ECHO118
KairosHour Mage5483812692Right-Time Borrowed Second Δ9 SealHASTE RES CLOCK123
JusticaeAccord Knight944310.745410Fair Plate Verdict BulwarkEXEC BULWARK THORN187
SeidonTide Seer600350.82564Undertow Current GeodesicAURA RES GIFT128
SancoraWeave Chemist798320.782564Chorus Flask Weave Bloom CoverREGEN GIANT AURA139
LightfatherArchitect · Δ9680370.842566Δ9 Seal Provenance CoverAURA GIFT GIFT152

Crypt creatures: 15

KitHPDamageRateReachArmourThree movesThree cardsPower
WraithCrypt Fodder420321.052562Wail Drain CloudHASTE NIMBLE LEECH105
BruteCrypt Fodder1090390.66489Ground Pound Ward FieldIRON GIANT THORN190
ImpCrypt Fodder403310.952371Imp Fire FlickerSTRIDE LUCK NIMBLE95
HurlerCrypt Artillery859470.72514Lob Stone Ground PoundMIGHT BULWARK GIANT137
ShadeVeilkin471360.92513Flicker SnatchVEIL LEECH NIMBLE101
ThiefTithe-Thief507351.05512Snatch FlickerSTRIDE VAMP LUCK106
DrainThe Unwriting700460.78644Unmake Drain CloudLEECH REGEN MIGHT128
GateNexus Guardian1735460.66113Gate Slam Ward Field Call the WardsIRON THORN GIANT269
CrownNexus Guardian1446520.62587Regal Volley Lob StoneMIGHT BULWARK WELL209
SmithNexus Guardian1020600.6425010Anvil Gate CannonMIGHT BRUTAL IRON223
Heart of the CryptNexus Guardian1900440.68588Heartbeat Ward FieldGIANT REGEN VAMP245
LeviathanNexus Guardian1080580.722436Tide Crush UndertowMIGHT STRIDE LEECH201
Tithe-KingNexus Guardian1000450.98645Tithe SnatchVAMP EXEC STRIDE179
Name-EaterNexus Guardian1008530.762626Nameless UnmakeMIGHT CLOCK LEECH196
The LockSuper Boss2100530.6626914The Lock Seals Gate CannonIRON BULWARK GIANT323

Beasts: 5

KitHPDamageRateReachArmourThree movesThree cardsPower
AshmaneWolf496370.96484Pack Bite RoarSTRIDE FURY LEECH114
SolstrideLion543490.9514Solstride RoarLUCK BRUTAL MIGHT138
IronhideBear1024380.745410Maul Ward FieldIRON GIANT THORN196
TuskwardElephant1033350.686111Stomp Ground PoundIRON BULWARK WELL197
GlassbarbScorpion448381.02514Glassbarb Clamp FlickerTHORN FURY VEIL115

6. The balance ladder

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.

What the harness checks: 90 assertions, 90 battles per run

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.

7. The hall

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.

8. Controls

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.

9. Lineage and credits

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).

The mouths, and the man with the microphone

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.

How the balance is measured

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.