MACH//BREAK

Dev log · pre-alpha · internal build · friends and family

Dev log

Three kinds of record, all generated. The dev notes are what goes to the Discord dev channel with each test-realm build. Every consequential change is an experiment: hypothesis, implementation, telemetry, result, keep or remove. The milestone notes are the phase records. Nothing here is written for the page; it is as honest as the numbers in it.

Generated 2026-10-25 by website/build_updates.py from the project's dev notes, experiment log and milestone notes. Internal engineering shorthand is translated to in-game vocabulary; anything that cannot be published verbatim is left out rather than paraphrased.

Dev notes

Dev notes · 2026-10-20

PTR, October 23 — the rules of the game, and the gun that fires where you point it

PTR: the rules of the game, and the gun that fires where you point it. The label that followed Tuesday's, promoted today. Everything below is measured against the reference and built to its number.

Splash under cover (items 20-21). Splash reaches a target the way the reference tests it: two lines, one from the burst and one from a metre above it, to the target's centre, all or nothing. A corner that blocks both lines protects you; a lip that blocks one does not.

The Vector Cannon's spread (items 20-21). Each round scatters about the gun's own axes, so firing straight up a shaft or straight down on a flag paints the same square as firing level. It used to tighten with the pitch.

The flag on a slope (items 24-29). A loose flag sleeps only on ground the reference would let it sleep on: friction against the slope, not a speed threshold. Thrown up a steep face it comes back down. The cap is the flag item's own box overlap, the return pays by how far from its stand the flag lay on THIS map, and a return during a stalemate pays fifteen, as the reference does.

Known problems in this build.

  • 17-18, the contact rule with its slope term and the ledge grace: built and measured against the reference, held one label by a single body-on-body gate; the jet's horizontal split follows it.
  • 17-18, doors and flag stands on both maps: players still pass through one another instead of blocking the path.
  • 20-21, fighting a moving player: the hit box is still axis-aligned.
  • 9-16, base turrets: ours engage anything in range with a clear line; the reference's engage only what the team's sensors see. In hand.
  • 24-29, the match rules: the dropped flag's return timer, out of bounds, warm-up, spawn invulnerability and the end-of-match rules are measured this week and are next.
  • 23, first-person hands: faceted shapes at arm's length; the interior material set is still ours.
  • 33, bots: an engineer can still stall at a court wall (fixed, ships with the contact rule); the reference's objective weights and skill rules are measured this week and are next.
  • The four older maps keep their old bases. Vehicles: the pads offer a Razor; the reference's flier is measured and being built.

Dev notes · 2026-10-19

PTR, October 19 — the light that never cast, the base, the scoreboard and the bots as the reference's

PTR: the light that never cast, the base, the scoreboard and the bots as the reference's. Two days after the movement batch, three builds' worth in one label: the rooms have light in them for the first time, the generator and the inventory station take damage and come back exactly as the reference's do, every weapon draws in the reference's half second, the flag throw is the reference's full-strength toss, the scoreboard pays for defence the way the reference's does, and the bots aim and hold their rooms as the reference's do.

The interior light that never cast (item 23, the stipulation). Every lamp we ever hung in a base hall was lighting the outside of the room. The interior material flipped its surface normal when seen from inside a closed box, so no lamp ever lit a floor and no body ever threw a shadow indoors; the halls read flat and grey because they were unlit, not because they were dark. The flip is gone. Lamps light the floor they hang over, a body in a generator room casts a shadow on it, and the plate gate for an interior now asks for real darks, no blown walls, and a shadow under the player. The roof deck keeps no light of its own by decision; it is lit by the sky, as the reference's is.

The generator and the inventory station as the reference's (items 12-13). Both objects now carry the reference's hull and the reference's energy shield in front of it: damage drains the shield first, at the reference's rate per point, and only then the hull; each goes disabled at the reference's fraction and is repaired back from there. Team damage is a server option, off unless the operator turns it on, as the reference ships it. The match log's format moved with these numbers, so the replays of this label do not play in the last one.

Energy at the station (item 13). Visiting a station refills you. Swapping frame at a station keeps your energy as the number it was, not as a fraction of a different bar, which is what the reference does and what a Heavy stepping down to a Light will notice: a full bar stays full.

The bots (item 33). A bot's own aim error is now read off the reference's shot log with the muzzle spread taken out of it, instead of being tuned to a hit rate; the reference's bot closes to about twenty metres before it fires at a standing target, and ours scatters the way its rounds do. A warden defending a generator room now treats the plant's shield as part of its health, so a raid that only drains the shield still raises the alarm and the warden finishes what it went in for; and the way into the room counts as the room, so a warden walking down the bore no longer dances in the doorway while the raider is inside.

The flag throw (items 24-29). The throw key throws at the reference's living strength, which is the same vector as the drop from a corpse at nearly twice the impulse: standing still with a level eye the flag leaves at 18.75 m/s forward and 11.25 up, and running adds your speed inside the multiplier, as the reference does, not beside it.

Falling (items 17-23). Fall damage is the reference's: the speed into the surface along its normal, at or above 45 m/s, times each frame's own scale. A long drop onto a face that falls away from you costs nothing and a shallower one into a rising face can, as in the reference; the old airtime condition is gone.

The scoreboard pays for defence (items 24-29). A kill is 10. Killing the enemy carrier pays 5 more, so does a kill within 20 m of your own flag or your own generator, so does an escort kill beside your carrier, and a return pays 10. A team kill costs 10. Points reach the board through the player's score, not new columns. A timed match now ends on the higher team score, as the reference's does; a tie plays on, and the next score of any kind ends it.

Housekeeping you will not see. The gliding ribbon reads as a ribbon at a player's range rather than a rope, the gliding eye's plate run no longer spends three GPU comparisons per sweep on a software renderer, the two plate suites write where the sweep can see them, the session log is written per process so two clients on one box no longer tear it, the human-session kit names this week's label, and the twelve rules of capture-the-flag that still read as different from the reference were each ruled: seven are now matched, two are scheduled, one is an experiment with an expiry, two are ours by decision.

Known problems in this build.

  • 17-18, both maps, airborne turns: jet steering still uses the older rule. The contact tick is measured and understood; it lands with the slope term it exposed, as one change, in the next label but one.
  • 17-18, doors and flag stands on both maps: players still pass through one another instead of blocking the path.
  • 20-21, fighting a moving player: the hit box is still axis-aligned.
  • 20-21, firing round a corner: splash reaches through cover differently from the reference, which tests two lines of sight and gives all or nothing. Measured this week, in hand.
  • 9-16, base turrets: ours engage anything in the barrel's range with a clear line; the reference's engage only what the team's sensors see, about 80 m for the turret's own. Measured this week, in hand.
  • 24-29, the return score: ours pays a flat 10; the reference pays 0 to 10 by how far from its stand the flag lay, and 15 on a stalemate. Measured this week, in hand.
  • 23, first-person hands: faceted shapes at arm's length; the interior material set is still ours.
  • 23, distant gliding players: the long trail is hard to pick out across the icefield.
  • 33, base attacks on both maps: bots can spend too long shelling a generator or returning to defence; every bot plays at one skill.

What we are building next, in this order.

  • Splash through cover; the base turrets' field of fire; the return score by distance; the cap trigger's square footprint.
  • The contact rule with its slope term, then the jet's horizontal split exactly as captured.
  • The hit box that turns with the body.
  • The first human PTR session: the owner names the night.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

3 lines of internal detail omitted from this note.

Dev notes · 2026-10-11

PTR, October 17 (written October 11) — the movement batch, by the numbers

PTR: the movement batch, by the numbers. This build is the reference's movement measured tick by tick from its own captures and matched: how you jump, how you jet, how fast you run in every direction, how you land, and how a round leaves the gun.

Jump and jet as one counter (items 17-18). Jump and jet share the reference's surface counter: after you leave the ground the jet is pure vertical for exactly eight ticks, then it splits. A jump is refused above the frame's jump speed, as the reference refuses it.

Running, in every direction. Three run speeds per frame, forward, back and side (15 / 13, 12 / 10, 7.5 / 5), on a move the game reads in sixteenths per axis exactly as the reference's server does; a shallow diagonal runs at the dominant axis's speed scaled by how hard you push it. The run controller no longer pushes you into a slope: a 31 degree traverse holds its heading to a tenth of a degree.

Landing. A landing keeps one full tick of gravity and then sheds the part of your speed that points into the ground, with your position swept to the contact and slid along it, never snapped. On a 31 degree climb our hop loss per bounce now matches the reference's within 12 percent.

Rounds on their own clock (items 20-21). Projectiles step on the reference's 0.032 s round clock, not the body's; every round flies 2.4 percent further per tick than before, which is the difference between a Impulse Launcher that lands short and one that lands where you aimed. The gun's spawn point follows your pitch the way the reference's animated pose does, from the captured table, for all seven weapons. The impulse jump reads 30 m Impulse Launcher-only and 39 m with a jump, which is the reference's.

Bots (item 33). Bots face where they go. A raider's route keeps clear of a flag it was not sent for, screens its approach posts against the enemy flag's grab radius, and no longer cuts through the stand on its last 50 m. The bots' wall-wait clock ran at half speed (it counted thinks, not ticks); it now charges the climb tank at the body's own rate. On Proving the raid walks in at the bar.

The look (the stipulation). The first-person weapon was being drawn at a quarter of the window's pixels and scaled up. It is drawn at the window now, with the world's anti-aliasing and a real shadow from the receiver onto the hand.

Maps. Proving's long slope had an 8 m toe at 39.6 degrees from a terrain generator blend; it is the 31 degrees the map documents again. The flag's fade now works on the Compatibility renderer too.

Measured, written down, and reversible. Two test calibrations are recorded as experiments (EXP-24, EXP-26) with their cause, their warrant and an expiry; the standing rule is that bars and scenes change only by a written record.

Known problems in this build.

  • 17-18, both maps, airborne turns: jet steering still uses the older rule, so sideways thrust and braking do not yet match the reference; a contact with jump held still counts as a landing (the next label).
  • 17-18, doors and flag stands on both maps: players still pass through one another instead of blocking the path.
  • 20-21, steep downward Strider blast jumps on flat ground: jumping with the shot reaches about 42 m instead of the reference's 37-39 m.
  • 20-21, fighting a moving player: the hit box is still axis-aligned and the Vector Cannon's spread uses two angles where the reference uses three.
  • 9-16, the generator and the station: hull, disable time and the station's lock still follow our older numbers; the reference's land in the next label.
  • 23, first-person hands and rooms: faceted shapes at arm's length; interiors have no light that casts a shadow yet.
  • 23, distant gliding players: the long trail is hard to pick out across the icefield.
  • 33, base attacks on both maps: bots can spend too long shelling a generator or returning to defence; every bot plays at one skill.

What we are building next, in this order.

  • The contact rule: a contact with jump held is a jump, not a landing; then the jet's horizontal split exactly as captured.
  • The generator and the inventory station as the reference's (hull, disable, shield, lock).
  • The hit box that turns with the body, and the Vector Cannon's three-angle spread.
  • Interior light that casts, and the interior material set.
  • The first human PTR session: the owner names the night.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

2 lines of internal detail omitted from this note.

Dev notes · 2026-10-10

PTR, October 10 — force fields on the base doors

PTR: force fields on the base doors. Build 11: every exterior door of every base is a force field, one door per base stays open, and the bots know which is which.

The doors are fields now (item 2). While a base's generators are up, its exterior doors are force fields: your own team walks through, the other team is stopped as a wall stops them, and shots stop at the face from both sides - a defender cannot fire out through its own door either. Kill the generators and the fields drop after a short coast; bring them back and the fields return. One door on each base is left open on purpose, so a raid is always possible while the power is up - the way the reference's own maps were laid out - and the field is what a defender uses as a shortcut and what stops a shot on the other door.

The bots play the doors (item 33). A raiding bot picks the open door, walks in and puts hull on the generator - on both focus maps now, Proving Ground included; a warden holding the room does not empty its guns into its own door any more (it was shooting the field it meant to walk through), and when it is released from the room it walks home round its own base instead of standing at a ramp with no way through. Vehicles respect the face too.

Running speeds are the reference's (items 17-18). The medium frame runs at 12 and the heavy at 7.5 - our earlier 11 and 6.5 were a tuning experiment, now retired. Forward only; the sideways and backward caps come with the next physics build.

Under the hood. The contact rule that decides "am I standing on something" is one function now, shared by the game and its own tests; and the capture readers that compare our jump and jet against the reference's recordings read the recordings tick by tick (which is how we learned our jump impulse was right all along).

Known problems in this build.

  • Movement is otherwise unchanged from build 9; the three measured rules (jump counter, side caps, carving) land together next.
  • On Proving Ground a raiding bot whose route passes the enemy flag will grab it and run home instead of raiding; the route is being taught to keep clear.
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Bots that face where they go, then the physics batch as one (items 17-18, 33).
  • Projectiles on the reference's clock, the Vector Cannon's spread, the splash rule (items 20-21).
  • The first "better than" look lanes: first person at window resolution (item 23).
  • A recorded voice pack for quick chat (item 25).

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

Dev notes · 2026-10-09

PTR, October 9 — capture the flag by the book, and the camera you were meant to have

PTR: capture the flag by the book, and the camera you were meant to have. Build 10: the BreakFlag rules as the reference played them, the camera you were actually meant to see through, and the first measured verdict on how we look.

Capture the flag, exactly (item 24). The dropped-flag return clock is 45 seconds, armed once when the flag is dropped and cancelled only by a pickup - it never restarts. The flag is lighter (every hit on a loose flag moves it 2.5x further). A hit on a carrier *rolls* to jolt the flag loose instead of always taking it. No grabs, returns or caps from a vehicle seat. The win is the team score at the cap limit x 100, and a return pays the team. The match clock defaults to 30 minutes. Respawn is 2 seconds, with 6 seconds of spawn protection on respawns only.

The camera you were meant to have (item 23). Our "90 degrees" was 90 vertical, which drew 121 wide - fishbowl. 90 is horizontal now, as in the reference, on every aspect ratio. The mouse follows the reference's law (sensitivity scales with field of view), a tap shorter than a tick is counted, and a "Low" fog preset for slower machines.

The look, measured (the stipulation). A ledger now scores 24 things you see at three eyes - 20 m, 200 m and first person - against the reference: 40 below, 19 level, 8 above. The 200 m view already wins (landform, sky, bodies and shadows, the HUD); everything a player touches at a metre - the ground, the gun in the hands, the jet, the hall, the generator - is still below. Nine lanes in order, first-person resolution first.

Known problems in this build.

  • Movement is unchanged from build 9 (see above).
  • On Proving Ground one bot-defence case still reads short.
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Force fields on the base doors, one door per base left open, and the bots that go with them (item 2) - built and gated, one bot behaviour short.
  • The physics batch: bots face their heading, then jump counter, run speeds and steering together (items 17-18).
  • Projectiles on the reference's clock: the 32 ms projectile step, the Vector Cannon's spread, the splash rule (items 20-21).
  • A recorded voice pack for quick chat.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

1 line of internal detail omitted from this note.

Dev notes · 2026-10-08

PTR, October 8 (evening) — the Lance in your hands, and the engine on paper

PTR: the Lance in your hands, and the engine on paper. Build 9, the second build today: one weapon finished, the plumbing for voices, and a day of homework that changes how the next builds get made.

The Lance is a Lance now (item 19). The bolt tapers and pulses along its length instead of drawing as a plain line; the charge shows on the gun in your hands as the bar empties; it has its own draw and impact sounds; the hit bursts. Damage, charge law and range are untouched.

Quick chat is wired for voices (item 25). A recorded line plays on the UI bus under the same wait rule the announcer uses - it never talks over a flag call - one line per sender at a time, and /mute drops a player's voice along with their text. The lines are still text today: the pack itself has to be recorded, and that is being arranged.

Known problems in this build.

  • On Proving Ground one bot-defence case still reads short (the stand left alone after a raid).
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Force fields on the base doors, with one door per base left open, and the bots that go with them (item 2).
  • The look ledger's first "better than" lanes (the ground at a metre, first-person resolution).
  • A recorded voice pack for quick chat.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

3 lines of internal detail omitted from this note.

Dev notes · 2026-10-08

PTR, October 8 — the real quick-chat tree, and bots that notice they're stuck

PTR: the real quick-chat tree, and bots that notice they're stuck. Build 8 is a smaller one: two things finished properly, and one held back on purpose.

Bots notice when they are stuck. A bot jammed against a face used to reset its own "am I making progress?" clock every time it recharged, so it could stand 90 m from its post for three minutes and never try anything. The clock survives the recharge now; a body that has not moved in six seconds breaks out. This is the fix behind the one Proving bot-defence case we listed yesterday.

Known problems in this build.

  • On Proving Ground one bot-defence case still reads short (the stand left alone after a raid).
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Force fields on the base doors, with one door per base left open (item 2).
  • The Lance to the catalogue: the beam's pulse, the charge in your hands, its own sounds (item 19).
  • Quick-chat voice: the plumbing now, a recorded pack when we have one (item 25).

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

2 lines of internal detail omitted from this note.

Dev notes · 2026-10-07

PTR, October 7 (evening) — Proving's bots fixed, the Vector Cannon spins, the launcher rechambers

PTR: Proving's bots fixed, the Vector Cannon spins, the launcher rechambers. Build 7, the second build today, takes two more weapons down the list and fixes the bots we have been apologising for since build 3.

Proving Ground's bots play the generator room now. The mortar raider puts the order on the generator in 8 seconds instead of 36, stays out of the base guns' reach while it walks, and the defender who comes out of the room walks home instead of standing on the hill. The flag carrier finds the flag court's gate instead of a ramp foot. Five of the six things we listed as rough on Proving are gone; one is left (a defender sometimes leaves the stand too long after a raid) and it is in the studio.

The Vector Cannon is a Vector Cannon now. The barrels spin up when you hold the trigger, run at full speed, and wind down over a second when you let go (half a second if you ran the belt dry). Spent cases leave the receiver. Rounds leave marks where they land - six different ones. Spin-up, spin-down, impact and an empty trigger each have their own sound. Fire rate, damage and spread are untouched.

The Grenade Launcher rechambers. After each shot you see the loading gate tip up, a fresh round go down, and the gate shut - the half-second the gun was always taking, now visible. An empty launcher holds the gate open with its own click. The round whistles in flight and the burst throws debris.

The icefield's base is unchanged from this morning (two floors, the ramps); if you found a place to get stuck in it, press SEND LOGS.

Known problems in this build.

  • On Proving Ground one bot-defence case still reads short (the stand left alone after a raid).
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Force fields on the base doors: your team walks through, the other team does not, shots stop (built, gating).
  • The Lance to the catalogue (item 19 lane 4).
  • The voice packs for quick chat.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

2 lines of internal detail omitted from this note.

Dev notes · 2026-10-07

PTR, October 7 — the generator room goes downstairs, V for quick chat, the Impulse Launcher spins

The Impulse Launcher is alive in your hands. The Impulse Launcher spins up when you draw it, idles with a hum, and whines through the air until it hits. Dry-fire has its own click. The numbers - speed, damage, splash - are untouched.

The inventory station has a face. The screen on the pillar shows your loadout, and the station hums when the base has power and dies with the generator.

Bots that do not get lost in doorways. A bot coming at a generator-room corridor from the side used to read the roof above the door as a wall and turn around. It does not any more, and the icefield's warden now walks off the deck and into the room instead of stopping on the roof.

Known problems in this build.

  • On Proving Ground the bots' generator-room play is still rougher than on the icefield; the fix is in the studio now.
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • Proving Ground's bots in the room (item 33).
  • The Vector Cannon and the Grenade Launcher to the catalogue: spinning barrels, shell eject, decals, their own sounds (item 19).
  • Force fields and doors on the two-floor base (item 2).
  • The voice packs for quick chat (item 25).

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

4 lines of internal detail omitted from this note.

Dev notes · 2026-10-06

PTR, October 6 — doors you can fly through, dishes that turn, an announcer

PTR: doors you can fly through, dishes that turn, an announcer. Build 5 answers what you told us about build 4 and takes three more items off the list.

The tower doors are bigger, and you cannot get stuck in them any more. You said the entrance was a bit small and that you could get stuck. Both were real. The mouth of each midfield tower is now 8 m wide and 6 m tall - a door you jet through across a slope, not a door you walk up to on the flat - and each tower has a proper doorstep cut into the hill, so the ground meets the sill the whole width of the opening instead of dropping away under one side. A heavy walks in from any line; a burning heavy clears the lintel with room to spare.

The towers look like concrete now, not a black slab. The outside has its storeys, buttresses and a head you can read from 200 m; the shaft up the middle is lit and open onto every floor instead of a sealed black tube; the rooms have an interior finish.

The two smaller towers and the five spires are rebuilt. The last jagged shapes in the icefield's midfield are gone: the two small inventory towers are buildings with a door, a climb and the station on the deck, and the five tall spires are proper landmarks you can see from across the map and collide with as the shape you see.

The base's power is alive. The sensor dish turns. The generator's core spins and hums. Cut the power and they coast to a stop; kill the generator and they die with it; repair it and they start again.

Known problems in this build.

  • Base turrets no longer flash their muzzle while idle; tell us if you see it again.
  • On Proving Ground the bots' generator-room play is rougher than on the icefield.
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • The icefield's base on two floors, with force fields and doors after it.
  • The weapons, one at a time: model, first person, effects, sound.
  • The inventory station: the screen, the loadout, the sound of it.
  • Quick chat and the voice packs.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

1 line of internal detail omitted from this note.

Dev notes · 2026-10-05

PTR, October 5 — the icefield's towers, and base guns you can read

PTR: the icefield's towers, and base guns you can read. Two things from the top of the list.

Base guns you can tell apart. One turret body, six barrels you can name at 20 m and at 100 m - the point defence, the plasma, the flak, the mortar, the seeker and the siphon. The body tracks what it aims at, recoils when it fires, goes dark with the generators and comes back with them, and dies as a wreck you can read from 100 m. Each barrel has its own sound.

Known problems in this build.

  • The two smaller towers and the spires are still the old jagged shapes; they are in the next build.
  • On Proving Ground the bots' generator-room play is rougher than on the icefield.
  • The four older maps keep their old bases and stepped edges.

