The ship flies itself, and the pilot decides where (docs/TODO/205 to 208) - #51
Open
cgreening wants to merge 60 commits into
Open
The ship flies itself, and the pilot decides where (docs/TODO/205 to 208)#51cgreening wants to merge 60 commits into
cgreening wants to merge 60 commits into
Conversation
The docked screens and the cockpit fit a phone, and nothing on the flight screen takes a touch. The brainstorm of 2026-09-10 set the shape: drag anywhere on the view to steer, hold a button to fire, a throttle slider, a row of five command buttons along the console's top edge, tappable prompts, and a flight menu of rows. No tilt, no swipe and no pinch. Five milestones, with the wanted-speed rule and the held-key methods first, because both run under node. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A touch slider needs to ask for a speed, and a touch button needs to hold a key. Input gains press and release, which hold a key as a keyboard does and never make a tap, and wantedSpeed, a fraction of the top speed or null. flightDemand gains one rule: with a wanted speed and no speed key held, the throttle opens below it, brakes above it, and coasts inside PLAYER_FLIGHT.throttleBand of it. A keyboard never sets a wanted speed, so nothing changes for it. test/flight.test.ts pins the rule on both sides of the band, and the key's override. test/input.test.ts pins that a pressed key is held and is never a tap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…de to set speed A new engine/touch.ts has two halves. TouchTracker is pure: a finger's down, move and up by pointer id, and the stick, the trigger and the wanted speed it writes into the input. attachTouch binds the overlay's pointer events to it, and Input calls it when the page has the overlay. A drag anywhere on the view is the mouse stick, with TOUCH_STICK_TRAVEL pixels for full deflection. A held finger holds its deflection, which stickHeld tells decayMouse, and a lifted finger decays to centre. FIRE holds the layout's fire key with press. The slider sets wantedSpeed. Two fingers are tracked apart. The overlay shows on a coarse pointer only, never under a screen, and the mouse-flight command steps aside while a finger has the stick. test/touch.test.ts drives the tracker with plain numbers. In Chrome at 390 by 844 with synthetic pointer events, the slider took the speed from 30% to 93%, a full drag right rolled the ship, FIRE heated the laser while the other finger steered, and the stick decayed after the lift. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Five buttons sit along the top edge of the console: MISSILE, E.C.M., JUMP, TORUS and DOCK. ui/key-help.ts paints them at boot from the binding table, so each carries the flight key it presses, and a tap takes the same path as a menu row. MISSILE carries both of its keys, and the HUD swaps the launch key in once a missile is armed and lights the button. TORUS and DOCK light while their state is on. The prompt line carries the code of each offer beside its words, and on a coarse pointer each prompt is a button. The browser shell listens on the row and the prompt line as it does on a screen, and the overlay leaves a tap on the row to that seam. test/key-help.test.ts holds the five buttons and their keys. In the 390 by 844 frame, a tap on MISSILE armed one, swapped the key and lit the button, and a tap on TORUS was refused as MASS LOCKED beside the station, which is right. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A MENU button, sixth on the touch row, opens a list over the view: the two charts, the four reading screens, the four views, pause, and the escape pod. ui/key-help.ts paints the rows at boot from the binding table, so each carries the flight key it presses and a tap takes the path a station row takes. The escape pod row opens a confirmation inside the list, and only YES carries the key, because the pod costs the ship and the cargo. The list is show and hide in engine/touch.ts, and it hides once a row is pressed. The plan named a screen under the screen host. A list of rows with the keys on them needs no screen, no id in the host's union and no input routing, and the seam it uses is the one every button already takes. test/key-help.test.ts holds the rows' keys and the pod's confirmation. In the 390 by 844 frame, MENU showed the list, LOCAL CHART opened the chart and hid the list and the overlay, and the pod's row opened its confirmation, which NO closed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…how to fly by touch The first touch on the view asks the browser for the whole screen, because a phone's browser bars take a fifth of it otherwise, and a refusal costs nothing. The manual's flight section and the briefing's first page each gain a paragraph on flying by touch: drag to steer, hold FIRE, slide for speed, the five buttons, MENU and the tappable offers. The two lists of touch buttons join the constants gate's list of prose tables. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
test/key-help.test.ts crossed the size ceiling at 420 lines, and test/flight.test.ts at 402, each with the checks this plan added. The command row and the flight menu are one subject, and they are test/touch-controls.test.ts now. The wanted-speed rule belongs with the touch tracker, and it joins test/touch.test.ts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The outcome is recorded in the plan, with what it did not have: the flight menu is a list rather than a screen, a synthetic pointer has no capture to give, node's strip-only mode refuses a parameter property, and the feel on a phone is not measured here. The plan moves to completed/, its line joins the archive index, and the queue in README.md and QUEUE.json is empty. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew the preview on 2026-09-11. The overlay stopped at the console, so a thumb on the console steered nothing. It covers the whole screen now, and FIRE, the throttle, the command row and the menu keep their places above the console. A drag down raised the nose, as a stick and the mouse do, and that is upside down under a thumb. A drag up raises the nose now. The sign flips in the tracker and nowhere else, so the mouse keeps its own convention. The manual and the briefing say so, and the plan records both. In the 390 by 844 frame, a drag that started on the console and moved up took the pitch marker to 93% and the docking port marker to the top of the view. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…hip flies it Chris flew the touch controls of 204 on a phone on 2026-09-11, and he could not fly the ship. He wrote MOBILE_CONVERSION.md and set a new direction. - 205: the pilot picks a course from a list, and the ship flies it. The launch needs a course. The touch controls of 204 go. - 206: in a fight, the computer aims at the pilot's target. The pilot fires the laser, the missiles and the E.C.M. The bought combat computer does all of it. - 207: the dock stays a challenge. Chris played four concepts and picked Match the spin. - 208: each of the eight mission verbs gets a course, and four faults that the review found are mended. Camera views wait, as GitHub #50. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris could not fly the ship on a phone by a drag and a FIRE button. He chose a different game: the pilot picks a course, and the ship flies it. So the code of 204 goes: the touch overlay, the wanted speed, the command row, the tappable prompts, the flight menu, the fullscreen request, and the manual's and the briefing's paragraphs on touch flight. Every file went back to its state at 81fcb2b, the commit before 204's code. Input.press and Input.release stay, with their test. A button holds a key with them. 206 needs one for the laser, and 207 needs two for THRUST and BRAKE. The 204 plan moves to retired/, with the reason, and the three indexes say so. 5,624 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A course is one thing the ship does next with no hand on the stick. The new game/courses.ts decides which courses the situation allows, in order. It follows prompts.ts: a flat view in, a ranked list out, and no key. The jump's refusal words stay in hyperspace.ts. A row whose object exists shows even when the ship cannot fly it, and it says what the ship needs. A row whose object is absent never shows. The launch list cannot show the hermit. The sky is cleared while the ship is docked, and the launch draws the hermit. So the launch offers the jump, the rocks and the star, and the hermit shows in flight. The plan says so. 5,656 assertions, from 5,624. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
- The flight keys stay. A key takes control during a fight, and a new button takes a mouse click on a desktop. - The bought combat computer never launches a missile. A missile costs money, so the pilot targets and fires one by hand. - The rocks and the star are launch courses. Chris confirmed the reading. - MOBILE_CONVERSION.md stays out of the repository, so the plans quote it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A picked course now flies the ship. The new game/course-pilot.ts decides one frame and reports a FlightDemand, as the two computers do. The station course points at the station and runs the torus whenever the drive is free. Inside the docking computer's range it hands over, with no computer fitted, which is the stopgap until 207. The jump course flies straight while the countdown runs. The switch lives in flight-instruments.ts, beside the other switches that change who flies the ship. A flight key suspends the course (Chris: "keep the keys"). The course and the finished courses are session state, so a save carries them, and a dock or an arrival ends the visit. Measured over 20 systems: every trip docked, with a median of 148 s. The traffic holds the torus down for 111 s of it, three stops at the median, one in three with a hostile ship. Chris chose a skip forward button at one fixed speed, and the plan gains it as M7. 5,676 assertions, from 5,656. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…inside the planet An arrival course flies to a standoff from its target and stops there. The derelict course matches the generation ship's drift beside it. The hermit course arrives slow enough for his trade to open. The star course holds inside the scoop range until the tank is full. Every line goes round the planet at a clearance above its mass lock. The work found a hermit 1,545 units inside the planet. The scatter round the station never checked the planet: 2 of about 2,300 ships over 128 systems appeared inside it. The traffic placement now lifts such a ship out to SPAWN_PLANET_ALTITUDE, and it draws nothing from the seeded stream. The spawning test keeps lifted ships out of its scatter band, and checks that nothing sits lower. Measured with the arrival's traffic: 20 of 20 hermit trips, 20 of 20 derelict trips and 40 of 40 star trips arrived. The cabin peaked at 0.489. 5,694 assertions, from 5,676. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris's rule: no ship leaves without somewhere to go. The LAUNCH row now opens the launch list. A jump leaves with its countdown running. The rocks and the star leave on their work. A row the ship cannot fly refuses, and the console says why, so a commander with no target and no work stays on the pad. The last row opens the galactic chart. course-actions.ts joins the course list to the Game: it builds the view and applies a pick. The course rows send virtual codes from COURSE_KEYS, which the flight buttons of M5 will share. jumpCheck gives the jump key and the list one question. 5,708 assertions, from 5,694. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…r the view In flight the course list is a set of buttons at the top right of the view. It shows by itself when the ship has nowhere to go: after an arrival, when a course ends, and when a key takes the ship. A lit button says what the ship is doing, such as HEADING TO THE STATION, and a click or a tap on it opens the list again. A button the ship cannot use says what to do about it. It is not a screen, because the flight world stops under a screen. Each button sends a code, as a menu row does, and the course actions read the codes in flight. No key is bound (Chris: "We don't need to use the keyboard. We have mouse and touch."). The words are the player's (Chris: "A user does not have all our context."). 5,721 assertions, from 5,708. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
While the ship flies somewhere and no hostile ship is near, a FAST FORWARD button runs the world eight times faster (Chris: "Fixed speed - let's keep it simple."). The loop runs more fixed steps in each screen frame, and it changes no step: a test shows that a trip under fast forward reaches the same world as a normal trip. Fast forward stops by itself when the condition light turns red, when the ship arrives, or when a flight key takes the ship. A console line keeps its time in the player's seconds, so it can still be read. Four older constants that equal 8 take their own rule IDs, as the constants gate asks. 5,732 assertions, from 5,721. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…tons The manual's first hour and the briefing's FLY THERE page now teach the launch list, FLY TO THE STATION and FAST FORWARD, in place of the jump key and the torus key. The manual says that a steering key takes the controls back. The briefing's journey test now asks for the buttons. The browser run found two faults. The station menu's two columns split the launch list, so the list has one column. The course buttons showed during a tunnel, where a press does nothing, so none show then. 205 lands: the flight probe runs, and the dock probe docks 504 of 504 approaches with no scrape. 5,733 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The new game/targets.ts lists every ship, rock and derelict within scanner range. The ships that attack come first, nearest first, and the rest follow. Each row says what the ship is, and what an attack costs where the law protects it. A trader is on the list (Chris). The pilot's pick is one flag on the picked ship's state, so a save carries it with the ship. It sits on top of threat-lock.ts, which keeps its rule for the three places that share it. 5,746 assertions, from 5,733. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…puter the trigger A fight now gives every pilot the computer's aim. When a hostile ship is near, or the pilot picked a target, the co-pilot lines the ship up. It flies at the pilot's pick first, and at the threat lock's choice with no pick. Without a combat computer it never fires: the laser, the missiles and the E.C.M. are the pilot's (Chris). With one fitted, it also fires the laser and the E.C.M., and never a missile (Chris: "missiles cost money"). A flight key takes the controls, and the computer waits until the pilot picks a course or presses the aim's key. A course waits through a fight and flies on when the area is clear. The automatic start says nothing on the console, because a fight's first lines matter more. 5,756 assertions, from 5,746. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…M. and targets Bottom right of the view, above the console: FIRE LASER fires while it is held, the missile button arms one and then fires it, the E.C.M. button shows when one is fitted, and TARGETS opens the list of what the ship can fight. A row picks its ship and hands the stick to the computer. LET THE COMPUTER CHOOSE lets go of the pick. The keys of today still work, and the new buttons need none (Chris). engine/hold-buttons.ts holds a key down while a button is held, one pointer at a time. A target row's code names its ship, so a list that reorders by range cannot pick the wrong one. 5,774 assertions, from 5,756. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
RUN FOR IT is a course, and it leads the list while a hostile ship is on the scanner. It says whether the ship is faster, only a little faster, or no faster. The ship turns away, opens the throttle and runs the torus when the mass lock lets go. The computer's aim waits while it runs. Every offer the situation raises is a button now, under the courses: pay the police, jettison a tonne, dump the evidence, the distress beacon and the docking computer. Each names its key under the words, so a keyboard pilot loses nothing. The E.C.M.'s offer lights the E.C.M. button. The old line of prompt text is gone. The cockpit view crossed the size ceiling, so what a button says moved to cockpit-buttons.ts, and the cockpit's header says so. 5,791 assertions, from 5,774. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
COLLECT EVERYTHING flies the ship onto each canister and pod within scanner range, nearest first, and the pilot can stop it with another course. It needs fuel scoops, and the row says so when they are missing. It shows only with a clear scanner. MINE THE ASTEROIDS keeps the nearest rock picked as the target. The computer lines the ship up and the pilot fires, which is the fight of this plan applied to a rock. The course ends when no rock is left within range, and the ore is collected by the other course. 5,804 assertions, from 5,791. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The briefing's fight page and the manual's Trouble section now teach the buttons: FIRE LASER, TARGETS, ARM A MISSILE, E.C.M. and RUN FOR IT. They say that a missile costs money, that the bought combat computer does the rest, and that a steering key takes the ship back. The briefing's journey test names the buttons in place of the missile keys. 206 lands. The probes ran: ambush-probe survived 40 of 40 episodes, and the other three report as before. None of them flies the Game's own seat, so the measure of this plan is test/aim.test.ts and Chris's verdict. 5,804 assertions, from 5,733. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…ot checks the speed The station course hands the ship over at the docking computer's range when one is fitted, and that computer flies the slot as before. With none fitted, the course flies on to 1,500 units and hands the ship to the pilot. The computer then holds the ship on the slot axis, and the pilot matches the station's spin and the speed. The stretch ends if the ship leaves the approach. The slot now refuses a ship that arrives above SLOT_SPEED_LIMIT, which is 120. dockingOutcome answers roll and speed together, so no caller holds half the rule, and the console says which one refused. The docking computer settles at 110, and the dock probe still docks 504 of 504 approaches with no scrape. 5,807 assertions, from 5,804. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The last stretch shows three controls and no guns: a strip to drag for the roll, and held THRUST and BRAKE buttons for the speed. The strip is a new platform seam beside the click and the held button: it reads how far the pointer is from its middle and reports a stick, and a roll key still beats it. The label over the view says MATCH THE SLOT — GO IN SLOWLY, and the speed bar carries a mark at the fastest the slot will take. 5,820 assertions, from 5,807. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…brakes for it The manual and the briefing now say that the ship flies the approach and hands you the slot, that the roll and the speed are yours, and that a scrape bounces you clear to try again. A docking computer still flies the slot for you. The browser run found three faults. The course list showed during the stretch, and it shows none now. The hand-over came at full speed, so the station course arrives on its own braking plan: measured over 25 systems, the hand-over comes at 224 to 228 units a second with 1,500 units left, which is half a second of braking. The mark on the speed bar is green, so it reads over the amber fill. 5,820 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…e speed Chris played the four concepts and picked Match the spin. The course flies the approach and hands the pilot the last 1,500 units at the slot's own speed. The computer holds the ship on the axis, and the pilot matches the station's spin and the speed. A ship that arrives rolled wrong or too fast scrapes and bounces clear, and tries again. A docking computer still flies the slot. 5,820 assertions, from 5,804. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A live job now shows as the first row of the course list, in its own words, and the ship flies it. Five verbs need a course, and four shapes cover them. A hunt picks its ship and the computer's aim flies it, as mining does. A scan holds 1,200 units off and keeps the nose on the ship. An escort flies 600 units off its charge, so the fight comes to the pilot. A recover and a rescue are a scoop. A deliver, a smuggle and an ambush end at the station, which the station course already flies. The test accepts each side job through the mission machine and flies it by its button alone. 5,831 assertions, from 5,820. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
SLIP PAST THE POLICE is the smuggling leg's own row. It flies the station course on a line wide of every policeman in the way, at the warning band of 4,400 units, which is outside the 2,600 a policeman reads a hold at. One rule holds both detours now: sidestep aims beside a thing in the way, on the same side as the line, and half as far again. The planet's detour uses it too. A leg with no tagged ship is still a leg, which the first draft of the lookup skipped. 5,841 assertions, from 5,835. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The derelict course ends beside the hull, and the console says what is there: a cold reactor, a breach along the spine, bays emptied from the inside. The words are read off the world's own seed, so a derelict tells the same story on every visit and two worlds tell different ones. No draw from the world's stream moves. 5,845 assertions, from 5,841. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
A live job is the first button over the view, in the job's own words, and the ship flies it. A hunt picks its ship for the computer's aim. A scan holds the ship off and keeps the nose on it. An escort flies alongside. A recover and a rescue scoop. A smuggling run goes wide of the police. A delivery and an ambush end at the station. Three faults the review found are mended, and a derelict tells what the scan finds. The manual says how a job is flown. ambush-probe survived 40 of 40 episodes. The queue is empty. 5,845 assertions, from 5,835. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Deploying harmless with
|
| Latest commit: |
cb7785b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://9112af55.harmless.pages.dev |
| Branch Preview URL: | https://the-ship-flies-itself.harmless.pages.dev |
Chris: "The local chart should be used for setting a destination, not the galactic chart." The local chart shows what the tank can reach, which is the question a destination answers. The launch list's row opens it, the button in flight opens it, and the jump row's reason says CHOOSE A SYSTEM ON THE LOCAL CHART FIRST. The galactic chart keeps its own key and its own row on the station menu. 5,846 assertions, from 5,845. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew a trip to the station. He reported no mass lock on neutral traders. The rule did fire: a trader on the line dropped the drive at 4,457 units. Two things hid it. The message named nothing. It said "MASS LOCK — TORUS DISENGAGED", so a trader read the same as the planet. `massLockCause()` now names the nearest thing that holds the drive down, and `massLocked()` is a wrapper over it. The name comes from `targets.ts`, so one ship reads the same on the target list and in the message. The drive is also off for most of a trip. Of 26 trader passes inside the lock radius over 20 trips, only 7 dropped a running drive. So a neutral trader that comes close is announced one time (`close-pass.ts`). It never stops the ship: the pilot chose the course, and the line only says that the chance is there. A line for every neutral ship gave a median of 6 per trip. A trader only, outside the station's own lock, gives a median of 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew a course and reported that the ship rotates all the way to its target. He is right, and the measurement is large: a median trip to the station cost 41 full turns of roll with traffic, and 7.8 with a clear sky. The steering (`bankToTurn`) gates the pitch by the cosine of the roll error. That soft gate leaves a little pitch at a wide bank. Near the nose it is enough to hold a cone, because a bearing turns by `pitch / theta`. A trace caught the equilibrium: 0.25 rad/s of roll held against 0.0087 of pitch, at a fixed 0.035 off the nose, for ever. `bankToTurn` takes a hard roll gate now. Above that roll error it asks for no pitch at all, so the roll alone turns the bearing, the bank arrives, and the pitch then closes the angle. The course pilot passes 0.05 radians, with a 0.02 deadzone at the nose. Every combat caller passes none and is untouched: its targets manoeuvre, and a pitch held still costs time on the gun. With a clear sky the trip is the same trip: 20 of 20 reach the hand-over, at the same time and the same speed. The roll fell from 7.8 full turns to 0.2. With traffic the median fell from 41 to 1.3, and what is left is all in a fight, under the combat computer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris picked a rock and the ship flew on into it. A trace of a picked rock 53,000 units out showed the whole fault: the ship flew in at 397 units a second, braked to 4.3, and then coasted the last 500 units into the hull over two minutes. The cause is the speed deadband. `FlightDemand.throttle` is only a sign, so a band of 6 units stops the throttle flipping every frame. Inside the standoff the wanted speed is zero, and a ship coasting at 4.3 sat inside the band, so nothing braked it. The band holds a speed the ship is asked to keep, and zero is not one, so a wanted speed of zero now brakes. The standoff was also held to the target's CENTRE. A rock is 54 units across the radius and the generation ship is 340, so the commander stopped 160 units off the derelict's hull. The throttle measures the range from the hull now. The gun keeps the true distance, because a shot travels to the hull by itself. A picked rock now stops at 554 units from the centre of a 54 unit rock, at rest, and holds it. The derelict holds 838 units from the centre of its 340 unit hull, matching its own drift. The fight is unchanged: a target that moves asks for a speed above zero at any useful range. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew 207's docking on a phone and it did not work. The touch input was fine. His diagnosis was right: "lining up is not actually very accurate until the last few moments. So we aren't actually flying straight and rolling can send you off away from the slot." Measured: the pilot took the ship at 1,494 units, 407 to 886 units off the slot axis, at 224 units a second. The slot channel is 26 units across the half-width. One stick has two jobs, because the ship has no yaw axis: a roll is how it aims, and the slot wants that same roll for the wings. The pilot held the roll, the computer held only the pitch, so every roll turned the computer's own correction. An ideal pilot docked 0 times in 4. The same pilot with no roll at all docked 2 times in 4. So the stretch is two stages now, which is the shape Chris asked for: "we actually get lined up by the computer and then hand off to a 'mini' docking game. Something that is completely on rails." The computer lines the ship up with both sticks. The console reads LINING UP, and the pilot has no controls. The rails then take the ship 645 units out at 107 units a second, which is about six seconds of game. They hold the line and point the ship down it, so the pilot cannot miss the hole. The roll and the throttle are the pilot's, and they are the whole game. A scrape lets the rails go, and the computer lines the ship up again. An ideal pilot now docks 8 times in 8. A pilot who never touches the strip docks 3 times in 8, which is luck: the slot fits either way up, so it is lined up about two turns in five. `game/dock-rails.ts` is the one file in the game that moves the commander's ship other than by flying it, and `test/docking.test.ts` names it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew the mini game: "That works - but it's a bit too easy I think. Maybe reducing the speed to nothing so the user has to thrust forward would be good." He is right. The rails took the ship at 107 units a second, so the speed was already made and a hand on the roll was the whole game. The computer brakes the ship to a halt now, 632 units out on the slot axis, and the pilot flies the whole run in. Measured over the same seeds: thrusts in and matches the slot 6 of 6 docked, first go thrusts in and never matches scrapes, 2 or 3 goes, 1 of 8 died touches nothing waits there, and never docks So nothing docks by accident any more. The console reads THRUST IN, AND MATCH THE SLOT, and the manual says the ship waits for you. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew it and reported two faults. The combat computer struggles to lock onto a fast ship that circles the commander. It also oscillates when it chases a ship that weaves. The pitch rate is not the limit. A probe of the control law measured the bearing rate of every one of those targets between 0.24 and 0.79 radians a second, against a pitch cap of 1.45. The nose can always out-turn the bearing. The co-pilot read where the target IS, and it never read where the target goes. Two rules answer it, and each needs the other. 1. The aim leads the target. The gun is hitscan and needs no lead, but the NOSE takes time to swing. The lead is the swing that is LEFT, so it falls to zero as the nose arrives. A FIXED lead measured worse than no lead at all. 2. The throttle matches how fast the target RECEDES, not how fast it flies. A ship that circles you holds its range, so the commander now turns like a turret instead of flying past it. The lead takes `velocityOf`, which is the rule the pirates lead their shots with and the HUD draws its lead marker from. Its ceiling is the attack run's `MAX_LEAD_SECONDS`, which is the same rule with one home. Measured over 40 scripted targets, at real pirate speeds and a real pirate radius. The share of frames with the target inside the gun cone rose from 27.5% to 46.7%. The lead alone scores 41.7%, and the radial throttle alone scores 31.2%. Roll rate against a close circler falls from 1.08 to 0.78 radians a second, which is the oscillation Chris saw. The probe is not the game. It flies scripted targets rather than pirate brains, and it does not shoot. A 13-target sample and a 40-target sample disagreed about the radial throttle. Re-measure on the exercise wave harness before any retune. Four tests pin the pair, and each one fails when its rule is broken. The circling regression scores 5% on the old law and 29% on this one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew it and reported the fault. The console says the computer is lining the ship up, and then the rails take the ship before it is lined up. Two faults, one report, and a trace of the shipped approach measured both. 1. The hand-over never asked which way the ship points. `railsReached` asks for the last leg, `RAILS_LATERAL` and `RAILS_RANGE`. The ship was 69.7 degrees off the slot axis on the frame the rails took it. 2. The rails then turned it straight inside ONE frame. The comment on `dock-rails.ts` claimed both of its corrections were eased. Only the position was. The rotation went on in full. The computer cannot close that last turn itself, and this is the part that took the measurement to find. `dockingSticks` pitches onto the heading, and a pitch cannot fix a sideways error. It got away with that while the ship flew, because the motion sweeps the pitch plane round. The brake stops the ship first, so the sweep stops, and the nose stalls at 68.4 degrees for ever. So the rails make the turn, and the pilot waits on it. `RAILS_TURN` eases the rotation as `RAILS_PULL` always eased the position. `SessionState.dockHold` is the stretch where the rails hold the ship and the computer still owns it. The buttons and the console read LINING UP through all of it, because they read `dockRails`, which now waits for `RAILS_CONE`. Measured on the shipped approach. The nose was 69.7 degrees off at the hand-over, in one frame. It is now 10 degrees off, and no frame turns it more than a few. The extra wait is about half a second. `npm run dock-probe` is unchanged at 504 of 504 docked, median 16.4 seconds, 0 scrapes. That path is the bought docking computer, which does not use the rails. Three tests pin it, and each one fails when its rule is broken. The one-frame test reports the old 69.7 degrees when the ease is taken out. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris flew 109676c and called it: *"It's still a little too fast - there's a jerk to bring the station into alignment. The docking computer should be doing this for us?"* He is right on both counts, and he set the shape: the computer flies the ship to the right distance from the station and to a nose that faces the port. It hands over from there, to a pilot or to the bought computer. 109676c gave that last turn to the rails. The rails are the one thing in the game that moves the commander's ship other than by flying it, so the turn was a correction rather than a manoeuvre, and it read as one. THE COMPUTER CAN FLY IT, and the earlier note that it could not was wrong about the cause. `dockingSticks` cannot: it spends the roll on the letterbox, and a pitch alone cannot answer a sideways error once the ship is stopped and the pitch plane stops sweeping. But `bankToTurn` is the law for pointing a yaw-less ship, and the slot has no claim on the roll until the pilot holds the ship. So the computer's last turn uses that law, with both sticks, through the commander's own envelope. `DockPlan.steer` holds which vertical that turn banks through. It sits beside `swing` for the same reason, and it is saved in the same walk. The `dockHold` session flag of 109676c is gone, because nothing needs it now. Measured on the shipped approach, from 70.2 degrees off the axis: worst turn in one frame 69.7 deg before 109676c, 10.0 with the rails, 1.5 now nose at the hand-over 69.7 deg before, 7.3 now, against a cone of 10.3 the turn itself about a second, at the hull's own pitch and roll `npm run dock-probe` is unchanged at 504 of 504 docked, median 16.4 seconds, 0 scrapes. The bought docking computer flies its own approach and does not come through here. Three tests pin it, and each one fails when its rule is broken. A pitch-only turn never hands over at all, which is the stall this replaced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…hter Chris asked for two things on 2026-09-12. *"I think we should merge both paths?"*, and *"we should tighten up the criteria - at the moment we can be lined up with quite a wide angle difference."* ONE LINE-UP, WHOEVER TAKES THE SHIP IN. The station course used to ask who flies the slot BEFORE the line-up, in `Instruments.handOver`. A commander with a docking computer fitted went straight to it and never saw a line-up at all. Measured, that computer took the ship 147 degrees off the slot axis. Now the course hands every commander to the same stretch. The computer flies the ship to the right distance, stops it, and turns the nose onto the axis. `world-step.ts` then asks the one question. A fitted docking computer takes the ship in from there. A commander with none gets the rails and the mini game. `autopilot.ts`'s `handOverToDock` still engages the computer from the pilot's own key, and that path is unchanged. The two never had two line-ups to merge. Both already flew `planDocking` through `dockingDemand`, end to end. What differed was where the course asked the question, and it asked too early. THE CONE IS 0.03 RAD, ABOUT 1.7 DEGREES. It was 0.18 rad, about 10. A trace of the turn with the gate as good as open measured the whole curve. The nose falls from 68 degrees to 1.8 in one second. It rings once, and it settles near 1.2 degrees by two seconds. Below that it creeps: 1.2 degrees to 0.6 takes eight more seconds. So the new cone sits just above the crawl. Measured over six approach geometries: nose at the hand-over 0.73 to 1.70 degrees, from 7.3 before the turn itself 1.0 to 3.6 seconds worst turn in one frame 1.3 degrees A fitted computer now takes the ship at 1.1 to 2.0 degrees off, and docks about five seconds later, on all six. `npm run dock-probe` is unchanged at 504 of 504 docked, median 16.4 seconds, 0 scrapes. It engages `dcEngaged` directly, so it measures the flying and not the hand-over. The four new tests cover the hand-over, and each fails when its rule is broken. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"I think the fly to the station is a bit confusing. I think
you should be able to click it to disengage it, and when it should also be
disengaged by combat or other events."* And the principle behind it: *"anything
that breaks the journey should let you reconsider what you are doing - at the
moment you are stuck on a course."*
THE LIT BUTTON STOPS THE COURSE. It used to open the course list over the
running course, and close it again. A lit button reads as ON, so a tap now turns
it off. The list then shows by itself, because it always does with no course.
`COURSE_TOGGLE_KEY` is `COURSE_STOP_KEY`, and `CourseActions.opened` is gone
with the CLOSE button it drew.
A HOSTILE SHIP DROPS THE COURSE. `flight.ts` asks the co-pilot before the
course, so the course already flew nothing while the aim fought. It took the
ship back the moment the last pirate died, and the button said the ship was
heading somewhere all along.
Three things the shape of that turns on, and each cost a measurement:
- A HOSTILE SHIP is the test, not whether the computer took the stick. A
picked rock takes it too, and the first rule ended every mine run at the
first rock.
- EVERY FRAME, not the frame the fight starts. A pilot can pick a course
mid-fight, and the computer already holds the stick by then.
- With no co-pilot, `ccEngaged` stays false and the course flies on. The LIVE
BRAINS row set to NONE leaves the course the only pilot there is, and fast
forward is then what a hostile ship stops.
THE COURSE LEFT `flight-instruments.ts`, to `flight-course.ts`. The additions
put that file at 417 lines, over the ceiling `tools/sizes.mjs` calls a detector.
The course was the switch with a machine of its own, so it is the split the
detector found. 417 lines becomes 225 and 274. The course reaches the DRIVE
switch back through a host, rather than holding a second copy of the mass-lock
rule.
Six tests cover it, over three files. Two old ones pinned the behaviour this
replaces, and they now pin the new one: a fight no longer leaves a course to
fly nothing, and fast forward goes with the course rather than outliving it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"I think we should tighten it for the mini game - docking is supposed to be hard. But we can 'cheat' for the docking computer and give it more leeway."* THE MEASUREMENT THAT PROMPTED IT. A probe flew the mini game 20 times, at a different slot angle each time. A pilot who matched the spin docked 20 times, first go. A pilot who never touched the roll ALSO docked 20 times, after one scrape. The skill saved a shield. It was not what got the ship in. The slot took a roll 37 degrees out, which is 41% of every angle it presents. SO THE ONE TOLERANCE IS NOW TWO, and the caller says which. `ROLL_TOLERANCE` is the hand's, and it is 0.24 rad, about 13.75 degrees. `COMPUTER_ROLL_TOLERANCE` is 0.65, which is what both used to be. They have separate rule ids and they must not be fused: one is a difficulty setting, and the other is the equipment's licence. `dockingOutcome` takes the figure rather than reading one, and the default is the HARD one, so a caller that forgets is strict rather than lax. `world-step.ts` picks on `dcEngaged`. The HUD's alignment aid picks the same way, so the green light still means the dock test would pass. `docking-sticks.ts` aims the computer's wings at the computer's own figure. THE CHEAT IS NOT A COURTESY. `npm run dock-probe` reports the computer arriving up to 13.8 degrees off the slot's long axis, which is outside the hand's 13.75. The shared constant at that figure cost 2 scrapes over 504 approaches. With the two split it is back to 504 of 504, median 16.4 seconds, no scrapes. What the mini game is worth now, over the same 20 approaches: matches the spin 20 of 20, first go, no scrape ignores the roll 16 of 20, and 4 died of repeated scrapes Three tests pin it, and each fails when its rule is broken. The old 0.65 in the hand's place leaves a lazy pilot unscathed, which is the fault this fixes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The review of 2026-09-12 and the artifacts it was measured with. It is a record of what somebody measured, so it is committed as written. The code that answers it cites this file by name, and a cited document has to exist. Its probes reproduce byte for byte against revision 5abc879. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Review recommendations 1 and 2 (docs/COMBAT-COMPUTER-REVIEW.md). The computer acquired a target within about three seconds, then rolled around it without a useful shot. THE CAUSE. `bankToTurn` scales pitch by the cosine of the bank error, and that never reaches zero. The residue moves the target's bearing while the roll chases the same bearing, so the nose can circle the target for ever. A vertical orbit needs pitch alone and tracked 100%. The horizontal and tilted orbits need both axes, and they fell to 17%. `pitch-roll-steer.ts` already held the answer, behind the `rollGate` argument the course pilot passes. Its comment said the combat computer must NOT take it, on two grounds: a manoeuvring target cannot afford a pitch held still, and the wide gun cone hides the fault. The review measured both false, and that comment is repaired. `COMBAT_ROLL_GATE` IS 0.27 RAD, and three measures chose it. Two are the review's own probe, over 21 prescribed paths and a separate grid of 54 orbits. The third is new: `npm run combat-aim` flies 72 combat exercises against real pirate brains with real guns, which is the check the review's closing words ask for. | gate | 21 paths, 60s | 54 orbits, 90s | 72 fights | | 0 | 64.9%, 7.5% | 53.9%, 3.8% | 39.5% | | 0.27 | 98.3%, 88.5% | 97.6%, 77.2% | 57.5% | The second figure of each pair is the worst case. The full sweep is beside the constant. Tracking is flat from 0.08 to 0.3 and falls away by 0.4, and the fights plateau from 0.2. So 0.27 is the middle of a plateau, not a peak, and the exact figure inside that band is not load-bearing. Acquisition is what it costs: the worst first lock over the 21 paths went from 3.28 to 3.87 seconds. THE TESTS NOW REJECT WEAK TRACKING. Two old ones went. One accepted 20% of frames inside the gun cone and passed at 29%. The other measured a fixed 0.1 rad rather than the real cone, ran 20 seconds, and flew a target sideways with its nose pointed elsewhere — and the co-pilot reads that nose for the velocity it leads on. `test/co-pilot-tracking.test.ts` replaces both. One path gives position, orientation and speed. It measures four things per case, because a high average hides a target lost for 40 seconds: time on the gun, acquisition, the longest gap, and roll travel. Six paths, and the limits are per case. With the gate at zero, four of the six fail, at 32%, 17%, 14% and 8%. Those are the review's own figures for the same paths. The two that pass are the two it said track well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
The three confirmed findings of the review (docs/COMBAT-COMPUTER-REVIEW.md). Two of them made the commander an Offender in its reproductions, for a ship the computer never aimed at. All three now reproduce clean through the review's own integration probe. THE VIEW. `autoEngage` turns the front view on when it takes the stick. A pilot may look astern afterwards, and the trigger then fired the REAR laser at whatever was behind. In the reproduction that was a trader. The computer aims the NOSE, so its shot is the front view's alone. A pilot who looks away keeps her own trigger. THE LINE. The co-pilot works in ANGLES: a target inside the gun cone is one it will shoot, and nothing asked what stands in the way. A trader between the commander and a pirate took the automatic shot. `autoFireAllowed` now traces the beam with the same `traceShot` the shot itself uses. A clean ray still fires, because the gun grazes a silhouette the ray can pass by. The pilot's own pick still fires, because she may choose to shoot a trader. Anything else first holds the shot. Both rules live in `flight-weapons.ts`, which is the gun's home, rather than half there and half in `flight.ts`. THE WARHEAD. `CoPilotStep`'s disengage carried no E.C.M. request, so with the last hostile dead and a missile still closing the co-pilot said AREA CLEAR and asked for nothing. A warhead is a threat with or without a ship to steer at. The disengage now carries the answer, and one press is a complete answer because `Ordnance` caps the sky at one warhead. A MANUAL OVERRIDE still asks for none, because the pilot took the ship and the E.C.M. is a key she holds. The dormant trained seat had the same defect, and it is fixed the same way. Nine tests pin the three, and each fails when its rule is broken. The line test carries its own control: with the way clear the same shot is taken, so it cannot pass on a computer that never shoots. An earlier version of it did exactly that, because the geometry drifted a hair outside the gun cone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…e fight's Review recommendation 5 (docs/COMBAT-COMPUTER-REVIEW.md). It found three ways a resumed co-pilot flew differently from a fresh one at the same geometry. All three are the same mistake: the controller kept a private copy of state the world already holds, or held state a new engagement should not inherit. THE RATES ARE THE SHIP'S. `ScriptedCoPilot` kept its own `pitchRate` and `rollRate` and ramped from those. `PlayerShip.update` writes the ship's from the demand it flew, so in ordinary flight the two are the same number. They differ in exactly the two places the review measured. A manual override left the copy holding a roll the ship had stopped, and re-engagement put a −1.13 rad/s roll back on a target dead ahead. A restored save put the ship's own roll back correctly, then replaced it on the next step, turning a continued −1.48 into −0.17. So the ramp reads `player.pitchRate` and `player.rollRate`, and the controller keeps no rates at all. `AutopilotShip` carries them, which is the one interface change. Nothing new has to be saved, because `persistence.ts` already carries the ship's rates. THE MEMORY IS THE FIGHT'S. `reset()` cleared the lock and the rates and left `steerMem.side` behind, so a reset controller could bank the opposite way from a fresh one. It now clears everything. There is no state here a new engagement should inherit, so there is nothing to choose between. The review's own integration probe measures all four reproductions as exact matches now. `save_mid_turn` reports an orientation difference of 0, from 0.0219. `reenage_after_manual_override` and `reset_retains_steering_side` both report the resumed controller asking exactly what a fresh one asks. Six tests pin the contract. The bank-side one needs a geometry where the memory actually decides, so it commits the bank with a target BELOW, then asks with one LEVEL, where the flip margin holds whichever side is committed. Its first assertion checks the two controllers still disagree, so the second cannot pass on a fixture that stopped testing anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
…urement Review recommendations 3 and 4 (docs/COMBAT-COMPUTER-REVIEW.md). Both asked for a measurement before a change, and the two measurements disagree about whether to change anything. THE LEAD NEEDED NO CHANGE, and the concern that prompted the ask has gone. The review measured a lead costing the straight-crossing case 74.3%, against 98.7% with none, at a bank gate of 0.1. At `COMBAT_ROLL_GATE`'s settled value the same case scores 89% WITH the lead. A sweep of `PURSUIT_LEAD_GAIN` over both grids and 72 real fights puts the shipped 2.0 on the best worst case of both grids, and at 3.0 one orbit is lost outright. The table is beside the constant, so the next reader does not repeat the sweep. THE COMMITMENT DID. `ENGAGED_CONE` called a target an attack in progress on its angle alone. The review held one 3,000 units away and 23 degrees off, with a second hostile 500 units dead ahead, and ten seconds later the easy shot was still refused. A cone says where a ship is. It does not say the attack is going anywhere. `ENGAGED_PATIENCE` is the answer: the veto now also asks that the target fell inside the gun cone within the last 3 seconds. `THREAT_MIN_HOLD` and `THREAT_SWITCH_MARGIN` are untouched, so a switch still needs a much nearer target held for long enough. THE VALUE IS NOT PINNED BY MEASUREMENT, and the constant says so. A sweep from 1 second to 999 moved nothing: not the 21 prescribed paths, which carry one target and cannot exercise a switch, and not 72 fights through `npm run combat-aim`. The rule is dormant in everything this repository can measure, and it fires in the case the review built. That case is a fight a player can meet, so it ships with the honest note that 3 seconds is reasoned rather than fitted. The test carries its own control: a commitment that IS landing shots keeps the lock. With the patience effectively off, the first assertion fails and the control still passes. The co-pilot's tests now divide by the question each asks. What it asks for stays in `scripted-co-pilot.test.ts`. Where the nose goes is `co-pilot-tracking.test.ts`. Who it holds, and what survives a handover, is the new `co-pilot-engagement.test.ts`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"The alarm sound for a pirate is a bit too much. I think a simple couple of pings would do."* WHICH SOUND THAT IS. Nothing in the game is named an alarm, and no sound repeats while a pirate is near. The one thing that marks a pirate turning up is `combatComputerEngaged`. `autoEngage` plays it when the computer takes the stick for a fight, and it prints no console line at all, because the lines that matter arrive at the same moment. So that beep is the whole announcement. It was a 1000 Hz SQUARE wave for 0.12 seconds at the standard gain. That is a klaxon by construction. 1000 Hz sits in the band the ear is sharpest in, and a square wave carries every odd harmonic above it. It is now two SINE pings, 880 Hz then 1175 Hz, 0.05 seconds each at a lower gain, the second landing after the first rather than over it. A sine carries no harmonics, which is the difference between a buzz and a ping. Two of them rising a fourth say attention where one said alarm. BOTH ARE SCHEDULED UP FRONT. `sweep` takes an offset now, rather than the `setTimeout` that `dock` and `ecm` use for their later voices. A timer puts the second half of a cue outside the call, where `test/audio.test.ts` cannot see it and a paused tab can lose it. The cue is the one named occasion that is not a single square tone, so it leaves that table and asserts its own claim. Six assertions, and all six fail with the old beep in place. I cannot hear it. The change is reasoned from the waveform and the band, and the test pins the shape rather than the taste. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"Hmm, I just want a couple of beeps."* The previous commit answered *"a couple of pings"* with two sine voices rising a fourth, which is a design where none was asked for. It is the house beep now, twice, at 800 Hz for 0.06 seconds each. The same square voice and the same gain as every other named occasion, so it sits in the family rather than over it. It was one 1000 Hz beep for 0.12 seconds, which is half again as long as the standard beep. The offset that `sweep` gained last commit stays, and `tone` takes one too. Both beeps are scheduled up front rather than through a timer, so the whole cue exists the moment it is asked for, and the test can see all of it. Six assertions, and four of them fail with the old single beep in place. The two that do not are the voice and the gain, which is the point: everything about a beep is unchanged except that there are two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"It back to the horrible beep - what's going on?"* He is running a dev server, so this is the sound itself and not a stale build. WHAT WENT WRONG. He called the cue too much, then asked for a couple of beeps. The second commit read "beeps" as the house `tone`, which is a SQUARE wave, and only shortened it and dropped the pitch. That changed nothing that mattered. A square wave carries every odd harmonic above its pitch, and this cue sits where the ear is sharpest. The harshness was never the count or the length. It was the voice. So it is two SINE beeps at 700 Hz, 0.06 seconds each, 0.12 apart. Sine carries no harmonics at all. Two of them, at the same pitch, with no interval and no design: a couple of beeps, and nothing else. It is now the one named occasion that is not the house square voice. That is a real difference rather than an exception. Every other named sound is a cockpit acknowledgement the pilot asked for. This one interrupts her, and it is the only sound that does. The test asserts the voice rather than the taste, and it fails on square. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"how about a whoop whoop - like a klaxon just two of them though"*. Each whoop is a sine rising from 320 to 880 Hz over 0.22 seconds, and there are two, 0.28 apart. A GLIDE is what makes a whoop, and that is a different lever from the voice. Three earlier attempts confused the two: the harshness was the square wave, and the klaxon is the rise. A real klaxon runs until somebody silences it. This one says its piece twice and stops, because the fight it announces is already under way. `test/audio-fixtures.ts` records a glide's target from today. It dropped it before, so a test of a whoop would have passed on two flat tones. Eight assertions cover the cue, and six of them fail on the beep it replaced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"why does it go on for so long? Several long seconds?"* He was describing the sound a fight makes, and four commits had already tried to fix that sound. It was never the sound. TWO RANGES FOR ONE QUESTION. `autoEngage` asked the condition light whether there was a fight, and that looks `PLAYER_INTEREST_RANGE` out, which is 9,000 units. The co-pilot only looks `THREAT_RANGE`, which is 6,500. A pirate between the two engaged the computer, and the co-pilot refused it in the SAME frame. Measured, with a pirate held at 7,500 units for 5 seconds: before 300 engagements, 300 fight cues, AREA CLEAR on all 300 frames after 0 engagements, 0 cues, and nothing on the console So the cue played 60 times a second, each copy overlapping the last. That reads as one continuous noise for as long as a pirate sits in the band, and no waveform would have survived it. The console held AREA CLEAR the whole time as well. `engaging` in hostility.ts takes the range now, and the two callers pass what they actually mean. The condition light and the bribe key keep the ship's own engage range. `Autopilot.fightOn` and the co-pilot's own filter both pass `THREAT_RANGE`, and they share the one predicate rather than spelling it twice. THE FIRST VERSION OF THE TEST PASSED ON THE BUG, and that is worth the record. It sampled `session.ccEngaged` at the end of each frame. The flag never survives a frame here: it goes true in `autoEngage` and false again in the co-pilot, both inside `pilotDemand`. So the test measures the console instead, which is what a player sees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"I visited a rock hermit and then when I left clicked fly to the station - the computer crashed me straight into the rock hermit - we need to have some avoidance of obstacles"*. The line already went round the PLANET, and round a POLICEMAN on a smuggling run. It went through everything else. Reproduced with a hermit 600 units ahead on the line to a station 60,000 out: the ship passed 26 units from the hull at 399 units a second, and the commander died. It now goes round anything solid too, at `COURSE_OBSTACLE_CLEARANCE` from the HULL. The same flight clears the hermit by 257 units and lives. THE CLEARANCE IS THE SHIP'S OWN TURNING CIRCLE, not a number chosen by eye. At top speed the tightest circle the commander can fly has a radius of speed over turn rate. A quarter circle moves a ship one radius sideways, so a line that passes that far from a hull leaves exactly the room a quarter turn needs. It moves with the flight envelope rather than against it. THREE ROLES, AND NO SHIP. A rock hermit, an asteroid and the derelict sit still and are big enough to kill you. Every other ship moves, and a course that bent round those would bend round a fight. A thing at the END of a line is never in the way of it, which is `distanceToSegment`'s existing rule. That is what still lets the hermit course fly to a hermit. The additions took two files over the 400-line ceiling, and both had the same seam. `course-clearance.ts` is where a line goes; `course-pilot.ts` is what a course does for one frame. The tests follow the same split. Eight assertions cover it, and the flight it came from is the last two: without the detour it still reports `dead`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"Collecting cargo often seems to be difficult - we miss it quite a lot - especially when it is moving."* A probe named the faults by flying ONE canister against five. One alone was never missed, at any drift, in three seconds. Five were missed seven times with the cargo at rest. So the count was the fault, not the drift. THE PICK. The course took `loot[0]`, the nearest, recomputed every frame. The ship closed on one canister, a second became nearer as it moved, and the course turned away from the first. It now holds the one it is flying at until the canister is aboard or off the scanner. That is the rule the combat co-pilot already keeps for a target it is lined up on, and the same fault it was fixed for. THE AIM. It was where the canister had been. A canister drifts at up to 45 units a second and the scoop reaches 45, so an aim at its old place arrives a whole scoop behind it. The lead is the time to cover the gap at the course's own speed, capped by `COURSE_COLLECT_LEAD`. THE CAP IS WHAT IS FITTED, and it needed 80 runs a row to see. One run of five canisters flips between all five and two of five, so an earlier sweep of 5 runs chose a value the wider one rejects. Over 240 canisters at the three moving drifts: | cap | collected | mean time | | 0 | 191/240 | 75.0s | | 2 | 210/240 | 58.6s | | 5 | 231/240 | 50.8s | | 6 | 219/240 | 59.4s | Cargo at rest is untouched at every cap, because a still canister has no velocity to lead on. It was 80 of 80 in 21.6 seconds throughout. The tests pin the two rules rather than the flight, because the flight that measured them is two minutes of simulation a run. A nearer canister does not steal one the course is on, and a drifting one is aimed ahead of. Each fails when its own rule is taken out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"I have a docking computer - but instead of it being activated I still get the lining and only once that is done does the computer activate. I think we should have an options when in range 'Docking Computer' and if you click it activates. Otherwise we have the lining up and manual docking."* The course asked who flies the slot BEFORE the line-up once, and a fitted computer never saw one. Then cb248f6 moved the question AFTER it, so a commander who had paid for the fitting still sat through a line-up she did not want. The question is hers now, and it is open for the whole stretch. A DOCKING COMPUTER button stands above the line-up's own row, and above the roll strip in the mini game. A commander who fumbles the slot can still hand it over. A commander with no computer fitted is offered nothing, as before. It is the key's own code, so the button and the C key are one path rather than two. `Instruments.dockingComputer` ends the trial when the computer engages, because the two cannot both fly the ship. Without that the buttons would read LINING UP while the computer flew. `Autopilot.handOverToDock` goes with it. It was the course's own hand-over, and nothing calls it now. Eight assertions cover it, over the buttons and two flights. A pilot who presses it is flown in. A pilot who does not keeps the ship, and the slot is never taken because she flies nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
Chris, 2026-09-12: *"collecting cargo seems to be broken sometimes. I seem to run at maximum speed, then slow down and then miss it and then run at maximum speed."* That is the trace exactly. `arrive` picks its speed from a braking curve, which is right for a big target on a straight line. A trace of a stuck run showed 400 units a second with a canister 250 units off, for two minutes. A SHIP THAT FAST CANNOT CORRECT. Its tightest turn has a radius of 276 units and the scoop reaches 45, so a lateral error the steering leaves at that point cannot be closed at all. The ship sails past and comes round again, for ever. `COURSE_COLLECT_CAP` is twice `COURSE_COLLECT_SPEED`, the speed the course settles at. That is a turn radius of 83 units, under twice the scoop's reach. Measured over 64 runs of five canisters, at four drifts and four scatter directions: | cap | left behind | mean time | | 400 | 4 of 64 | 43.5s | | 150 | 0 of 64 | 32.0s | | 120 | 0 of 64 | 29.8s | | 90 | 0 of 64 | 32.6s | | 60 | 36 of 64 | | At 60 it is the collect speed itself, and the course can no longer catch a canister drifting at 45. HOLDING ONE CANISTER MADE THE LOOP PERMANENT rather than causing it. 2d7af57 stopped the course switching away, and switching away had looked like progress. Both are needed: the hold, and a speed the hold can act at. The test file passed the 400-line ceiling again, and the seam is `CoursePilot.arrive`: one function with four callers that fly to a thing and stop. Those move to `course-arrivals.test.ts`, and the four fixtures both files need move to `course-fixtures.ts` rather than being copied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The ship flies itself, and the pilot decides where
Chris flew the touch controls of docs/TODO/204 on a phone on 2026-09-11 and
could not fly the ship. He wrote
MOBILE_CONVERSION.mdand set a newdirection. This branch takes the touch controls out and builds it: the pilot
picks where the ship goes, and the ship flies there. Four plans landed:
205 to 212. The queue is empty again.
What a player does now
pad. A jump leaves with its countdown running; the rocks and the star are
choices too, so a commander with no money for fuel can still earn.
station, a derelict, the rock hermit, the star, the next jump. A dimmed
button says what the ship needs first. FAST FORWARD runs the world eight
times faster while nothing hostile is near, and it changes no rule.
the laser, the missiles, the E.C.M. and the choice of target. The bought
combat computer does the rest, but never spends a missile. RUN FOR IT, the
bribe and the cargo dump are buttons.
course hands the pilot the last 1,500 units at the slot's own speed. The
computer holds the ship on the axis. The pilot matches the station's spin
with a roll strip and the speed with THRUST and BRAKE. The slot now refuses
a ship that arrives too fast, and a scrape bounces it clear to try again.
SCAN, ESCORT, RECOVER, PICK UP or SLIP PAST THE POLICE.
flight keys stay, and a key takes the controls back at any time.
What the work found and mended
systems. The traffic placement lifts such a ship out now.
fledbranch that three story arcs carry had no sender.1,494 units out, 407 to 886 units off the slot axis, at twice the speed the
slot accepts. One stick has two jobs: a roll is how the ship aims, and the
slot wants that same roll for the wings. So every roll turned the computer's
own correction, and an ideal pilot docked 0 times in 4. The stretch is two
stages now. The computer lines the ship up and brings the ship to a halt on
the line. The rails then hold the line, and the pilot thrusts in from a
standing start and matches the slot. A pilot who does that docks 6 times in
6. A pilot who touches nothing waits there and never docks.
The ship held its standoff, then coasted the last 500 units into the hull
over two minutes, because the throttle's deadband held a drift as well as a
speed. It brakes to a stop now, and it measures the standoff from the hull
rather than the centre. A picked rock holds 500 units of clear space.
Chris saw it and reported it. The steering held a cone: the nose circled the
target at a fixed angle, and the roll never stopped. The course pilot now
holds the pitch still until the bank arrives, which breaks the cone. A median
trip fell from 41 full turns of roll to 1.3. The fight keeps the law it has.
reported no mass lock on neutral traders. The rule did fire: a trader on the
line dropped the drive at 4,457 units. The message named nothing, and the
drive is off for most of a trip, so 19 of 26 trader passes were silent. The
drop now names what stopped the drive. A neutral trader that comes close is
announced one time, so the pilot can choose to pirate it. It never stops the
ship.
Measurements
npm run checkpasses on every commit. A fight now stops a trip until thepilot fires, runs or pays, which is the design: a measurement with traffic
left 8 of 20 trips short of the station with nobody firing.
Still yours to judge
Whether a fight is fun, and whether the trip feels right on a phone. Camera
views outside the cockpit wait as issue #50.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Cpi86XPSQzZQhVhRnZED58