OpenMap Editor, opens in a new tabStories and map packs installed in this browser, where the game finds them. Nothing is uploaded; keep your files, as clearing site data clears these too.
Fragyard is a browser deathmatch in a raycaster with a Wolfenstein 3D core. You can customize your character, join an arena, and frag your friends. Networking is peer to peer over WebRTC. You simply share a link and your friends join the game in the same room. Pick a mode: deathmatch, team deathmatch, gun game, or last man standing, and go.
The basis is the raycaster that powered Wolfenstein 3D: a grid of walls, a fan of rays, and a vertical strip of pixels for each one. Modern additions sit on top of it rather than burying it. I built Fragyard as a tribute to the 90s and to the gaming spirit of that era, when a game was one clear idea made with obvious love.
Mostly I wanted to make something fun and simple that could grow into multiplayer without a server behind it. Fragyard runs on a serverless, peer to peer architecture, so players jump straight into a match with nothing to download and nothing to install.
To see more of my work, and read my rumblings on graphics, rendering, and other topics visit my website at 4rknova.com.
Thank you for playing, and see you in the arena.
Nikos Papadopoulos
Sometimes it plays a single frame of the raise and falls straight back to the hip, so a scoped weapon never shows its scope at all. Once it happens the button is dead until it is released and pressed again.
What it cannot do yet is count, or ask a question first. Three players standing on the plate, only while your flag is still home. That is the difference between a map that responds to you and a map that runs something.
Letting one pick from a set of exits, or choose on the state of the round, turns a fixed arena into one that can rearrange itself.
Doors are built already, six types, each taking its opening direction from the map and any of them able to be see-through, and a switch is a wall you work rather than walk through. What is left is the wall that goes somewhere.
One that stops at chest height is cover, and cover is the thing that separates what you can see from where you can walk.
- What each module does and its public API. - The boundary: what belongs in src/engine and what in src/game. - The data formats the engine reads (yml schemas, sheets, maps). - A minimal example game.
Builds on switches and harmful floors.
HOLLOW's river becomes a route.
A player whose first attempt fails has no reason to try a second.
Music that never starts and a backdrop that never appears are the same problem seen from the other end: the load did not finish, or it asked for the wrong path and said nothing.
A sprite cannot: water, fire and every other prop that moves has to spend a frame per step. Giving sprites the same route would cost a prop animation what it costs a wall, which is nothing.
Doing it as an ordered dither instead reads as deliberate where a smooth gradient reads as modern.
Carrying that light down the path of the round would make sustained fire strobe the walls, which matters most in an arena whose lights have been shot out.
Feeding one a live view from another part of the arena turns a wall into intelligence, gives a defender something worth standing near, and makes shooting out a camera -- or the screen it feeds -- a real play.
Could ride on cutscenes (#8).
Builds on frame capture.
Pairs with #15.
The seccam breaks in one step (idle -> crack1 -> broken, with debris borrowed from cagelight). Revisit the destruction: more animated steps (sparks, the housing tilting on its arm, the lens popping, a flickering LED before it dies), its own debris cells, and a matching feed breakdown on its monitors (rolling interference before static).
A hit inside a monitor's screen rect kills that screen (cracked, dark). Today the live feed repaints over bullet holes within 1/hz s. Needs a wall-face screen hit message and a protocol bump.
Not a circle on a map -- a sequence the mapper writes, so every arena collapses its own way.
The other side wins by naming the killer rather than by shooting back.
First to 20 frags wins.
No friendly fire. First team to 40 frags.
Defend yours. First to 3 captures.
Knife to demote. Finish the ladder.
Last alive takes it. First to 3 rounds.
Waves of the dead.
Teaches the map and makes a good kill land.
Every player gets a line, not only the winner.
Map rules could trigger them (#4).
The tension is in the weapon rather than bolted on: it shows you the room and shows you to the room.
The answer to a flare, and the aggressive version of a blackout.
Slot 1, no ammo, and it rides every gun game rung.
Slot 2, hitscan, reloading from its own ammo pool.
Slot 3, and the one weapon that loads a shell at a time, so you can break off early and fire what you have.
Slot 3, with a magazine to reload.
Slot 3, sharing the chaingun's ammo pool, with a magazine to reload.
Slot 4, aimed rather than lobbed blind.
Slot 4, melee, with its own pool of thrown axes.
Slot 4.
Slot 4.
Slot 5, drawing fuel rather than a magazine.
Slot 5.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Add a look-to-the-side idle fidget, as the pistol, shotgun and knife have.
Cheap once #88-#93 land.
Faster, no sights.
The holder glows and the match hears it taken.
Today only health drops.
Shootable.
Bolts can be picked back up.
Sits between the grenade and the missile launcher.
A loud idle rattle gives you away.
Several can be out at once.
The rifle is tuned not to be one.
Slot 3; replaced the chaingun.
Slot 3, with a magazine.
Slot 2, with a magazine.
Slot 1, no ammo.
Slot 4.
Slot 4.
Slot 6.
A broken crate leaves a socket.
Hits are tested against the sprite's texels.
Ends when shot or when the clock runs out.
No healing and 2 HP/s drain while it runs.
They lose it, you gain it.
Every perk trades something away.
An AZERTY keyboard, a left-handed player, or anyone who cannot comfortably reach that corner of the board is locked out rather than inconvenienced. It is the cheapest item here measured against who it excludes.
On some mice it moves several, so picking a weapon mid-fight is a gamble rather than a choice. How far a notch travels is a property of the mouse and not of the game, which is what makes this something the player has to be able to set.
A player who finds the music too loud or the mouse too fast has to leave the match, change it and come back, which in a game you join by link means asking everyone else to wait.
Add crouch, and a slide when crouching at a run, to get under fire. Pairs with the vaulter and lightfoot perks and with low walls (#7).
Plays the existing enemy_spotted and on_my_way callouts.
Everything else has a real recording behind it.
Someone walking up behind you is silent, and your own movement has no weight under it. Walking and running want different cues, and what you are standing on should pick which one plays.
Two of the four stages are done: the shared palette, and the re-draw of the flat props. What is left is props that animate and break over more than two frames, and props that look different from every angle instead of the same from all of them.
Draw cells 51-53 per character (see the downed presentation plan, task 9).
Nothing reads it yet: the runtime substitution is sub-project B, a generic display painter that replaces the hand-coded `arenaname` painter in `src/game/art/walldisplays.js` with data.
And one rule row reads with a stutter, repeating a word it has already shown.
Each row links to its band. Built at build time from ROADMAP.json; no script.
No aim assist, no rebinding UI. Spec: docs/superpowers/specs/2026-09-28-gamepad-input-design.md
Stories and map packs installed in this browser, where the game finds them. Nothing is uploaded; keep your files, as clearing site data clears these too.
Browser tools for building things for Fragyard. They run entirely in your browser; nothing you make is uploaded anywhere.
OpenMap Editor, opens in a new tab
OpenWall Editor, opens in a new tab
OpenParallax Editor, opens in a new tab
OpenProp Editor, opens in a new tab
OpenCharacter, opens in a new tab
OpenWeapon Editor, opens in a new tab
OpenStory Editor, opens in a new tabEvery notable change to Fragyard, newest first. Each entry is one release. Pick one from the rail to read it; the sections of the release you are reading are listed underneath it there.
0.13.1
Cloth everywhere, flesh that tears, photo mode filters and a frame tracer.
0.13 filled the arena. 0.13.1 dresses it in cloth: canopies, washing lines, curtains, flags, drapes and tarps that tear, burn and hide you. Hits tear an animatronic's skin open to the machine. Photo mode filters and freezes, and a console command traces a whole frame.
wound [n] in the console stamps hits on your own body (#735). DOUBLE has its own voice, a leading man off worn tape (#807).trace frame in the console dumps one frame as a zip: passes, buffers, timings and memory, with a viewer, graph views and a pixel probe (#804, #805).0.13
Rooftops, rides, shop interiors, glass that reflects, and a city that breaks.
0.12.1 let you take the arena apart. 0.13 fills it. Lifts, stairs and ladders play a ride, roofs are dressed, shops have interiors with props that burn and break, and glass reflects whoever passes. The street wears neon, rain, puddles, pipes, awnings and vendors. Under it, the renderer got faster, lit and anti-aliased.
restore in the console puts the arena back (#722).0.12.1
Walls that break, cars that burn, live wires, night vision and awards.
0.12 gave the arena a story. 0.12.1 lets you take it apart. Walls crack, breach and come down, cars dent, burn out and stay as cover, and overhead lines snap and shock whoever stands under them. Night-vision goggles join the flashlight, everyone grunts when hurt, and the results board hands out awards. Under it, the renderer moved into the engine.
0.12
Stories and THE LAST REEL, four new arenas, snow, shadows, bounce light, crouch, a flashlight and BERSERK.
0.11 rebuilt what stands in the arena. 0.12 gives it a story. STORIES opens with THE LAST REEL, eight voiced missions in a quiet town where the dead do not stay down, and the Story Editor writes your own. WHITE DOCK, NORTH ISLE, FOG TOWN and MERCY join the arenas and CLOISTER is rebuilt. Snow falls, everything casts a shadow, light bounces off walls and puddles reflect. You can crouch, slide, carry a flashlight and go BERSERK.
pixels off|x1|x2|x3|x4.thirdperson in the console follows you with a chase camera (#588).0.11
3D bodies, a cast of twenty, 3D props, trains, two new arenas and a sandworm.
0.10 was about what a player carries into the arena. 0.11 rebuilds what stands in it. Every body is now a 3D model, moved by motion capture and holding a real gun, and the cast grows from four to twenty, all made in a new model editor. Props are 3D too: crates, barrels, lights, furniture, cover and plants, drawn in the same pixel art by a software rasterizer. Trains run sunken tracks through SUBWAY, a sandworm owns QASR's sand, the Parthenon stands whole on its Acropolis, grass and fog cover the ground, and the results board lets the room vote on the next arena.
characters block.wounds block.enemy add puts them in any match from the console. Assault leaves the list too until it is built. Protocol 52.debug cycles line, budget, full and off. Budget sums each stage of the frame against 16.7 ms over a timeline of the last 150 frames. debug <level> on|off sets it directly.meshchars, pixelize and pixelsize toggle 3D BODIES, 3D PIXELS and PIXEL SIZE; eye and pace tune body scale, eye height and gait live; train <i> sends a track's train now.npm run trailer cuts a release trailer from a shot list.0.10
abilities, seven new weapons, a training course, and a camera that tours the arena before FIGHT.
0.9.3 filled the arena with things that are neither walls nor players. 0.10 is about what a player carries into it. A kill now banks a point to spend on an ability, and there are five: REGENERATE, DEFLECT, TELEPORT, INVISIBLE and X-RAY. The arsenal grows by seven -- battle rifle, SMG, crowbar, railgun, flare gun, molotov and stun grenade -- plus the sat phone's airstrike, and the old guns are redrawn to match. A training course teaches all of it. Every match now opens on a flight through the arena and a called countdown, plants and street furniture break and burn, and a gamepad plays the whole game.
While it flies, a window opens in the drone feed's corner, a size bigger: riding above the plane as the ground scrolls under it, the bombs drop and shrink away, the blasts bloom below. The phone stays out of your hands until that window closes.
One strike per pickup, and only a heavy crate carries the phone; a strike pickup is ammo for one already drawn. Bots never touch it. Its sounds are placeholders and its art is painted in code, not cut from a render.
0.9.3
walls you can pull, a sky with things crossing it, and distance that fades to the horizon.
0.9.2 rebuilt the room around a respawn. 0.9.3 is about everything in an arena that is neither a wall nor a player. There are levers on the walls now, and pulling one is a thing you do with a key of its own -- bots pull them too. The sky above an open arena has birds, airships, satellites and meteors crossing it, and the birds roosting on a parapet scatter when someone fires nearby, so a flock going up is information. Underneath both, distance stopped fading to black: an outdoor wall now fades toward the colour of the sky behind it, over a reach scaled to how far the arena can actually be seen across, which is the single change that most alters what an open arena looks like.
[C] PULL LEVER prompt on the HUD, with a progress bar for a switch that wants holding; on a touch screen a USE button appears in the same moment and stays hidden the rest of the time.dressing: none and have an empty one.fog: may now be a block setting the reach, the curve, the strength, the brightness floor and the colour, over defaults that moved out of the renderer and into game.yml where they can be read and changed. The older fog: 55 scalar still means the reach and nothing else. An authored colour beats the sky the game would have sampled, so an arena can be lit for a mood its dome does not have.board=<source> on an arenaboard cell picks what it reads out, right-click in the map editor to choose one, and board=custom shows free text the map writes in its own boards: block -- thirty characters over three rows, so an arena can name its own rooms.OK and FAIL down a header rule, in a typeface and a vocabulary nothing else in the game used -- it now comes up on the plate every other wait in the shell already stands on: the wordmark breathing over a progress beam, a line naming the stage, and a running count of files and bytes. The same three facts, in the shell's own language.FRAGYARD BIOS V0.9.3 header that appeared for a second and was never seen again. The version is on the main menu now, in the top bar on the right, where it can be read at any time instead of only during a boot nobody watches twice.OPTIONS > GRAPHICS used to spend two rows on it -- a DISPLAY toggle for the in-page look and a separate OS FULLSCREEN action -- and the two could disagree. They are now a single DISPLAY row stepping WINDOWED, SCALED and FULLSCREEN: the small centred picture, the same picture grown to fill the browser window, and the browser's own fullscreen with the chrome gone.FULLSCREEN is the browser's state rather than a saved setting, so leaving it with Esc or F11 puts the row back on whichever of the other two was saved, instead of claiming a fullscreen that ended without asking. Stepping off it returns to that saved mode.SCALED rather than offering a step that would do nothing. Installing to the home screen is still the way to lose the address bar there.0.9.2
the arena builds itself around you, bots learn what a door is for, and the backdrops get an editor.
0.9.1 filled the menu in. 0.9.2 is mostly about the moment you come back. A respawn is no longer a blue wash over a room that was already standing there: the place assembles itself around you, edges first and near before far, and it is finished before you are shootable. Behind it, the AI finally understands a doorway -- until now a shut door was solid rock to every bot and every survival wave, so any arena with a door in it had rooms the enemy could not enter. And the parallax backdrops the shell stands in front of get a browser editor of their own, the sixth tool shipped with the site.
spawn.reveal in game-data/config/game.yml sets how long it takes, and colors.overlay.constructLine what colour it draws in. 0 turns it off./tools/parallax-editor.html, for the backdrops the menu stands in front of. Its layers are dragged over the renderer the shell actually draws them with, and the two rules a scene has to keep -- a plate wide enough to cover the frame at both extremes of its drift, and blur that never increases toward the back -- are reported while a number moves rather than by a test long after. Sweep runs the aim to both limits, which is the only way to see a plate that is too narrow.menu.js exports rather than copied, so it follows when a menu row moves: the scrim, the rows, the value column, and the box the forest's cabin aims its lit window into.<id>.asset that npm run asset -- import installs, checked on the way in like every other kind of asset.scene.yml survives being edited by a tool.die [gib] [head] kills you on the spot, so the death presentation -- the fall, the corpse, the loot drop, the kill feed line, the respawn clock -- can be looked at without arranging to be shot. gib bursts the body and head marks the headshot, two things that otherwise need someone else's explosion or someone else's aim. Nobody is credited the frag, survival still turns it into a down while a squadmate is standing to pick you up, and it beats god, because an order to die is not damage. Like every cheat it refuses in a room with another person in it; a bot room -- the one you want when you are looking at your own corpse -- runs it.0.9.1
the lobby keeps a list, the backdrops get weather, and a floor can kill you.
0.9 turned the menu into a place. 0.9.1 is about what is happening in it. The ROOM screen stops being a blank field with a code in it and starts showing you the rooms that are actually open, with the mode each one is playing and how many people are in it. The backdrops the shell stands in front of come alive: rain, ash, mist, a flicker in the lamps and a shimmer through the water, all of it declared by the scene rather than by the code. Your own soldier steps out of the black plate the personnel file kept him in and stands in the carpark, and when the credits finish rolling he walks in and takes the platform. And a floor can now cost you health, which is the substrate one new arena is built on.
Trystero cannot be asked what rooms exist, so a listed room announces itself instead: its referee re-broadcasts an advert into the lobby every five seconds, and a room drops off the list twenty seconds after the last one. Several periods rather than one, so a single dropped packet does not blink a live room out of the list. A room is keyed by its mode and code rather than by the peer that advertised it, which is what makes a referee handover invisible: the new referee's advert overwrites the entry in place, and the old one's eventual leave finds an entry it no longer owns and leaves it alone.
The table itself is pure and has no mesh and no clock in it, because every interesting case here is a race between an advert, a leave and the clock, and its tests drive all three by hand rather than against a relay.
A bot now fills a slot the mode is waiting on, holds a place in the standing count, and can be the last one standing and take the round. CO-OP is excluded and the mode declares that itself rather than the code testing an id: a squad bot is not something you are fighting, and survival reads an empty alive set as "every player is down, the run is lost", so counting the squad there would stop a run ever ending.
harm block -- damage a second, and a burn that outlives the cell -- and any arena laying that material as its floor charges every body standing on it: the player, the bots, and a survival wave alike. Nothing about it crosses the wire. Each client bills the bodies it already simulates, a fatal tick names the body as its own killer rather than crediting a frag to whoever happened to be nearby, and godMode is refused outright.NUKAGE's shallow nukage is the first floor to carry one, which retires the joke it has been telling since it shipped: the pools you wade through are the reason the arena is named, and until now they were scenery with a hazard stripe painted round them. Ten health a second, and the burn they light follows you out onto the walkway for a second and a half after you have left the pool, which is the part that actually decides whether the short way was worth it.
What the burn does NOT do any more is take the legs out from under a monster. A floor bills a body every frame, and the flinch every blow in the game causes re-arms itself the frame it expires, so the first version of this froze any enemy that stepped in acid until it dissolved -- half a cell in four seconds, measured. Environmental damage now takes the health and leaves the gait alone. A blow still flinches.
The close-up went with the plate. There is no push-in on the part under the cursor any more -- the whole figure holds on every row, and the swatch beside each colour is what a part is judged against. The ledger that was under the plate moved to the top of the column beside him.
The veil itself was broken in a quieter way as well: it was named in the config and read by the shell, but the colour table carried no default for it, and a key with no default is dropped at parse. So the value never reached the shell at all, and every scene-backed screen was veiled with whatever fill the scene had last used.
The turn takes a step. Two poses with nothing between them is a pose swap, which is what read as mechanical; each half of the gesture now borrows a walk cell for seventy milliseconds, because a standing person pivots with their feet. The two footfalls take opposite phases of the walk cycle, so the return is not the departure played backwards. That is the smallest movement this art can make and it is now measured rather than assumed: every pose in the sheet repaints between 76% and 125% of the standing figure, so there is no legs-only frame in it. A real settle is one cell per facing, thirty two across the cast, and a taller sheet.
The three option toggles were a column of labelled rows as wide as the list, for three fixed values; they are one row of sprites with their value beside them. Repeat keeps its value in text, because it has three states and a digit inside a ring that size is not legible.
ambient block and six layers that read it: rain, ash, mist, a flicker in the lamps, a storm over the whole frame, and a shimmer through the water the scene stands in. Every one is optional and every one is declared by the scene that wants it, so a scene that says nothing pays nothing.One depth per particle. Every streak, mote and haze bank rolls a single number, and that number sets its fall speed, its size and how far it swings when the frame is steered -- in the same units as the scene's own layers, so the weather is inside the picture rather than on the glass in front of it. Rain straddles the front layer deliberately: at one depth a streak falls behind the railing and at another it falls in front of it.
The pass is split in two, and the split is not arbitrary. The lamp glow and the shimmer are in the scene -- the lamps are painted into the art and the reflections are the art -- so they go on under the screen's veil with the pixels they belong to. The rain, ash, mist and lightning are in the air between the camera and the scene, so they go on after it. Veiled weather reads as weather behind glass.
ARENA stands in the dry dock, the JUKEBOX in the train shed, the CREDITS at the flooded station and the OPTIONS tree in the cable car. The screens between ARENA and SURVIVAL name no scene of their own any more and inherit the one above them, so walking down through mode, map and bots holds one place instead of cutting to a new one at every step. The station and the dry dock are the two scenes carrying weather today.
The new rule measures a pixel along the axis the plate stands out on rather than as a distance in colour space, because an edge pixel is a blend of plate and art and a blend moves along that axis and no other. The alpha is how much art is left, and the colour is that art recovered by taking the plate's share back out -- a pixel left at full alpha with the spill still in it is the pink rim this exists to stop. It cannot fire on a scene the old rules already handle: measured across all 62 source renders in the library it fires on the dry dock's three cutouts and leaves the other 59 alone.
He is behind the foreground rather than in front of it, and that is the point: the near layer at this end of the platform is the flood and a parked flatbed cart, so it closes over him at the chest wading in and crosses him at the waist standing under the lamp. The occlusion is the depth. In front of it he was a sticker on the picture.
The behaviour is a state machine rather than a function of the clock. The pacing walker on the front page is the same clock every time, which is what makes a metronome of it; this one rolls for how long it stands, which way it looks and whether it walks at all. His legs are driven by the ground he has covered rather than by a frame rate, so the feet cannot outrun the floor at any speed -- the first version paired a stride written down twice with a cadence in frames per second, was out by a factor of three, and shuffled on the spot.
/tools/parallax-editor.html, for the backdrops the menu stands in front of. Its layers are dragged over the renderer the shell actually draws them with, and the two rules a scene has to keep -- a plate wide enough to cover the frame at both extremes of its drift, and blur that never increases toward the back -- are reported while a number moves rather than by a test long after. Sweep runs the aim to both limits, which is the only way to see a plate that is too narrow.The menu's own furniture draws over the art, read from the constants menu.js exports rather than copied, so it follows when a menu row moves: the scrim, the rows, the value column, and the box the forest's cabin aims its lit window into. A character can be stood or set pacing anywhere in the scene, and standing is a pose the format states now rather than one it fell into by accident. Erase and pick repair what a chroma key left behind on a plate. New starts a scene from loose plates. Export writes an <id>.asset that npm run asset -- import installs, and saving splices the values that changed into the lines the old ones occupied, so every measured comment in a scene.yml survives being edited by a tool.
0.9
the shell becomes a place, the loadout becomes a choice, and it rains.
0.8 filled the arena. 0.9 is about everything around the fight. The menu stops being a list on black and becomes a street inside the cordon, with your own soldier pacing under the lamp and a personnel file that says who you are; the loadout stops being a colour and becomes three perks off a pool of twenty five, each one trading something away for what it gives. An arena can be under weather. There are thirty one of them now rather than sixteen, every one of them with ground underfoot and a sky overhead, and the walls they are built from can carry a decal, cycle their own palette, and stop shimmering at a distance. Everything a tool makes leaves it as one file and installs itself with one command. And a body no longer lies where it fell forever: it comes down into ash and blows away.
A perk is data and carries no code. It names signed deltas against a namespace of tunable keys -- walk speed, magazine size, reload time, revive hold, hearing range, minimap visibility, the lot -- and the three you equip resolve into one frozen bundle when the match starts. Multipliers fold as a product, additions as a sum, booleans by OR, and every key is clamped, so three stacked perks cannot reach an absurd number and a multiplier can never invert a sign.
What reads the bundle is everything: movement, the reload and the magazine, damage dealt and damage taken, throws, sentries and crates, the revive hold, how far you hear, what the minimap shows about you and what it shows you. The perks live in game-data/config/perks.yml, so the pool and its numbers are an edit to one file.
The front layer carries a defocus, so the fence, the barrier and the drum sit in front of the picture rather than in it, and the walker and everything behind him stay sharp. It is a runtime blur paid for once when the layer is decoded, and its radius is authored in the scene's own file rather than in code.
The record well down the right gave the middle back. It was 191 pixels of mostly empty plate sitting over the lit shopfront and the billboard, which is the part of the backdrop worth looking at; it is 95 now, with its right edge exactly where it was.
game-data/art/parallax/scenes.yml -- rather than a name written into the shell, so moving a screen to a different place is an edit to one line. A scene needs neither a walker nor a ground to be usable, which is what lets the centred screens have somewhere to stand too.< and a > on the selected row, the same rule the caret already follows, and both are pressable. Before this there was nothing on screen to aim at and nothing to discover: the keys worked, and you had to already know they did.The photograph is 160 by 224 and the figure in it is at a whole 4x, the close-up at a whole 5x. Every view of the figure is blitted at a whole scale and none of them takes a scale as an argument: each is handed a rectangle and works out the largest that fits, so a sprite that used to be the one resampled thing in the game cannot become one again. The close-up tilts toward the part of the figure under the cursor, aimed off mask.png, because nothing else in the data knows that a hazmat visor is a band across the face while a hero's skin is a face plus two forearms.
SUBJECT names the role -- HERO, POLICE OFFICER, CIVILIAN, HAZMAT -- rather than the person. The file is about the name you type in, and printing DANNY VOSS one row under SOLDIER gave one figure two names, the bigger of which was not the one it answers to. The lore is not deleted, only unread here: it is still in each character's yml, and the story page and the character editor go on reading it.
Three presets ship. clear is nothing at all, and costs one boolean read a frame. drizzle is wet without being weather: the streaks and the sound and none of the drama. storm is the front actually overhead, firing a strike every six to eighteen seconds -- rolled inside that range rather than counted off a metronome, because a strike that lands on a beat stops reading as weather.
Rain belongs to the outdoors, not to the cell you happen to be standing in. Every streak has a place in the arena, so what decides whether it falls is the sky over the point it is falling through rather than the sky over you: stand in a doorway and the street ahead is full of rain while the room behind you stays dry, and stepping out has nothing to switch on. A splash is settled once by the ground it lands on, so a ring out on wet tarmac keeps rippling while you watch it from inside. An arena with wind rains at a slant, along the same two map keys the ash already falls on, and it rains just as hard: the slant widens the band the streaks are placed in and the count widens with it, so turning on the spot does not thin the rain out.
The rain bed cannot be placed that way -- it is one voice standing in for every drop in the arena at once -- so it changes character instead. Indoors it drops to about a third and loses its top end, which is what a wall does to rain, and it is the same voice throughout: crossing a threshold no longer stops the loop and starts it again from the head of the clip on the way back.
A strike lights what the frame can see of the sky. The wash is full out in the open, weaker through a doorway, and nothing at all in a windowless room, so a basement no longer lights up as brightly as the road outside it. Thunder is never cut, because thunder carries through a wall: from inside it rolls at about half the level and with the crack taken off it, which is what still tells you a storm is on once the flash has been scaled away.
A map declares it with one weather: key, and the map editor picks it from a dropdown beside the sky. The developer console has a weather verb -- weather storm, weather rain 0 through 10, weather lightning, weather thunder -- and it drives your own client and nobody else's, because waiting on a round trip to see a flash would make it useless as a tool.
The protocol moves to 33. A client on 32 lands in a different room rather than in one where half the arena is raining -- so this build does not connect to the one before it.
A strike washes the screen and does not light the walls. Lifting the arena's own ambient for the frames of a flash means either rebuilding the flat-fill shade table every frame or re-baking the lightmap every frame, and those two paths are required to produce identical pixels at a uniform ambient, so lifting one and not the other would break that promise. The wash covers the whole frame anyway, the weapon in your hands included. It is a deliberate fallback rather than an oversight, and the walls flaring is the part that was given up for it.
No shipped arena is under weather. The library, the runtime and the editor row are all here; which arenas should be rained on is a decision about each map rather than about this, and none of those has been made. Until one is, the editor and the console verb are how you see it.
The four clips -- the rain bed and three thunder rolls -- are the repo's own generated placeholder tones rather than recordings. Each carries a brief in PENDING_AUDIO_ASSETS.md saying what a real one should sound like.
openair is a border that draws nothing at all, so the ground can simply run out of the world with no visible edge.It is cheaper than the mirror it replaces -- 2.84ms off the wall pass at the deepest band a body can stand for -- because the mirror sampled a 512 by 256 sky face where the cast samples a resident 64 by 64 tile.
swell is a summed sine field with equal frequencies -- isotropic and tiling by construction -- and it animates by frames, so the world past the border learned to resolve its texture per frame.YARD's ground is a rule rather than a shape, so the rule lives in a script, the rectangles it prints live in the map, and a test re-derives the rule against the parsed arena: move a wall and it names the cell whose pavement did not follow.
wayfind, beacon and cratemark leave the wall registry and come back as a decal group of their own, joined by a neon shop sign for the upper storeys of a raised block.A map places them through a decals: block, one line per surface, stacked in file order, with a flip and an axis-aligned rotation per placement. Each decorated surface composes its stack onto the tile once at load, so the renderer samples a decal exactly as it samples any other texture and pays nothing per frame. A decal composes onto floor and ceiling cells as well as wall faces, cycles if what is under it cycles, and emits light -- spread into the cells around it the way a wall emitter's light is, because a light at the centre of a solid cell contributes nothing. A material a bake cannot serve falls back to a per-texel overlay, and a decal on a surface with no material at all is skipped.
Baked rotation is what retires wayfindback, the pre-rotated material that existed only because the floorcast samples world x,y straight into texel x,y and can never turn a tile. A baked texture arrives already turned.
cycle: bands, each band a rate and an ordered list of colours that already exist somewhere on the material's own texture; the game advances each band on the shared art clock at its own rate, rotating which of those positions each colour sits at, so the surface moves with no second frame of art anywhere and no sprite sheet to cut. The decode of the material's PNG IS the live texture buffer -- nothing is copied or repainted -- and only the texels a band actually owns move on any given tick, so the cost stays tied to how much of the tile is cycling rather than to the tile's size. HOLLOW's rock face carries the first one: water runs down it, drawn from eight colours in a strip nobody re-renders a frame of.Nothing about a cycling phase crosses the wire or touches simulation. Every player advances the same band off their own clock, so a peer seen mid-frame is not out of sync -- it is looking at a different moment of the same water.
The wall editor authors a band the way it authors any other layer: paint a ramp, mark the run as a band and set its rate, and the preview animates it live at that rate, in the same panel that already turns a recipe into an exported material.
Three new layer kinds in the wall editor put those positions on a tile. sector assigns them by angle, so rotating the ramp sweeps a wedge around a circle; streak scatters sparse runs each carrying the ramp end to end, so a bright head travels along every one at once; marks lays out a row, column or ring of lamps, bars or chevrons and gives each the next position, so the lit one walks the arrangement. Unlike a band, none of the three covers the whole tile, which is what lets a fan keep a static hub, a beacon keep its housing, and a tile carry two cycling layers at different rates for the first time.
A generator now says for itself whether it cycles, rather than the exporter keeping a list of the kinds that do, so a ninth kind needs no edit outside its own file. And the shipped materials are gated: every committed tile is re-rendered from its own recipe on every test run and compared texel for texel, which catches a palette colour edited without a re-render before it becomes a boot error naming a band position and nothing else.
The swap waits for your hands. A pickup taken mid-burst, mid-reload or down the sights is remembered and raised once the shot has played out, so the setting never takes a gun away from you in the middle of using it, and reaching for the digit row cancels a pending swap outright.
It is suspended rather than cancelled. An aim you never let go of comes back up on its own the moment the magazine is in, and an aim pressed during a reload waits its turn: it raises nothing while the reload runs, and it does not abandon the reload the way a trigger pull does. The four weapons with both sights and a magazine -- the pistol, the shotgun, the chaingun and the rifle -- all read the one rule.
A full reserve no longer means walking past. A box you have no room for is taken anyway when the weapon in it is new to you, the same courtesy a floor gun already extended. Nothing is put in your hands either way -- the throwables sit outside the autoswitch ladder, so the box arms you and leaves the weapon you were holding exactly where it was. Bots pick a box up under the same rule.
drop.png, which means overriding one is dropping a 64 by 64 file into place.weapons.yml, which made an art manifest's ordering load-bearing for a rule in another file; it is weapon ids in game.yml now, validated at load against the roster, and each rung's reserve is read off its own weapon.What that buys is a way to lose ground. A frag dealt with the knife promotes the killer the way any frag does and DEMOTES its victim one rung, down to the bottom of the ladder and no further. Killing the leader with the weapon nobody has to earn is worth two rungs of the gap between you.
Your rung is still your frag count and nothing else, so a demotion is a frag taken back: the RUNG and LEAD cells, the weapon you come up holding, the rank and the frag column all follow one number, and a demoted player's board reads the rung they are actually on. Which weapon does it is the ladder's business rather than a rule about the knife -- it is the bottom rung, whatever that is authored to be, so the weapon you can always fall back to and the weapon that costs its victim a rung cannot drift apart.
A bot is a combatant now. It fills a slot the mode is waiting on, it holds a place in the standing count the round is decided by, and it can be the last one standing and take the round. What it still does not do is count as a person: the squad size is worked out from the humans in the room, so a bot never makes way for itself.
It sits in the band at y 84 to 146, which is the only strip of the view that was free. The phase banner owns the top of the screen and the music widget the top right; the crosshair sits at y 150 and the gun below it. A card an inch lower would cover the reticle at the exact moment a fight starts.
The wipe rides the same cubic ease the screen transitions run on rather than a curve of its own, and reduced motion gets the whole card at once with no ramp either side, the way every other fading overlay already behaves. An arena with a name too long to fit steps the type down a size rather than running off the edge, so an authored map cannot break the card by being called something long.
26 is the ceiling, and it is a real one rather than a shrug at a round number. The renderer sizes the tables its floor is cast from off that limit, and the same clamp is what bounds how steeply anything can be thrown -- at 26 degrees the steepest aim is still a throw and not a mortar. The ladder itself is data, in game-data/config/game.yml, so the rungs are a config change and not a code one.
The gate stays where it is for players, because it is there for a real failure: a peer the relay drops without ever announcing it must not win on a row nobody is behind any more. A bot has no such failure to guard against. It is owned by the machine running it and mirrored from there, so it exists exactly as long as its owner says it does.
An arena has a wind direction and a map can set its own, so the drift travels rather than piling. A grain never overtakes the one it landed on, which is what stops a drift reading as noise, and the whole drift paints itself into one sprite once it has settled rather than staying a cloud of points. The pool caps and evicts, so an arena full of bodies costs a bounded amount.
The clips moved with the decision. Each character's 21 readings live in its own directory beside its sheet and its mask, and the lines and the casting brief they were rendered from are a voice: block in its character.yml, so everything one character is made of is in one place. WRENCH had no character to belong to and is gone with the bank it came from.
Nothing about this crosses the wire as a new field. An appearance no longer carries a personality at all; the clip is composed from the character every hello has always sent.
PENDING_AUDIO_ASSETS.md saying what a real one should sound like..asset -- a store-only zip carrying a manifest and a payload -- and npm run asset -- import places the files, joins the registry, records a ledger, and refuses when the tree has moved under it.All five families are on it: props, wall materials, characters, weapons and maps. The character editor is the one this was for -- it fired one download per file, and a character with a full voice bank is 24 of them against a browser cap of about ten, so it could not hand over a complete character at all.
The manifest says what a subject is and never where it goes, because a wall material's tile index, and the counts four tests pin on it, are answers about the repo on the day of the import rather than the day of the export. A material's import performs five registrations, three of which edit test files, and every one of them is computed against the tree at import time.
masters/ is retired on the way: a master sits beside the character or the creature it describes, under game-data/art/, which is the only place a browser tool can fetch one.
.asset now carries the map plus one complete nested asset per subject the map names, walked transitively, so a facade kit brings its own materials and the rooms behind its glass. The export dialog says how many went in and how large the file came out, and offers no way to leave one behind, because a map with a dependency deselected is a map that does not install.Installing one is the same command it always was, and it does the least it can. A dependency the tree already has byte for byte is left exactly where it is -- the ordinary case, since anyone with the base game has most of what any arena names -- and asset remove map <id> takes back only what the install itself wrote, sparing every file that was already there. A dependency present with DIFFERENT bytes is a real disagreement about what a prop or a material is, and it refuses the whole import by name; --force does not lift that one, because overwriting a prop's sheet changes every arena in the tree that stands one.
Two things an arena deliberately leaves behind. Its track: names an entry in a game-wide music library, and a single ogg runs to several megabytes. Its crate tables name weapons, and following one reaches the whole weapon library. What travels is what an author placed in the arena.
Both halves are on screen now. Selecting a door brush raises a DEPTH row in the rail, three buttons wide, showing which depth the brush is loaded with; and right-clicking a door already placed opens a DEPTH section that changes that one door where it stands, keeping its type, in a single undo step. R still cycles the brush for anyone who learned it.
The menu names the edge rather than the vocabulary. near and far are map coordinates -- near is the lower one -- so a row reading Near tells a mapper nothing about which side of the cell they are choosing. The menu knows which wall the door sits in, so it offers Flush west, Centred and Flush east for a door in a north-south wall, and Flush north and Flush south for one in an east-west wall. The rail's buttons keep the bare words, because a brush has no cell yet and so no edge to name.
.ogg imported from disk. A field that would not survive the round trip is marked where it is typed and Export refuses while it stands, so a curly quote is caught on screen rather than an hour later.drop.png is visible in the editor, held to the same rules the shipped art is held to, and carried out in the weapon's asset.0.8
the arena fills with monsters, and the game leaves the browser.
0.7 handed the room over to the person authoring it. 0.8 puts something in it worth fighting. Survival is the first mode that is not soldiers shooting soldiers: a co-op stand against waves of the dead, twenty drawn creatures, bosses that call up help while they live, and a squad that crosses the floor to pick you up when you go down. The arsenal stops being handed to you and becomes something you collect out of crates and off the bodies of people who were carrying it. Guns hold magazines and have to be worked between them. A building can be several storeys tall, an arena can open onto a sky, and three of the things you throw are machines that fight for you. And the game stops being only a browser tab: there is a build for Windows, Linux and macOS, and a phone can put it on a home screen.
dusk puts the sun on the horizon with a wide bloom around it, so one wall of an arena is lit gold while the wall opposite is already night, and which way you face changes what you see a body against. dawn is its morning: navy overhead, a cold cream along one edge, thin high cloud. sodium is the one that could only be here -- a sealed district still burns its own streetlights and a low deck throws every one of them back down, so the brightest thing in the frame is the ground, reflected, and there are no stars because from under that you would not see any. smog is daylight arriving through something, with no blue in the picture at any elevation. ashfall takes the sky away entirely and leaves the sun a red coin you can look straight at. storm is the only one brighter above you than at the horizon, which turns every skyline in an arena from a lit face into a black cut-out.
starry shipped as an even scatter of stars, which is a night sky with nothing in it and nothing to steer by; it now carries a band arcing across the north-east where stars crowd and a mist of unresolved ones lifts the sky between them. Every heading under it looks like a different piece of sky.overcast deliberately keeps the old field: a flattened deck is a look, and one sky in the library should not be wearing it.A wave spends a threat budget rather than counting bodies. Every creature costs its tier, one for fodder, two for a mid, three for a heavy, so the same budget buys a horde of shamblers or a handful of brutes and nothing can buy twelve brutes. The mid and heavy tiers unlock on how far into the run you are rather than on the wave number, so a short run meets heavies early and a long one holds them back. Arrivals are spaced out and the spacing tightens as the run goes on, because a wave that arrived all at once would be a wall rather than a wave. A finite run is won by clearing its last wave; an endless one keeps climbing.
A creature can declare a ranged attack in its own file and take a firing pose for it, so a spitter spits and the bolt crosses the room. Until now every monster in the game had to reach you to touch you, and backing away was a complete answer to all of them.
Four creatures exist only as bosses and never turn up in an ordinary wave: the warden and the broodmother close on you, the censer and the oracle shoot. A boss is drawn at its def's own size rather than scaled up. The arena's walls are exactly one cell high, and a body drawn taller than a cell paints across the ceiling and through the room behind it.
Somebody does come. A bot on your side breaks off what it is doing, crosses to a downed teammate and revives them, so a run with a squad in it is a run with company rather than one you lose the moment you are the last one standing -- and a standing bot counts, so the run no longer ends the instant the last human is on the floor. The scoreboard says who is out and who needs picking up.
The camera is the whole of it for now. A downed body was meant to draw its own pose to a peer as well, and no character declares one yet, so what everyone else sees is still the settled death cell.
A blade now arrives at a monster for the throw's own damage, at a deployed sentry or drone for the same, at a destructible prop the way a shot triggers one, and at a pane of glass, which it shatters and then drops to the floor of the hole it made rather than hanging in it. Your own machine is a target now, which it was not: it was filtered out by allegiance while your own bullet went into it happily, and an axe you cannot aim at the turret in front of you reads as a broken weapon. The guided rocket keeps that filter, because a round that detonated on the drone escorting you would kill the escort on every launch.
The blade was also drawn a quarter of a cell above where it actually was. It leaves at chest height, which is eye height, so a level throw should cross the horizon -- instead it came in level with the top of a head while it bit at the chest, and aiming at what you could see was aiming high. Every other thrown thing is a grenade, a sentry puck or a drone, and those come to rest, so the renderer put the bottom of the art at the object's height. An axe never rests in flight, it turns, and the point it has to be drawn at is the point it turns about. A weapon now says which it is, in its own file, and the axe's eight tumble cells are cut around the middle of their cell to match.
A throw is tested at the height it is actually at now. A blade flies against the arena's real heights, clears a wall it has visibly cleared, and hits only what is in the air with it; one that comes down on a roof stays up there, out of reach, rather than dropping back into play. The grenade, the sentry and the drone are on the same rule, and it is the grenade the rule matters most to: it clears a one unit wall about a cell out and lands four cells past it, which is the range you actually fight at. A machine that settles on a roof knows it has landed and deploys there, instead of counting itself airborne forever and quietly never arming.
The scope is not the rifle's alone. Any weapon that already aims down sights can declare one in its own data, name a picture, and say how far into the raise the picture arrives.
A reload says so at the reticle rather than in the corner of the screen: a ring fills around the crosshair, a per-round reload draws a pip per shell so you can see how many are in, RELOADING fades in underneath, and the ammo cell turns gold. It tells the other side too. A reload is a sound, and anyone close enough hears the gun being worked and knows you are not shooting for a moment. No reload art was drawn: a weapon with no frames of its own dips out of the frame and comes back, which is what let this ship without touching a sheet.
game.yml now, so the numbers can be tuned without touching code.bot console command takes verbs now: bot add n [level], bot remove name, bot remove_random n, and bot set [name] level to retune one bot or the whole roster, with a bare bot listing it. Each bot carries its own difficulty (bystander, easy, normal, hard), so a hard bot and a bystander can share an arena. The old bot n form is gone.It also covers two states now rather than one. Catch fire and live, and flames take the bottom third of the view: enough to lose your footing and the floor of the room, not enough to lose the horizon you aim along. Die in it and the fire climbs nearly the whole view and runs up both screen edges, which the old version could only suggest, and it burns out on its own rather than being crossfaded to a grey rectangle. The tones live in game.yml beside every other colour the game paints with.
The upper windows are not painted on. They are the same virtual rooms the shopfronts at ground level already use, one per window, chosen by hashing the cell and the storey, so a block's lit and dark floors are scattered rather than repeated and every player sees the same building with nothing on the wire. A storey too small on screen to read draws the flat band instead, which is what keeps a skyline affordable.
Nothing about the fight changes. A raised cell was already solid, already stopped a bullet aimed at any height, and already hid whatever stood behind it. Light does not gain a third dimension either: a muzzle flash in the street lights the shopfront and leaves the tenth floor alone, which is the storey fade doing on purpose what the lightmap cannot do at all.
The map editor gains a Height layer beside Floor, Walls and Sky, with its own brush for the storey count and the kit, the same rect drag and Shift-drag erase the other surface layers use, and a wash that labels every raised run with what it is.
tools/weapon-editor.html opens any of the eleven weapons, groups its yml into the things you tune together, and puts a live viewmodel beside them. Hold the trigger and the gun fires at the authored rate, on the authored cells, throwing cases from the authored port. The timing is not a model of the game's, it is the game's, called straight out of combat.js and the shell pool, so a fire rate that reads well in the editor reads the same in a match. A strip under the gun draws the shot's beats against the cooldown, which is what turns a frame starting at 0.4 into something you can see rather than guess at.Saving rewrites only the lines that changed. The library carries a lot of hand-written comment explaining why its numbers are what they are -- the rifle's eject block is a good example, a long explanation of why its ejection port sits where it does rather than where the art draws the opening -- and an editor that rebuilt the file would have deleted every line of it. Before it hands a file back, the editor re-reads what it just wrote and compares it against what it meant; a disagreement refuses the export rather than shipping it.
That also reaches four poses the tool could never draw. arm and throw on the axe and sight and locked on the launcher are picked by rules rather than named in the shot schedule, so nothing in the editor had ever shown them. The wind-up's draw-back and the throw's follow-through were pinned at zero and are now the game's own curves. R plays the reload, which is the only picture the reload block's numbers have ever had. A readout beside the timeline names the cell on screen, including when it is the idle fallback standing in for art the sheet has not got.
tools/prop-editor.html opens any of them and edits both halves at once. The sheet is painted texel by texel on the master palette every prop is drawn from, with a strip above the art showing every cell and chips saying which of idle, breaking, broken and debris claims it; a chip refuses a claim the parser would refuse, so a prop is stopped at the strip rather than at the export. One cell copies over another, which is how a breaking frame starts life as a bruised idle one, and the paste undoes like a brush stroke. The sheet is read and written as PNG bytes rather than through a canvas, so an export carries back exactly the texels it opened. The metadata is the same file's numbers as grouped fields over the rules propdef.js enforces, and the prop stands beside them on a lit stage at a distance and a facing you pick, breaking, fusing or venting on the game's own frame clock over a real lightmap bake. Its sounds play from the page, and an .ogg you drop on a clip field auditions from the file itself. Export rewrites only the lines that changed, so the library's hand-written comments survive, and two guards refuse the file if the patch changed what it means or touched a line nobody edited. New makes a prop from nothing: an id, a cell size and a frame count give a blank sheet, a document that parses and a props.yml with the id appended. What the stage does not do is throw debris: gib launch, the baked spin and the stipple fade belong to the particle system, so the debris cells are shown as a strip and labelled as exactly that.tools/wall-editor.html opens a material, draws its layers over a tiled panel so a seam is visible while you work on it rather than after you ship it, and writes the file back.host/ joins a room the always-on hosts keep open, as a second spectator, and OBS pushes what it sees out as a broadcast: the arena exactly as a drop-in player would find it, with the sound the spectator camera hears. It is opt in by compose profile and refuses to start without a stream key, so nothing goes live by accident. Nothing about the game changes.colors:, falling back to the same value in code, so a retheme is a data edit. Nothing changes on screen.PENDING_AUDIO_ASSETS.md carries a brief for each, though its own header count is stale -- the encoding is what says whether a clip is real.0.7
the arena becomes content.
0.6 filled the room with light, glass, doors and voices. 0.7 hands the room over to the person authoring it. Glass stops being a solid block of a cell and becomes a panel standing on one edge of one, with floor on either side. Transparency and breakability stop being facts about a material and become decisions a mapper makes cell by cell, and so does the shop behind a window. The last art registry living in engine source moves out to yml, the sounds that hung off no file at all get one, and everything the browser fetches at runtime pools under a single directory. What is left in JS is the game; what stands in front of it is content.
A wall material fills its cell, and so did every pane of glass in the game: walk up to a shop window and you stop at a block of glass a metre thick. A screen is a panel standing on one edge of its cell instead. The rest of the cell is floor you can stand on, and the panel blocks its own plane and nothing else, which is what a shop window, a balcony rail and a glass partition have always wanted to be.
It is one authored key on a cell, screen=<dir>. The editor sets the facing from the cell menu and draws a corner badge for it, the map format round-trips it, and the parser refuses it on a material that cannot be a pane and on a cell that already carries a room. The renderer draws the panel on the boundary it stands on, from the front, from behind and edge-on, rather than on the face of a whole cell.
Rendering and collision agree because they read the same table. A boundary between two cells either carries a panel or it does not, and walking, a grenade's flight, a blast's reach, a bot's path and a line of sight all put that one question to it. So a bot paths around a panel rather than through it, a grenade bounces off the glass you threw it at, and the nudge that separates two bodies cannot push either through a pane.
PANELS joins the arena list to show them off: four sheets of glass standing in open floor with room on both sides of each, and nothing else in the map to look at.
A map can say something about a single wall cell now, beyond which material it is. An overrides: block lists the cells that depart from their material: a glazed cell is seen through where the material is opaque, and a breakable one can be pinned solid where the material would otherwise shatter. The raycaster, the lightmap and the breakables all read the departure, so a glazed facade lights, draws and breaks as the glass it now is.
The two glazed shopfronts are repainted to match. Their panes carry alpha and their frames do not, so a see-through cell reads as a window in a wall rather than as a tinted slab, and a real room carved behind one is visible instead of being marked unreachable.
A material also declares its own sidedness. A run of glass reads as one sheet rather than as a stack of tints, which is right for a nine-cell shopfront and wrong for a glass box, whose far wall used to vanish because the near wall claimed the column. The wall's own file settles which it is.
A room is authored on a cell, not derived from one. In 0.6 the shop behind a window came from the material's kit set plus a hash of the coordinates, with a map-wide pin to override it; a mapper could not simply say which shop sits behind which pane. Both mechanisms are gone. A right-click on the editor canvas opens a menu of kits, the choice lands in the map as a coordinate and a name, and a badge marks the cell that carries it. A cell with no assignment shows nothing.
The shipped arenas are unchanged on screen: the values the hash produced were frozen into HIGH STREET and POLICE STATION exactly as they were, so the same shop stands behind the same window as before.
The cell menu is where the other per-cell decisions live too, in three sections: the room behind the pane, whether the cell is see-through, and whether it breaks.
The status bar shows what you are carrying. The weapons you own draw as a row of flat icons on the panel, the one in your hands lit brighter than the rest, and a weapon you have just picked up flashes gold for half a second so an acquisition in the middle of a firefight is something you see rather than something you discover later. A weapon with no ammo left dims. Weapons you do not own are absent rather than greyed out, so gun game's single weapon sits alone in the middle of the strip and a full loadout fills it.
A weapon's viewmodel timing comes off the weapon. The sequence of frames a shot plays, and how long each holds, is authored in weapon.yml instead of being one of two tables in JS picked by whether the sheet happens to carry an unpin cell. The flamethrower is the reason: under a held trigger it flickered through frames timed for a pistol, and it now holds a steady jet for as long as you hold the key. Recoil is authored the same way, a kick and a settle per weapon, so a fast weapon leans into its burst instead of sawing through it.
The mouse wheel switches one weapon per flick. A detented wheel reports several notches per gesture, and switching on each of them meant a flick up and a flick back did not return you to the weapon you started on. Notches that belong to a flick already spent are dropped now, and a switch that is interrupted resumes from where the weapon actually is on screen rather than snapping to the bottom and dropping again.
A pick on the comms wheel says the line you picked. Two gates stood in the way, both silent: the bus refused a callout while another was still sounding, and a six second floor per speaker refused for seconds after that. A soldier cuts themselves off on a pick now, the floor drops to a debounce, and the wheel greys out for the window that still refuses, so a key that will not fire says so. The gates that matter still hold: a frag shout cannot talk over the callout you chose, and a teammate's callout no longer mutes yours.
The voice bank is re-rendered against what each line says rather than against how long it runs, which retires the clips that spoke something other than their caption.
A spawn point says where a body stands and never which way it faces, so a fresh life started looking at +x and a respawn kept the facing the corpse died with. On the shipped arenas that is a wall within three tiles for 29 of the 81 spawn cells. The facing is read off the geometry now: the eight compass directions are probed with the body's own collision, the ones with three tiles of floor ahead survive, and one of those is picked at random, so two bodies spawning on one cell look different ways while a corridor still offers only its two ends. Players, bots and the parked spectator camera all use it.
Every arena declares the modes it is offered for. The map rail filters on the manifest without loading a single map file, and RANDOM draws from the same filtered list, so a mode never offers an arena it cannot be played on and never withholds one it can. Claiming capture the flag is a claim that the arena marks a base for each side, and the suite checks the claim against the map rather than taking it.
RANDOM rolls again on every rematch. A room founded on RANDOM used to resolve one arena and then play it until somebody left to the menu; the pick is remembered now and rides the referee's snapshot, so each match after the results board is played on a different arena, drawn from the arenas the room's mode offers and never the one just played, for everybody in the room at once.
Play goes straight into the map. The editor's Play button used to hand off to the front door, which meant the boot intro, the room code and the bots screen between every edit and the thing being edited; it founds a room of one and loads the arena directly now, and EXIT comes back to the editor with that map open, whether the tab it came from is still there or not.
The canvas gains the right-click cell menu described above, with a corner badge for each of a room, a glazed cell, a fixed pane and a screen's facing, laid out so they do not overlap at low zoom. The kit menu scrolls with the wheel instead of closing under it, and erasing is the floor brush rather than a modifier key.
Wall materials are files. Every material was a JS object literal in a long array in the art pack, which made adding a wall, or giving one a new property, an edit to engine source; each one is game-data/art/walls/<group>/<name>.yml now, beside its own art, exactly as props, weapons, doors and interior kits already were. The game, the editor and the art export all load the same corpus, and the migration changed no material: every record comes out byte identical.
The clips that hang off no def move into game-data/config/game.yml: the boot intro sounds, the menu blips, the flag cues and the portal whoosh, which were four hardcoded path tables in JS. They come from one cue registry seeded from that file, along with the boot intro's logo hold, which was a constant.
The shipped data pools under one roof. art/, audio/, maps/ and config/ move under game-data/, which leaves the repo root saying which directories the browser fetches at runtime and which are authoring sources that never ship. Names, formats and layout inside those trees are unchanged.
Protocol stays at 17. Nothing on the wire changes shape this release, so a 0.6 client and a 0.7 client still land in the same room.
TURN gets a TLS fallback. Both configured entries asked for UDP, so a network that blocks UDP outright had no relay at all, and a turns: entry on 443 is what an egress filter passes as HTTPS. It is necessary and not sufficient: a probe that gathers with relay-only candidates shows the public Open Relay endpoint returning nothing on any entry, which means the mesh has had no working relay regardless of transport. That is what a player behind symmetric NAT or CGNAT has been hitting, and it reads on screen as an empty arena.
npm run turn-check is the probe. It asks each configured server for a relay candidate directly, runs two controls first so a dead network cannot be mistaken for a dead server, and takes a candidate provider on the command line so a replacement can be tried before it is committed. The background in plain language is in docs/networking.md, the findings and the options in the roadmap, which now carries the open question of whether the transport stays peer to peer.
The console scrollback walks with alt+up/down, because macOS reserves ctrl+up/down for Mission Control and the scroll never reached the game there. The mouse pointer is hidden only while it is captured, so it behaves normally on the boot screen, in the menus and after Esc. The jukebox drops the caption under its mode row, the notes listing on the site drops its release dates, and the flamethrower's spritesheet and the hero's art are both redrawn.
PENDING_AUDIO_ASSETS.md carries a brief for each.0.6
the arena grows depth, and the people in it start talking.
0.5 filled the room. 0.6 builds the room around them. An arena is lit rather than uniformly bright, so a corridor can be dark and the hall at the end of it warm, and a lamp you shoot out takes its corner with it. Walls can be seen through, shot out, opened, and walked straight through into somewhere else. The shopfronts have rooms behind them. Six arenas join the rotation, capture the flag joins the modes, the flamethrower joins the arsenal, and every soldier in the match has a voice.
Your soldier has a voice. Five personalities ship, each with its own catchphrases: SARGE, GHOST, ROOKIE, WRENCH and CHAPLAIN. Pick one on the VOICE row of the CHARACTER screen, where Enter auditions it, and it travels with the rest of your look, so everyone in the room hears you as the soldier you built. Bots draw a voice from their slot, so a solo match hears all five.
Lines come out of the match itself. A soldier calls the frag, answers a death, takes the lead, wins the match, and speaks on every flag beat in capture the flag. One voice sounds at a time and one line covers one moment, so a firefight stays legible rather than turning into five soldiers talking over each other, and a speaker waits before speaking again.
Hold V for the comms wheel and pick a line to say it on purpose: NEED BACKUP, ENEMY SPOTTED, COVER ME, ON MY WAY, or a TAUNT. The four callouts are team coms and carry to your teammates flat and at full level wherever they are; the taunt is heard by everyone in earshot, from where you stand. Touch players get the same five on a COMS button. Speech rides its own volume slider in OPTIONS, next to music and effects.
Protocol 17. A soldier's personality travels with the appearance and a spoken line is its own action on the wire, so this build and a protocol 16 one land in separate rooms.
A third mode joins deathmatch and gun game. Two flags, one per side, each on a base its team spawns around: take theirs, carry it home, and score it against your own flag standing. A carrier who dies drops the flag where they fell, either side can reach it, and a dropped flag walks itself home on a timer if nobody does. A match clock runs the mode to a finish, and a tie at the end is a draw rather than a winner picked on a technicality.
The flags are readable from anywhere. They stand in the world and show on the minimap, every take, drop, return and capture is announced and carries its own cue, and a flag that is out of place is marked so you can see at a glance which half of the fight you are losing. Bots play the mode properly: they take the enemy flag, run it home, and turn back to defend their own when it goes.
Two arenas ship with it. DEPOT is open ground between two sheds, a long run with nothing to hide behind, and BAZAAR is covered lanes with no clear shot down any of them. A map declares its bases and team spawns in its own legend, and the menu offers only the arenas that have them when the mode needs them.
A sixth weapon takes the last slot. It fires a short, wide cone that sets what it touches alight, and the burn goes on damaging its victim after the flame stops: a target that walks out of range still has a few seconds to think about. Fuel is its own ammo, dropping in its own boxes on the same pads as the rest. The plume is drawn in the world rather than only on the shooter's screen, so a flamethrower down a corridor is a thing everyone in the arena sees, and a burning body burns for everybody.
An arena carries its own lighting. A map declares its ambient level and its floor and ceiling materials, floor and ceiling are textured and lit the way the walls are, and a per-cell lightmap is baked when the map loads, so a corridor can sit dark while the room at the end of it is warm. Props emit light: ceiling fixtures, cage lamps and neon signs light the rooms they hang in, and shooting one out drops that room to the arena's ambient level and relights everything around it. Shattering a pane relights too.
An explosion lights the room it goes off in. The blast throws a red glow across the walls and floor around it for as long as the fireball lasts, then the room settles back to how it was lit before, and the bloom on the screen matches the light it is throwing.
A wall can be see-through, and some of them break. A material says for itself whether it is opaque, so the raycaster walks past a railing or a run of cell bars and draws what stands behind them, composited over the scene through a per-pixel depth buffer. Glass cracks through three stages before it drops, to bullets and to blasts alike, with a shatter effect and a sound of its own. Broken panes travel: a peer joining halfway through inherits every pane already cracked or gone rather than walking into a shopfront rebuilt for them alone.
A door is content now. Six types ship across three motions - the sliding door, the blast door, a roller shutter that lifts rather than slides, a glass door and two gates - each one a door.yml beside its own art, exactly as props and weapons already work. A door takes its opening direction from the map instead of putting its handle on the wrong edge of two faces out of four, a type may be see-through so a gate shows you the room before you commit to it, and every type has its own open and close sound. The map format and the map editor both carry the type, so an arena can be authored with any of them.
Two new materials cut a hole between two points in an arena. Step into one end and you come out of its paired match, facing wherever the pair points. A solid pair looks like an ordinary wall, animated and faintly shimmering, until you walk into it; an open pair is see-through, so you can look, aim and throw through it before you ever cross. Every pair works in both directions, bullets and grenades pass through an open one exactly as a player does, and bots path and aim through them like anyone else. An arena carries up to four colour-coded pairs, placed with the map editor's own Portals brush.
Threshold joins the rotation to show them off. Two private spawn rooms sit in opposite corners, linked by a solid pair for a straight shortcut through the wall between them. The second pair sits open in the arena's own outer wall, looking the length of the long central corridor.
Six arenas join the rotation, which takes it to eleven. CITY MALL is a glass-fronted shopping centre around a planted atrium: every shopfront is a pane you can see through and then shoot out, so the sightlines change as the match goes on. POLICE STATION is darker and tighter, with a bullpen of frosted partitions, a holding-cell corridor you can see down but not shoot down, and a basement lit by four lamps that anyone can take out. CLOISTER is a ruined abbey, four halls around a fallen chapel, and it brings a ruined stone wall set with it. THRESHOLD arrives with the portals, DEPOT and BAZAAR with capture the flag.
Foliage, vending machines, filing cabinets and police barricades fill the new floor space, and the five original arenas are lit rather than left flat. A prop can hang from a ceiling and face a direction it was placed at, and the solid ones - crates, barrels, canisters - stop you walking through them instead of being scenery you pass through.
A window on a facade shows a room behind it rather than a flat texture, with real parallax: the back wall slides against the frame as you pass, the shelving inside shifts against the back wall, and the floor and ceiling open and close as you move. Each room is lit from within rather than by the street, and breaking the glass leaves the room standing.
Seventeen kits ship, and each of the two glazed facades carries a district. The blue glass is a high street - cafe, bakery, boutique, electronics, bookshop, florist, arcade - and the green glass a back street: laundromat, pharmacy, hardware, barber, noodle bar, pawnshop, garage, butcher. A derelict shop sits in both sets, which leaves about one shuttered unit in nine on either street. A mapper picks the district by choosing the facade they were already painting with, and a map that pins nothing at all gets the variety by itself.
POLICE OFFICER joins HERO on the shelf, so the roster is no longer one drawing and a promise. A body flinches when a shot lands on it: a character can declare a hurt pose, and the ones that ship do.
Recolor runs off a mask. A character declares its parts against a per-pixel mask carrying a part index and a blend strength, in place of colour ramps against a palette, so a tone that two garments share tints in proportion instead of being forced whole into one of them. The bake builds one lookup table per part per look, which is what makes the tool below able to preview a recolor at the speed you paint it.
There is a character editor, shipped with the site under the Tools tab. It opens a character onto a canvas you pan and zoom, and lets its recolor mask be painted by hand: paint, erase, bucket-fill a flat garment by its sheet colour, and a strength control for the pixels that belong to two things at once. A tone strip claims a tone across the whole sheet in one action, overlay modes and coverage counts show what each part has claimed and what nobody has, and every stroke is one undo step.
The preview is the game's own bake, not an approximation of it, so what you see while painting is what the arena will draw. Parts carry editable anchors that can be measured back off the art, cells can be framed, stepped through and named, work autosaves to the browser and survives a reload, and the finished mask exports back to disk.
Firearms throw brass. A weapon declares an ejection port and the frame its case leaves on, and the pistol, the shotgun and the chaingun each throw a spent case per shot, tumbling out of the gun in view space with tuning of their own; the shotgun throws its hull on the pump stroke rather than on the trigger.
Bots carry the whole arsenal. They pick the weapon that suits the range, swap at the same cost you pay, collect every kind of ammo box, and throw grenades whose arc they actually simulate before committing to it. Their callsigns come from an authored pool rather than a number.
Health packs drop on the ammo pads. A pack grants 25 health and fills past 100, up to 150, so one taken at full health banks a buffer that stays until something shoots it off. The overheal shows as a row of pips above the health bar. Bots collect them too, but only walk to one when they are actually hurt.
The guns are loud now. Every def owns its clips: a weapon, a prop or an effect directory holds its sounds beside its sheet and its yml, and a clip can fire off a named animation frame or a named event rather than only at the moment of the shot. Fourteen of the thirty gameplay clips ship real recordings - the six weapons, the explosion, blood, the ricochet and the four flag cues - and the rest still play a synthesized placeholder tone.
Sound travels. A remote's weapon fire is spatialized at the peer that fired it, every remote runs its own frame clock so a peer's frame-triggered sounds play on your machine, and a thrown grenade sounds at the projectile on every client rather than at the thrower. A sustained weapon throttles its own fire clip, so the flamethrower reads as one continuous roar instead of a stutter.
You can be in a match without being in the fight. SPECTATE on the main menu joins a room with no body: you are named in the roster, you can referee and own the bots, and you are not counted towards the players a match needs to start. The camera follows anyone in the arena, players and bots alike, draws the weapon the body you are watching is carrying, rides a dying target through its fall and dwells there before moving on, and parks itself when the arena empties. Press fire and you become a player, in place, without leaving the room.
The room code comes first. Pick the room and the mode, and a match already running takes you straight in. The arena and the bots are only asked for when the room turns out to be empty and yours to found, and while you pick, the room is held for you: anyone arriving waits for your arena instead of starting their own.
The MAP screen is rebuilt around the arena rather than around a list. The map is a dim full-bleed backdrop with a crisp framed inset, its name, author and blurb in one column, and a rail that scrolls once the list outgrows the screen, with the arenas you built grouped apart from the shipped ones. The mode screen draws a mark for the mode under the caret, and ASSAULT joins SURVIVAL as a mode that is coming.
The editor gains shapes: line, rectangle and ellipse, each with a drag preview, each writing the whole shape in one undo step. The palette shows the real tile art instead of coloured squares, the canvas paints the real wall and prop art, the selected brush is previewed, and the header operations carry icons.
New brushes cover the new content: a Portals group, team bases and team spawns for capture the flag, and the door types. An Environment section sets the arena's floor, ceiling, ambient level and interior kits, with a swatch picker for the material choices, and a map round-trips all of it, reporting the keys it does not recognize rather than dropping them silently.
The editor marks cells a player can never reach or see: a sealed room, the filler inside a thick wall, the corners of the border. They draw black with an X across them, and a toolbar toggle hides and shows them.
The killfeed names the frag in the corner of a live match. The minimap sits in a panel of fixed size and cues its clipped edges rather than growing with the arena. Overheal shows above the health bar, portal pairs are colour-coded on the minimap, and the flags show on it in capture the flag.
The credits roll names the arenas. Every shipped arena appears with the person who built it, read from the map itself, so a new arena credits its author the moment it ships. The special thanks list moves into a file of its own and the attributions page is generated from it, so adding a name never means editing the credits.
The landing page's arena board is boxed with its columns aligned, the live arena widget becomes a card, and a room with a match actually running is marked with a pulsing dot. There is a Character Editor card on the Tools tab beside the map editor.
The renderer can photograph itself. A console command captures every draw stage of a frame separately - walls, sprites, decals, the viewmodel - and a headless script poses a reproducible scene and shoots the same set from the command line, which is where the frame breakdowns on the site come from.
Protocol goes from 9 to 17. Eight steps, each one closing a way two clients could silently disagree rather than error: v10 and v11 add a voice and take it back out again, v12 and v13 put breakable glass on the wire and add the founding claim that stops two clients building different arenas in the same empty room, v14 adds the flamethrower's slot, its fuel box and its burn, v15 adds the health pack as a sixth drop kind, v16 puts capture the flag's flags, captures and match clock in the referee's snapshot, and v17 gives a soldier a voice again, this time from files. A 0.5 client and a 0.6 client never meet.
The in-arena spectator is a third role on the mesh alongside player and observer. TURN servers join the signaling config, so two peers behind restrictive networks can actually connect rather than sitting in a room together and never finding each other. A net console command opens a live traffic panel with per-peer stats and relay counts. An operator can run an always-on host with the container and compose file that ship in host/: a headless spectator that holds a room open so a match is there to join.
The boot screen is an intro now, not a two-second loading gag. It opens on a POWER plate and waits there: a browser will not play sound until you have told it to, and this one carries sound, so it asks first, with a press of any key, a click or a tap. That wakes a CRT - a line blooms open, overshoots, and settles into the amber field as the header fades up - and then runs the same three loading stages as before, LOADING ART, BAKING SPRITES and LINKING PEER MESH, paced to be watched this time instead of blinked past: each one clatters while it works and reports OK or FAIL when it lands. The lines fade, the logo scans in a row at a time the way a tube draws a frame, and a half-second fade hands off to the menu, its music starting as the CRT hum dies out under it. About 4.7 seconds end to end on a warm cache.
Any key, click or tap during the show skips it, but never the loading underneath: every stage is still awaited, because the menu cannot open before the art it draws with has loaded. A power-on thunk, a hum bed, drive chatter under each stage, and a swell on the logo all currently play as synthesized placeholders, briefed in PENDING_AUDIO_ASSETS.md alongside the rest.
PENDING_AUDIO_ASSETS.md carries a brief for each.0.5
the room stops being empty.
0.4 ended on an admission: still no bots, an empty room is an empty room. 0.5 answers it. There are people in the arena now, they path and hunt and take cover, and they are drawn rather than computed - eight facings, a real walk, a death that plays out. Around them the game grows the parts that were missing: a front door you arrive through, an editor for building your own arena, a jukebox for the soundtrack, and a console for prying the whole thing open.
You can fill a match with bots, up to seven, chosen on a BOTS screen that follows map selection. They are not target dummies. A bot paths across the arena with A* over the tile grid, decides between hunting, rearming, retreating and wandering, and shoots at you with an aim error and a reaction delay rather than with perfect instant fire. It rearms at ammo pads and backs off while it does.
Four levels set how well that goes: BYSTANDER, EASY, NORMAL and HARD. Skill moves aim error, reaction time and sight range together, so a HARD bot sees further, turns on you sooner and misses less. BYSTANDER is the odd one out - it cuts combat entirely and only roams, which makes it useful for walking an arena you are building, or for filling a room with something to watch.
Bots live on the wire, not in one client. A single peer simulates each bot and broadcasts its position, hits and deaths, and if that peer leaves, another adopts the bot and carries it on. Everyone in the room sees the same bots doing the same things, and they score on the same board you do.
The figure you play is a character now, and the first one is HERO. A character is one spritesheet plus a small file beside it that says where each pose sits on the sheet and which of its colours are skin, hair, shirt, trousers and boots. The PLAYER screen gains a CHARACTER row that names the one you are wearing, so a second character is a folder of art rather than a code change.
All eight directions are drawn. The old sprite painted five and mirrored the other three, which meant a soldier seen from their left was the same soldier seen from their right, flipped. Every facing is its own picture now, so a player turning in front of you turns through eight drawings instead of five and their reflections.
Walking is four frames instead of two, and there is a separate standing pose, so a player who has stopped stands still instead of marching on the spot.
Dying is a sequence rather than a swap. Seven frames carry a body from the hit through the stagger to the floor, it plays once, and it holds where it lands instead of snapping to a corpse. A body already on the ground when you arrive stays down rather than dying again for your benefit.
A peer shooting at you head on looks like it. Two more poses cover the moment of a shot - the gun raised, then the muzzle flash - and they replace the walk while the shot is fresh, so the player in front of you visibly fires the round that hits you. They are drawn front on, because that is the view that matters.
Five colour rows dress a character: SKIN, HAIR, SHIRT, TROUSERS and BOOTS. Skin, hair and boots each get their own palette of tones that suit them, rather than sharing the arena's sixteen. The preview turns with A and D so you can check a look from every side before you take it into a match.
Glasses and hats are gone. They were switches on a painted sprite and there is nothing to switch on a drawing; what replaces them is art, and a character that wants a hat wears one its artist drew.
A weapon is a folder now. Each of the five - knife, pistol, shotgun, chaingun, grenade - is a weapon.yml beside a spritesheet, and the game fills its weapon table from that library instead of from code. The sheet carries everything the weapon shows: the first-person viewmodel, the projectile it puts in the world, and the debris its blast throws.
Switching is animated. The old gun drops out of frame, the new one rises, and the trigger is locked while the swap plays, so a switch costs you the time it looks like it costs. Firing animates too, with fire and recover frames, and the grenade pulls its pin before it goes.
The spread weapons spread properly. The chaingun sprays into a cone rather than down a line, and shotgun pellets scatter on both axes instead of only sideways. A grenade's throw distance follows how long you hold the trigger, so a lob and a hard throw are different shots.
Looking up and down changes what you hit. In 0.4 the vertical axis moved the view and nothing else: a shot went where your feet pointed no matter where your eyes were. Pitch decides the shot now. Aiming high hits what is high, aiming at the floor hits the floor, and the impact stamps its decal at the height the shot actually struck rather than at chest height.
The props become a library on disk. Each is a folder holding a prop.yml and a sheet that describe its size, its behaviour, what it does when it breaks and what it throws, and a migration tool generated the set from the twelve the game already had. Thirteen ship now: fire is new, an animated idle that hurts anything standing in it through a proximity aura that ticks damage while the prop is intact. A prop can also be consumed by its own destruction.
Shots respect a prop's shape. A bullet crossing a transparent texel of a sheet no longer counts as a hit, so a shot threads the gap in a railing instead of stopping at its bounding box. Maps validate their legends against the loaded library, so a map naming a prop that does not exist fails loudly instead of quietly drawing nothing.
A prop's wreckage used to look the same no matter what killed it: a barrel knifed open flew apart exactly as hard as one caught in a grenade, because a single speed and spread served every weapon. Debris now takes its cue from what broke it. A knife crumbles a barrel at its base, a bullet knocks its pieces a little wider, and a blast throws them clear across the room.
Authors can tune each case per prop. A prop's destruction.particles in its prop.yml gains optional melee, hitscan and blast blocks that override count, size, speed, up, ttl or tint for that cause alone. Anything a block leaves out falls back to the prop's shared particle values, and then to the engine's own default for that cause. No shipped prop declares a block yet - the defaults already carry the whole effect.
Death by explosion is messier than death by bullet. A body caught in a blast bursts into giblets instead of falling into the dead pose, thrown by the same physics pool that carries prop fragments, and the pieces fade out rather than blinking away. Bots and corpses already on the floor go the same way. The giblets are hand-drawn on a grid sheet now rather than painted at boot, and a GIBS flag under a new GAMES settings category turns the whole thing off.
Effects come off spritesheets rather than out of code. art/vfx/ holds an explosion and a blood burst, each a sheet plus an fx.yml saying how it is sliced and timed, and the procedural painters that used to draw them are retired. A sheet can declare an integer scale, so a cell smaller than 64x64 still draws at the size the effect wants.
An explosion scorches what is near it. A blast scans the wall faces around it and stamps deterministic scorch decals, so the burn marks fall the same way rather than landing at random.
Two city arenas join the three industrial ones: HIGH STREET, a shopfront avenue, and PLAZA, a civic square. They bring an urban wall palette - glass, stucco, shutter, awning, neon and sandstone - and the street furniture to dress it: hydrants, planters, mailboxes, lightpoles and benches.
Every map carries an author, credited on the map screen. Legends are per map now: the digit binding that made every arena share the same nine materials is retired, and a map says for itself which tile means what.
There is a map editor, shipped with the site under a Tools tab. It is a canvas you pan and zoom, with the palette grouped into tiles, walls and props, undo and redo, resize, in-page dialogs in place of browser prompts, and a guard so you cannot walk away from unsaved work. Maps you build are kept in your browser and appear in the menu beside the shipped ones.
Maps travel. A peer who does not have the arena you picked asks the peer that named it for the body and gets it over the wire. Received maps sit beside your own, marked as shared, and are capped so a stranger cannot fill your storage.
Music is a registry. music.yml keys every track by id and the game asks for tracks by name rather than by path, so a map naming a track it does not have falls back to a default arena song instead of playing nothing.
There is a JUKEBOX screen. It lists the soundtrack with genre and lyrics chips and cover art, plays, seeks, shuffles and repeats, and remembers all of it between sessions. Behind it sits a winamp-style player window with a spectrum analyzer driven by an FFT tap on the music bus.
The music follows you out of the menu. L raises a small music widget on any screen and in-match, fading in and out rather than snapping, and a NOW PLAYING toast names the arena track when a match starts.
OPTIONS splits into GRAPHICS, AUDIO and CONTROLS. Controls gains a scheme selector that resolves against your device, and the in-match keys come from the scheme you picked rather than from one hardcoded set. Graphics gains an FOV slider, a SHOW FPS readout, a fisheye lens toggle, and the CRT interlace overlay as an option that applies in the match too.
The SOLDIER root item is now PLAYER, and there is an EXIT item in the main menu and the pause overlay. You run with shift, and an AUTORUN row inverts it so you run by default and hold shift to walk. Respawning waits for you: instead of snapping you back, the game prompts PRESS FIRE TO RESPAWN and holds until you do.
Screens dissolve into each other. A transition plays between screens and into the match - melt by default, with fade, wipe and cut available and per-route overrides declared in game.yml.
The game plays on a phone. An on-screen overlay carries movement, look and fire, with a look-sensitivity control in options; a menu pad lets a touch player drive the menus and get back out of them; and the room code is edited with the device's own keyboard.
~ drops a console into a match. It has a command registry, a parser, editable input with history, and a scrollback you can walk with alt+up/down. The commands cover inspection (pos, peers, fps, and debug for a render-stats overlay), display (nametags, minimap, fisheye, gibs, interlace, fov, sens) and cheats (god, noclip, infammo, give, bot). Every hook defaults to the behaviour the game already had, so a console you never open changes nothing.
Fragyard has a front page. The root serves a landing page and the game moves to /play/, with an arena card carrying the Play button and tabs for About, Roadmap and Tools. The live arena widget watches real matches: it lists rooms, names the top three fraggers with their kill counts, and narrates frags as they land with a typewriter reveal. These release notes are generated from RELEASE_NOTES.md and served at /release-notes.
The game has a logo: a reticle-skull lockup that also supplies the favicon and the link preview image.
Protocol goes from 6 to 9. Three steps, all of them additive to the room but none of them backward compatible: v7 carries bot spawn, position, hit and death; v8 adds an observer role with filtered rosters, so someone watching a match is not counted as a combatant; v9 adds map request and reply so a peer can fetch an arena it does not have. A 0.4 client and a 0.5 client no longer meet.
An invite URL auto-joins the match it names after boot instead of dropping you at the menu. Sitting in the menu counts as watching, through a quiet lobby room, so the site can tell how many people are around without pretending they are playing. The signaling relays move into game.yml, with damus swapped out for primal and nostr.band.
0.4
the game gets a voice, and everything you see becomes a file you can edit.
0.3 turned the arena into data. 0.4 does the same to the rest. The art loads from PNGs you can open in any editor, the title and credits come from a config file, and the whole thing finally makes sound. The soldier is repainted at four times the resolution, walls come in fourteen materials and remember where they were shot, and you can look up and down and fall over when you die.
Fragyard has audio now. A small Web Audio engine sits behind two buses - music and SFX - each with its own volume, set on a new OPTIONS screen and remembered between sessions. Music streams rather than decoding whole: a menu track, a credits track, a victory sting when a match ends, and a per-map arena track that each map declares in its own yml. A missing track is silence, not an error, and a same-map rematch restarts the arena track cleanly rather than layering a second copy over it.
The context stays asleep until your first keypress on the boot screen, because a browser will not let a page make noise before you have touched it. Sound effects share the same plumbing - including a spatial layer that pans and attenuates by position - but only the menu speaks so far: moving the cursor and confirming a choice each have a sound. The guns are still silent (see Known limitations).
Static art - wall textures, props, the HUD, the soldier - is no longer painted in code at boot. It bakes into PNG files that ship in art/, and the game loads them at boot instead. An export tool regenerates the whole set from the painters, so the pixels are still procedural at the source, but what the game reads is a folder of images you can open in any editor and change by hand. Soldiers bake from separate body, hat, and glasses layers rather than one flat sheet.
Title, version, and the credits roll now live in game.yml, read by both the boot screen and the menu, and the old credits.yml is gone. A CREDITS entry in the menu plays a scrolling roll built from that config. Credits text is limited to the pixel font's charset, so the file documents which characters render and which come out as gaps.
The SOLDIER screen moves onto the canvas and drops its old DOM island, so the whole shell is now drawn the same way. The sprite pipeline moves to 128px and the soldier is repainted in detail, with shaded garments in place of flat fills. A 16-color palette picker with a typed hex field sets each part, and a live preview shows the result.
Wall textures are a registry now: fourteen materials painted with a shared toolkit, with the minimap and thumbnail colors routed through the same registry so an arena reads consistently at every scale. A map's grid can reference any of the first nine materials by digit.
Walls also remember where they were shot. Every hit stamps a decal - a blood splash or a ricochet scorch - onto the exact wall face it lands on, resolved to the nearest solid surface and stored sparsely per face with an LRU cap so memory stays bounded. Impact bursts become world-anchored billboards that sit in the scene and animate in place rather than flashing at the screen center.
Mouse look gains a vertical axis: you can look up and down within a clamp, and strafe under the pointer lock. Death is staged now - the camera drops to the floor, the world rolls as the body goes down, and the fall plays out before the spectator camera takes over.
The mouse wheel cycles weapons, skipping the ones you cannot hold. The Tab scoreboard renders on the canvas in the pixel font. A new OPTIONS screen holds the volume sliders and a display mode that fills the window by default, replacing the old corner fullscreen button.
The ladder reverses. Gun Game used to hand you the knife first, the hardest rung, so early kills went to whoever walked into a body. It now starts you at the grenade and demotes toward the knife, so the lead narrows as it grows and the last kill is the hardest one.
Protocol stays at 6. Nothing this release touches the wire: sound, loadable art, wall decals, and the death fall are all local. A 0.3 client and a 0.4 client still meet in the same room and agree about the world - they just hear and see it differently.
0.3
the arena stops being a constant, and the published site stops being the source tree.
0.2 shipped four modes and something to blow up, all in the one U-shaped arena that had been baked into the code since 0.1. 0.3 turns the arena into data: maps load from files, three of them ship, and you pick one before the fight. The site served from the web also changes shape - it is now a built artifact rather than the raw source folder.
The arena is a file now. An arena is one ASCII picture in maps/<id>.yml, listed in maps.yml: digits are wall textures, D is a door, S a spawn, A an ammo pad, and single letters place barrels, gas canisters, crates, pillars and lamps. You author a map by drawing it, and a test parses every shipped map and rejects a grid that is not rectangular, not walled all the way round, or missing a spawn or a pad - so a broken arena fails the suite instead of someone's browser.
Three ship:
A map-select screen sits between the mode and the room code, with a top-down preview of the highlighted arena and a RANDOM entry that rolls a real map before the match starts, so the share link always names the arena you actually got.
Unlike the mode, the map is not part of the room's identity. The Trystero room is still <mode>:<code>, and the arena is whatever its founder picked: whoever reaches an empty room first sets the map, and everyone who joins after inherits it. This is the only honest way to offer "pick a map if the room does not exist yet", because a peer-to-peer mesh cannot be asked whether a room exists without joining it.
So a join now holds on an ENTERING ARENA screen for a beat while it learns the arena - from a peer's catch-up in the common case, or by founding its own pick if the room really is empty. A share link carries the map as a hint (#room=TEST&mode=dm&map=yard): it reproduces the arena for a friend who arrives first, and is quietly overridden if the fight is already running on a different one.
The site served from fragyard.4rknova.com is no longer the source tree. It is a bundled, minified, lightly obfuscated dist/, built and deployed by CI. Around 180KB of hand-written modules collapses to one hashed bundle, roughly 20KB over the wire, and the raycaster, the procedural art, and every comment in them are no longer served raw to anyone who opens the network tab. This is not anti-cheat - the game is peer to peer with a trusted referee, and a determined cheat sends bad packets rather than reading code - it just deters casual tinkering and makes a published build look published.
The repo stays a plain folder of ES modules that runs under python3 -m http.server with no build step. Building is a publishing step, never a development one.
Protocol 6. The map now travels on the wire - it rides the referee's 2 Hz snapshot and the join catch-up - and a prop id is an index into the chosen map's props rather than one fixed array. A 0.2 client carries no map field and would play its own hardcoded arena while everyone else played the loaded one, so the protocol version steps up and keeps the two apart: mismatched clients land in different rooms and never meet, the same guard that has kept every silent-desync change honest since 0.2.
0.2
the game gets a front end, a rule set, and something to blow up.
0.1 is a deathmatch that never ends, in an arena you cannot damage, on a page that drops you straight into it. 0.2 adds a boot screen, a menu, four playable modes with real win conditions, explosives, and props that come apart when you shoot them.
Pick one in the menu. The mode is part of the room's identity - the Trystero room is <mode>:<code> - so two players who choose different rules land in different rooms and never meet. Share links carry both: #room=TEST&mode=tdm.
Matches now have a shape: warmup, live, round over, match over, rematch. A results board closes each match, and the arena resets between them, so props you destroy come back and the third rematch is not played in a burnt-out room.
Phase, clock, scores, and teams belong to a referee: the peer whose Trystero id sorts lowest. Nothing is elected - every client derives the same answer from the peer list, so the role migrates for free when the referee leaves. The referee broadcasts a snapshot at 2 Hz and everyone else adopts it, ticking the clock down locally in between. The referee is trusted; with no accounts and no ranking, that is the right trade.
Keys 1-5 select: knife, pistol, shotgun, chaingun, grenade. Each gun draws from its own reserve, so running one dry does not touch the others.
The arena is destructible now. Explosive barrels detonate when shot or caught in a blast, and chain into each other. Gas canisters vent and skitter across the floor before going off. Wooden crates splinter. Stone pillars are indestructible cover: they block movement and eat shots.
The page opens on a DOS-style boot sequence, a BIOS gag whose progress lines are honest. Each stage awaits a real dynamic import: the texture bake, the sprite bake, and a prefetch of Trystero. That last one is the substantive one - it warms the module cache so the first connect of a match resolves instantly instead of stalling. A stage that fails prints FAIL and carries on rather than trapping you.
Boot hands off to a canvas-drawn retro menu that owns the screen until a match starts and takes it back when one ends: mode select with per-mode blurbs, the arena and room code, controls, and the soldier screen (the old 0.1 lobby, which no longer decides anything on its own). Esc during a match opens a pause overlay. It is an overlay, not a pause, because a multiplayer arena cannot stop for one player. Leaving tears the room down and returns you to the menu.
main.js splits up: procedural art into art.js, particles into fx.js, the pixel font into text.js, combat math into combat.js, plus the new modes.js, match.js, menu.js, boot.js, and spectate.js.node --test tests/*.test.js.camera that normally aliases the player. That indirection is what lets an eliminated player be repointed at a teammate's eyes without gameplay noticing.Protocol 5. The Trystero room is namespaced by protocol version, so incompatible clients land in different rooms and never discover each other. This matters because prop ids and the ammo-drop schedule are derived on every client from a shared seed rather than broadcast: a mismatched client would silently disagree about the world instead of erroring. A version mismatch now looks like "waiting for players", not a desynced arena.
Door state, prop destruction, and canister behavior all sync now - three places where two clients could quietly resolve the same blast against different worlds and never reconcile.
0.1
first tagged release.
A browser deathmatch in a Wolfenstein 3D-style raycaster. Customize a soldier, join an arena, and frag your friends. Everything renders to a 2D canvas with classic DDA raycasting, and the multiplayer runs peer-to-peer with no backend to deploy.
Networking is serverless. Peers find each other over Trystero - WebRTC signalled through a pinned set of public Nostr relays - so there is no backend, no account, and nothing to host. Each client owns its own avatar and broadcasts its state; hits are routed directly to the peer that was hit.
Secure-context requirement. WebRTC signalling needs crypto.subtle, which browsers only expose on secure origins. Fragyard works over HTTPS and on http://localhost; on a plain-HTTP LAN address it degrades to solo play and says so plainly instead of failing silently.