What we are building next, in this order.

  • The two smaller towers and the five spires, rebuilt.
  • The sensor dishes that turn and the generators that spin and hum.
  • An announcer for the flag and the generators.
  • The icefield's base on two floors, with force fields and doors after it.
  • The weapons, one at a time: model, first person, effects, sound.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

1 line of internal detail omitted from this note.

Dev notes · 2026-10-03

PTR, October 3 — both sides can take the flag, and a spawn shield

PTR: both sides can take the flag, and you get two seconds when you appear. Yesterday's build put the new bases on the icefield and Proving Ground. This one is the list we said came next, the fix for the worst thing in that build, and the first item from the next list.

The icefield's base guns have moved. All four now stand on the back edge of the deck, over the plaza and the ramp, where they are for what they were always meant for: a vehicle, or a heavy frame that stops in front of them. None of them looks down the approach to the flag any more. The radar and the command mast stand on the two towers.

Bots on the icefield. One side's runner used to die on the way in, every time, and never touched the flag. Both sides now reach it, take it and bring it home. Their runner also knows how to get up the tall face under the deck, and a bot carrying the flag no longer sails off the back of the base into the plaza.

Spawn shield. For two seconds after you appear you take no damage. It ends the moment you fire, throw, deploy, light a pack or touch a flag. Your body carries a cold tint while it is up and the HUD says SPAWN SHIELD. A blast still shoves you; base guns and mines still fire at you - they just do not hurt.

Bots go inside a base. A raider walks in for the generator room once the guns are down, and a defender goes in, holds it and repairs the generator. In yesterday's build that fight was yours alone.

Walls. Your body stops at a wall at its shoulders, not at the middle of its feet. No more standing half inside one.

The look. The ground an icefield base stands on is a paved plaza, with kerbs and lights on the ramp. The ring round the sun on the cloudy maps is gone.

Proving Ground has no vehicles. It is the map for fighting on foot. The icefield keeps its vehicles and is where the rest of them will arrive.

Known problems in this build.

  • On Proving Ground the bots' generator-room play is rougher than on the icefield; it is next.
  • The four older maps still have the stepped ramp edges and their old bases.

What we are building next, in this order.

  • The icefield's midfield towers, rebuilt: walk in, climb the shaft, the sensor dish and the inventory inside; the jagged edges go.
  • The base turrets, whole: five barrels you can tell apart, bodies that track and recoil.
  • Base interiors that are lit like a place: lamps that cast shadows, stations and generators in their own pools of light.
  • Duel: one against one on Proving Ground, first to 15 or ten minutes.
  • An announcer for the flag and the generators, and the quick-chat menu.
  • A vehicle bay at the icefield's base.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

Dev notes · 2026-10-02

PTR, October 2 — bases you can walk into

PTR: bases you can walk into. The two maps we play most, the icefield and Proving Ground, have new bases at the size this game is meant to be played at. The other four maps keep the bases they had until each new one is placed without changing how its flag is reached.

What a base is now.

  • A hall fifty metres long, with two doors a heavy frame walks through with room to spare.
  • Four inventory stations on one wall of the hall, lit, in view from both doors. One more in the generator room and one on the roof by the flag.
  • The generators are inside the base, in a room of their own behind the hall. A shell on the roof no longer reaches them: to cut the power you go in.
  • Flat walls and flat ramps. The stair-step edges are gone on these two maps.
  • Every spawn point is checked for room to stand before you are put there, so you no longer start inside a wall.

The two maps. On the icefield the base is the block you know with a hall and a generator room cut into it. Its doors and one long ramp are on the side away from the enemy, and the flag is still on the deck between two towers. On Proving Ground the base stands seventy metres behind the flag, and the flag stays on its knoll inside a walled court.

The launcher is new too (1.2.0). These notes are a feed you can scroll, and the build you are about to play is named above Play. It updates itself the next time you open it.

Bots. They know the new buildings: the ramp, the doors, the walled court, which stations they can reach. They do not go inside a base to fight for the generators yet. For now that is yours to do.

Known problems in this build.

  • A bot ordered into the hall may walk up the ramp instead.
  • On the icefield one side's bots rarely get the flag off the other side's deck: a base gun kills their runner on the way in.
  • The four older maps still have the stepped ramp edges.

What we are building next, in this order.

  • Your body stops at a wall at its shoulders, not at the middle of its feet. No more standing half inside one.
  • The ground an icefield base stands on: a paved plaza, kerbs and lights on the ramp.
  • The ring round the sun on the cloudy maps is fixed.
  • New seats for the icefield's base guns on the new deck, so both sides can reach a flag.
  • Bots that walk into a base: a raider for the generator room and a defender who holds it.
  • Duel: one against one on Proving Ground, first to 15 or ten minutes.
  • New bases for the other four maps, one map at a time.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: press SEND LOGS in the launcher and send Chris the file.

Dev notes · 2026-09-29

PTR, September 29 — the studio's fifth batch

PTR: batch five. Twenty-one studio branches, one label, qualified the new way: a clean full sweep on the exact commit, then TITAN against the lab, then the channel. Live is untouched.

A first vehicle is in the code. The Razor, a light two-seat hover, exists in the simulation, on the wire and in the replay - not on any map yet. It is the prototype the vehicle work builds on once humans have played the PTR; it changed the wire version, so an old client will be told to update.

Two humans at once. The harness can now drive two real clients on one PTR server, which is how the first two-player match will be measured before friends are invited. The first run of that path found that every human join sends one packet over the MTU; that is the next fix.

Under the hood. Every windowed test runs with the warm-up off on the software rasteriser (the sweep is honest about wall-clock again), the HUD's station glyph is measured on a HUD of its own, the session report keeps the destroyer credit for humans, the map design doc reads the mission's own Glide lines, and the 32 ms tick stays: moving to 60 Hz was priced and the price is not worth it yet.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

2 lines of internal detail omitted from this note.

Dev notes · 2026-09-27

PTR, September 27, second push — the studio's first batch

PTR moved again today. A second build is on the PTR channel, the first one carrying work from the new studio team: four specialist agents (production, gameplay, art, engine) working tickets off a board, handing finished branches to the captain, who reads, merges, sweeps and ships. Live is untouched.

The icefield looks like its concept now. The big map's light was re-tuned against its painting: the fog takes the sky's colour instead of a grey of its own, the key light is up a third, the snow is a touch darker so shapes read, rock shows through on every steep face, and the daytime gas giant is gone from the sky. Side by side with last week's build the difference is the mid-distance: ridges have faces now.

The server got 40% cheaper on the icefield. Every hot loop in the match sim (rounds in flight, the sensor net, item pickups, station walks, the void boxes under the bases) now skips work it could prove pointless before doing it. The icefield's tick went from 4.4 ms to 2.7 ms with 32 players. We put the batch in front of three independent code readers with one question: can any of these shortcuts change an answer, a hit, a take, a detection, and not just its cost. The verdict was no, with the proof written down, and the recorded match replays hash-identical before and after.

Under the hood. A stale terrain bound that two of our own test rigs tripped over is fixed; the plate suites that took pictures of nothing now fail when they render nothing; the sweep grew nine suites.

In test now, next push: the icefield's field of rock fins as real geometry (the lanes untouched, the base-to-base route the same length to the metre); the Strider's production body; the first hero weapon model, the Ion Repeater, plus a fix that had the three existing hero weapons showing a quarter of their texture; the client frame trimmed (the HUD and the base status line were the two biggest costs); and a fix for a long-standing one where a human player's kills could be misread as a base gun's. After that, a look pass per map against its concept plate, and concept plates for the three maps that never had one.

The rotation. PTR runs the icefield and Proving Ground. Live keeps its four-map rotation.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

Dev notes · 2026-09-27

PTR rollback, September 27 evening

PTR is back on this morning's second build. The third build of the day passed every local check on the test rig but, on the remote run against the real server, reconnected once during the switch to the icefield and played rougher after it. We have the trace and a ticket; the earlier build had a clean remote run, so it is the one on the channel tonight. Nothing you need to do: the launcher follows the channel.

What we changed about how we ship. Four pushes in one day was too many. From here the PTR gets at most one build a day, and only one that has passed the full sweep and a clean remote run through a map rotation. The studio keeps working at the same pace; its work waits in the integration lane until it earns the channel.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

Dev notes · 2026-09-27

PTR, September 27, fourth push — the studio's third batch

PTR, fourth push today. The studio's third batch, seventeen branches, is on the PTR channel. Live is untouched.

Three maps got their light. Echo Ridge had been wearing Ascent Prime's weather: a warm sun and a green valley on a map whose painting is a cold overcast bowl of wet black rock. It has its own sky now. Ascent Prime was measured against its own render for the first time and its haze goes blue with distance instead of white. The icefield got its second pass: the fins from the last push are now lit the way the painting is lit, from the angle of the sun and the depth of the air, not from painting the rock darker.

Nine more hero weapons. Repair Gun, Siphon, Range Finder, Grenade Launcher, Arc Prod, Spotter, Plasma Thrower, Gravity Mortar and Seeker Launcher all have modelled first-person bodies with distance meshes. Every weapon in the armoury now has one. Along the way the generator that builds them turned out to wind a third of its faces inside out; fixed for all of them.

The icefield's ground was drawn five times a frame. The map's ground was a single mesh, so the renderer could never cull any of it, and the sun's four shadow passes each drew all 717,000 triangles again. It is chunks now. That was most of the "three times any other map" cost.

The client, four fixes you will feel. Leaving a PTR game no longer offers you the Live address; a join that never gets an answer now times out with a reason instead of sitting forever; a stalled server is noticed by the client in three seconds instead of up to thirty; and a map switch now re-dresses the world and clears the HUD, so no more old-map markers, no phantom health flash on respawn, and a rejoin no longer eats the kit you had queued.

Under the hood. The weapon warm-up at startup had been culled for weeks and compiled nothing, so first sight of a weapon stalled; the bots' flag grab no longer runs a blocking cross-map search; the plate suites crop in the right coordinate space and pin their window; the icefield's lanes are measured by the sweep at last.

Waiting on Chris. Concept boards for Tallow Reach, Proving Ground and Skerry Yard are in front of him; their look passes start on his yes. The Ranger's production body is built and waits on a fix to the friend-or-foe gate that cannot see hue on a dark pixel.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

1 line of internal detail omitted from this note.

Dev notes · 2026-09-27

PTR, September 27, third push — the studio's second batch

PTR, third push of the day. The studio's second batch is on the PTR channel. Live is untouched.

The icefield has its rock fins. The concept's field of rock teeth is in the map as real geometry: twenty-metre teeth on the plain, forty-metre blades on the flanks, seventy-metre ranks on the high ground. The lanes run between the clusters, never through them. We checked the things that matter before shipping it: the base-to-base route is the same length to the metre, the run times are identical, the bots' routes, footing and defences all pass, and the sim tick moved 1.5 to 1.6 ms. The landform reads now. What it did not do is darken the frame the way the painting is dark; that turns out to be the light, not the shape, and it is the next art ticket.

The Strider has a production body. The light frame gets the same treatment the Bulwark got last week: a real body from its concept plate, on the shipped skeleton, with a distance mesh that wears the same textures. Fingers, exhausts and paint get the same polish pass the Bulwark's are waiting on.

The first hero weapon, and a fix for the three you already had. The Ion Repeater, the gun every respawn hands you, has a modelled body now. And the three hero weapons that shipped on September 4 turned out to be showing a quarter of their own texture: they were authored at 2048 but imported at 1024, uncompressed. They now ship at full resolution and compressed, four times the detail for a fifth less memory.

The client frame is lighter. Two cuts: the base status line on the HUD was recomputed every frame from a walk of every prop on the map (2.3 ms on the icefield; now it updates when the snapshot does), and the HUD itself stopped redrawing every element every frame. Bodies outside your view are posed every fourth frame; bodies in view are exactly as before.

One long-standing bug. A human player's network id could collide with the ids the base guns use, so a defender who repaired what you broke was never paid for it, your kills could count towards a base gun's tally, and one pedestal gun could not hit you at all. Fixed; three bugs under one line.

Under the hood. Twenty plate suites share one format-safe image blit (the old one silently drew nothing on the card); a client-frame profiler and bench with a committed baseline; the movement test's icefield line moved off a fin.

Next: a look pass per map against its concept plate, concept plates for the three maps that never had one, and the base buildings themselves as production architecture. Then vehicles.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

Dev notes · 2026-09-27

PTR, September 27

PTR moved today. The build with the new Bulwark bodies is on the PTR channel. Live is untouched.

The Bulwark, in production. The heavy frame has real bodies now, four of them, and the pack you wear picks the one you get: Assault by default, Guard with the Barrier Pack, Siege with the Munitions Pack, Support with the Mender Pack. Friendlies read blue and enemies read red whichever team you are on. The jets moved to the boots, the belt and the back of the body. Hitboxes, stats and the network format are unchanged: this is how the Bulwark looks, not how it plays.

  • Textures. The first cut of the bodies carried 2.4 GB of textures, which no card should be asked to hold for one frame. They ship compressed at 2048 now, 85 MB for all four variants and their distance meshes, with no visible change at the studio lens.
  • Still to polish: open fingers under the weapon grips, the exhaust particles, the daylight tint of the paint.

The rotation. PTR runs the big icefield map and Proving Ground: one big map, one dueling arena. Live keeps its four-map rotation.

The review. We did a full read of the code against the plan it started from, six in-house readers and two independent ones. The verdict, honestly: the movement and combat phases are done. The core prototype phase has all its content (BreakFlag, three frames, thirteen weapons, six packs, eleven deployables, base defence, six maps, bots, the launcher, the backend) but its exit test, an 8-a-side session with humans that produces real tactics, has not been run; the records hold no match with more than one human in it. One blocker was found (the host rebuilding its defence visuals every frame) and nineteen majors, most of them things a human session would hit before a bot does. Those are the fix list before that session.

Next: the first session with two humans on the PTR, with the evidence kept; the "before humans" fix lane; then vehicles, the next big push, with the bots taught to use them and to counter them.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

Dev notes · 2026-09-13

PTR, September 12–13

PTR moved tonight. Two rounds of work since the last dev notes, both on the PTR only — Live is untouched.

Round 2 (Sept 13): what you told us after playing.

  • Flag timer. A flag nobody holds — not a player, not its stand — comes home after 60 seconds, wherever it lies. A flag that ends up under the ground surfaces first.
  • The "flag stuck in the mountain." That was a bot carrier hiding on its own flag-room floor, under the deck, waiting out a standoff for up to three minutes. Carriers now leave any room by the door and wait the standoff out on their stand, in the open.
  • Inventory stations you can see. Every station on every map is drawn now (several were invisible boxes with a signpost). Each has a large ground ring in your team's colour that refits you when you stand in it, a glyph on the radar, and Tallow, Ascent and Proving got a field pad near each base so the first walk is seconds, not twenty.
  • Respawn kit. Before you pick a loadout and after every respawn you carry a cell pack, the Impulse Launcher with 10 rounds and the Vector Cannon with 50 rounds. Anything more, hit a station.
  • Repair gun. If you drew the repair gun with F, a number key could fail to switch you off it on a slow-frame client. Fixed.

Playing it: the launcher's PTR channel updates itself. Bugs, hangs, a stale build: send Chris your launcher.log.

3 lines of internal detail omitted from this note.

Gameplay experiments

EXP-39 · 2026-10-18

bot_aim_check static hit-rate is FOUND until MAC-612 (MAC-528 muzzle spread) — CLOSED 2026-10-18

Closed 2026-10-18 by MAC-612, the ticket this record was waiting for. The three static-rate rows and both low-skill static COLUMN rows are check(...) again, with no EXP prefix, and they are green: static 85 % at skill 1.0 and 77 % at 0.5 against the capture's 81 % / 79 %, and 74 % / 81 % at skill 0.0 / 0.25 inside its static COLUMN [45, 100]. AIM_STATIC_DEG moved 0.45 → 0.99 deg, re-derived from the capture's own i33 shot lines rather than from the sensitivity table MAC-528's spread made impossible to invert, and the static case is now read at the 22.1 m its rounds were measured to leave at instead of the 50 m the probe placed the target at. This is the restore the Non-precedent paragraph below names, not a new widening. The record stays as the evidence. MAC-613. Stamp sweep #9 on lane-b13 (2026-10-18) went red on game/tests/bot_aim_check.gd's three STATIC HIT-RATE rows: static at skill 1.0 (43 % against the capture's 81 %, band [60, 100]), static at skill 0.5 (40 % against 79 %, same band), static at skill 0.25 (43 % against the capture's static COLUMN [45, 100], its own row 54 %). The low-skill skill-0.0 COLUMN row is the same check and is held with them. The ticket asked for EXP-38. That id is already MAC-598's weapon-draw ruling on this tip. This record is EXP-39. What is held. The static hit-rate checks at skill 1.0 and 0.5, and both low-skill static COLUMN rows (skill 0.0 and 0.25), report as FOUND. Mover rows, direction rows, and model rows stay checks. AIM_STATIC_DEG does not move in this record. Class. TEST CALIBRATION. Not a capture waiver. Not a physics change. Not a bot-aim change. Independent warrant. The stamp reds, MAC-528's spread, the suite header's own sensitivity table (0.45 deg was the capture's 79-81 % under the tighter spread; 1.00 deg hits 42 %), and MAC-612 filed to Gameplay at critical. Non-precedent. No other bot_aim_check row becomes a FOUND under this id. The direction-row reds of the same stamp are already fixed on the lane (b51be949: the bands read the pi). A later static-rate green after MAC-612 is MAC-612's restore, not a new widening. Revert / restore. The three static-rate sites come back as check(...) with the same text, no EXP prefix. That is the day MAC-612 lands. Expiry does not itself restore them. Expiry. The day MAC-612 lands, or 2026-11-01, whichever first.

10 lines of internal measurement detail omitted from this entry.

EXP-37 · 2026-10-17

OWNER RULING · BreakFlag stalemate carrier reveal stays unbuilt until week 10 — RE-SCOPED 2026-10-20

Re-scoped 2026-10-20 on origin/lane-b13-label4 (9ddd4e13). This record answers audit A24, and A24 is now the carrier reveal only. MAC-608 (label 3) shipped the other half of R1.6: stalemate_ms counts while both flags are away, and a return from STALEMATE_MS (60 s) pays the player 15 (audit B8 MATCH, scoring_check F14–F22). The 45 s standoff_return (EXP-15) is still default-on and is not this record. Ours, on this tip (mach_world.gd). stalemate_ms and SCORE_PER_STALEMATE_RETURN 15 are in (MAC-608). standoff_return defaults true. STANDOFF_RETURN_MS 45000. STANDOFF_HOME_M 30. tests/standoff_check.gd is the gate of the auto-return, not of the reveal. There is no carrier reveal. Week. Week 10. Gameplay turns standoff_return off and builds the reveal. Spec flips the register's Flag stalemate row. The plan's week-10 gate is flag_check green with standoff_return off. Class. OWNER RULING. Not TEST CALIBRATION. No sim change, no bar, no suite in this record. EXP-15 stays the measurement of why the auto-return exists. The named bot roles in EXP-15 are not this record. The scoring half is B8 / MAC-608 and is not this record. Revert. Restoring a standing auto-return after week 10 is a new record after the game is operational. EXP-15 does not survive the week-10 reveal as the default. Expiry. 2026-12-27 (the last day of week 10). If the reveal has not shipped by then, this record stops authorising the auto-return as the standing default, and the row needs a new sentence. Expiry does not itself turn standoff_return off. Turning it off is the week-10 label.

4 lines of internal measurement detail omitted from this entry.

EXP-28 · 2026-10-12

Vector Cannon heat and overheat — CLOSED 2026-10-20

Originally written on studio/MAC-515-20261012-register-after-batches-24-49 (faaa0e9e) and skipped on this parent. Brought here to close it against labels 2–4. Expiry. None. Closed.

2 lines of internal measurement detail omitted from this entry.

EXP-33 · 2026-10-16

An R7 return the window observed is a measurement (TEST CALIBRATION)

