A single long session, July 2026. What follows is the honest order things happened in, including the parts that didn't work.
"I loved playing Elite when I was younger. I want to keep the wireframe style graphics, but maybe planets and suns could be more interesting?"
That one sentence set the whole aesthetic policy, and it turned out to be a good one: ships stay 1984, everything astronomical gets the modern treatment. Wireframe hulls are the soul of Elite; the flat white circles that stood in for planets were a limitation, not a choice.
Stack: TypeScript, Vite, three.js. No game engine — Elite's charm is that it's a small pile of maths, and a small pile of maths is exactly what this turned out to be.
The first real code was galaxy/galaxy.ts: the original procedural
generator. Three 16-bit seed words, a Fibonacci-ish "twist" that shuffles
them, and out falls an entire universe — names from a digraph table,
economy, government, tech level, population, productivity, planet radius,
and the commodity market's price gradients.
The satisfying part is that it's verifiable. Galaxy 1, system 7 must be Lave, Rich Agricultural Dictatorship, Tech Level 5. First run:
7 LAVE TL:5 Rich Agricultural Dictatorship
Diso, Leesti, Riedquat, Zaonce, Reorte — all present, all in the right places. Forty-two years later, the same three numbers still produce the same galaxy. That check became invariant 4 in the project's CLAUDE.md and has been re-run after every risky change since.
First pass at ships: vertex and edge lists, exactly like the BBC data,
rendered as LineSegments. They looked right and were wrong — you could
see straight through every hull, including its own far side.
The fix is the classic hidden-line trick: give each ship a matte-black
filled mesh of its faces, sitting just behind the glowing edges via
polygonOffset. Suddenly ships occlude the stars, the planet, and their
own rear surfaces.
That change had a non-obvious consequence. The renderer had been using a
logarithmic depth buffer (sensible, given a sun 320,000 units away and
wireframes 30 units away). Log depth writes gl_FragDepth, which
disables polygon offset. So: no log depth buffer. It's invariant 6
now, because it's the kind of thing that silently un-fixes itself.
The planet shader is where the brief got spent. Procedural fbm noise gives coastline contours; a lat/long graticule keeps it feeling like vector graphics; there's a day/night terminator, a fresnel atmosphere rim, and a solid body fill so the disc reads as a sphere even on its dark side. Every parameter — hue, continents, sun bearing — derives from the same 1984 seed that names the place.
The sun got animated fbm granulation, limb darkening, and an additive corona. Neither would have run on a BBC Micro. Both feel like what Elite was pointing at.
The middle stretch was systems, in roughly this order: flight model (no inertia — the ship goes where the nose points), the elliptical 3D scanner, the Coriolis station with its rotating slot, manual docking with a roll-alignment check, the market, the charts, hyperspace with a fuel-range circle, combat with lasers/missiles/shields/energy banks, bounties, ratings, and save-on-dock.
Then a research pass against the original 1984 manual (fetched over
plain HTTP — the fan-site's certificate has expired) plus Wikipedia, written
up as docs/GAP-ANALYSIS.md. That document drove everything after: cabin
temperature and sun-skimming, the energy bomb, ECM, cargo canisters and
scooping, mining lasers, CLEAN → OFFENDER → FUGITIVE legal tiers with
contraband scans, witch-space Thargoid ambushes, dodecahedral "Dodo"
stations at high-tech worlds, the Navy missions, and the manual's exact
equipment price/tech-level table.
Two details worth the effort: the species generator ("Population: 4.1 Billion — Black Furry Felines") is the original's seed-bit table, and the launch/dock tunnel effect — concentric rings rushing past — takes about 70 lines of canvas drawing and does more for the 1984 feeling than anything else in the project.
The scripted NPC AI was decent: traders that flee, pirates that pack-hunt, police that enforce. Then came the question that changed the project's character.
The answer was yes, and the reason is worth stating: the entire flight model is about ten numbers per ship with closed-form updates. That's the regime where simple methods beat sophisticated ones.
The architecture:
src/ai-training/core.ts— a render-free copy of the combat physics, own vector/quaternion maths, no three.js. Node runs it flat out; the browser viewer replays identical episodes. (Deleted at Run 17: the copy drifted from the game three times, each costing a training round. Episodes now step the realNpcShip,PlayerShip,gunnery.tsandcollisions.ts— see chapter 13.)src/ai-training/policy.ts— a 1,899-parameter MLP (14 → 32 → 32 → 11) whose observation is entirely in the ship's own frame, and whose outputs are the same discrete keyboard controls a human gets: pitch ±/0, roll ±/0, throttle ±/0, fire y/n. No cheating with continuous steering.train/evolve.ts— population evolution strategy. Elites survive, offspring are gaussian mutations at mixed sigmas, and every genome in a generation is scored on identical episode seeds (common random numbers). No dependencies, no Python, no GPU.
400 generations, 210 seconds of one CPU core. Fitness went from −0.5 (random flailing) to 18.36 — statistical parity with the hand-scripted hunter's 18.34, which gets perfect continuous steering for free. The network had to learn intercept geometry, throttle management on approach, and how to hold a firing cone using only key taps.
Train a trader policy against that pirate. The scripted trader scores about 1 in that matchup — it dies in seconds. The evolved evader reached 14.44, surviving most or all of a 45-second episode in a slower, less agile, unarmed ship.
Then the evaluation tournament made the arms race legible in one table:
| matchup (40 held-out seeds) | kills |
|---|---|
| trained pirate r1 vs scripted trader | 100% |
| trained pirate r1 vs trained evader | 0% |
Textbook. Which is exactly why league play exists.
This deserves its own note, because it's the part that's easy to skip.
train/evaluate.ts is the gate everything must pass:
- Held-out seeds. Training consumes seeds below ~400,000; evaluation starts at 10,000,019. A policy that scores well there generalises rather than memorises.
- Baselines on identical seeds — the scripted AI as an upper bound (it's an aimbot, honestly labelled as such) and an untrained random policy as the floor.
- Behaviour metrics, not just fitness: kill rate, time-to-kill, shot accuracy, survival time, losses, and the mean angular spread of attackers at the moment shots land — a flanking measure.
One league round (seeded from the r1 champion, trained against the evader)
produced a pirate that beats the evader 98% of the time while still
taking scripted traders 90%. That brain flew every pirate in the game for the
next several rounds, because human players fly evasively. (It is
pirate-attack-g3 now — src/game/brains.ts is where that is stated.)
The pack phase is the honest failure. Three ships sharing one policy with
packmate observations and a shared reward hit the training target (25.04,
matching the scripted-pack benchmark)… and then the held-out tournament
caught it: 70% kill rate versus 100% for three copies of the solo
brain. When it does kill, it's the fastest anything managed (0.6s — a
coordinated alpha strike), but it had learned a gamble, not a strategy. The
solo trio stayed shipped. The failure is written up with its likely causes
in docs/TRAINING-LOG.md, because a training log that only records
successes isn't a log, it's marketing.
The best story in the project. The question was simple: can an autopiloted commander actually make money? So a script was written to drive the real game through its debug handle — real markets, real fuel, real pirates, real witch-space, real docking physics, real legal system.
- MkI died on his first day. The autopilot aimed its nose at the station without correcting lateral drift, bounced off the hull repeatedly, and was finished off by pirates. Validated: collision damage, bounce mechanics, pirate lethality.
- MkII is the tragedy. He survived a Thargoid witch-space ambush,
docked successfully — then during a clumsy approach rammed another ship
to death, was branded a criminal, had his savings confiscated as the
fine, and ended stranded with 1.3 credits and a 1.5-credit fuel bill.
A poverty trap, emerging unprompted from systems that were each behaving
correctly. (Two-thirds of his losses were later traced to a harness bug:
the test script's
sellAlltook the cargo and never credited the commander. An embezzling accountant.) - MkIII traded profitably — +38, +26, +38 credits a leg — until pirates killed him on the Lave approach holding two tonnes of computers.
Conclusion: the economy works (agricultural↔industrial runs make money; Lave↔Diso, both agricultural, does not — which is exactly the original's design), and survival, not economics, is the binding constraint.
So the constraint got trained away. Run 5: an armed trader policy versus two of the shipped pirates.
| trader | died | survival | enemy accuracy |
|---|---|---|---|
| scripted armed trader | 100% | 14.0s | 20% |
| trained "Jameson" policy | 10% | 41.9s / 45s | 1% |
Evasion-first flying that makes it nearly unhittable, with opportunistic
return fire. It went on to fly every armed trader in the game — attack a
Python and you're fighting a commander that fights back. (The shipped
defence brain is jameson-defend-g1 now, and the 90% was measured in the
deleted simulator on the retired damage scale; docs/TRAINING-LOG.md's
header says why no figure from before 2026-08 transfers.)
MkIV, running the trade autopilot with that brain at the stick during combat: 100 → 461.5 credits in six legs, five kills, two witch-space ambushes survived, zero deaths. Per-leg profit grew from +45 to +82 as capital converted into higher-value cargo. The same corridor that killed MkIII, five times, uneventfully.
The difference between MkIII's grave and MkIV's fortune is one trained policy — which then became a purchasable Combat Computer the player can buy and engage with a key.
Three independent audit agents were turned loose before release: one on game code readability, one on training reproducibility, one fact-checking every claim in every document against the code.
They earned their keep. Real bugs found: the galactic hyperdrive left you permanently flagged as being in witch-space; traders' attacker lists were never pruned, so they'd dogfight ghosts; two spawn calls used a per-axis offset that biased ships into one octant of the sky; the hostility predicate was hand-copied in three places and drifting. The docs audit caught a README roadmap still advertising implemented features as missing, and a key binding documented on the wrong key.
The reproducibility audit's most useful finding was a footgun: retraining silently overwrites the committed brains the game imports. That's now warned about in three places, and the trainer says so before it writes.
- Verifiable authenticity is a gift. "System 7 must be Lave" caught regressions all session. Find the equivalent invariant in whatever you're building.
- Small models, small problems. 1,899 parameters, no GPU, 210 seconds to parity with hand-written AI. Reach for the sophisticated thing second.
- The evaluation harness is the product. Training fitness lied twice (the pack phase, and the r1 pirate's apparent dominance). Held-out seeds and baseline comparisons told the truth both times.
- Let the AI play the game to test it. Three dead commanders exposed more real behaviour — collision damage, legal escalation, poverty traps, a save-corrupting harness bug — than any amount of clicking around.
- Document failures with the same care as successes. The pack-phase write-up is the most useful page in the training log.
A complete Elite: 256 systems per galaxy across 8 galaxies, 21 wireframe hulls, trading, mining, scooping, four laser mounts, missiles, ECM, energy bombs, escape pods, legal status, Navy missions, Thargoids in witch-space, Coriolis and Dodo stations — with the classic 1984 keyboard layout as the default and a modern WASD alternative one keypress away.
And both sides of every dogfight run on neural networks that taught themselves to fly, in a simulator built from the game's own physics, using nothing but the keys you have.
(Two chapters on, both halves of that paragraph have moved: there are 38 hulls, not 21, and there is no simulator — the brains train ON the game. See chapters 11 and 13.)
Right on, Commander.
Some time after "it works", the brief sharpened: treat this as a homage rather than an exact recreation — it must be recognisable to anyone who played the original, never "they've ruined it", but we've learned a lot about game design since 1984.
That reframing unlocked the most valuable changes in the project.
Fetching the original Players' Guide alongside the manual answered questions no amount of reasoning could. Two mechanics we'd missed were pure canon and went straight in:
- Missiles arm before they lock. T arms one (amber pylon); it locks by itself when a target enters your crosshairs (red pylon, beep). The old behaviour — press T, instantly locked — was both wrong and less fun, because it removed the act of flying the shot.
- Hull hits cost you things. "Once a shield is depleted... may destroy items of cargo or ship fittings." Losing your combat computer mid-fight is a genuinely alarming moment.
The guide also settled provenance on the "rare encounters" list. Ian Bell's own preface notes that generation ships were never coded — they're fiction from The Dark Wheel. Rock hermits and Trumbles are Oolite inventions, Trumbles arriving in 2005. So they went in as clearly-labelled homage, each given a reason to exist rather than being decoration: hermits deal ore and ask no questions (the place to fence contraband, if you can find one), and Trumbles can only be purged by taking the cabin temperature up on a sun-skim — tying the joke to an existing mechanic instead of bolting on a cure.
The sharpest design note in the whole project: "One thing that really frustrated me in the original was that you didn't get any missions until you were way up the skill level."
That is exactly right, and it's the kind of thing 1984 couldn't afford to fix. So every station now runs a bulletin board — cargo runs, courier jobs, pirate-clearing bounties, with deadlines measured in days that pass as you jump. Available from your first landing at Lave. The Constrictor hunt and the Navy courier run still sit at the top of the ladder, so the original's structure survives; there's simply a bottom rung now.
A commander with 100 credits used to have no direction at all. Now they have three or four concrete reasons to pick a destination.
"We're on much more modern systems now with mouse pointers" turned into:
- The whole UI is clickable — menus, market rows, equipment, contracts, action buttons — implemented by having clicks inject synthetic key presses, so mouse and keyboard run through identical handlers.
- BUY MAX / SELL ALL. Filling a hold used to take twenty keypresses.
- Click a star on either chart to target it (projections inverted, snap radius measured in screen pixels so it feels identical at both zooms).
- Pointer-lock mouse flight (V) — a self-centring analogue stick, the nearest thing to the joystick the original supported.
- Target brackets with a lead marker — the single biggest fun-per-line-of-code change in the project. Dogfighting went from spray- and-hope to something you can get good at.
And yet the default flight keys are now the authentic 1984 set — S/X
dive-climb, < > roll, A to fire — with WASD available on a toggle.
Recognisable first, accessible second.
The unit tests guard the maths. Nothing guarded gameplay — until the
combat AI got repurposed into an autonomous playtest agent
(test/playtest.js): a commander that takes contracts, trades, equips,
jumps, fights (flown by the trained defence brain), docks, detours to
hermits, and asserts invariants every 30 frames — finite positions,
non-negative credits, hold within capacity, valid modes, no screen showing
without a mode behind it.
Its first run found a real defect in two of eight legs: the agent got stuck in unending dogfights, because the defence policy is trained to evade, and against a pirate it can't kill, the fight simply never ends. A human would break off and run for the station; now the agent does too.
Its second run came back clean — six legs, four systems, seventeen in-game days, thirty-six seconds of wall clock — and produced something better than a pass: balance telemetry. Successful legs netted +28 to +55 credits, with the agent reinvesting everything in cargo, and in six legs it never accumulated the 400 credits for its first upgrade.
That is the original's grind, measured rather than guessed. Which meant the next change could be made against evidence instead of vibes.
The last big idea had been on the list since the first session: "I'm wondering if we could simulate a whole environment of ships doing their own thing flying between systems."
The naive reading of that is expensive — 256 systems of NPCs, ticking forever, 255 of them unwatched. The version that actually shipped is a two-level simulation, and the split is the whole trick:
- Level 1 (
src/galaxy/living.ts) is records, not objects. A convoy is{from, to, commodity, qty, arrivesOn}— nine numbers. Time only advances when the player's clock does, and it advances in whole days, because a jump costs days anyway. A year of galactic trade is a few hundred integer updates. - Level 2 is the NPC spawner that already existed. When you arrive
somewhere,
populateSystemasks the living galaxy what's due in — and materialises those records as real wireframe ships flying real physics.
So the galaxy is cheap everywhere and expensive only where you're looking. Convoys depart in proportion to productivity, are lost to piracy in proportion to lawlessness, and on arrival nudge the destination's prices. The 1984 seeded economy stays the baseline underneath; this layer only ever stores deltas, caps them at ±25%, and decays them back toward zero when trade stops. The save grew by about 26 KB.
The first was invisible until the numbers were printed: danger never accumulated. Systems gained 0.1 danger per convoy lost and decayed 0.08 per day, so within a day of any incident the galaxy forgot it happened. Nothing crashed; the feature simply didn't exist. Slowing the decay to 0.015/day gave piracy a memory — a reputation for danger should outlast a single ambush.
The second was funnier and much worse. With danger fixed, the report came back with Lave as the most dangerous system in the galaxy. Which is absurd: Lave is the tutorial. But the code was doing exactly what it was told — danger was accumulating with traffic, and Lave is the busiest system in human space, so the safest place in Elite had become its worst pirate haven.
The fix is a one-line reweighting, and it's the sort of thing that only looks obvious afterwards: piracy scales with lawlessness, not volume.
const lawlessness = (7 - this.systems[c.to].government) / 7;
dest.danger = Math.min(1, dest.danger + 0.22 * lawlessness);…with decay scaled the other way, by how much government there is to do the policing. After a simulated year: anarchies average 0.143 danger, corporate states 0.000, and Lave sits at 0.000 with 242 convoys in flight across 51 systems that carry any risk at all. Hotspots now emerge along genuinely lawless routes instead of being scripted — which was the point of building the layer rather than hand-placing pirates.
One more thing had to change for it to feel like a galaxy. Trade partners were originally drawn at random from all 256 systems, which produced a uniform smear of nothing. Restricting each system's partners to its ten nearest neighbours within jump range — because a 7 LY drive means trade is inherently local — made lanes appear. Trade routes are an emergent property of the fuel tank.
The autonomous browser agent (chapter 11) plays the real game, which is its strength and its limit: it takes four minutes to play eight legs. Balance questions need hundreds of careers.
So npm run campaign runs the game's actual economic code headlessly —
the real galaxy generator, the real market model, the real living galaxy,
the real contract rules — with only flight abstracted into a dice roll.
That constraint is what makes it trustworthy, and it's why the contract and
market logic was extracted into src/game/contracts.ts in the first place:
so there is exactly one copy of the rules, and the simulator can't drift
from the game by construction.
Forty commanders, sixty legs each, in 0.4 seconds:
WEALTH median net worth 3661.5 Cr, from a 100.0 Cr start
SURVIVAL 32/40 never went broke · 1.4 deaths per career
PACE median day 136 after 60 legs · first upgrade at leg 7
CONTRACT 22.0 completed · 14.2 failed per career
RATING median Mostly Harmless
GALAXY living prices ranged 0.75x..1.25x · mean system danger 0.050
It asserts those invariants and fails the build if the economy stops working. Its first verdict was that contracts paid too little to be worth the deadline risk — rewards went up about 1.9×, on evidence. The equipment progression line is the one I find most satisfying: everyone finishes with a large cargo bay, three quarters have fuel scoops, and only 5% ever afford an extra energy unit. That's a difficulty curve, printed as a fact.
Meanwhile the AI got a third round, and it failed twice — which is worth writing down properly.
Hypothesis 1: the pack's reward was wrong. Run 4's pack learned an all-in alpha strike; the theory was that reshaping the reward toward sustained pressure would teach coordination. Result: 68% kills against r2's 70%, with accuracy falling from 9% to 3%. The reshaping moved the training score without moving the behaviour. The bottleneck is not the reward function — it's that the policy can't see its mates.
Hypothesis 2: train the attacker against the best evader. Seed from r2,
train against trader-evade-r2, get a better pirate. Training fitness went
up — 18.40, the highest of any run. Kill rate went to 3%. Textbook
self-play collapse: the r2 evader is so good at running away that the
fitness landscape rewarded closing behaviour that scores points without ever
landing a shot. The number went up and the ship got worse.
Both r3 brains are committed, unshipped, purely as evidence. The game flew r2
at the time; it flies pirate-attack-g3 now. The evaluation harness caught this in one command — which is, for
the third time in this project, the actual lesson: training fitness is a
proxy, and proxies lie. The held-out tournament is the product.
- Cheap simulation beats no simulation. Records-not-objects made a living galaxy affordable enough to ship; the fidelity you skip is the fidelity nobody can observe.
- A feature that silently does nothing is worse than one that crashes. Danger decayed faster than it accumulated for a whole development cycle. Print the aggregate; look at it.
- Correlate with the right variable. Danger-follows-traffic and danger-follows-lawlessness are the same amount of code and produce opposite games.
- Share the rules, don't copy them. The campaign simulator is only
evidence because it imports the same
contracts.tsthe game does. - Publish the refutations. Round 3 is two failures and no shipped artefact, and it's the most informative run in the log.
Everything before this chapter was a reconstruction. The hulls were hand-written vertex/edge/face tables in the style of the original data, the damage model was a normalized "fraction of a Cobra" invented to make fights feel right, and both were defensible: neither the ship files nor the combat arithmetic were to hand.
Then they were. An analysis pack of the released Elite-A ship data — 15 flyable player hulls, 38 designs with their exact geometry, 23 blueprint sets, 260 exact variants, 713 slot assignments, and 20,070 tabulated hit rows — became available, and ten commits replaced the reconstruction with it.
The full account is docs/ELITE-A.md. What is worth recording here is the shape of the work, because it is the same shape as the sim-core deletion in chapter 6.
Vendor it, hash it, generate from it. The pack lives in
reference/elite-a/source, verbatim, with every file's SHA-256 pinned by hand
in the importer. Nothing in src/ may read it; npm run generate:elite-a
turns it into six generated modules and three test fixtures, and
-- --check fails the build if any of them has drifted. A pack that hashes
differently stops the importer rather than quietly becoming the new truth.
Separate the rule from the data, and then check the rule against every row.
elite-a/combat-math.ts is pure — it imports nothing at all — and holds the
arithmetic: (byte & 0x7f) >> 1 for a player laser, maxEnergy & 7 for a
target's defence, laserPower << 2 for an NPC's. catalogue.ts holds the
numbers and no arithmetic. Then the exhaustive part: all 15,600 player-to-NPC
rows, all 3,900 NPC-to-player rows and all 570 summaries are reproduced by the
oracle — and reproduced a second time through the live game, by a separate
suite that is forbidden from calling the oracle for a number it is checking.
Two independent claims, 40,000 rows apart.
Three ids, because three things move independently. Which hull the commander is in, which design is on screen, and which exact released build of that design an NPC is. Saves carry the id and never the record. That is what makes a future shipyard a state change rather than another data extraction.
Say which numbers are not theirs. The pack tabulates registered laser hits
and nothing else — it is silent on a ram, a canister, a station wall, a missile
warhead and the energy bomb. Those five are ours, they live in one file
(impact-damage.ts), and docs/DAMAGE-PATHS.md is the inventory that says so
row by row. The two Harmless-only ships, the rock hermit and the derelict
generation ship, carry harmless: ids for the same reason: they must never be
presented as recovered source data.
- A provenance hash is a rule, not a comment. The importer refusing to run is what stops "the same data" degrading into "the file with that name".
- An exhaustive check is cheaper than a representative one. Twenty thousand rows cost less to write than choosing which twenty to trust, and they found the cases nobody would have chosen — the Constrictor halving before defence, a Coriolis carrying six missiles and no laser, a Dragon whose seven points of defence eat an Adder's entire pulse hit.
- Two units, both real, no adapter. The project ran three damage scales at once and two conversion functions between them; correct rules sat beside them and looked fine. There are two now — a ship's energy bank and the commander's pools — both whole numbers on the released byte scale, both branded so one cannot be spent as the other, and a test that names every deleted bridge and fails if one comes back.
- Exact data invalidates trained brains, on purpose. One combat model means the trainer flies the real game, so changing what a laser is worth changed the training world too. That is the trade chapter 6 bought, and this is the first time the bill arrived.
Thirty-four sets of neural weights were committed to src/ai-training/brains/.
The game imported nine of them. Three flew.
The other six were loaded into every player's browser to sit behind a picker as evidence: generation 1 and 2, the round-18 engine brain, and TODO 29's three retrains — each of which beat what ships on damage and read as a turret, which is the finding this project has now made three times. Keeping them pickable was a real argument. A candidate that wins every measurement and loses the only one that counts is exactly the thing you want a playtester to be able to fly, and twice that rig is what caught it.
It stopped being the right trade when the picker got long enough that the three
that matter were four rows down a list of eleven, and Chris said so: "we should
clean up all the experiments that aren't shipping — so we can just show the
combat AI that we actually ship." The measurements are not in the weights. They
are in docs/TRAINING-LOG.md and train/logs/, written down at the time, on
stated seeds, with the commands to reproduce them — which is where evidence
belongs, and which is why deleting 31 files cost nothing that had not already
been kept.
Two things fell out of it that were not the point.
The first is that the combat viewer was lying, and had been since round one.
Its "Pack of 3 vs armed trader" scenario flew pirate-pack.json — an early pack
policy, not the pirate-pack-r4-selectonly the game ships — because the weights
came from an import and the label came from an <option> in the HTML, and
nothing tied the two together. The same structure had already produced "Shipped
pirate (league r2)" over a brain named CANNOT BE FLOWN. The fix is not a better
label: the rows are a table in main.ts now, each row's weights come from
brains.ts and its name from brain-names.ts, the <select> in the HTML is
empty, and npm test fails if any file in src/ other than game/brains.ts
imports weights at all.
The second is that /viewer opened on the design gallery. The two had been
one page with a G between them, which meant the combat viewer — the thing the
landing page links to as "watch the trained AI fight" — read as deleted unless
you knew to press a key nothing told you about. They are two pages now, one
thing each, no mode key: /viewer and /gallery, both entries in
vite.config.ts, sharing a canvas-and-bloom module and nothing else.
- Evidence you cannot delete is not evidence, it is inventory. The test to apply is whether the measurement survives the artefact. Here it did, in a log this project has an append-only rule about, so the artefact was free to go.
- A label and the thing it names must come from one place, or the label is a
guess. Both of the viewer's false claims were structurally identical and
neither was a typo. The
<option>could not have been right for long. - A directory can be a claim, and then a test can hold it. "These are the brains we ship" was prose in three files and true in none of them. It is the contents of a directory checked against the imports of one module, and both halves are read rather than typed.
- A flag whose subject is deleted is not automatically a migration. Six A/B flags went with the weights they named. A save carrying one still loads, is not rewritten, and flies the shipped brains — because nothing reads it — and the trainer's own row says the selection cannot be named and offers to take it back. The only requirement was that it must not throw, and that is a test.