Spec & Balance, MAC-559, on the measurement MAC-554 took of MAC-556 ⊕ MAC-557 composed. This is the population EXP-32 reserved and did not authorise: a real leaving-the-room return whose release is after 106 s. No bar moves. One condition in game/tests/bots_defence_check.gd does. Spec does not edit the suite and did not start a second godot. MAC-554 holds the tree. The warrant is that ticket's logs and the one-line diff between merge/composed.gd and merge/composed_plus_probe.gd, read here. What the record states. - A return the window observed (back_s >= 0) is judged against the bar that map already has, even when release_s + ret_bar runs past W7_RAID_S + W7_QUIET_S (175). The per-draw check is the existing one: back_s <= ret_bar. - A return the window did not observe (back_s < 0) with release_s + ret_bar > 175 stays unjudged, printed [REPORTED], and does not enter ret_judged. That is MAC-334's rule, kept: a clock the window does not contain is not a measurement, because a never cannot be told from a late. Choice. Judge the observed return. The condition Gameplay lands, and the only executable line: ``gdscript # a return the window OBSERVED is a measurement; only an unobserved one is unjudged (EXP-33) if float(on["back_s"]) < 0.0 and float(on["release_s"]) + ret_bar > W7_RAID_S + W7_QUIET_S: ` The print under that if stays. After this conjunct its back_s >= 0 format arm is unreachable, which is the point: the arm only runs for an unobserved return, and that print already says never. Do not retarget the print into a pass. Three refusals sit next to the choice. - Moving W7_QUIET_S, or any other constant above. A release at 153.792 s plus a 69 s bar needs a scene past 222 s in order to time a walk the log already finished in 3.104 s. EXP-32 refused 184 s for the same reason. Still refused. - Moving 2 or 69. There is nothing to calibrate. The observed returns are 3.104 / 4.992 / 0.0 against a bar of 69. Cause. The cliff and the observation are different questions, and the old if asked only the first. The scene runs ticks := int((W7_RAID_S + W7_QUIET_S) * TICKS_PER_S) and stamps back_tick only inside that loop, when the body is alive, d_stand <= W7_HOME_M, and d_stand < vault_gap of the lent room (vault_gap is 0 inside the box). So an observed back_s satisfies release_s + back_s <= 175. Whenever that draw is also past the cliff (release_s + ret_bar > 175), the remainder is shorter than the bar, and therefore 0 <= back_s < ret_bar. An observed return in the short remainder is inside the bar by arithmetic. The per-draw check cannot fail it, and it cannot launder an over-bar return into a pass: an over-bar return cannot be stamped before the window ends. What the floor still does. Two observed homes and one censored never judge as 2 of 2 and pass the floor of 2. A true never that the window *did* contain is already a note under EXP-31, and 2 homes plus that note also pass. A draw that would have been over 69 s, but was not observed, stays unjudged: MAC-334 already forbids failing that clock. One observed home and two censored draws judge as 1, and coverage still fails. An empty judged set still fails. The floor is not dropped to 1. The diff of the two suite files is that one condition. Nothing else. Proving on the composed tree, without the conjunct (bdc_proving_composed.log): returns 22.272 / 5.088 / 24.288 against the unmoved 60 s bar, suite ok. Seed 1 is released at 116 s with back_s 22.272 and is [REPORTED] only because 116 + 60 > 175. The window contained the return (116 + 22.272 = 138.272). Under this record that draw becomes a judged pass inside 60 s. Seeds 2 and 3 are already judged (releases 93 s and 75 s). Proving has no coverage aggregate. This was not re-run with the conjunct. The expected reading is the arithmetic above. Fix. Gameplay, on the MAC-556 ⊕ MAC-557 compose, the one condition above, and this record appended to docs/GAMEPLAY_EXPERIMENTS.md if it is not already on the tree they cut from. No sim file. No brain file. No constant. Gate. One godot at a time, the suite's own 3 draws: ` godot --headless --path game -s res://tests/bots_defence_check.gd -- --map=proving --seed=1 ` Class. TEST CALIBRATION. Not a capture waiver. Not a physics change. Not a bot rule. The doctrine stays. The floors stay. The home predicate stays. The instrument was discarding a clock the window had already contained. Non-precedent. One conjunct. An unobserved return past the cliff does not become a note or a pass. A later single observation does not drop W7_CITA_RETURN_MIN from 2 to 1. 69 does not authorise 70, and 60 does not move because proving seed 1 is now judged. W7_QUIET_S does not grow. W7_HOME_M, W7_CONTEST_S, W7_ROOF_MIN, W7_RAID_S do not move. EXP-32's home predicate and stint clock do not move. EXP-31's R2 seed count does not move. Expiry. Chris owns the promotion of this conjunct to standing spec. If this record is not promoted by 2026-11-16, it stops authorising any further change to the per-draw skip. Expiry does not restore the old if`. Restoring it is the revert, and it is Chris's to take. EXP-31's expiry and EXP-32's expiry are unchanged, and this record does not extend them.

25 lines of internal measurement detail omitted from this entry.

EXP-36 · 2026-10-16

OWNER RULING · Generator, inventory station, team damage off

This record supersedes the 2026-09-06 generator and station directive (no shields, generator disabled 1.75 / destroyed 5.0, station disabled 1.3 / destroyed 2.0, team damage on). The citations that kept those numbers are the DEFENCE_DESIGN lines the parity table named for Generator and BaseInventory. Damage scales and shield scales are the same on both objects (StaticShapeDamageProfile). The full type table is the capture file. The rows that are not 1.0 / 1.0: Impulse Launcher 1.15 / 1.0, Grenade 1.2 / 1.2, Missile and Mortar 2.0 / 3.0, Plasma 1.25 / 1.5, Impact 1.25 / 1.25, Explosion 1.0 / 2.0, Blaster shield 0.8, ELF 0 damage and shield 1.0, SatchelCharge 4.0 / 6.0, Lightning 5.0 / 5.0, BellyTurret 1.2 / 1.5, ShrikeBlaster 2.0 / 2.0, TankVectorCannon shield 1.5, TankMortar 2.0 / 3.0. The same file is what base_object_scales_check reads, row by row. Friendly fire, team damage off (the oracle's $Host::TeamDamageOn 0): a friendly hit is capped at disabled − 0.05. On the generator the capture's three samples are a half point, then the cap, then the cap again. With team damage on, the same hit goes through to Destroyed. The cap is the friendly hit only. Deployed objects are exempt. The sample values are in the capture file. Power, in the same capture: the station lost power in the same 100 ms sample the last generator went Disabled, and regained it in the same sample the generator went back to Enabled. There is no restore delay past the threshold. Ours, on this tip (game/data/tuning/items.json). Generator: maxDamage 5.0, disabledLevel 1.75, destroyedLevel 5.0, isShielded false. Base BaseInventory: maxDamage 2.0, disabledLevel 1.3, destroyedLevel 2.0, isShielded false. Team damage default ON (game/scripts/session_rules.gd, team_damage := true). Class. OWNER RULING. Not TEST CALIBRATION. No sim change, no bar, no suite in this record. Revert. Restoring the 2026-09-06 hulls, the absent shields, or team damage default on is a new record after the game is operational. The 2026-09-06 directive does not survive this one. Expiry. None on a date. Chris owns the next change, after the game is operational. The mine row of that scale table is one half on damage and one half on shield. It is the same capture file, and it is not a limpet-damage ruling.

8 lines of internal measurement detail omitted from this entry.

EXP-35 · 2026-10-16

OWNER RULING · Shock Shock Lance front and rear

This record supersedes the 2026-08-31 keep. That keep is docs/SPEC_WEAPONS_2026-10-05.md (damage 0.45 / ×3, ours 0.10 / 1.80, "decision 7, owner 2026-08-31 skill weapon. Keep") and parity-audit B25 (docs/PARITY_AUDIT_COMBAT_MOVEMENT.md: front 0.10 / rear 1.80, "Rear: owner rule 2026-08-31"). Ours, on this tip. game/data/tuning/weapons.json ArcProd.frontDamage 0.10, ArcProd.rearDamage 1.80. The sim applies those two fields (mach_world.gd, the melee branch). The projectile block's directDamage is already 0.45 and is not the number the hit reads. Week 4 edits frontDamage and rearDamage. Class. OWNER RULING. Not TEST CALIBRATION. No sim change, no bar, no suite in this record. Reach 16 m, the rear cone, and the muzzle point are not this record. Non-precedent. Weapon switch timing is not this record. The Vector Cannon spread is week 4's other row and has its own capture. Revert. Restoring 0.10 / 1.80 is a new record after the game is operational. The 2026-08-31 sentence does not survive this one. Expiry. None on a date. Chris owns the next change, after the game is operational.

3 lines of internal measurement detail omitted from this entry.

EXP-34 · 2026-10-16

OWNER RULING · frame pools, the Bulwark jet cap, and the hit boxes

Ours, on this tip (game/data/tuning/frames.json). Ranger maxDamage 1.2. Bulwark maxDamage 1.7. Bulwark maxJetForwardSpeed 17. Ranger box 1.595 × 1.595 × 2.64. Bulwark box 1.875 × 1.875 × 2.99. Strider box 1.2 × 1.2 × 2.3, maxDamage 0.66. Class. OWNER RULING. Not TEST CALIBRATION. No sim change, no bar, no suite in this record. Revert. Restoring 1.2 / 1.7 / 17 and the grown boxes is a new record after the game is operational, with our metrics. It is not a silent return to EXP-13. Expiry. None on a date. Chris owns the next change, and it is a recorded one, after the game is operational.

11 lines of internal measurement detail omitted from this entry.

EXP-30 · 2026-10-12

Proving's W piece-down gate is 2 of 3 seeds, per shape (TEST CALIBRATION)

The id skips EXP-28 and EXP-29. Those records are on studio/MAC-515-20261012-register-after-batches-24-49 (faaa0e9e): Vector Cannon heat, and Veil/Static jam. They are not this ruling. The captain named EXP-28 as the next free id on this parent, which ends at EXP-27. This record is the ruling on MAC-521, dated from the captain's direction of 2026-10-11 19:45. What the record states. - The W row's down >= 1 is a BEATABLE proxy, not a measurement. The header above _wave says so: the defence has to be beatable, and on a 6-turret map a plan that loses one turret instead of three is a 17% strip a percentage floor would call a failure. - The instrument is 3 seeds × 2 shapes on one map (proving). - Under the reference's touch, seed 3 PLAN reads 0 of 6 while RING reads 2 of 6 on every arm. - R6b's hull row moved the other way under the same change: a printed 120 s fail became 119 s. That is EXP-27's one-tick epsilon. This record does not touch RAID6_HURT_S. Choice. (a) retarget the floor. The captain refused (b): a bot rule written to lift a proxy floor is tuning bots to a bar. (c) is refused here too. A waiver would leave the per-seed floor asserted and call the row chaos. EXP-27 held four bars that still had an owner. This row does not. Cause. Item 24 (cdb44193, parent of this commit) makes the flag touch the reference's box overlap. On proving, plan down moves by one turret on every seed: 3, 2, 1 with the 4 m radius becomes 2, 1, 0 with the box. Seed 3 is then 0 of 6 against a floor of 1. That is two of the three seeds still taking a piece down (2 and 1), and one seed taking none. MAC-495's four arms, --only=W --map=proving, logs in that worktree's .studio/store/MAC-495/: The keep-out band and the route screen are not the cause. The touch is, and the touch is the reference's, so it stays. With pickupRadius 4 an offence bot passing the stand was served the flag. With the box it is not, so the wave's engagement changes. This tree's own W block reproduced the plan row: 2, 1, 0, with seed 3 at 2 rounds, 45 turret shots, closest 3 m, 4 of 4 alive. The ring on the same three seeds is 2, 2, 2. (b) is the wrong tool. A rule whose effect is to serve the flag at 4 m, or to fight as though it had been served, puts the non-reference touch back. The wave on the reference touch still arrives (3 m against a 60 m shut-out), still fires, and the net still fires 45 shots. Nothing in the pinned arms is a path the bots take wrongly. (c) is the wrong tool. EXP-27 did not waive a red to paint a suite green. It held four bars and named what each remaining red waits on. R6b's hull line moving from a printed 120 s fail to 119 s under the same touch is that one-tick epsilon landing on the green side of a bar EXP-27 already refused to move. It is a different row. It does not make W noise, and a waived W has nothing left to wait on: the touch is the reference, and the band and the screen have been excluded. Fix. game/tests/bots_defence_check.gd only, plus this record. Where it actually landed (MAC-523, 2026-10-12). The ruling named studio/MAC-521-20261012-proving-w-floor, parent cdb44193. That branch carries NO commit of its own: origin/studio/MAC-521-20261012-proving-w-floor is still cdb44193 itself, and MAC-521's .studio/COMMIT_MSG never reached a push, so the gate existed nowhere in git. MAC-523 re-wrote the predicate from this record (which is the specification, and is complete) onto studio/MAC-523-re-run-bots_defence_check-proving-x3-under-exp-30-and-set-mac-495-ready, cut from 475003a5 and materialised byte-identical to MAC-495's head 4a997421 (all 7,437 tracked files checked by blob hash). So the one branch to merge is MAC-523's: it is MAC-495's item 24 whole, plus this gate, plus the stamp below. MAC-521's branch is a no-op and can be dropped. WAVE_PROVING_SEEDS_MIN is 2: per shape, at least 2 of 3 seeds take >= 1 down. A turret total is not the gate. 2 + 0 + 0 passes a total of 2 and fails this floor; the measured 2 + 1 + 0 passes it (two seeds) and the ring's 2 + 2 + 2 passes it (three). The per-seed piece check stays for every map that is not proving, and for a proving run that does not use the suite's own 3 draws. No sim file moves. No band moves. Before / after. Same tree, same touch. Before is MAC-495's W_mine.log (the assertion as the lane wrote it). The downs below are this tree's W block, log .studio/store/MAC-521/W_proving.log, run while the check was still the three-seed turret total. Those same downs are what the seed-count floor reads. The seed-count lines themselves were not re-executed in the heartbeat that aligned the predicate: other godot processes held the machine, and a second one was not started. MAC-495's proving ×3 is the run that stamps the new lines. `` godot --headless --path game -s res://tests/bots_defence_check.gd -- --only=W --map=proving --seed=1 ` Class. TEST CALIBRATION. Not a capture waiver. Not a physics change. Not a bot rule. The touch stays the reference's. The floor moved because a per-seed minimum of 1 on a 6-turret scene was asserting inside the swing the block's own header already called a proxy. Revert. Delete WAVE_PROVING_SEEDS_MIN and the proving branch of the piece check, and restore per-seed plan["down"] >= 1` on proving. Seed 3 prints FAIL, 0 of 6, and the W block exits 1. That is the point of the revert. It is not taken in this record. Expiry. Chris owns the promotion of this floor to standing spec. If this record is not promoted by 2026-11-12, it stops authorising any further move of proving's W piece-down gate. Expiry does not itself restore the per-seed floor. Restoring it is the revert, and it is Chris's to take.

23 lines of internal measurement detail omitted from this entry.

EXP-27 · 2026-10-11

R6b's 120 s bar stays; the raider walks round a lip the burst cannot buy

The captain, 2026-10-11 21:42, ordered the design call on the evidence already in the log. MAC-440's split is that evidence. This record is the verdict. No constant below changes in the commit that carries it. Choice. (c) a bot rule, filed to Gameplay as [MAC-465](MAC-465). When the lip in front of the body is above that frame's standing burst, walk round now. Do not charge the climb tank and wait for the hatch. A lip the tank can buy (the 7 m shelf) still waits and climbs. Burst heights from bot_climb_tank_check on this tree (bot_brain.gd): Strider 31.09 m, Ranger 21.18 m, Bulwark 11.26 m standing. A Ranger's climb window is 9.28 s (297 ticks: one charge of the tank, 7.34 s, plus 2 s). MAC-440's go-around heading already exists and fires only after that window. On origin/lane-b13 with the muzzle table (9bc78af3, ancestor of 06a6d74a), MAC-480 records Proving R6b's walk-in green and the hull line red at a printed 120 s. The check is hurt_at <= int(120 * 31.25) and the message prints %.0f. Tick 3750 is 120.000 s and passes. Tick 3751 is 120.032 s, prints 120, and fails. That tick is the epsilon this record refuses to spend a bar on. The other clocks, same reasoning. A one-second miss, and a time printed by a solver that is about to change, are not a measured legal path. Fix commit. None for the constants: the four values in bots_defence_check.gd are byte-identical to origin/lane-b13 06a6d74a, and the comments at those lines name this record. _raid_drop is untouched. The rule's commit is MAC-465's, on origin/lane-b13 06a6d74a or later. Gate, on that lane: bots_defence_check whole on both maps, bot_match 3 seeds both maps, warden_route_check both. No bar in those suites moves. MAC-480 owns the firing solution in bot_brain.gd; MAC-465 owns the climb gate and does not take the solver hunk. The post flip (27.8 s, MAC-470) is the other mechanism and stays out of MAC-465. Before / after. This commit does not re-run the suite. Before, on the anatomy the captain named: walk-in and hull 121 s against 120, with 33 s standing. After this commit: the same constants, so the same suite still reads the same. The after that the rule owes is a walk inside 88 s of that anatomy. MAC-465's own trace, already logged and not the number this bar is derived from, read 38.6 s dead and 82.4 s with the stands cut, on lane-b12's files. Class. BOT RULE. Not a test calibration. Not a capture waiver. Not a physics change. The bars did not move because a suite was red. Independent warrant. 88 s is MAC-440's counted stands subtracted from the composed walk, and the burst table is bot_climb_tank_check, which does not read the 120 s bar. The stands finish inside a window that was written as one charge of the tank. The warrant is not "the line was red at 121." Non-precedent. RAID6_IN_S, RAID6_HURT_S, W7_RETURN_S, RAID5_SETTLE_S, and ARRIVE_S do not move again without a new measured legal-path time and a new record. 121, a printed 120, 19.6, 19.7, 61, and 62 do not authorise a move. The same metric on the same map does not move to make a suite green. Revert. The constants have nowhere to go back to: they are the lane's. Reverting this commit restores EXP-25's hold and the comments. It does not change a timed value. Putting the hold back means blocking MAC-465 again. That revert is Chris's to take. Expiry. Chris owns the promotion of the round-over-climb rule to standing doctrine. If this record is not promoted by 2026-11-11, it stops authorising any bar move that cites it, and it stops authorising a further change to the climb gate without a new record. Expiry does not itself move a bar, and it does not delete a rule that has already landed.

16 lines of internal measurement detail omitted from this entry.

EXP-26 · 2026-10-11

replay_drive position bands, re-derived on the corrected harness (TEST CALIBRATION)

The engineer was told not to touch the bands. This commit is Spec & Balance. Test only: game/tests/replay_drive.gd. No steering rule. The Glide-steering commit 9f4f8702 was reverted by 48acc570 on origin/lane-b13, and the harness snap went with it. The snap in this commit is that revert's inverse, the 11-line hunk from studio MAC-401 9b5cc4be (blob 6b8997f9). replay_drive.gd at the parent of this commit is byte-identical to origin/lane-b13 48acc570. MachWorld.box_foot and contact_test do not differ between 19e04d89 and 48acc570. Old / new. Headroom stays 25 % of the base arm, which is what the suite header already says the cells are. Base arm means no steering rule, on the corrected harness. 7.5 * 1.25 = 9.375, written 9.38. 8.1 * 1.25 = 10.125, written 10.13. Class. TEST CALIBRATION — harness correction. Not a capture waiver. Not a physics change. The captures are unread. The steering rule stays reverted. Independent warrant. The suite's own header: the cells are the fit the port achieved, plus 25 % headroom, re-measured on this tree. The base arm's own numbers on the corrected harness are the fit. 25 % of those numbers is the table above. The warrant is not "Ranger was red." Non-precedent. These six cells do not move again without a new harness or physics cause and a new record. A later steering rule that improves the fit is a new cause. It may tighten a cell only by a new record. It may not cite this one. Expiry. Chris owns the promotion of these six cells to standing spec. If this record is not promoted by 2026-11-11, it stops authorising any further move of them. Expiry does not itself restore the old cells. Restoring them is the revert, and it is Chris's to take.

25 lines of internal measurement detail omitted from this entry.

EXP-25 · 2026-10-11

The raid's 120 s bar stays; the raider walks round a lip it cannot burst

Closed by EXP-27. The captain, 2026-10-11 21:42, closed the wait: MAC-440's anatomy is the split this entry was holding for, and the decision is a design call. The paragraphs below stay as the evidence. The verdict, the warrant, and the numbers the other bars wait for are EXP-27. RAID6_IN_S and RAID6_HURT_S stay 120. _raid_drop stays. [MAC-465](MAC-465) implements the rule.

Hypothesis
bots_defence_check R6b on Proving is 121 s against a 120 s bar because the composed route is longer (northern posts screened, the defenders' flag kept out) and because the raider stands 33 s charging climb tanks at three walls. The bar was authored before that doctrine existed. Either the bar moves (as EXP-24 did for M2), the drop's +30 deg bearing is reshaped, or the bot chooses round over climb when the lip is above the frame's burst.
Implementation
none in this ticket. RAID6_IN_S and RAID6_HURT_S stay 120. _raid_drop is unchanged. The rule is filed to Gameplay. Telemetry, all on the composed lane (origin/lane-b12 19e04d89 or its MAC-440/460 ancestors), MAC-440's six arms plus MAC-460. R6b's raider is a Ranger. The route answers the OPEN door on 231 of 231 thinks, closest approach 6 m. The walk loses 33 s in three ~8.5 s stands at wall_near (wall_ahead false, charging 14 of 82, honest, not a jam). MAC-445's toe: identical. Keep-out off: never (closest 25 m) — the keep-out HELPS. Wall-wait hatch doubled: bit-identical. ROUND_POST_STALL_S 6 → 3: never (92 m). Tank sized by the wall: 149 s, worse, reverted. Counting ticks alone, before the window was asked of the tank: never, 23 m. MAC-460 composed (window = one honest charge + 2 s, in sim ticks): still 121 s. 121 − 33 = 88 s. The 120 s bar contains an honest walk that does not stand. A Ranger's climb window is 9.28 s (bot_climb_tank_check: tank 60, charge 7.34 s + 2 s). Each 8.5 s stand is inside that window, so MAC-440's go-around heading never fires. The hatch is the wrong gate for a lip the burst cannot buy. What a burst buys, bot_climb_tank_check arm A (flat contact, hysteresis modelled, to the jet rule's 4-energy floor): Every frame buys the engineer's 7 m shelf. No heavy frame buys the defender's 12 m bank. The drop: _raid_drop searches the axis, then ±30, ±60 … ±150, first landing that clears stands 85 m behind the stand, so the axis is refused and +30 deg is the nearest legal bearing. That touch-grab, run_home). The other sign is the bearing that happens to pass. Reference: RB-I25G (oracle 2026-10-10, OracleFlat, Light): every bot that holds a target fires at 0, raiders do not stand at walls for 8 s. RB-I46BA_s1.meta.json) (a) kept its task and never touched the flag it passed within 7.6-15 m of (8/8 at skill 0.5 and 1.0; no playerTouchEnemyFlag, task stayed AIAttackObject on the weight-100000 trials), and (b) never entered the generator room from the stand-crossing approach (8/8, no generator damage inside 190 s) or from any of six bearings (12/12). On the bearing sweep it parks on the roof directly above the generator, 23.6-24.7 m away, with 70 m of its own path unused (getPathDistance still 69.8-71.0 m; bearing 0 seed 1 stalls on the ledge by the flag stand instead, 105 m of path left). The stand-crossing approach reaches that roof in 2 of 8 and stalls on the same ledge in the other 6. So the reference walk-in time is NONE and the 120 s bar is MACHBREAK's own standard: the raider that walks in at 120-121 s already beats the reference. Caveat: the map's own objective shops at an inventory station first (desiredEquipment ShieldPack, buyEquipmentSet HeavyAmmoSet); that step is skipped here. This makes the pending decision a design call, not a parity one. The ruling at the top of this entry stands: the bar stays until the 121 s split is logged. Result. Refused: - (a) the bar. RAID6_IN_S / RAID6_HURT_S 120 → 125. EXP-24 moved a physics floor because the restored face printed 11.3 against 12. This 1 s miss is 33 s of wall-standing. Widening the bar hides the stands. - (b) the scene. A drop on the base-to-base axis, or the bearing that passes. +30 deg is the nearest flag-clear bearing. The axis is the flag.
Decision
(c) a bot rule. Keep. When the lip in front of the body is above that frame's standing burst (31 / 21 / 11 m), walk round now. Do not charge the climb tank and wait for the hatch. MAC-440's go-around heading already exists and fires only after the hatch; the rule is to ask it before the charge, on a lip the tank cannot buy. A lip the tank can buy (the 7 m shelf) still waits and climbs. Same question, two other clocks, same answer. 1. warden_route_check apron. ARRIVE_S is 55 s. Measured, unchanged by every physics-batch lane: level — the fair scene, not a drop we can slide. 92 s to enter a room at the body's own level is a stall. ARRIVE_S stays 55. HOLD_FRAC stays 0.25. The same round-over-climb rule is the first lever. A leftover corridor-jamb miss (MAC-378, already reported on the deck arm) is a second bot rule, not a bar. for 88 m and 41 m of descent. 88 m in 60 s is 1.5 m/s — the bar is not tight. The window is W7_RAID_S + W7_QUIET_S = 175 s, so a release after 115 s cannot contain the bar; those draws are already [REPORTED] (MAC-334), and they always were: on build 6 every draw on both maps released at 122–150 s. MAC-380's faster mend made some releases fit, and those bodies then never broken. Do not widen 175 s (that would judge a walk that starts after the raid is over). Do not widen 60 s. The remaining defect is the home walk (MAC-384: origin snap, lookahead chords, jam hatch — jam is MAC-387, done). File the leftover with the same gate. Gate for the rule, on the composed lane: bots_defence_check whole on both maps, bot_match 3 seeds both maps, warden_route_check both. No bar in those suites moves. The gate waits on the split named at the top of this entry.

13 lines of internal measurement detail omitted from this entry.

EXP-24 · 2026-10-10

Proving M2 floor 12 → 10 (TEST CALIBRATION)

Rewritten 2026-10-11 into the captain's record form. The floor does not move again here. The body that landed the number is f6e7f2ec on studio/MAC-447-20261010-proving-m2-floor, taken onto the lane as 81588cfc. Fix commit. f6e7f2ec (MAC-447). movement_acceptance.gd only: proving m2_lo 12 → 10. No change to MachWorld.resolve_ground, MachMovement.step, SLOPE_DEG, or the glide line. MAC-445's terrain verdict stands. Before / after. Filed bare-sim on origin/lane-b12 73161c47 plus the MAC-435 fix. This heartbeat did not re-run it; the arithmetic is the one EXP-24 already checked, and the Art Director checked the restamped cells against the probe. 40 * cos(31°)² = 29.39. The restored arm leaves the face at 30.70, so the 9.30 is the entry shed of the documented face. 40 - 9.30 - 5 * 3.897 = 11.22 against the reported 11.30. The per-hop loss is the MAC-435 parity number (3.897 / 3.895 against the engine's 3.898 / 3.896 on the analytic 31.0295-degree plane). The two toe touches on the baked arm (3.57 then 8.76, 12.33 together) become one touch of 9.30. The saving is 3.0 m/s (8.34 → 11.30), not the 5.2 a subtraction predicts, and 14.8 does not occur. On wave3, which does not contain MAC-435, the same heightfield prints 17.6 and the unrepaired one prints 16.1, both inside 12..32. The floor does not move a tree that is already green. It is the bound for the composition. Class. TEST CALIBRATION. Not a capture waiver. Not a physics change. The movement model did not change. 12 was gating hop phase on a face the reference segment does not Glide, and it was last satisfied on this line when the pre-MAC-414 footing hole dropped the window to 4 or 5 touches (16.07 baked / 17.62 restored). That hole is closed. Independent warrant. The capture-gated hop on the intended 31-degree surface ends the window at 11.30, and the same loss the capture measures (3.897 m/s per hop) reproduces 11.22. The phase band around that print is 11–13. 10 sits under that band. It is not "the suite was red." 10 also sits above the 8.34 the 39.6-degree toe still prints, so a tree that still has the toe stays red. Non-precedent. The same metric on proving does not move again without a new geometry or physics cause and a new record. Chasing 12 by shortening the 31-degree face is refused. A standing waiver that leaves the lane red is refused. Revert. Put proving m2_lo back to 12 (the inverse of f6e7f2ec's movement_acceptance.gd hunk). The restored face prints 11.30 and M2 goes red again. That is the point of the revert. It is not taken in this record. Expiry. Chris owns the promotion to standing spec. If this record is not promoted by 2026-11-11, it stops authorising any further move of proving's M2 floor. Expiry does not itself restore 12. Restoring 12 is the revert, and it is Chris's to take.

13 lines of internal measurement detail omitted from this entry.

EXP-23 · 2026-09-07

Let the attack shoot the base guns, and let the wave form on a long map (lane SG)

EXP-22's own closing order, done. Its first two items were "let the run-in shoot the thing that is killing it" and "make the wave form on a long map"; its third was "the map itself". This pass does the first two, verifies the _ramp_foot bug the sweep flagged, and reports the third with the geometry measured rather than argued. Everything below is on the merged wave3 tree (the base remodel, the spawn-kit secondary, the interiors), and the baseline was re-measured on that same tree with only bot_brain.gd and bot_team.gd reverted, so the map art, the kit and the terrain are held fixed and only the bot layer differs. tools/sg_measure.sh runs the cell (bot_match for the objective side, 16 bots = EIGHT A SIDE, 600 s); tools/sg_table.py folds it into the table. ### 1. The run-in could not shoot a base gun, because a base gun is not a deployable SAP_KINDS (bot_brain.gd) is GroundSentry / WallSentry / FieldRadar and _sap_pick walked thing the attack never fired at. _sap_pick now walks both, and takes the BaseRadar as well as the BaseTurret: a base gun's lead-error cone is the contract's row 3 while its own dish is live and row 0 without it, and the dish is 4 launcher rounds to put under its disabled line against a gun's 6-7, so it is the cheapest thing on the map that makes every gun on it miss. Two things had to be learned by measurement, and both are written into the constants: - Base assets rank as a CLASS behind the field net (SAP_BASE_RANK), not in front of it. The first cut gave a base gun a 45 m nearness bonus because it is what the death log names. On Skerry the wave then put one round into the deployed network over a whole run-in against ten to eighteen before, and bots_defence_check W - "the wave takes a piece of the network down every run" - went from 3 of 6 turrets down to 0 of 6. A Skerry base gun stands 19-26 m off the stand, inside the sentries the defence just planted, so preferring it does not add a target, it swaps one. Ranked behind, the base guns take the rounds the habit had nowhere else to put, which on a 1060 m map with no deployable on the crossing is most of them. - The capper shoots at base guns only in company (SAP_BASE_MATE_M, 150 m). With the runner in carries getting 120 m clear to 2 of 6, and the sweep's own seed from a cap to a death on the roof. A lone runner gains nothing by opening a gun that is whole again long before it comes back. A runner arriving *with* a wave is doing the breaker's job for the bodies behind it - and the them out, on none of the three. Gated on a live teammate inside 150 m, grab_check's unopposed scene is byte-identical to the shipped bot's (same 6-seed summary to the tick). The raider's value table was left alone. RAIDER_VALUE does rank BaseTurret (5) under Generator (10), BaseRadar (8) and BaseInventory (6), but that table and the 600 m falloff are the BOT1 contract and bots_defence_check R1 asserts both literally (raider_score == 10 x (1 - d/600), "nothing scores past the reach" at 800 m) while R5 asserts the mortar raider shells the *generator* through a live base. Re-ordering them is a contract change and belongs with lane B1. The finding this pass acts on is the *attack's*, and the attack is where the fix is cheap. ### 2. The wave could not form on a long map, because the rally is a fixed distance from the ENEMY RALLY_M is 150 m from the enemy stand, so every metre a map grows lands on the un-timed leg in SOLO_WAIT_TICKS / FORM_FALLBACK_TICKS gave both the same five seconds to absorb the arrival not ready yet - its distance to the rally over WAVE_TRAVEL_MPS (14.0, the measured mean speed over a crossing) - clamped to [FORM_FALLBACK_TICKS, WAVE_WAIT_MAX_TICKS] (5-20 s) and *only ever spent on somebody who will actually be there inside the cap*. Nobody inbound, or the nearest one still a whole crossing away, and the answer is EXP-14's five seconds exactly as before, so a short map keeps the "objective gravity, no rally wait" it was tuned with. It is the same shape EXP-16 gave the hunt's form timeout, for the same reason. Verified with a probe over all six maps. _ramp_foot returns null on both gaps of both the ring centre (exit 3.3 m at 18 m; team 2's entry axis has no step at all and its exit a 5.0 m one). There is no ramp there, so "no ramp" is the correct answer. Every other ring in the game returns a foot on at least one gap. Of the five paths the sweep listed, four already carry a written fallback for exactly this case and words), the breach posts, the attack path's tail (cut at 45 m instead of 80 m, [ingate, stand] instead of [foot, ingate, stand]) and _lineup_point (LINEUP_MIN_M). _ramp_tail does return its path unchanged, and that too is right - there is no ramp to route by, and EXP-18's _wall_step plus the run-in's step-fly is the machinery that replaced it. The one that is genuinely wrong is two_sides, the two-sided hunt split: it asked whether the ring has two RAMP FEET when it means whether it has two WAYS IN. So on the only map whose gaps are 180 degrees apart and whose ring is 8 m across, the split silently never happened and every hunter funnelled through one 6 m hole. It now asks the ring's gap count, which _parse_ring records. The far group's climb was already handled: their_ring_back swaps the wall steps along with the gaps, so the run-in's step-fly reads the far gap's face. Still open, and named here rather than changed without a measurement: _ramp_home and the two "come home up the ramp" fine-lookahead branches are gated on entry_foot != null as well, so a face (the step-fly reads their_ring only). It matters less than it looks - carry_end_d says lane in these files should fix it with the carry measured. ### 4. A bot standing in its own bunker was blind (found by the sweep's rack_check, fixed here) Reported as "rack_check fails on Ascent and Echo": the engineer takes the rack's mender in 1.2 s and never draws it. It is neither of those maps' fault and it was failing on four more. _los_solid and _los price a sample against terrain.height_at, which over an interior room returns the ROOF. Measured: the team-1 base inventory and its rack stand at y 152 on Skerry with height_at 162 over them, so every point in the bay reads as ten metres underground and the brain's sight line refuses *everything*, including the object four metres in front of the bot. Five since the interiors landed - MachTerrain.in_void, whose comment is "the rays use it" - and these two walks were the only rays in the tree that never asked. Nothing here opens a wall: a line leaving the room passes through samples that are not in the void and are under the roof, and is refused exactly as before. The scene was wrong as well, and the two hid each other. rack_check F snapped its hurt asset to height_at, which on those five maps put the object the bot is asked to mend on the roof over the room the bot is pinned in - a beam nothing could pass, measuring the roof rather than the bot. It now stands on the floor the rack stands on (void_floor_at, which returns -1e9 outdoors, fails on the LOS, and the LOS fix alone still fails on the roof. asset from a tenth of its hull back to whole. ### 5. Measured: before and after 600 s, both sides summed over the three matches. "before" is wave3's bot_brain.gd / bot_team.gd on this tree. carrier lives half as long again, and the thing the pass is about comes down: base-gun kills 29 to 20, from 17.9 % of every death on the map to 14.0 %, while the rounds the attack lands on a base asset go up 75 to 99. One of the three seeds now passes bot_match's floors outright, where none did before. Two do not, and section 6 says why. Skerry does not get worse where it is measured as a floor: green on all three seeds before and after, with pickups up 43 % and stand approaches up 58 %. Caps are 16 against 12, and that is worth reading rather than summing: before, three of the six sides scored nothing at all (0 0 1 5 5 5); after, none did (1 1 1 2 2 5). The total is lower and the shut-outs are gone. At three seeds a four-cap difference is at the edge of what this harness can resolve - EXP-22's own caveat - and the per-side distribution is the more honest read. deployables placed swings 4-17 per team-match on this map in the baseline alone, so 37 against 29 (and the defended fraction that follows it) is inside that noise and is reported, not claimed. ### 6. What is left is the map, and here is the geometry five other maps, and its two gaps are 180 degrees apart against 90 everywhere else, so the entry and the egress share one 6 m hole (this is why EGRESS_GAP_COS exists at all). The two bases are not the same base, and the results split along that seam: Team 1 attacks a flat entry; team 2 has to fly a 6.5 m face under a point-defence gun sitting 17 m over the flag it is running at. Over three seeds, team 1 picked the flag up 6, 3 and 0 times; team 2 picked it up 0, 2 and 0. That asymmetry is in the baseline too (team 1: 0, 4, 0; team 2: 1, 0, 0) and it survives every bot change in this pass. A wall on one team's only approach, with a gun directly above it, is level design, and the two remaining red seeds are that wall. EXP-18's own next-steps and EXP-22's third item said the same thing; this entry adds the numbers and the bearing of the gun. spawned at all - dies on 2 of 6 seeds and gets 120 m clear on only 4 of 6, and that is true of the shipped bot as well as this one (identical 6-seed summary). A base that kills a third of the cappers that reach it with nobody home is a base gun placement question, not a bot question. ### Checks - defence_map_check: ok on all six maps. - grab_check: ok on all six maps at the sweep's own seed. - defence_check, stats_check: 0 failures. - replay_match: deterministic over 940 ticks, and the fixture did not move (spec/traces/fixture_skerry.rpl, writer v11). lowered and no assertion was edited; the only test file touched is rack_check's F scene, which now stands its asset on the floor the bot is standing on instead of on the roof over its head.

67 lines of internal measurement detail omitted from this entry.

EXP-21 · 2026-08-31

The owner's composition, a midfield with a job, and the counter triangle as a test

docs/TEAM_COMPOSITION.md writes down the strategic layer the game is for, and our bots did not match it. A 16-player team is 5-6 flag defence, 2-4 midfield, 5 offence, 1-2 cappers; the EXP-15 roster dealt 2 runners, 4 escorts, 2 chasers, 4 defenders and 4 floaters — no midfield at all, and four bots whose whole job was orbiting a carrier. The same document names a *testable* claim: no composition is dominant, and each extreme fails for a different reason. This pass does three things: reshape the roster, give midfield a real job, and turn the counter triangle into tests/composition_check.gd. ### 1. The roster (bot_team.gd ROLES / ROSTER) escort and floater are gone. Escorting the carrier is now a job the coordinator hands an offence bot for one carry (the two nearest live wave members, EXP-16's lead 18 m / trail 10 m unchanged), not a slot four bots stand in; floater was a second offence line under another name. midfield is new, and offence rank 0 of a wide offence line is the siege heavy. tests/composition_check.gd --table prints this table against the owner's row. ### 2. What the midfield actually does (bot_brain.gd role "midfield") The owner's line is "kill the other midfield, the heavies and the mortar teams", and the failure mode to avoid is a role that ends up orbiting one base or the other. So a midfielder: - takes a post on the attack route half way across, spread sideways by rank (MID_SPREAD_M 70 m, so three of them are a front and not a queue), and never posts inside MID_KEEP_M (190 m) of either stand; - leaves it only for prey: the enemy carrier crossing the band first, then, by a scored distance, a Bulwark (worth 160 m of closing) and something carrying a Gravity Mortar (260 m more). Prey inside 190 m of the *enemy* stand is not ours — a Bulwark standing on its own flag is a defender and prising it off is the offence's job; - does not rally, does not join waves, does not press the enemy stand and does not patrol its own. ATTACK_ROLES is now ["runner", "offence"], so the wave, hunt, regrab and handoff code never reaches for a midfielder. Kit: a Tremor Sensor on its own post (the only sensor that sees a cloaked player, and the team's sensor net is what known_enemies() reads), then a mine layer, then a patch carrier. ### 3. The siege heavy, and why the counter triangle needed one All-defence has to lose to *something*, and the document names it: heavies and mortar teams that bombard the base. Nothing in the bot layer had ever fired the Gravity Mortar. Offence rank 0 of an offence line of four or more now takes the Bulwark and the mortar, crosses to the rally with the rest of the attack, and stops the moment it is inside the shell's window (95-165 m of the enemy stand), where it bombards on a 1.5 s cadence. It is filtered out of the wave, the hunt, the regrab and the handoff, so the attack is not one bot short of a Bulwark. Its kit is the Field Refit: ten mortar rounds is fifteen seconds of bombardment, and the forward station it drops at its own post is what makes the bombardment continuous. It is deliberately a 12- and 16-a-side role. The owner's table is a 16-player team, and a three-wide offence cannot spare a bot that never runs the gap: dealt at 8 a side it is an eighth of the team parked behind the fight, and it cost the flag run outright — Skerry seed 1 went from one cap to two pickups a side and no cap at all in four minutes, which is bot_match's own floor failing. SIEGE_MIN_OFFENCE is 4, so the siege heavy appears at 12 and 16 a side and composition_check runs 24 bots by default (12 a side, the smallest roster that fields one) rather than 16. _mortar_aim solves the arc against the sim's own numbers (muzzleVelocity 75.95, gravityMod 1.1, no drag, velInherit 0.5) and rejects any solution that lands sooner than the shell's 1.5 s arming delay — a flat shell at 100 m arrives in 1.3 s unarmed and bounces off the roof instead of going off over it. siege_targets() offers the enemies on the stand, then their emplacements, then the stand, and the brain takes the first with no friendly inside the 19 m blast (team damage is on). ### 4. Attackers carry a kit too EXP-19 armed the defence and left the attack with a mine and a frag — defensive kit in an attacker's hands — and caps fell 28 %. The three attacking tools in the registry went unused. All three now do: - Flash Grenade. It did nothing to a bot, because nothing read blind_amount: the sim has carried it since the items lane landed and only the local HUD ever looked. Bot aim error now multiplies by up to 8x with the whiteout, which is what makes the item worth carrying. It is thrown *only* at something the thrower cannot see — over the lip onto the stand it is about to run in on — because _flash_blind whites out everyone inside 100 m with a clear line to the burst, thrower included, at a full 0.9 whiteout 25 m away. A teammate with line of sight inside 60 m stops the throw the way a frag on a teammate does. - Breach Charge. Set at the foot of the enemy's entry ramp (or just outside its entry gap), and detonated when something of theirs walks inside 22 m of it with nothing of ours inside 26 m. It is an errand with a walk in it, like the defender's emplacements — a presser is never standing still at the gap on its own. the world's refit code has always taken it. What was missing is that _nearby_station only looked at fixed map stations, so a forward station would have refitted nobody. ### 5. Six bugs the pass turned up, all of them invisible before something used the item set 1. A frame swap pinned the new frame to the old frame's weapons. loadout_for sent the weapons the bot was holding, and the frame swap and the loadout arrive in the same station tick — so the siege heavy went to Bulwark carrying the Ranger's four weapons, which is a Bulwark with no Gravity Mortar. Measured: 0 shells, both sides, a whole 240 s match. A bot changing frame now asks for the weapons of the frame it is changing into. 2. Nothing read blind_amount. A flash grenade did nothing whatsoever to a bot (§4). 3. _nearby_station could not see a deployed Field Refit (§4). 4. A refused placement retried forever. _bad_post is only consulted by the errands that pick from a list of posts, so a bot placing where it stands read it not at all: one siege heavy on a slope produced 26 "surface too steep" refusals in one match. A refusal now backs the whole errand off for 20 s, and the station errand checks the ground's slope against the item's own maxDepSlope before pressing the key at all. 5. Both stuck-recovery hatches were resettable by the bot's own recharge cycle. _wall_wait goes back to 0 on every burn tick and the wall branch pushed _progress_tick forward, so a bot that alternates "charge, burn, fall back down" never reached five consecutive seconds of waiting and never counted as stuck either. Measured: a Bulwark that dropped 10 m off its own bunker on Skerry seed 3 stood in that loop for the whole match, 25 m from its own stand, at 0.0 m/s. _wall_total now counts wall time since the last real progress and survives the burn ticks. 6. An empty override table meant an empty roster. composition_check passes {} for "the shipped roster"; want_roster treated it as a weight table summing to zero and dealt every bot the fallback role, so the first run of the triangle had an all-offence side standing in for the balanced one. Caught because the balanced side fired no mortars. ### 6. The counter triangle as a test (tests/composition_check.gd) weights to whatever the team size is by largest remainder, so {"midfield": 1} is "everyone holds the middle" at any size and {} is the shipped roster. Each leg sets one extreme (team 1) against the balanced roster (team 2) over six seeds and asserts the *predicted* failure, not merely that the extreme lost — losing for the wrong reason is not the claim: A prediction that does not hold prints PREDICTION NOT SEEN with the measured numbers and the run exits non-zero. Nothing here is tuned until it passes. --table prints the roster table and stops; --leg= runs one leg. It is a SLOW suite in tools/run_all.sh, so --quick skips it. ### 7. Measured: the shipped bot against EXP-19's bot_match, 16 bots (8 a side), 240 s, both sides summed. "Before" is main's bot_team.gd / bot_brain.gd — the EXP-19 bot — run on this same tree, so the frame resize, the map erosion pass and the item and sound merges are all held fixed and only the bot layer differs. Caps are up on both maps and cap attempts are up by more than caps are — the flag gets home more (27 → 20) with a third fewer waves on both, so the attack is grabbing about as often out of fewer, fuller waves rather than trickling: EXP-15's five thin attackers at 8 a side are now four that arrive together, and the flag survives the carry more often (Skerry handoffs 3 → 10, deaths 168 → 119). The spread is the number the owner named, and on Skerry it moved for the first time. Before, ten sides scored 0 0 0 0 0 1 1 1 1 2 (sd 0.66); after, 0 0 0 0 1 1 1 2 2 3 (sd 1.00). A 3-cap side and a 0-0 turtle are both now reachable from the same rules on the same map, which is what EXP-19's minutes, which is the map and the clock rather than the composition. Bases are defended for half of a Skerry match instead of a third, with fewer placements refused (the station-slope check) and more throwables landing (the flash the attack now carries). ### 8. The counter triangle, measured composition_check --map=skerry --seeds=1,2,3,4,5,6 at its defaults: 24 bots (12 a side, the smallest roster that fields a siege heavy), 150 s a match, team 1 the extreme and team 2 the shipped roster, both sides summed over the six seeds. All defence: the predicted failure appears, all three parts of it. It never scores — it has no attacker to score with — while the balanced side caps in two of six matches. It takes more of the bombardment than it deals back (6.5 against 5.1, from 80 shells it cannot answer with any of its own), and six of its deaths are inside its own base against the balanced side's none. It loses at home, slowly, to a bombardment: the owner's sentence in numbers. The siege heavy is what makes this leg exist at all — before this pass nothing in the bot layer had ever fired the Gravity Mortar, so all-defence had nothing to lose *to*. **All midfield: the predicted failure appears, and it is the *bypass*, not an absence of contact. 0 caps against 10, and 10 of the balanced side's 16 carries came inside 170 m of a live midfielder and still reached a cap attempt. The midfield is not being avoided; it is being gone past, which is the owner's claim word for word. (The 89 % "dead or in transit" is the composition, not a fault: a team that is all midfield is by construction never near either stand.) All offence: the mechanism appears and the outcome does not — say it loudly. The stagger is exactly as described: 60 % of an all-offence team is dead or in transit at any moment against the balanced side's 45 %, and it has 0.8 of 12 bots inside the enemy base at any moment — a 12v12 that is really 12v5. It also loses far more bodies at home (9 against 3) and eats three times the shells. But it still out-caps the balanced roster, 3 to 1**, winning three of the six seeds and losing one. The predicted failure does not appear, and that is a finding about the game, not a test to tune: - Our arenas are too small for the stagger to cost anything. Skerry is 540 m stand to stand and a crossing takes 20-35 s; release maps are planned at roughly 10x that (docs/TEAM_COMPOSITION.md), where "a third of your team is always in transit" means a third of the team is two minutes from the fight rather than twenty seconds. The stagger is real — the 60 % and the 0.8-of-12 measure it — and what is missing is a map on which it is expensive. - And the flip side: our *defence* does not convert. The balanced side's four defenders, one chaser and two midfielders turned a permanent advantage in bodies at home into one cap in six matches while conceding three. A composition that keeps its base is not being paid enough for keeping it. The honest reading is that the third leg is currently a *map* problem and a *defensive value* problem, in that order, and neither is a bot-composition problem. composition_check prints it in numbers and exits non-zero every time it runs, so it fails the build until it stops being true — which is the point of writing the claim down as a test. (--quick skips it with the other slow suites, so the quick sweep is unaffected.) ### Checks All of these are on the rebased tree (main at 24dd8bb: the frame resize, the map erosion pass and the item, sound and weapon-fidelity merges), and the before/after table above was re-measured there rather than at the branch point. Both builds were re-run on the tree as it stands, so the frames, the terrain and the weapon tuning are held fixed and only the bot layer differs. - Determinism: bot_match --map=skerry --seed=3 --log twice is byte-identical, 122 lines (the ms/tick line is wall clock and is excluded). - bot_match on all four maps at its default seed: ok on all four, before and after (the floor assertions pass). - combat_check, standoff_check, grab_check on all four maps: 0 failures, 12 runs of 12. - replay_match: passes as part of the sweep below. - DISPLAY=:99 tools/run_all.sh --quick --headless-only: 45 passed, 0 failed. The windowed screenshot suites were run separately in batches (a single full sweep exceeds the 10-minute command budget under load) and also passed, 0 failures across the lot. - Tick cost with 16 brains is 3.4-6.7 ms depending on the map, in the same band as the EXP-19 bot on the same tree (the mortar arc solver runs 60 iterations for one bot per team per 1.5 s, and only at 12 a side and up). ### Decision and next Keep. The roster is the owner's, the midfield has a job that is not a second offence line under another name, and the triangle is a test that has already earned its keep twice: it caught the empty-override bug, and it is the only thing in the tree that says out loud that the all-offence leg does not hold. Next, in the order the numbers argue for: both. The offence line the owner's distribution deals is enough to soften a 540 m map's stand and is not enough to soften a 1060 m one, where the run-in is twice as long and the wave arrives thinner. That is the first thing to look at, and it is about the wave's timing and size on a long map, not about the roster. 2. A match long enough, or a map big enough, for a spread on every map. Skerry now produces metric the owner named, and on the longer map this harness still cannot see one. 3. The bombardment still costs its own side. On the all-defence leg 0.9 of the 7.5 mortar and breach damage in the match landed on the side firing it — down from a near-even split before the flight-time guard went in (the shell is 2-4 s in the air, so "is anyone standing there now" was never the right test), but not zero, and it is the one number the siege heavy should be judged on next. The remaining case is our own wave arriving inside the ring while a shell is in the air. 4. Breach charges are set about once per side per match. The pressers spend most of a match at the rally waiting for a wave, and the charge can only be set once they are at the base: the wave cadence, not the charge, is the limit. 5. RelayPad, EyeGrenade and MarkerBeacon are still unused, and the Flare Grenade has no effect a bot can exploit — it pulls seeker lock, and no bot fires a Seeker Launcher. Arming bots with a flare today would be cosmetic; the honest order is a bot that uses the Seeker Launcher first, then the flare that answers it.

73 lines of internal measurement detail omitted from this entry.

EXP-20 · 2026-08-31

Frame size: the medium and the heavy read as their classkeep

Owner, after playing, verbatim: "Heavies need to be bigger. Meds need to be bigger, lights look right." He is describing a measurement. At the fighting range (40 m, 13.5 px per metre at 1080p / 90 degrees) the three frames stood 34 / 36 / 39 px tall: the Strider-to-Ranger step was 1.7 px, which no player can see, and the Bulwark was 5 px over a Strider, which reads as "a bit closer", not as "a heavy". The width-to-height rule of docs/SILHOUETTE_SPEC.md section 1 was doing all the work and absolute size was doing none.

Hypothesis
the class read fails because the frames are nearly the same *size*, not because their proportions are wrong.
Implementation
scale the Ranger and the Bulwark uniformly by x1.10 and x1.15; leave the Strider alone (it is the world's scale reference and the owner says it is right). Uniform matters: every band in section 8 is a ratio, so a uniform scale cannot move one, and the gate proves it - 27 of 27 rows still in band, on the same authored geometry. This is a gameplay change, not an art change. boundingBox is the hit volume (mach_world.gd _ray_players), the player-collision volume (_step_collisions), the explosion falloff origin (explode, which subtracts box.x from the range) and the flag pickup test, and eye() / center() hang off box.y. The mesh and the box therefore moved together, by the same factor, in one commit: FRAME_GROWTH in player_model.gd (rig + third-person weapon scale), FRAME_GROWTH in tools/mach_frame_kit.py (applied on export to the authored glTFs) and boundingBox in data/tuning/frames.json (re-applied by tools/build_tuning.py so a regenerate The steps are now 5 px and 6 px at 40 m (15% and 16%), and light to heavy is +34% of height and +103% of frontal area. Plates: research/screenshot_framesize_{lineup,field}_{before,after}.png (game/tests/screenshot_framesize.gd) - a true-scale lineup with a metre ruler, the same three frames at exactly 13.5 px/m, and the gameplay camera at a Strider's own visor height 40 m and 6 m out. What it costs, measured before and after on all four maps. * Hit probability. The cross-section column above is the proxy: a Ranger is 21% easier to put a line through, a Bulwark 32%. The azimuth-averaged projected area (4wh/pi, since the box is square in plan) moves by the same percentages, so the frontal number is not flattering the change. The heavy-to-light hit-area ratio goes 1.54x to 2.03x. * Explosion falloff. explode measures distance_to(center()) - box.x, so the effective blast reach against a Ranger grows 0.145 m and against a Bulwark 0.245 m, on top of the bigger body. * Eye and centre height. eye() is box.y * 0.83: Ranger 1.99 → 2.19 m, Bulwark 2.16 → 2.48 m. A heavy now sees 32 cm further over a crest and fires from 32 cm higher. center() (the aim point bots lead) rises 0.12 / 0.20 m. * collide_check, four maps, all green. The block outcomes are identical, because the trade is mass and brace, not volume: a Strider at 60 m/s into a planted Bulwark still ends 60 → 0.00 m/s with the Bulwark shifted 0.000 m, and a Bulwark at 60 m/s into a planted Strider still keeps 60 → 34.00 m/s and throws the Strider 12.8 m. What did move: the boxes meet 0.12 m further apart (Strider into Bulwark, centre to centre 1.415 → 1.5375 m), the C6 four-tick-stale prediction error 0.320 → 0.359 m (band 0.5), the C4 heap residual 0.0067 → 0.0037 m, and C7's worst-heap tick 4.55-6.05 → 4.58-5.28 ms (budget 16). * The movement sheet did not move. movement_acceptance and arena_lines are byte-identical on and nothing in MachMovement reads the box (terrain contact is a point query). combat_check passes on all four maps with every damage, hit, radius and BreakFlag assertion unchanged; the only lines that move are the *placement* coordinates of a deployed eye camera and a thrown charge on a grown frame, because both are launched from pl.eye(). * Terrain and pathing. Props carry no collision and the walkable structure layer is a heightfield, so a taller frame cannot wedge in geometry; grab_check is byte-identical on all four maps and bot_match stays inside its usual spread (skerry median bot speed 13.8/15.4 → 14.5/15.1 m/s, base-to-base crossing median 34.6/27.0 → 29.8/25.9 s). bot_match --map=tallow failed its "at least one cap in 240 s" assertion on the *baseline* (0 + 0) and passed after the change (1 + 0): that gate is stochastic and its flakiness is older than this experiment. * Deploy reach, the one real regression found. A deployable is placed where the aim ray from pl.eye() meets the ground, inside the item's maxDeployDis, and no closer to your feet than minDeployDis - so how far down a frame must look to place something depends on how tall it is. Only the Relay Pad (reach 4.25 m, min 2.5 m) is tight enough to notice: a Bulwark's aim window moves from 31-41 degrees below horizontal to 36-45, narrowing by 1.2 degrees; the Ranger's is 31-41 (was 28-39) and every other deployable keeps 15 degrees or more. items_check had hard-coded a -0.6 rad (34 degree) test pitch, which sat inside the old window and outside the new one, and failed on tallow / proving / skerry until it was made to sweep the angle the way it already swept the distance - it is a test of the transfer code, not of a frame's height. Worth the owner's eye: if a heavy should place pads as easily as before, the dial is RelayPad.maxDeployDis 4.25 → 4.9, an item number, not a frame number. * Art gates. screenshot_silhouette 27 of 27 band rows in band, triangle counts unchanged (5280 / 5772 / 5688); pack_check 190 checks green with the pack bay carried through the same growth (fin-root air gap 0.09 / 0.16 / 0.25 m, minimum 0.04); art_contract_check green. Weapons had to come with the frames. Section 3's outline families are defined as a length "of H", so a fixed-size gun on a 10% taller Ranger would have dropped the Plasma (0.44 → 0.40) and the Impulse Launcher (0.42 → 0.38) out of family A. The third-person weapon scale is therefore number in section 3 is unchanged. First person is camera space and is untouched.
Decision
keep, pending the owner's eye on the plates. Not done, and deliberately left for him: 1. Mass and health are unchanged. A Bulwark now absorbs 32% more incoming for the same 1.7 maxDamage and the same 180 kg. That is a coherent trade only if "the heavy is the thing you have to shoot around" is the intent; if it should stay a wall, its health is the dial, not its size, and changing mass would move both the block table above and the fitted movement sheet. 2. ~~The base kit is now short for a Bulwark.~~ DONE 2026-09-01. The rear hatch surround was 3.05 m against a 3.35 m Bulwark, so a heavy could not have walked through its own back door. Surround raised to 3.72 m, door frame to 3.70, with the emblem panel and its EmblemDoor mount moved up to match (tools/gen_base_kit_glb.py). Nothing collided either way - props are decoration - but it is the doorway a player walks through every life, so it reads worse than it costs. combat_check, art_contract_check and station_runtime_check green after.

10 lines of internal measurement detail omitted from this entry.

EXP-19 · 2026-08-31

Player-versus-player collision: heavies can body-block capperskeep

Hypothesis (Chris, playtest note): players pass through each other, so a Bulwark cannot stop a Strider coming in at speed and the defensive half of the flag room has no body in it. Bodies that block, and that trade momentum by mass, give the heavy frames a job the light ones cannot do — a defensive tool that costs nothing to learn and is impossible to fake. Volume. The collision box is the frame's boundingBox, the same axis-aligned box the weapons already hit (Player.box, _ray_players, explode's falloff, the flag pickup). Not a capsule: a shot that hits and a body that blocks have to agree about where a player is, and the reference hangs every hit zone off the same box (boxNormalHeadPercentage 0.83, boxNormalTorsoPercentage 0.49). scripts — frame::onCollision mounts vehicles and picks items up, frame::onImpact is the ground-impact damage _step_player already applies, and the physical response is engine C++ dv = impulse / mass, no angular part — how explode already kicks players) and the masses. The rest is MACHBREAK's own: a swept AABB over the tick's relative motion (a Strider crosses 1.9 m per tick against a 1.63 m box, so an overlap test alone would tunnel), the closing speed along the contact normal removed in full (frames never bounce off each other), and the split between the two decided by inverse mass plus the terrain anchor of a frame planted on a run surface, whose capacity grows with the square of its mass (BRACE_ANCHOR_DV = 18 m/s at the Strider's 90 kg). Heavier is not just heavier; heavier is better planted. Measured (tests/collide_check.gd, all four maps, identical): The block holds at 40 / 60 / 90 / 140 m/s (0.0 / 2.0 / 6.0 / 22.7 m/s left, always on the near side of the blocker), which is the swept test doing its job: at 140 m/s a Strider crosses 4.4 m in a tick. Prediction. Collision that only exists on the server rubber-bands, which is the failure EXP-3 and EXP-4 were both about. The client builds the remote players near it into ghost bodies out of fields the snapshot already carries — position, velocity, frame, alive — with "planted" derived from the shared heightfield, so this needed no new wire field; both halves of each contact are applied locally, the remote half to the ghost, because the server moved both frames and a prediction that pushed only its own half drifts further apart on every tick of a sustained contact (that one detail took the load-test mean from 51 mm to 4 mm).

Telemetry
net_check.sh 40 --scripted-seek, three runs each, mean / max correction — collision predicted 0.0040 / 1.79 m, 0.0197 / 1.38 m, 0.0024 / 0.56 m; the same build with the ghosts switched off (server authority only) 0.0310 / 3.32 m, 0.0245 / 1.23 m, 0.0880 / 1.40 m. Deterministically, with the snapshot four ticks stale and no reconciliation at all, a predicted block lands 0.32 m from where the server put it; the same client without ghosts is 22.5 m out. Cost. perf_check at 32 players: mean world.step() 3.26 ms before, 3.34 ms after (budget 4 ms), 95th percentile 11.1 → 9.97 ms (budget 16 ms) — players in a real match are spread out, so the pass costs its one O(n²) hull compare (496 of them) and no heightfield samples at all. The case perf_check cannot see is the pathological one for this pass, thirty-two frames inside a twelve-metre circle all walking into the middle: 2.3 ms without the pass, 4.6-6.7 ms with it, bounded by COLLIDE_MAX_RESOLVES and gated by collide_check C7. The movement sheet did not move. movement_acceptance (4 maps), replay_drive (3 frames), replay_traces and arena_lines (4 maps) are all single-player harnesses — with one body in the world there is no pair to resolve — and every number in them is unchanged. replay_match was already diverging on main at tick 32: spec/traces/fixture_skerry.rpl was cut at 347ddec and dc12a8f (the cell-pack retune) changed the sim after it without re-cutting. Re-recorded, and it verifies deterministic over 940 ticks; the wrong-seed negative test still diverges.
Decision
keep. Open: teammates block each other too (deliberate — it is what makes a body a body — but it wants a playtest); the pass gives up overlap rather than tick time once a heap exceeds the resolve budget, which is the right thing to give up first but is a soft failure worth watching; a body slam does no damage, which the reference did not do either and which is the obvious next experiment now that the momentum exists.

7 lines of internal measurement detail omitted from this entry.

EXP-19 · 2026-08-31

Bots use the item set: emplacements, throwables and packskeep

Two reviewers independently called this the gap that keeps solo play from feeling like the real thing: bots Glide, take roles, escort and hand off, and use none of the item set. mach_world.gd has taken loadout / grenade / deploy / detonate / relay in the input dict since the items had to change.

Hypothesis
a defended base is a base with a sentry on it, and a bot that never presses those keys is playing a different game from the one the owner is playing against it. ### What changed (bot_brain.gd, bot_team.gd, bot_match.gd only) A frame fields exactly one throwable type and one deployable type (the registry's slot contract), so what a bot carries is chosen once per life by the role it was dealt and asked for the same way the frame already is — the loadout input, in the spawn window or on a station. The weapons come from whatever the bot already has, so only the pack and the two item slots ever change, and the server's _loadout_ok is still the only gate. Rows inside a role are dealt by role_rank, so a two-defender base gets one emplacement and one mine layer instead of two of the same: Which sentry is measured off the map, not assumed: sentry_kind() reads the stand's own surface, and Proving's open stand takes the GroundSentry ("terrain"). Placing the wrong one is a WRONG_SURFACE refusal and nothing else. Emplacements are the team's, not the bot's (emplacements, free_post). The sentry and the sensor sit inside the stand looking down the gap the attack routes actually use; the mines go on the approach — on the ramp where there is one, otherwise well out along the entry axis — and never inside the ring, because a mine has no idea whose boot it is and inside is where our own defenders patrol. A defender takes the nearest free post *on its own level*: getting onto a roof is the hold mode's job, not the errand's. Placement itself is a key press at a point 3.6 m in front of the bot (inside every item's 5 m maxDeployDis, outside a mine's 2.5 m proximity trigger, pulled in where the ground falls away so the aim ray still finds a surface), and the server decides whether it lands. Throwables are three cases and only three: the carrier of our flag, an enemy we know about but cannot see, and whatever is standing on a stand being contested. The split between the two types is the reference's own — a concussion does no damage, so it is the one that goes at a carrier, and the frag is area denial: a static target, thrown from a stop, from outside its own blast radius. Packs have an on threshold and a lower off threshold, because they drain the tank the jets use: barrier while we are being shot at, cloak on a chaser sitting on a gap at home, jamming field inside the base we are pressing (no turret sensor sets detectsActiveJammed), and the mender's repair gun out beside a hurt teammate — which is the pack, so while it is out the bot stops naming a weapon. Two things outside the item set had to be fixed for any of it to happen. A defender below its own roof steered straight at a patrol point 12 m overhead and stood on the bunker wall: on Skerry seed 1, bot 1008 sat at (-279, 150, -22) at 0.0 m/s for most of a four-minute match, in "hold" the whole time. The way up is the ramp, the same one the hold mode already uses further out, taken waypoint by waypoint (lookahead = 6) — the rule EXP-16 gave carriers on their own ramp. And known_enemies() buys that a bot can act on. ### Measured bot_match, 16 bots, 240 s, seeds 1-12, both sides summed (24 sides per map). "Before" is the same build with BOT_KIT=x, which is byte-identical to the EXP-18 bot — checked against the recorded seed-1 logs, column for column. "Base defended" is the share of the match with a live deployable of a team's own within 45 m of its own stand — the new counter this pass adds to the harness, along with the other four. Placements by kind, 4 matches each (a mine that goes off counts as destroyed; that is its job): ### Say it plainly: caps went down, and part of that is the point and part of it is not 46 caps across 24 matches before, 33 after — a 28 % drop. Ablations, same 12 seeds: So roughly half the loss is the placements and half is everything else (better-posted defenders, grenades, the cloaked chaser). A base with a sentry on it *should* be harder to cap, and pickups are nearly flat (88→78, 72→68) — the flag still comes off the stand at the same rate, it gets home less often. But this is the number to watch, and two intermediate cuts were rejected for crossing the owner's own line ("a base so mined that nobody can cap is a regression"): and cap attempts to 24 of 40. Moving them to the ramp foot, or 18-28 m out along the entry axis on mines near a stand: 3. - Frags thrown on the move. Measured over 4 Skerry matches, the bots' hand grenades scored 34 kills on the enemy, 14 on the thrower and 9 on a teammate: a frag has a 1.5 s fuse and a 10 m radius, and a bot covers 20-45 m in 1.5 s, so the thrower ran into its own grenade. Restricting the frag to a static target thrown from a stop, and giving the carrier case to the concussion (which does no damage at all), took that to 7 self and 7 friendly with 40 enemy kills. It is not zero and it will not be — a player does this too — but it is no longer the dominant use of the item. An earlier cut also let pressers mine the enemy gap; their hold point strafes 9 m either side of it every 1.6 s and they walked back onto their own mine inside three seconds of it arming, every time. That case is gone; the chaser body-blocking our own exit gap keeps it. - A wider "quiet" radius for laying kit (45 m instead of 70 m) cost 3 caps on Skerry and 6 on ### Checks byte-identical across two runs (148 / 171 lines; the ms/tick line is wall clock and is excluded). combat_check, standoff_check and grab_check: 0 failures on all four maps. DISPLAY=:99 tools/run_all.sh --quick: 68 passed, 1 failed — replay_match, which fails identically on the unmodified tree (the committed fixture predates the cell-pack retune in dc12a8f) and is not part of this lane. BOT_KIT (an env var, unset in every run above) selects a subset of this pass for ablation: l loadout, d placements, g throwables, p packs, m the defender's ramp approach, s the sensor picture.
Decision
keep. Next, in the order the numbers argue for: (1) the sentry is placed 3 times in 4 Skerry matches and then survives the rest of the match — the limit is the stand defender's own survival and time on post, not the placement rule, so the cheapest win is a defender that dies less. mean life) against Skerry's 51 s: on an 8 m ring everything crosses everything, and the mine budget there is being spent on the traffic rather than the attack. (3) Nothing yet uses BreachCharge, RelayPad, FieldRefit, TremorSensor, FlashGrenade, FlareGrenade or EyeGrenade. ### EXP-19 addendum · 2026-08-31 · The owner on the cap drop: not a regression The lane reported caps down 28% with bases defended for the first time, and I read that as a problem. The owner's correction, and it changes how the number should be judged: *"Getting the flag off the stand and out of the area of the enemy base is just the first hurdle. Then you have to make it past all their midfielders and vehicles, then hope you shift back enough so they don't take your flag while you are returning. It is the game. It's fun. The mines on the flag are part of the game — the capper needs to shoot or nade the area. The defense can even throw a mine and shoot it for extra damage. High scoring games are fun. Turtles/stalemates can be fun too. I think it is good to have both situations with a chance of happening because both require adjustments."* Three consequences for how we measure and build: 1. Cap rate is not the health metric. The goal is that both a high-scoring game and a turtle are reachable from the same rules, because each forces a different adjustment. A bot pass that lowers caps while raising "base defended" from 0% to 40-57% has moved the game toward that, not away from it. What we should watch instead is the *spread* across matches — if every match lands on the same score, that is the failure. 2. The mines the lane moved off the ring gap belong back on it. They were removed for costing six caps on the tighter map; the owner names that exact situation as intended play with named counters (shoot it, throw a grenade at it). Restore as a measured change, and report the cap spread rather than the mean. 3. Release maps are planned at roughly 10x our test arenas. Skerry is 540 m stand to stand; a release map is a several-kilometre field where the base is the first hurdle rather than the whole run. That changes what the midfield is for, makes vehicles structural rather than optional, and means our current cap rates are measured on a map far smaller than the one the game is for. ### Counterplay check (2026-08-31) The counterplay the owner names is already implemented and reference-faithful: an armed mine that is shot detonates with its full radius, damage and kick, and blasts chain to other deployables so a field goes off together. A breach charge shot down does reduced "thwart" damage rather than its full detonation, so disarming one at range is safer than letting its owner trigger it. An unarmed mine is simply removed. Open design question for the owner: *"throw a mine and shoot it for extra damage"* does not work yet, for two reasons. Our mines are placed rather than thrown (docs/WEAPON_FIDELITY_PLAN.md item 7), and an in-flight mine is unarmed, so shooting it would remove it rather than detonate it — which is the reference rule. Making a thrown mine detonate in mid-air is a deliberate divergence, and it is the same play as "blow up mines midair" that he named when defining the signature weapon. Worth deciding together with item 7.

42 lines of internal measurement detail omitted from this entry.

EXP-17 · 2026-08-30

Teach the bots to Glidekeep

EXP-16's own conclusion, and the owner's next task: every remaining objective target is bounded by travel. Skerry's stands are 540 m apart and its scripted middle Glide line runs stand to stand in 13.5 s (peak 54.2 m/s, never under 49.7 on the floor, tests/arena_lines.gd); bots crossed the same ground at 10-15 m/s, so a full flag cycle cost 45-70 s and a 240 s match held four of them.

Hypothesis
the gap is not the wave rules, it is that the bots walk a polyline instead of playing the terrain — so give them a movement layer that reads the ground ahead along their own velocity and a route layer that prices a line by the speed it can be skied at, and the pickups and cap attempts follow. Constraint (design law): bots use the same mach_movement.gd, the same energy budget and the same resist curves as a player, so a bot that beats the scripted line is a bug, not a result. ### What changed (bot_brain.gd, bot_nav.gd, harnesses only; movement, world, net, frames untouched) 1. A Glide layer in bot_brain.gd (replaces the EXP-8/14/16 glide and burn rules). It decides the two keys from the terrain the frame is actually on, the way arena_lines.gd's scripted skier does: - *Hold the glide.* Above 12 m/s the Glide is held whatever the ground does. Releasing it puts traction on the next contact and brakes back toward the run cap, which gives back every metre of height the line just bought. EXP-16 already held a glide above 14 m/s, but only while the velocity stayed within 25 degrees of the wanted heading — on rolling ground with a route waypoint every 16 m that test failed constantly and every hop threw the speed away. The heading test is now 70 degrees: past that no glide can take the corner and the bot runs it. Below 12 m/s the EXP-8 rule still stands (descents along the fall line only), which is what stops a bot sliding off a traverse. - *Read the ground ahead along the velocity, not along the route.* _climb_ahead() is arena_lines' test, ported: sample the ground 8, 16, 24 and 32 m ahead along the horizontal velocity and compare it with where the ballistic flight will be. A rise the flight would land on is flown over instead of landed on. This is the one place the A* node list is not consulted: a glide has no steering, so where the frame is going is not where the route says to go. - *Leave the lip with the tank shut.* EXP-16 had a rule "crest: launch off the drop — burn". It is gone. Thrust at a lip floats the frame over the descent and gives back the height it came for; arena_lines never does it. A new descending test (the ground ahead drops, or the frame is falling, and nothing is rising ahead) shuts off the flats burst, the pursuit burn and the air-sustain burn. - *One burn ladder instead of six overlapping rules.* Each reason to jet now names both the tank it needs to start (burn_start) and the tank it runs down to (burn_floor): a wall climb waits for 75 % of the tank and burns to 4; a climb the line is about to hit starts at 10 and burns to 4; a steering correction (the lateral part of the move key vectors the thrust, EXP-11) starts at 25 and stops at 12; the flats burst starts at 30 and stops at 12. So a climb is never starved by a burst that emptied the tank on the flats, and a burst never degenerates into the trickle that pinned EXP-14's carriers at 12 energy. - *A leash that grows with the speed.* The open-field route lookahead was a flat 40 m — 0.9 s of warning at 45 m/s. It is now 1.4 x the speed, 40-90 m, so a gliding bot is not steered corner to corner. The arrival brake is speed-scaled the same way (a Glide has no brakes; 25 m at 45 m/s was half a second). - *Line choice inside the corridor* (_ski_line): of five headings within +-23 degrees of the route heading, take the one that gives up the most height over the next 24-60 m, and only if it is worth 11 m of drop. Kept because removing it costs 11 s on the Skerry carry run below; made aggressive (a 3 m or 7 m threshold) it costs 2-8 s, because it pulls the frame off the route into troughs it then has to climb out of. Choosing the *line* is mostly the A*'s job, which is the next change. 2. bot_nav.gd prices an edge in seconds, not metres. The old cost was distance x (1 + uphill) + distance x 2 x steepness + 400 m per wall, with a distance heuristic. On Skerry that priced the launch faces — 30-degree slopes, the one feature the whole map is built on — as a 2x sidehill penalty and routed around them. Now every edge costs d / v where v is the speed a gliding frame can hold on that slope (45 m/s down the fall line, 26 rolling, 10 on a runnable climb, 5 on a face that has to be flown), and the sidehill penalty only counts the part of the slope the edge runs across (|grad x edge|): sliding down the fall line is the line, sliding across it puts the frame on the floor. A wall is 15 s. The heuristic is the straight line at 45 m/s, which stays admissible, and MAX_ITERS went 12000 → 40000 to pay for the weaker heuristic (routes are cached and shared, so the tick cost is unchanged). 3. Harnesses. tests/bot_ski.gd (new) is the head-to-head with the scripted line: --carry puts one bot on the *enemy* stand with no opposition at all, it takes the flag and runs home, and the run is timed on arena_lines' convention (from more than 15 m off the stand it leaves to within 25 m of the stand it is going to, at roof height). Without --carry it runs four bots of one team through their real roles on an empty map. tests/bot_match.gd gained a speed block: the distribution of horizontal speed in the open field (more than 90 m from either stand) and the stand-to-stand crossings each bot completes alive. Both are pure observation; the match harness stays deterministic. ### Speed first (the thing the pass is for) A. One bot, no opposition, stand to stand (tests/bot_ski.gd --carry, 240 s, deterministic): the bot is put on the enemy stand, takes the flag and runs home. Timed on arena_lines.gd's convention, so the two rows are the same measurement of the same span. The scripted human line on Skerry is 13.5 s with a 54.2 m/s peak. Skerry: 1.94x the scripted line → 1.69x, peak 0.74x → 0.87x of it. The bot is still slower than the human line and never beats it, which is the invariant this pass had to keep. Tallow's single carry trajectory is 1.4 s slower while its speed distribution moves a long way (p90 26.9 → 51.3): the carry there is 925 m through a valley and the time goes at the two base ends, not in the middle. B. In a 16-bot match (tests/bot_match.gd, 240 s, seeds 1-6, per side; "open field" = more than 90 m from either stand; a "crossing" is one bot getting alive from one stand's 90 m circle to the other's, 360 m on Skerry and 745 m on Tallow): Over 12 sides per map: Skerry median 14.8 → 16.0 m/s, mean peak 48.0 → 48.6, crossings completed 79 → 134 (+70 %); Tallow median 17.8 → 18.6 m/s, mean peak 59.8 → 65.1, crossings 53 → 89 (+68 %). The p90 column is where the change lives: Skerry 24-28 → 29-33, Tallow 24-27 → 40-52. ### Objectives (bot_match, 16 bots, 240 s, seeds 1-6 both maps) Totals, 12 sides per map: Tick cost 1.70-2.74 ms with 16 brains (before 1.43-1.95); the machine was heavily loaded during the after runs, so treat the upper end as noise -- the extra work per think is four height samples for _climb_ahead plus the line-choice probe. combat_check 0 failures on Skerry and Tallow; standoff_check 0 failures on both; arena_lines 0 failures and unchanged (13.5 s, peak 54.2, floor 49.7 -- the Glide layer does not touch it); a 60 s 16-bot Tallow server replay is deterministic (1919 ticks, 59 checksums); bot_match --map=skerry --seed=2 --log twice gives an identical log apart from the wall-clock ms/tick line. ### Result, against the targets set for this pass - Cross-map speed, isolated: target met. Skerry median 30.6 m/s (target >= 25) and peak 47.3 (target >= 45) on the one-bot carry run, at 1.69x the scripted 13.5 s line (target: within about 2x, never faster). Before: 22.4 / 40.1 and 1.94x. - Cross-map speed, in a match: not met on the median. 14.4-18.7 m/s (before 13.6-18.9), p90 29-33 on Skerry and 40-52 on Tallow, peak 45-53. The match median is not the same quantity as the median of a bot that is only travelling: it includes the fights, the ramp approaches, the run-in and the roof patrol, all slow by design. The honest travel numbers there are the crossing count and time, and those move a lot (79 → 134 crossings on Skerry; seed 1 side 1 median 37.7 → 23.0 s). - Cap attempts: target met on Skerry, close on Tallow. Skerry 9 → 18 over 12 sides (0-2 per side per 240 s against a target of 3), Tallow 8 → 13. Caps 11 → 18 over the 24 sides. Carriers survive: 25 carrier deaths → 15 on Skerry, 24 → 11 on Tallow, and six sides finish a match without losing a carrier at all. - Pickups: not met, and Tallow went down. Skerry 37 → 37, Tallow 40 → 30 (target 6 per side per 240 s, i.e. 72). The reason is in the same table: mean carry time roughly doubles (Skerry 28 → 51 s, Tallow 52 → 60 s with two sides over 80 s) because carriers survive. A flag being carried cannot be picked up, so a livelier flag path with longer carries produces *fewer* grabs and more attempts and caps. Pickups per 240 s is the wrong meter for what this pass changed; attempts and caps are the ones that moved. - Handoffs: 2 → 3 over 24 sides, still rare for EXP-16's reason (the killer stands on the flag). ### What was tried and dropped, with the number - *A local line picker with real authority.* Bending up to 23 degrees toward the biggest drop ahead helps only while it is nearly inert (an 11 m threshold; removing it entirely costs 11 s on the Skerry carry run, 22.8 → 34.1 s). Lowering the threshold to 7 m costs 8 s and to 3 m costs 2 s: the bend pulls the frame off the route into troughs it then has to climb out of. Kept at 11 m; choosing the line properly is the A*'s job. - *A turn threshold that widens with speed* (32 degrees at 14 m/s easing to 78 at 40, on the argument that braking to turn is dearer the faster you go): Skerry carry 22.8 → 27.0 s, Tallow 51.8 → 58.3. Dropped; the flat 70 degrees is better. - *"Below the run cap on flat ground, run instead of Glide"* (a Glide has no propulsion either, so it hops in place): right in principle, measured worse -- Skerry 22.8 → 24.4 s, Tallow 51.8 → 58.3 s. Dropped. - *Keeping the Glide up the home ramp* when the carrier is on the ramp axis at speed: Skerry 22.8 → 30.1 s. Dropped; the ramp block still brakes to a run, which is what an 8 m ramp beside a 12 m wall wants.
Decision
keep. Travel went where it was supposed to (isolated crossing 1.94x → 1.69x the human line, in-match crossings +70 %), cap attempts double on Skerry and caps rise from 11 to 18 over 24 sides, and no check regressed. Next, in the order the numbers argue for: (1) the two base ends, which are now most of a carry -- leaving the enemy ring costs 6-8 s at 7-21 m/s (the exit gap points across the way home and the frame has to re-accelerate off the roof onto a flat pad) and the home ramp costs another 4 s of deliberate braking; both are ring/route geometry, not the movement layer. (2) The run-in's damage budget, still EXP-16's second wall: a large share of carrier deaths are 1-10 s after the grab. (3) Pickups will not move until carries get shorter or the flag returns faster, which is a rules question rather than a bot one.

63 lines of internal measurement detail omitted from this entry.

EXP-16 · 2026-08-30

Carrier survival, hunt arrival and wave overlap (Skerry Yard and Tallow)keep

Next from EXP-15: carrier survival through the gate, the hunt's form timeout scaled by travel, and waves that overlap so pickups stop serialising. Measured on Skerry Yard (540 m stands, the default arena) as well as Tallow (920 m). Targets: >= 6 pickups and >= 3 cap attempts per side per 240 s on both maps, seeds 1-3, at least one handoff, no regression in time-to-first-chaser. ### What changed (bot_team.gd, bot_brain.gd, harness tests/bot_match.gd; movement, world, net, frames untouched) - Climb reserve. EXP-14 banked 0.7 of the tank for the home climb anywhere inside 450 m of home at 1.3 energy per metre of lift. On a 540 m arena that is the whole carry: Skerry's roof is 54-62 m above the floor, so the carrier reserved two thirds of its tank from the moment of the grab, never burst on the flats and crossed at 9-12 m/s (traced: 60 s for 540 m against the map's 13.5 s Glide line). Now the reserve is the last 150 m only and is capped at a third of the tank; further out a climb burns down to 20 energy instead of 8, so the tank is never pinned at 10. - Keep a glide that is already fast. With the jump key released a ground contact applies traction and brakes back toward the run cap (~11 m/s), so every jet burst was thrown away at the next touch. Above 14 m/s the bot now holds the glide while its velocity stays within ~25 degrees of the heading it wants (the EXP-8 "descents only" rule still governs slower speeds: it is what stops a bot sliding off a traverse). The flats burst also starts at 32 energy instead of 40 and counts a descent as flat. - Egress by threat. One way home (plus a "back door" that differed only in the last 100 m) became five: front, back door, a middle line and one route through each flank (a via point 0.32 of the base-to-base distance off the stand-to-stand line - the Skerry side crests). escape_pick() scores every candidate over its whole length (EXP-10 looked at the last 250 m, the part they all share) at 260 m of detour per known enemy within 80 m of the route, and the carrier re-picks every 2 s with a 150 m margin instead of once at 300 m from home. The run out of the enemy ring bends toward home after 55 m instead of running 110 m straight out on the exit axis (on Skerry that axis points north while home is due west: a 100 m detour across the pad the defenders overlook). - Escort. Lead 18 m / trail 10 m (was 25 / 15: 40 m of gap for a chaser to drop into, and out of reach of the flag when the carrier went down). The body-block stands 16 m from the carrier on the line to the nearest known enemy within 150 m (was 20 m, 120 m). - Handoff (new). When our carrier goes down with the flag, the flag lies where it fell and whoever touches it first owns it (MachWorld._step_flags: an enemy touch is a grab, an owner touch a return), so the killer standing over the body returns it inside about a second. The coordinator picks the nearest teammate within 80 m and gives it a 3 s window in mode handoff: straight at the flag, no route, no fighting, jets on. Logged both ways - team N handoff: carrier X down, bot Y goes for the flag Z m away and handoff done - and the harness marks the pickup HANDOFF (t s after the drop) and counts a handoffs column. - Hunt arrival. The form timeout is now the farthest hunter's travel time (distance / 14 m/s), clamped to 12-30 s, instead of a flat 12 s from the first arrival, and the push also goes as soon as every hunter is there. A hunter that dies while the hunt is forming keeps an attacking slot at its next deal, so it respawns back into the hunt (heading for the enemy stand) instead of being dealt a defender/chaser slot and walking to the home rally. - Wave overlap. A wave now forms as soon as our carrier is past midfield, not only when the flag is back home, and presses the enemy gap until it lands (its clock does not start until the flag is grabbable). An attacker counts as ready anywhere within 260 m of the enemy stand, not only inside the 30 m rally circle 90 m off the route: on Skerry seed 1 the flag was home at 154 s and the wave only launched at 168 s, when the last two attackers finally touched the rally. - Two stalls found by tracing, both fixed in the bot layer: - *A flag dropped on a bunker roof was unreachable.* The 16 m A* grid never sees the 8 m ramp, so every bot below the roof pushed at the bunker wall. Skerry seed 1: flag 2 lay on team 2's own roof from 155 s to 200 s - the whole 45 s return timer - with six team-2 bots in mode return 30 m away and team 1's regrabber doing the same from the other side. Return, regrab, handoff and a runner going for a dropped flag now approach it through the ring gate (ramp foot → gate → flag), the same way a grab does. - *The run-in ignored walls.* The axis-tracking correction was the one direction never wall-checked, and a roof ramp is 10 m wide with a 3 m step at its side: a runner arriving 5 m off the axis is pushed into it. Skerry seed 2, bot 1012 stood at (227, 5) with hs 0.0 for 40 s of a 240 s match, in mode grab the whole time. The tracking now goes through _avoid_walls, and a run-in stopped for 1.5 s holds phase 1 for 3 s (flipping the phase for one tick put it straight back on the wall). ### Results (16 bots, 240 s; "before" is main @2275d72 under the same harness) Seed-to-seed spread is larger than the change, so the same seeds 1-6 were run on both maps for the totals (24 sides per row): Tick cost 1.6-2.7 ms with 16 brains (harness only, unchanged). combat_check 0 failures on Skerry and Tallow; standoff_check 0 failures on both; a 60 s 16-bot Tallow server replay is deterministic (1919 ticks, 59 checksums); bot_match --map=skerry --seed=2 --log twice gives an identical log. ###

Result
the two headline targets are not met. Measured, plainly: - Pickups 1-5 per side per 240 s (target 6). Best case 5, typical 3-4; the 24-side totals rise from 68 to 77 (+13 %). The gate is travel time, not the wave rules. Skerry's stands are 540 m apart and its Glide line is 13.5 s (40 m/s); bots cross at 10-15 m/s even after the reserve and glide fixes, so a full cycle (respawn or cap at home → cross → line up → run in → grab) is 45-70 s and a 240 s match holds four of them. Traced examples: spawn to the rally 34 s for 390 m; a wave runner 33 s in mode grab from the launch to the flag; a carry of 540 m in 46-64 s. Six pickups a side needs a ~30 s cycle, i.e. ~20 m/s sustained, which is a bot gliding pass of its own (glide/jet cadence over the dune field and the launch faces), not a coordination rule. - Cap attempts 0-2 per side (target 3), and they went down on Skerry (16 → 9 over 12 sides) while caps went up (4 → 7). Both moved for the same reason: before this pass a carrier that reached its own stand usually could not cap, because its own flag was lying unreachable on a roof for the full 45 s timer, so it parked there and was counted as an attempt (the before runs show carrier deaths at 0.5, 22 and 45 m from home). With the dropped flag reachable again, our flag comes home, arrivals cap instead of parking, and fewer carries are counted twice. Caps per 24 sides: 7 → 11. - Carrier survival at the gate is still the second wall. Carriers now die a mean 113-926 m from home instead of 22-780, but a large share of the deaths are 1-10 s after the grab, at the enemy stand, to the stand defender plus the chaser: a Strider that has taken 60-90 % damage on the run-in dies to two Impulse hits, and the review's order leaves aim alone. The escort body-block and the tighter lead/trail help the handoff, not this. - Handoffs happen and are logged, but they are rare (2 in 12 matches): the window is about a second because the killer is standing on the body. Skerry seed 4 is the clean one - t6975 team 1 handoff: carrier 1012 down, bot 1000 goes for the flag 19 m away, then t7014 (224 s) team 1 pickup by 1004 HANDOFF (1.2 s after the drop). - Hunt arrival improved where a hunt formed: hunt goes up the ramps with 2 of 2 (formed 6 s, farthest 89 m) instead of EXP-15's "1-2 of 4". Standoffs are rare on Skerry (1 in 12 matches), so this is measured on few samples. - Time to first chaser is unchanged: 0.0-0.1 s on every run, both maps, before and after.
Decision
keep. The flag path is livelier (pickups +13 %, caps +57 % over 24 sides), two stalls that each cost 40-45 s of a 240 s match are gone, the handoff exists and is logged, and the hunt forms with the group instead of without it. Next, in the order the numbers argue for: (1) a bot gliding pass - glide and jet cadence over rolling ground, measured against tests/arena_lines.gd's 13.5 s Skerry middle line, because every remaining target is bounded by 10-15 m/s travel; (2) the run-in's damage budget (arrive at the stand above 50 % health, or grab from the side the defenders are not on), because that is what kills carriers 1-10 s after the grab.

18 lines of internal measurement detail omitted from this entry.

EXP-15 · 2026-08-30

Named bot roles and the standoff return rulekeep

Owner's pick after EXP-14: fix the mutual-carrier standoff at the design level (option a, a return timer for a flag held at home) and give the bots explicit roles.

Hypothesis
with a 45 s ceiling on a standoff the flag path never stalls for the rest of the match, and a full-wave push onto the parked carrier ends most standoffs before the timer does; named roles make the roster readable (scoreboard, harness) and put the right frame on the right job. ### Roles (bot_team.gd ROLES / ROSTER) Roster by team size (dealt in the order defender, chaser, runner, escort, floater; sizes between two rows take the smaller row plus floaters): 4 = 1 runner, 1 chaser, 1 defender, 1 floater; 6 = 1 / 1 / 1 / 2 defenders / 1; 8 = 1 runner, 2 escorts, 1 chaser, 2 defenders, 2 floaters; 12 = 2 / 3 / 1 / 3 / 3; 16 = 2 / 4 / 2 / 4 / 4. A bot is re-dealt at every spawn: the first free slot, preferring one whose frame it already wears. HUNTER is an overlay on runner / escort / floater, not a slot. Frames: bots never chose a frame before (the server's roster gave every fourth bot a heavy frame and the coordinator made the heavies defenders). Now the brain sends the role's frame as the "frame" input; it applies in the first-spawn window (the bot stands still on the ground for 1 s, the same spawn choice a player has) or at a friendly inventory station (a bot in the wrong frame for its role refits at a station inside 100 m, unless the enemy carrier is out). The wave picks a RUNNER-role bot as runner when it is within 60 m of the nearest attacker, a floater as breaker, and the ESCORT-role bots fill the two carry escort slots. The server roster (net_server.roster(), the reliable roster RPC) carries a role string per bot (the hunter overlay when active, "" for players); the snapshot codec is untouched. ### Standoff rule (MachWorld.standoff_return, default on; --no-standoff on the server) A flag held within STANDOFF_HOME_M (30 m) of the captor's own stand for STANDOFF_RETURN_MS (45 s, the dropped-flag timer) while the captor's own flag is away returns home: the carrier loses it, event standoff_return flag N from ID, then the usual flag N returned. The timer resets when the carrier leaves the 30 m or its own flag comes home. Consequence, by construction: the first timer to expire hands the *other* carrier (parked at its own stand with its flag now home) the cap on the next tick, so the side that got home second scores; a standoff lasts at most 45 s after the first arrival. tests/standoff_check.gd covers the timer, the reset, the cap and the option off. ### Results (16 bots, 240 s; "before" is 2275d72's brains and rules under the same harness) (The EXP-14 table's Tallow 1 row, 2 / 2 pickups, does not reproduce at 2275d72 under this harness; the before rows here are re-measured at 2275d72.) Tick cost 1.5-2.4 ms with 16 brains. combat_check 0 failures gives the same log; standoff_check 0 failures. What the standoff rule changed: in the runs where a standoff formed it ended. Tallow 1 before is the EXP-14 picture (both carriers home by 135 s, one pickup per side, nothing for the last 100 s); Tallow 5 after is the rule at work: both carriers home at 140 / 145 s, hunts up both ramps at 157 and 168 s (each went with 1-2 of 4: the hunters arrive over 30 s from up to 900 m away and the 12 s form timeout starts with the first), flag 2 taken from the team 1 carrier at 185 s, team 2 caps on the same tick, pickups follow at 188 and 208 s. A standoff now costs at most 45 s of flag path instead of the rest of the match, and the hunt did not once kill a parked carrier inside that window (the carrier plus two defenders on the roof against 1-2 hunters at a time).
Result
targets still not met (Tallow pickups 2-5 per side, attempts 0-1; target 6 and 3). Seeds 1-3 never reach a standoff any more: every carrier of the first waves dies 430-780 m from home to the chaser and the stand defender (the same deaths as EXP-14's second finding), so the rule has nothing to break and the numbers are the run-in / carry survival numbers of EXP-14, which this pass did not touch (aim untouched by the review's order). The seed-to-seed spread (2-5 pickups, 0-1 attempts on both sides of the change) is larger than the change; nothing here is a regression the harness can see, and the frame swaps cost 1 s at match start (4 refits per team in the spawn window) and no station trips in these runs.
Decision
keep (the roles are the vocabulary the next passes need, the rule bounds the worst case). EXP-37 keeps the standoff until week 10 (owner, 2026-10-16) and as of 2026-10-20 is the carrier reveal only — MAC-608 shipped the scoring half (return 15). The named bot roles in this record are not that. Next: carrier survival (arrive at the gate with 40+ energy, an escort that actually meets the chaser), then the hunt's form timeout scaled by the hunters' travel time.

22 lines of internal measurement detail omitted from this entry.

EXP-14 · 2026-08-30

Objective gravity: no rally wait, press the stand, regrab, hunt the standoffkeep

Design review verdict on the bot match: ~1 cap per 4 min is not enough for a solo playtest; wanted >= 3 contested cap attempts per side per match with escorts on the carrier and a chaser on the human carrier within ~2 s, in the order objective gravity > escort/chaser > strip pressure > spawn/wave cadence > readable air rules, aim untouched.

Hypothesis
the waves idle (wait for a full wave at the rally, wait 30 s for the "soft" defence, all go home with the carrier, idle at the rally while our carrier is out) and every idle is a lost grab; take the idles out and pickups and attempts follow. Harness (new, tests/bot_match.gd): two teams of 8 bots (the server's roster and frames), 240 s at 32 ms ticks, deterministic (world seed + brain seeds; two runs of a seed print the same event log). Per team: flag pickups, cap attempts (the carrier got within 30 m of its own stand), caps, kills, deaths, flag returns, waves, stand approaches, time from an enemy pickup to the first defender in chase / intercept / block mode and to the first one within 40 m of the carrier, carrier deaths (mean distance from home) and mean carry time. --log prints the flag/cap events with the carrier's killer and the coordinators' wave log, --verbose every bot's mode every 5 s, --trace=ID one bot per second. godot --headless --path game -s res://tests/bot_match.gd -- --map=tallow --seed=1 [--log] [--verbose] [--trace=ID]. Implementation (bot_team.gd, bot_brain.gd, bot_nav.gd; movement, world, net, frames untouched): - Waves launch with whoever is ready: two at the rally go at once, one goes alone after 5 s; late arrivals join the running wave as escorts; the runner's hold for a "soft" defence is 8 s (was 30); a wave times out at 40 s (was 60). Ready = at the rally or already pressing the enemy stand, and the runner is the ready attacker nearest the stand. - Press: while our carrier is out, attackers not escorting it stand off the enemy stand's entry gap (spot, shell, draw the defenders) instead of waiting at the rally, so the flag is grabbed the tick it comes back (seen: a pickup on the cap tick). Regrab: the enemy flag lying in the field is the nearest attacker's, no wave (in practice the killer returns it within a second, so this rarely fires). - Escort: the wave's two escorts (breaker first) stay assigned for the whole carry (one leads by 25 m, one trails by 15 m; nobody else joins), released when the carrier is home. The shelf defender no longer pursues beyond 100 m: the carrier meets the chaser and the stand defender, not three. - Standoff (both carriers at home, neither can cap): attackers within 300 m of the enemy carrier hunt it; a carrier on its own stand is reached like a grab (ramp, gate) after gathering at the line-up point (3 there or 12 s), the rest keep pressing where the flag will return. - Movement fixes found on the way, each worth more than the wave rules: (1) the flats jet rule wanted the ground under the bot, but a glide hops on every contact, so attackers crossed the Tallow floor at 15 m/s on a full tank: now burn on the flats above 40 energy down to 25 (22 m/s target). (2) The run-in hunted 12 s between phase 1 and 2 on a 4 m cone at the line-up point (cone widened to 3 m + 5 %, commit inside 40 m stays 4 m), and a runner pinned on the wall beside the ramp burned a trickle at 5 energy for 40 s (burn starts above 15; 1.5 s stopped on the ground backs out to phase 1). (3) The team's rally route starts on our own roof and its first waypoints cross the ramp's side: a bot spawned behind bunker 1 joined it across the wall and zigzagged there for two minutes (seed 2 bot 1012, 48-173 s). Team routes are joined only when the walk to the waypoint is wall-free, two stuck episodes inside 20 s drop the route for 30 s, wall avoidance keeps its side (the first open candidate alternated sides every think), and the A* prices walls on every edge, not only climbing ones (an edge across a 10 m outbuilding that lands back on the pad had no net rise and no cost). (4) Carriers burst (start above 30, down to 8) and coast instead of trickling at 12 energy, and keep a reserve for the climb home (1.3 energy per metre of lift inside 450 m). Results (16 bots, 240 s; "before" is 2275d72's brains under the same harness): First pickup on Tallow: 91 / 93 s before, 59 / 62 s after (rally at 41-44 s, was 43-52; line-up to grab ~15 s, was 26). Tick cost 1.5-1.9 ms with 16 brains (harness only). combat_check 0 failures on the same log.
Result
targets not met. Tallow pickups 2-4 per side (target 6), cap attempts 0-1 (target 3); the Proving Ground gets 4-5 pickups and 1-3 attempts. The time-to-first-chaser target holds everywhere (a defender at the stand flips to chase on the grab tick; one is within 40 m inside 0-7 s). What still caps the numbers, from the traces: - The standoff. Both first waves grab at ~60 s; both carriers get home at 115-140 s (55-75 s carries on a 920 m map, the last 300 m a climb) and then neither can cap. Killing a carrier on its own roof behind two defenders takes the hunt 20-120 s (seed 1: 60 s to 177 s, the whole middle of the match with one pickup per side). Every gate on pickups sits behind this: the flag is only grabbable when it is home. Nothing in the bot layer resolves it cheaply; it wants a design answer (a carrier that must keep moving, a return timer for a flag held at home, or a map with shorter carries). - Carriers that do not reach the standoff die 6-24 s after the grab to the chaser and the stand defender pursuing by air at 40-70 m (both as fast as a Strider carrier, which arrives at the gate with 15-20 energy after the run-in), or to the enemy attackers staged on the route home. Two escorts do not stop that without better shooting, which this experiment leaves alone. - A regrab is never available: the killer stands on the flag and returns it inside a second.
Decision
keep (the idle removals and the movement/route fixes are strictly better: earlier first grab, more waves, no two-minute spawn trap, deterministic harness for the next pass). Next: the standoff rule at the design level, then re-measure; a carrier energy budget through the gate (arrive with 40+); a carrier route that skirts the enemy rally. ### EXP-14 follow-up · owner note Targets (≥6 pickups, ≥3 attempts per side per 4 min on Tallow) not met; chaser-within-2-s met. The blocker is the mutual-carrier standoff (both flags out, neither cappable). Design options for the next pass, owner to pick: (a) held-flag return timer — a flag carried onto its captor's stand while their own flag is out returns after N s unless the carrier keeps moving; (b) shorter Tallow carries (base ring gap / stand placement); (c) bots commit a full wave to the enemy carrier on its stand instead of a 2-man ramp push. Solo human play is not affected in the same way (the human breaks the standoff), so the TITAN feel test can proceed.

11 lines of internal measurement detail omitted from this entry.

EXP-13 · 2026-08-30

Frame identity: Strider lateral, slower Ranger/Bulwark, bigger Bulwark life pool

Chris after the arena build ("so much better"): Strider still needs a bit more lateral; Ranger and Bulwark should be slower, Bulwark the slowest with a large life pool. Changes (frames.json, reference → new): Strider lateralJetControl 1.10→1.20, floor 0.75→0.80; Ranger run 12→11 m/s, jet-forward cap 25→22, jet horizontal share 0.5→0.45, lateral 0.72→0.65 (floor 0.5), maxDamage 1.1→1.2; Bulwark run 7.5→6.5 (back/side 5→4.5), jet-forward cap 20→17, share 0.3→0.25, lateral 0.48→0.40 (floor 0.3), maxDamage 1.32→1.70. Reference traces for the Strider unchanged. The RUN MAXIMA of this experiment are superseded by parity, 2026-10-09 (MAC-415): the owner's north star of again — Ranger 12/10/10 (EXP-13 had 11, back/side untouched) and Bulwark 7.5/5/5 (EXP-13 had 6.5 and 4.5/4.5) — which is what the engine capture measures (CM-A11F flat10_run_fwd on a true plane: 15.00 / 12.00 / 7.50). The override is removed from tools/build_tuning.py's post-pass, not commented out; to reinstate it, put maxForwardSpeed back in ### EXP-13 addendum · 2026-08-30 · Ranger lateral 0.65 → 0.75 (floor 0.5 → 0.55) Design review (research/2026-08-30-review3-grok.md Q2): the EXP-13 spacing put Ranger too close to Bulwark on air authority — "mid" read as a slow brick rather than the all-rounder. Strider 1.2 and Bulwark 0.4 kept. Validate on Proving Ground with the three role tests in docs/REVIEW_2026-08-30_reconciled.md. ## TODO · 2026-08-30 · Wall resolution vs the Glide hop cycle (review 3, code lane 4) Not changed yet. MachWorld.resolve_ground (mach_world.gd ~269-291) classifies walls from the current foot penetration, the previous footing and a steepness heuristic, which can turn a high-speed step contact into a hard stop; it is brittle against the 32 ms hop cycle and the 8 m / 2 m height layers. Planned experiment: sweep horizontally against the heightfield + structure layer in substeps, separate "step up" (<= ~1 m ledge) from "wall slide", and add regression cases for a shallow ramp, a 1 m ledge, a 2 m bunker lip and a high-speed side scrape to combat_check. Deferred because it changes movement feel; do it as its own EXP with traces.

2 lines of internal measurement detail omitted from this entry.

EXP-12 · 2026-08-29

Faster weapon switch + instant switch feedback — RETIRED 2026-10-17

**RETIRED 2026-10-17 by the owner's 2026-10-16 ruling; the record is EXP-38 below, the change is again. Labels 2–4 confirmed it on origin/lane-b13-label4 (9ddd4e13): the Vector Cannon's Ready-in-a-tick machine, not a second scale. What is retired is the NUMBER and nothing else: the half of this entry that is feedback — the per-event key handling, the weapon bar, the progress arc, the 50 ms fire buffer, the restart on a re-switch, the lowered view model, the cues, the wheel and Q — all stay, because they fixed dropped presses and a blind switch and they are not a number. Re-openable as a recorded change after the game is fully operational. Feedback (Chris, same playtest): "switching weapons isn't intuitive, I don't know what weapon I have until I shoot, weapon changing isn't instant or smooth". Two causes: the client polled the 1-5 keys from the 32 ms tick, so at 60 fps about half the presses landed on a frame without a tick and were dropped (fixed: keys are handled per event); and the reference draw (Activate 0.5 s, plus Preactivate on the launcher) with nothing on screen until the new weapon fired.

Hypothesis
a 0.28-0.35 s input-to-fire-ready commit with visible progress reads as instant without making the switch free.
Implementation
per-weapon switchTimeScale in tuning/weapons.json (one tunable, 0.6 for all eight; tools/build_tuning.py writes it), applied in MachWorld._image_set_state to the Activate/Preactivate states only; a fire press in the last 50 ms of the draw is buffered into the ready frame (FIRE_BUFFER_MS); re-switching mid-draw restarts the draw on the new slot (where the image allows a change; the Vector cannon's Activate does not, so that one queues). Client: weapon bar (slots 1-5, ammo, pack on R), the requested slot with a progress arc that tracks the replicated img_delay_ms, pulsing while the sim refuses (Fire/Reload), the view model lowers on the request and comes up with the draw, commit and ready cues (tools/gen_sfx.py), wheel cycles and Q is the last weapon. Telemetry (combat_check, before → after): Vector cannon from the launcher with the trigger held, shots in 1.5 s: 3 → 5 (switch 0.5 → 0.3 s, spinup 0.5 s, 150 ms cadence); the spindown re-press check in the same window 2 → 1 shots (pure phase shift: the last burst ends 2 ticks later, spindown 1 s + spinup 0.5 s complete at tick 98 of 102); grenade launcher request → first shot 12 ticks = 384 ms (was ~20 = 640 ms); a tap 120 ms early is dropped, a tap inside the last 50 ms fires on the ready frame; the Fire/Reload switch lock and everything else unchanged (0 failures on both maps).
Decision
ship for the next playtest with the bar; lock switchTimeScale once the bar has been played (candidates 0.5-0.7), then it stops being an experiment. WHAT IT COSTS IN PARITY, measured 2026-10-12 (MAC-528, oracle batch 82 / CM-W2B). The reference's switch table is now captured, and it is one number for every weapon: first round after the switch 0.46-0.54 s on all five of Impulse Launcher, Plasma, Grenade Launcher, Mortar and Blaster, which the capture itself reads as "Activate 0.5 s plus up to a tick". With switchTimeScale 0.6 ours is 0.384 s on all of them - 0.19 s early, and that is the the capture's column. The refire column (Impulse Launcher 1.79, Plasma 0.77, Grenade Launcher 0.96, Mortar 2.85, Blaster 0.38, Vector Cannon 0.16) carries no deviation at all and matches flat. Both columns were re-cut on 2026-10-19 by MAC-624 and neither changed the decision: the 0.576 s above carried two 32 ms ticks of our own state machine (the mount tick and a tick in a zero-timeout state), which the reference does not, so the first-round column is 0.512 s on all five and the refire column is Impulse Launcher 1.79, Plasma 0.74, Grenade Launcher 0.93, Mortar 2.82, Blaster 0.32, Vector Cannon 0.16 - a tick shorter per cycle and closer to every captured window, the Blaster's for the first time inside it. switchTimeScale is untouched. tests/weapon_switch_check.gd asserts both columns and prints the decomposition every run, so the decision is now a one-line change with a gate behind it: set docs/GAME_FEATURES_VS_T2_2026-10-01.md (it is listed MATCH there today, which is the one thing that is wrong either way). It is the owner's call, not a bug: this is Chris's own playtest note being served. Also in this change (loadout confusion, "I don't know how to change frame"): the L overlay with the three frames, blurbs and both presets; the choice rides in every input and applies at a station (1 s bar + REFIT flash) or inside the first-spawn window (SPAWN_GRACE_MS 6 s, frame strip at the top, default Strider; respawns keep the last applied loadout); station beam markers + a compass marker with the distance.

5 lines of internal measurement detail omitted from this entry.

EXP-10 · 2026-08-29

Rally off the route, the way home by known enemies, two escorts, side intercepts, air fightingkeep

Hypothesis
EXP-9's carriers died in the enemy wave staged on their own approach; move the rallies off the routes, let carriers pick the way home by what the team knows, give them two escorts and side-intercepting chasers, and bots cap. Then (owner feedback from a live match) bots must fight and chase in the air and while gliding. Implementation (bot_brain.gd, bot_team.gd; main's EXP-11 / EXP-11b lateral jet control merged in before measuring): - Rally 90 m off the attack route, on the side with terrain cover from the enemy stand (LOS test from 2 m above it), off the stand-to-stand line, on flat ground (both rallies come out "covered" on both maps); the rally route is its own A*. - Two ways home per team (front: entry side; back door: exit side, where 75 m out is not a flank); the carrier picks at the grab and again inside 300 m of home by the known enemies (marked for us or within 150 m of a live teammate) within 80 m of the last 250 m of each way. Carriers burn the descents to 40 m/s. - Waves of four (breaker, runner, two escorts); an escort with a known enemy inside 120 m of the carrier stands 20 m out between them (one each flank of that line), shooting. - Chasers and (now) both defenders intercept 35 m off the carrier's route on their own side, ahead of it, never inside 60 m of their own rally; pursuit, interception and escort burn below 24 m/s. - Fighting: with an enemy inside 60 m a bot never holds: it strafes across the enemy's line (side swapped every 0.7 s, downhill side preferred), keeps the objective's pull, closes outside 35 m and opens inside, and jets the strafe (lateral jet control). Design-review rules: (1) lead is the intercept from target velocity and flight time (as before), aimed a touch high-side when both are airborne, and a burn for a small altitude edge over an airborne enemy; (2) energy as a state machine: engage above 40 % of the tank in 0.3 s bursts, panic below 15 % (jets off, glide for speed, no fire) until back above 40 %; (3) disengage on geometry: panic energy, the enemy 6 m+ higher and closing at 5 m/s while we are under 40 %, or a second enemy inside 60 m; exit downhill on a glide out of their cone, no fire. - Tick cost: the terrain LOS test per target is cached 5 ticks, and a bot thinks every other tick (repeating its input on the off tick) except in the precision moments (run-in, inside a ring, the Matches (16 bots, 240 s, four in parallel). EXP-9 final = the "before" row. the ring) and 2/2 on Tallow. Time to the rally 45-73 s. are in range (28 / 27; 12.3 / 11.2 m/s against the 12 m/s target, one seed short); the cap targets still fails: pursuing by air (10-35 m away): the air pursuit works, on both sides, and a Strider that has just burned out of the ring (energy 9-12) has nothing left for the duel. The carrier that got clear capped after a 94 s trip. - Tallow: all waves grab and clear (4/4 per match) but only one carrier per match gets up its home ramp; the others still meet the enemy wave at 60-80 m (the 90 m rally offset sits inside the 100 m fire range) or run out of energy on the pad blend. Kills stay under 12 because the teams meet only there and at the stands; combat speed is dragged down by the defenders holding on the stand (25-31 % of the combat samples are under 4 m/s, almost all of them stand and shelf defenders with an attacker at 40-60 m).
Decision
keep (caps from 0 to 1 per match everywhere, tick cost under target, no regressions). Next: the carrier's energy budget through the gate (arrive with 40+ and take the exit on a glide, not a burn) so it can duel the pursuit; a defender who moves on the stand instead of holding a point; the rally 120+ m off the route on Tallow.

16 lines of internal measurement detail omitted from this entry.

EXP-11b · 2026-08-29

Lateral share numbers from the design review

Grok: keep the per-frame lateral lever, no free air-strafe; Strider 1.10 / Ranger 0.72 / Bulwark 0.48; shape by speed (full below 35 m/s, easing to a floor of 0.75 / 0.55 / 0.35 at 75 m/s); ticks 0–2 off the ground ×0.4 to stop hop wobble; lateral thrust costs the normal jet energy. Applied as lateralJetControl / lateralJetFloor. A/B for the next playtest: (A) these numbers, (B) Strider 1.0 with a 10% lateral energy discount.

EXP-11 · 2026-08-29

Lateral jet control per frame (first deliberate departure from the reference)

Feedback (Chris, after winning a match on Tallow): a Strider needs too much effort to move left/right with the jets; heavier frames may stay stiff.

Hypothesis
the reference vectoring (horizontal share tapered by forward speed, vertical-only for 9 ticks after leaving the ground) gives no lateral authority at speed.
Implementation
the part of the move input perpendicular to the velocity gets the frame's horizontal share × lateralJetControl (Strider 1.0, Ranger 0.7, Bulwark 0.5) from the 3rd tick off the ground and is not tapered by forward speed; the forward part is unchanged. Tests: movement_acceptance M7 lateral cases; reference traces unchanged (jet_forward 2.35, Glide 1.73, run_up 0.81 — forward-only inputs).
Decision
ship for the next playtest; tune the three numbers on feel.

EXP-9 · 2026-08-29

Grab at speed, glide only along the fall line, the way home by the rampkeep

Hypothesis
EXP-8's two diagnosed causes (the glide rule slides bots off every traverse; the runner arrives on foot and spends 5 s at 5-10 m from two defenders) are what stops caps; fix them, then make the defenders beatable, and bots cap. Implementation (bot_brain.gd, bot_nav.gd, bot_team.gd, new harness tests/grab_check.gd; mach_world.gd, mach_movement.gd, the map generator and the ring defenders + 1 chaser + 5 attackers): - Glide (jump held) only when the slope under the bot runs along the direction of travel and down (gradient from normal_at, dot with the move direction > 0.6); flats as before; never on a traverse or a climb (run + burn with a 20/6 reserve). A glide has no steering at all (no traction, no air control without jets), so it is also released whenever the velocity is more than ~25 degrees off the wanted heading. Routes price sidehill cells (bot_nav.gd: +2x by the steepness measured over +-24 m), so they keep ~24 m inside bench edges. - Grab at speed: the runner lines up on the entry axis 60 m out (Tallow: 25 m past the ramp foot, 79 m), then the run-in owns the controls: along the axis with jet-vectored lateral tracking (run key when > 2.5 m off, > 1 m inside 35 m), glide keeps the speed, jets pulse below 22 m/s and lift straight through at speed; 90 degrees apart (Tallow) run via a turn point 3 m out on the exit axis. Committed inside 40 m (no retreat to the line-up point). Exit gate, 110 m out, then home. - Waves: the runner goes when the wave launches (no breaker stage; it never got to the Tallow ring in 40 s), holds only while >= 2 healthy enemies are within 25 m of the stand (30 s at most); a wave launches only for a free flag; the wave's escort leads the carrier by 25 m. - Found on the way, each worth more than the headline changes: (1) bots impulse-jumped themselves to 26 % health (a self-impulse is 0.5 x the frame's 1.5 Impulse scale = 0.74) so every attacker arrived at the stand one hit from death: off. (2) Nearest-waypoint pursuit steered bots back to the waypoint behind them on every slope (breakers hunted between two route points on the pad blend for 40 s): the route index is monotonic now. (3) The 16 m A* never sees an 8 m ramp: a route home ended against the 12 m bunker wall; the escape route now ends line-up (20 m before the foot) → foot → gate → stand with damped axis tracking, and comes in by the exit-side ramp when the ground 75 m out there is not a flank (Tallow team 2). (4) Rally points on a slope (both Tallow rallies sat on the bar's flank; attackers slid 30-66 m to the floor) are now scored by steepness. (5) Carriers crawled the Tallow floor at 12 m/s on a full tank, saving it for a home climb that is a runnable ramp: they burn below 24 m/s the whole way. (6) A 55 degree step read as a wall; the pad blend is runnable: 63 degrees. (7) Stand defender chases only inside 40 m; the shelf one never. Harness (godot --headless --path game -s res://tests/grab_check.gd -- --map=M --team=T [--defenders=2] [--runs=N] [--trace]: one attacker dropped at its rally with the wave already running, traced to the After: Matches (16 bots, 240 s, --seed=N, four in parallel; "before" is EXP-8's final brain on this branch): Tallow 55-77 s (target 60 s; Tallow seed 2's team 1 spawns furthest from its rally). at the end; Tallow 0), and Tallow kills (7-9) are below 20; grabs and clears are up a lot (Tallow: go through the ring in 1-2 s and mostly unhurt). What still fails, from the carry endings in the telemetry (x,y,z are Godot): - Tallow: every carrier dies 90-170 m from home, at 5-25 m/s, with 2-3 enemy attackers 13-60 m away: the enemy's rally point is 120-180 m from our stand on the attack route, which is our way home, so the carrier runs into the next enemy wave staging on our own pad approach (seed 1: bot 1003 died 88 m from home with 1006/1008/1012 at 49-63 m; bot 1010 113 m from home with three at 21-37 m). The exit-side route home (75 m back door) does not clear that group on team 1's side, where the exit ramp faces a 45 degree flank. The escort leads by 25 m but one Strider does not stop three. home) and the stand defender still gets its one point-blank shot at the gate (3-4 "runner died" per match, always inside 2 s of the grab). - Kills on Tallow stay under 10: the teams only meet at the stands and at the rally-side approach. above the 4 ms target; the defenders' 120 m scans and the per-tick normal_at for the glide rule are the additions since EXP-8.
Decision
keep (grabs, clears, route and ramp fixes are strictly better and combat_check holds); caps stay open. Next: put the rally 60-80 m off the attack route (it is the enemy carrier's route home), route a carrier around a staged enemy group, and give the carrier a two-escort wave; on

28 lines of internal measurement detail omitted from this entry.

EXP-8 · 2026-08-29

Bot team coordinator: waves through the ring (Spot → Close → Soften → Secure)keep

Hypothesis
EXP-7's solo grabbers die to two defenders within seconds; a team that spots, closes together, softens the defence and secures the exit should cap. Implementation (bot_team.gd new, bot_brain.gd): - Coordinator per team (one object per world/team, updated once per tick by the first bot that thinks). Roles are dealt at every spawn from live teammates only: 2 defenders (heavy frames first), 1 chaser, the rest attackers. A rally point is the attack route's waypoint 120–180 m short of the enemy stand. - Waves. When ≥ 2 attackers are at the rally (30 m) with > 60 % energy and > 60 % health (after 10 s, whoever is there; a third attacker is waited for up to 15 s as escort) a wave launches: the healthiest is the runner, the next one with impulse rounds the breaker, the rest escorts. Breaker: stands off the ring's entry gap at 16 m, strafes, holds the spot key ("spot": true, marks the nearest defender for the team; marked enemies count 30 m nearer in everyone's target choice) and shells the ring. Runner: closes along the route to 100 m, holds until 2 s after the breaker is inside 50 m (or dead), then enters gate → centre → flag and leaves through the far gap; escorts trail the runner by 20 m; once the flag is up, every attacker within 200 m escorts the carrier. Between waves attackers regroup at the rally; an attacker with an empty launcher walks back to a station. Waves end on a death, a grab that clears 120 m, or 40 s. - Ring geometry from terrain.struct_meta["rings"] (Godot vectors): entry gap = the gap nearest the enemy bearing, exit = the other, shelf = the tower lip. Tallow bunkers add ramp feet (probed along the gap axes: a smooth drop of > 10 m within 80 m) so the way up is ramp foot → gate → centre. Canonical team routes: attack_path (stand → stand, its tail replaced by foot/gate/centre), rally_path (up to the rally), escape_path (centre → exit gap → 70 m out → A* home); a bot that strays > 100 m rejoins the route at its nearest waypoint instead of re-planning from a snapped origin. - Defenders: one on the stand (patrol inside the ring, facing the entry gap), the second on the shelf; both engage at 120 m (fire inside 100 m) and pre-aim at marked enemies they cannot see; the stand defender chases a carrier only inside 80 m. Chaser: v3 route-ahead intercept, and body-blocks the exit gap while an intruder is inside the ring and the chaser is within 60 m of home. - Nav fixes found on the way: route goals other than the stands were snapped to a 160 m grid, so bots parked on a snapped route end (a roof, a pocket) 80 m from their target (_route(..., exact_goal)); walls are now a step of > 2.2 m within 1.5 m, so Tallow's 45° flanks are climbed (runnable, jet-assisted) instead of waited out as walls; arrival at a ring gate is on foot (the 6 m gap cannot be taken at 26 m/s airborne). Telemetry (16 bots, 240 s, --server --bots=16 --duration=240 --seed=N --telemetry; the "4x" rows ran four matches in parallel on one machine, which inflates the tick times by ~0.5 ms): attackers at the rally with full tanks), the breaker reaches the entry gap and holds it for 10–30 s, the runner enters on foot through gate → centre, grabs, and leaves through the far gap; the escort trails it. Grabs per match (a grab is about three flag events) are 1–4, as in v3, waves carry spot marks (58–118 mark events per match, was 0 from bots), kills sit at 28–37 (target 30–80: met), and the tick cost is 3.3–3.9 ms single-run (v3: 3.25; the target of < 3.5 is marginal, the extra is the defenders' 120 m target scans and per-tick mark lookups; the coordinator itself is a few tens of microseconds).
Result
acceptance for caps (≥ 3) is not met on either map. Where it fails: defenders (kill <runner> by <defender> at the stand in every wave of both seeds). The ring is 8 m across with 6 m gaps; a runner on foot spends ~5 s at 5–10 m from a defender who kills a Strider in two impulse hits. Softening does not happen: the breaker at the gap trades with defenders that have 1.1–1.32 damage frames and the shelf, so the "defence soft" go condition almost always falls back to the 12 s timer. The one carrier that got 80 m out (seed 2, t5759) was killed by a defender firing at range, which is the posture the task asked for. Two defenders in a ring beat any wave of Striders that must enter on foot; the next lever is not coordination but the ring's geometry or the defenders' frames/ammo (an experiment on base_ring.py: wider gaps or a lower wall so the runner can take the gap at speed). - Tallow: attackers reach the rally in 50–200 s, so 1–4 waves per match. The route from spawn to the enemy bench is ~950 m with two 45° flanks; bots that fall off the route re-plan from a snapped origin and A* with a 6x slope penalty sends them 900 m along the valley floor instead of 100 m up the flank (bot 1007, t3s2: within 36 m of the rally, then a 950 m loop east). The rally now follows the canonical attack route and strays rejoin it, which cut the loops but the flank climb itself still costs a tank of energy per attempt. Kills 14–18 (below 30): the teams rarely meet. for 76 s per match); no change since EXP-7.
Decision
keep the coordinator and the nav fixes (grabs and marks up, kills in range, no regressions in combat_check on either map); caps remain open. Next experiment: ring geometry (EXP-9), then re-measure. ### EXP-8 follow-up · Tallow reachability (rings regenerated with 10 m gaps on main) Changes: A* uphill cost capped (bot_nav.gd: d * (1 + min(slope, 1.5) * 1.5), was 1 + 6*slope + 8 above 0.9, which priced a 100 m flank above a 900 m valley detour; the stand-to-stand route is now 1034 m for a 926 m straight line and runs along the bench); staged ramp approach (to the ramp foot, then straight up the 8 m axis to the gate, back to the foot when 5 m off the axis; 6 m route lookahead inside 110 m of a ramped stand); grab at speed when lined up with the entry axis (jets on, no on-foot arrival), carrier burst to 60 m out; flank climbs burn with a 20/6 energy reserve instead of waiting for a 75 % tank; the coordinator logs time-to-rally and approaches (BOT_TEAM_DEBUG=1). Measured (16 bots, 240 s, four parallel processes): Not met: the 60 s time-to-rally, and no approach at all. What the traces show (bot 1006, seed 1): the canonical route leaves the bench along x = -160 for 560 m; attackers Glide or jet off the bench edge onto the 45° flank within the first 15 s, slide to the floor 200–300 m east of the route, and every re-plan from the floor to the rally (exact goal) is a fresh A* that crosses the valley floor sideways (x from +177 to -164 and back) around the bar; a bot that climbs back onto the bench slides off again. The flank is runnable in isolation (a Strider walks 90 → 170 m in 8 s in MachMovement, see the flank probe), so the loss is steering: the Glide/glide policy holds the jump key whenever the ground ahead falls away, which on a 45° flank means no traction and no turning until the floor. Map parameter, if changed: the valley flank angle is set in tools/gen_tallow_reach.py by the bench band BENCH_A0, BENCH_A1 = 360.0, 400.0 against FLOOR_HALF = 200.0 (line 164: the flank is a smoothstep over BENCH_A0 - FLOOR_HALF = 160 m for an 88 m rise, so its mid-slope is ~45°). BENCH_A0 = 460 would make it a 260 m, ~27° flank that the glide rule tolerates (EXP-1/2 hills are ≤ 30°); the bases stay where they are because the bunker pads are placed by BASE_U/BASE_A, not by the band. The alternative without a map change is a bot-side "no Glide on slopes steeper than 35°" rule plus a route that stays 30 m inside the bench edge; neither was done in this pass.

18 lines of internal measurement detail omitted from this entry.

EXP-7 · 2026-08-29

Bot brain v3 (roles, routes, stand defence, carrier escape)keep

Hypothesis
the 8v8 bot match is dead because the server deals roles by global index while teams alternate (team 1 all cappers, team 2 all defenders/chasers), bots pile into building walls and tower pockets, defenders and chasers loiter at their spawn, and carriers die inside the enemy base. Implementation (bot_brain.gd, new bot_nav.gd): roles re-dealt per team with heavier frames defending (3 cappers, 2 defenders, 2 chasers, 1 escort); A* with 4 m sub-sampled edges that prices building walls as detours, a binary-heap open list and a height cache (33/9/20/11 ms per base-to-base route, was 79/78/98/40); one route kept per bot, origins and moving goals snapped so 16 bots share cached routes; wall avoidance, height-aware waypoint choice, a progress-based stuck breaker, and a bounded "recharge, then climb" rule for unavoidable walls; defenders patrol the stand roof within 15 m facing the route's entry gap and return dropped flags; chasers intercept on the base-to-base route ahead of the carrier; escort trails the carrier, or leads the capper in at the enemy stand to draw the defenders' fire; refit at friendly stations (health < 50 % or no primary ammo, 60 s cooldown, only stations at the bot's level); carriers burn out through the back gap, save energy for the home climb, jet-cushion hard falls and jink under fire; cappers grab at speed; aim error 3 deg and 30 % lead misjudgement (the exact lead hit 41 % of Impulse Launcher shots). Telemetry (16 bots, 240 s, --server --bots=16 --duration=240 --telemetry): Intermediate v3 runs during tuning: 35 kills / 0 caps, 29 kills / 12 stand approaches, 23 kills / 1 cap, so kills sit at 18–35 by seed. Bot cost: brain 2.1 ms/tick of which nav 0.3 ms (was 8.3 / 6.1 before the route fixes). All 16 bots leave their spawn point inside 5 s in every run (min displacement 3–10 m; a defender that spawns at its own stand stays by design).
Result
loitering at spawn is gone and the roof defence, interception and refits work as specified, but the acceptance targets for caps (>= 3) and kills (30–80) are not met, and two bots still average under 8 m/s: - Caps. Both stands sit on a ground-level plateau between two 17 m blocks, open on two sides; the route enters through one gap and both defenders patrol within 15 m of the stand facing it. Cappers now reach the stand 10–19 times a match (was 1–2) but every grabber dies on the plateau within 1–9 s of the grab, at 0–10 m from a defender, having arrived with 60–90 % damage (Impulse hits, 0.385 each; two kill a Strider). A solo capper cannot survive two Vector Cannon at 10 m for the ~5 s a grab takes. The escort-leads tactic did not draw enough fire. Next: coordinated waves (cappers rally and enter together), or a defender posture that engages from range instead of point-blank. - Kills. Fewer point-blank executions of runners, more fire at range with 3 deg aim error; 18–35 by seed. - Speed. The Bulwark (7.5 m/s run cap) and Ranger defenders average 3–7 m/s: a heavy patrolling a 15 m circle cannot average 8 m/s, and one heavy chaser per match is still trapped in the pocket south of the team-2 tower (engine 56, 264) after spawning on the tower roof and falling in: its damaged frame recharges at ~0.3 energy/s, so it cannot jet out and the pocket's only exit is behind it. Next: move the tower spawn off the roof or pathfind the pocket exit at 8 m resolution.
Decision
keep v3 (strictly better traffic and no spawn loitering); caps need the coordination pass above.

5 lines of internal measurement detail omitted from this entry.

EXP-6 · 2026-08-29

Lag compensation for hitscan and meleekeep

Implementation
the server keeps 16 ticks (512 ms) of player positions; each input carries the server tick the client was rendering remote players at (render delay 4 ticks); Lance and Shock Lance rays test targets at that tick. Projectiles are not rewound (they are visible and lead-able).
Test
a target moving 15 m/s hit at its 190 ms-old position with rewind, missed without. Remote players are extrapolated up to 3 ticks along their last velocity when a snapshot is late; corrections above 20 m are classified as teleports (respawns), not prediction error.
Decision
keep; fairness cap = 512 ms.

EXP-5 · 2026-08-29

Redundant inputskeep

Hypothesis
residual error at 100–150 ms with 2–3% loss is lost input packets starving the queue.
Implementation
each input packet carries the last 4 [seq, input] pairs; the server queues unseen seqs.
Telemetry
16p/32p at 100 ms + 2% loss + 20 ms jitter — movement p95 0.77 / 1.20 m, 38 rubber-bands/min → 0.000 m, 0.7/min.
Decision
keep. Bandwidth per peer measured 11 KB/s (16p) / 20 KB/s (32p), snapshots 830 B mean.

EXP-4 · 2026-08-29

Server input queue instead of latest-input-per-tickkeep

Hypothesis
prediction error under jitter comes from the server duplicating/dropping client inputs while the client re-simulates exactly one tick per input.
Implementation
per-client FIFO consumed one input per tick (6-tick cap, repeat last when starved); weapon state machine replicated in the per-recipient block and re-run for in-flight inputs during reconciliation.
Telemetry
bench cell 4 clients, 20 ms RTT, 5 ms jitter — movement correction p95 1.2 m / 67 rubberbands per min → 0.000 m / 0; local bot brawl mean 0.165 → 0.001 m.
Decision
keep. ADR-3 amended.

EXP-3 · 2026-08-28

Predicted weapon fire and self-impulsekeep

Hypothesis
impulse jump snap-back is the largest visible correction.
Implementation
local single-player world for forward prediction; impulses logged by input seq and re-applied on reconciliation.
Telemetry
bot brawl with periodic impulse jump, mean correction 11.4 cm → 7.2 cm.
Decision
keep.

EXP-2 · 2026-08-28

Ground-impact damage only after real airtimekeep

Hypothesis
hop-cycle landings on 8 m heightfield creases register as impacts.
Implementation
impact damage requires ≥ 8 ticks airborne before landing plus the 45 m/s normal-speed threshold.
Result
complaint gone in the same playtest.
Decision
keep; revisit when terrain collision becomes triangle-exact.

EXP-1 · 2026-08-28

Ground contact while climbingkeep

Hypothesis
forward drive felt weak on hills because contact was dropped when moving upward.
Implementation
contact = at surface and not moving away along the surface normal.
Telemetry
reference uphill run trace (run_up): error 13.5 → 0.8 m/s; live playtest.
Result
"much better" (Chris).
Decision
keep.

6 experiments omitted whole: their titles name internal test material.

Milestone notes

Milestone notes · 2026-08-28

Phase 2

  • Internet net hardening — after the first friend playtest (binary snapshots and input validation already in). Predicted weapon fire, local projectiles and self-impulses DONE 2026-08-28: the client runs a single-player MachWorld forward (same code as the server), logs each predicted self-impulse by input seq and re-applies it during reconciliation; server projectiles/effects owned by the local player are hidden. Bot brawl with periodic impulse jump: mean correction 11.4 cm → 7.2 cm (--no-predict-fire for the baseline). Still to do: delta/interest snapshots, Vector Cannon lag compensation, jitter buffer, remote extrapolation on loss.
  • Vehicles — not before a year.

6 blocks of internal measurement detail omitted from this note.

Milestone notes · 2026-08-28

Phase 1: movement fit

Pipeline

  • game/tests/replay_drive.gd: replays each segment through MachMovement + MachTerrain with the recorded inputs and prints RMSE of horizontal speed, vy, energy, position.

Known gaps

  • Move direction in the replay must come from the teleport yaw, not peak velocity.
  • Hop phase: vy rmse 5-8 on hop segments is timing drift of the hop cycle, not amplitude.

Next

  • Re-check vy handling on landing (jump_rest / jet_vertical vy error is on touchdown).
  • Then medium/heavy captures (same drive program, frame switch server-side).

Combat and BreakFlag (MachWorld, 2026-08-28)

Measured in the sim: standing light impulse jump = +47 m/s, 38 m peak, 0.5 damage (76% of 0.66); Impulse Launcher on a light at 40 m = 0.5 damage (survives at 24%), pushed 22 m/s; 7 Vector Cannon shots in 1.5 s, 0.101 per hit.

Still approximate:

  • Explosion point is the projectile hit point (no projectile radius offset); projectiles start at the eye, not the weapon mount; Vector Cannon spread is a two-axis random rotation.
  • calcExplosionCoverage reduced to a terrain line-of-sight test (no interiors yet).
  • The shooter is skipped for activateDelayMS (200 ms) after firing.
  • Flags sit on the terrain at the mission x,y (no base structures in the slice).
  • Team review 2026-08-28 reconciled in research/2026-08-28-phase1-review-reconciled.md.

Networking (Phase 1 slice)

net.gd (RPC endpoint), net_server.gd (authoritative MachWorld, latest-input-per-tick, full snapshot every tick via ENet unreliable-ordered), net_client.gd (32 ms input sampling, local prediction with the same MachMovement, re-simulation of unacked inputs on each snapshot, remote-player interpolation over 2 ticks). Solo play = listen server with the local client short-circuited. tests/net_check.sh runs 15 bots + 1 scripted client headless and reports server tick cost and client correction error. Snapshots are a binary quantised codec (snapshot_codec.gd, ~1 KB for 16 players + 40 projectiles, stale ticks dropped); client inputs are validated (MachServer.sanitize_input); remote entities render 4 ticks behind from a tick-indexed buffer. Not predicted yet: weapon fire and self-impulses (impulse jump arrive as corrections; 3.7 cm mean over a 40 s bot match on LAN).

20 blocks of internal measurement detail omitted from this note.