v0.1.0 — undo, the crew's reach on the board, workers on the card, and four engine bugs
Fifteen items from two playtest sessions. Three that read as drawing faults were engine bugs: cars could be added to a train that was not being made up (50 offers in 8 games), the make-up panel merged two trains and could couple a car to the wrong one, and an Office upgrade silently deleted what a Modifier had added. A fourth was a sentinel inside a coordinate's own value range — a Mainline placement travelling as row -1, which is an ordinary district row. Trains are now drawn the way they stand: west on the left, nose toward the way the engine faces, on both the district card and the Division chip. Undo steps back through the game by replaying the save without its last intent. The switching walk keeps its rejections, so the board can say why a square is not offered. Laborers and Porters are on the card, and the rule that a district only grows outwards is finally written down. Versions start here: third digit for fixes, second for a feature set, 1.0 for a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GgtkX8JnvKa8y2tuJ8aQf4
This commit is contained in:
+157
@@ -6,10 +6,167 @@ the things that turned out to be wrong live here.
|
||||
Measured figures are 100 solitaire Standard games with the developer bot unless stated otherwise.
|
||||
The target is 20 Revenue over 5 Days.
|
||||
|
||||
## Versions
|
||||
|
||||
Every commit carries a bump, decided with Jesse rather than assumed:
|
||||
|
||||
- **third digit** — bug fixes to what is already there.
|
||||
- **second digit** — a new set of features.
|
||||
- **1.0** — the first release we think is solid enough to call one.
|
||||
|
||||
The number lives in `package.json` and nowhere else; `scripts/build-web.ts` stamps it into every
|
||||
page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identified from the page itself.
|
||||
|
||||
---
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.1.0 — 2026-08-08
|
||||
|
||||
The first numbered build. Everything below the "second playtest pass" heading was made under
|
||||
`0.0.1`, across twenty commits, which is exactly the problem the version convention above exists to
|
||||
fix: "the current build" was only ever answerable by a commit hash.
|
||||
|
||||
### A third playtest pass — and three of the "display problems" were engine bugs
|
||||
|
||||
Fifteen items came back from two playtest sessions. Several of the ones that read like drawing
|
||||
faults were not: the board was reporting the game accurately and the game was wrong.
|
||||
|
||||
**Cars could be added to a train that was not being made up.** `newTrain.placeCar` accepted any tray
|
||||
with room in its consist, while the New Train Phase's own idea of the train it is waiting on —
|
||||
`trainNeedingCars` — requires the tray to be **at a Division Point** and short of its card. So during
|
||||
any New Train Phase the Division Yard would hand cars to a train standing on a siding in your own
|
||||
district, or one halfway across the Division. Measured before the fix: **50 such offers across 8
|
||||
solitaire games**, including Train 9 mid-crossing with three cars already aboard. That is the
|
||||
"cars magically appeared on my train" report, and the answer to it.
|
||||
|
||||
The two questions — "is this the train being assembled?" and "may this car be added?" — now come from
|
||||
one predicate (`isBeingMadeUp`, in apply.ts beside `check`), which is what stopped them disagreeing.
|
||||
`newTrain.passCar` takes the same guard.
|
||||
|
||||
**The make-up panel merged two trains into one.** Two trains can be built in the same Stage — a
|
||||
timetabled train and a Second Section, or an Extra — and the panel collected every option from every
|
||||
tray, titling the result with whichever tray came first out of the map. Reproduced at seed 99,
|
||||
Day 3 Stage 12: eighteen car chips under "Making up Train 8", covering two different trains. Worse
|
||||
than the wrong caption: the Division Yard chip binds to the FIRST matching option, so clicking a
|
||||
hopper could couple it to the other train. The panel is now scoped to the one tray the engine is
|
||||
waiting on. This was reported as the history announcing Train 9 while the panel said Train 10.
|
||||
|
||||
**Upgrading the Office deleted Modifier bonuses.** `officeUpgraded` wrote the new tier's printed
|
||||
numbers straight over the facility, so a Restaurant beside a Depot — +1 passenger out, +1 porter —
|
||||
was silently erased by the next upgrade, after the card had been spent. The tier is applied as a
|
||||
**difference** now, so the upgrade raises the Office by exactly what it is worth and leaves what is
|
||||
standing beside it alone.
|
||||
|
||||
**A sentinel inside a coordinate's own value range.** ABS Signals goes on a Mainline card, and the
|
||||
node index travelled as the fake coordinate `{ row: -1, col: node }` — but row −1 is an ordinary
|
||||
district row, the first one below the Running Track, where most districts start. So ABS Signals
|
||||
highlighted whichever district card sat at that column, and an ordinary Enhancement laid one row down
|
||||
was described as being "out on the Mainline, node −2". `card.play` now carries `node` as its own
|
||||
field and a Mainline placement has no coordinate at all; the board simply does not light up for it.
|
||||
This was the other half of "why is my Depot highlighted?" — the first half being that Enhancements
|
||||
legitimately attach to played cards, which nothing on screen said.
|
||||
|
||||
### The train, drawn the way it stands
|
||||
|
||||
**`consist` is ordered nose first, and both renderers drew index 0 leftmost whatever the train was
|
||||
doing.** A westbound train therefore came out right and an eastbound one came out mirrored —
|
||||
reported from seed 270861860, Train 10 running east and drawn engine-first at the WEST end, which
|
||||
reads as an engine shoving its whole train ahead of it. The board is a map, so the drawing obeys the
|
||||
map: west on the left, nose toward the way the engine faces. A crew on a north-south spur keeps
|
||||
nose-left and lets its ▲/▼ say the rest.
|
||||
|
||||
**The Division chip draws the train now.** It was a name and a figure, and the figure was Stages left
|
||||
to cross — read once as the car count, and once it was labelled `· 2⧗`, read as redundant beside the
|
||||
position the card already draws. It is: the region position is DERIVED from that countdown. So the
|
||||
chip carries the consist instead, in the same vocabulary the district card uses, and the countdown
|
||||
moved into the tooltip where there is room to say what it is. Stages are not regions — a card is two
|
||||
regions of fixed distance, and how many Stages a train needs over them depends on the card speed and
|
||||
the train (`crossingStages`); they coincide only for a 30 card and a normal train.
|
||||
|
||||
**Loaded or empty, told the same way in both places.** An occupied slot on a district card took a
|
||||
generic blue fill and only a LOADED car overrode it, so at 26×15px empty read as blue-ish and loaded
|
||||
as brown-ish — while the tray beside it made the same distinction unmistakable. Reported as "I left a
|
||||
car in a siding and now I can't remember if it was loaded". The card now matches the tray, and every
|
||||
slot and every car in a consist carries its own words as a tooltip.
|
||||
|
||||
### Saying what the game already knew
|
||||
|
||||
**Why a switching move is not offered.** The switching game was played off a list of coordinates —
|
||||
`move to (0, -2)` as a button, nothing on the board, and no account at all of the squares missing
|
||||
from the list. `exploreMoves` returns the walk's REJECTIONS alongside its destinations, out of the
|
||||
same traversal, so a reason on screen is the rule that actually refused the square rather than a
|
||||
second guess at it. Five conditions: the rails do not meet, another train is on the card, the
|
||||
industry is locked by MEN AT WORK (§9.3 — no train may enter it *or cross it*), the coupling would
|
||||
overfill the consist, or it is a turnout you may run through but not stop on.
|
||||
|
||||
The board now shows where the crew is, where it may go, and marks the rest with its reason on the
|
||||
card's own tooltip. A turnout is drawn differently from an obstruction, because it is not one — and
|
||||
it is kept out of the Blocked panel, where every turnout in the district would otherwise appear.
|
||||
|
||||
**Mandatory coupling is named on the button.** "Move to (0, -2)" becomes "move to (0, -2) — couples
|
||||
loaded hopper, caboose on the way (onto the nose)". Coupling is compulsory (§A.4) and was announced
|
||||
only in the history panel, which is the one place a player is not looking while switching. 70 move
|
||||
buttons in five games now say what they will pick up.
|
||||
|
||||
**The workers are on the card.** "How do I see the number of laborers in an industry card?" — you
|
||||
could not; the number that decides every Cargo phase was in a side panel. Porters were worse:
|
||||
`porters` had been on the view-model all along and **no renderer had ever drawn it**, which is why a
|
||||
Restaurant's +1 porter had "no indication anywhere". Both are on the card now as free-over-total, and
|
||||
the Facilities panel shows porters beside laborers.
|
||||
|
||||
**Ships-out and receives, in words.** A Refinery and a Grocer's Warehouse were distinguished by the
|
||||
stroke colour of one 13×12px box and the direction of a 10px chevron. Rendered both and diffed the
|
||||
SVG to check the complaint: identical shape, identical sign, same box count in the same place. The
|
||||
card says `SHIPS OUT` or `RECEIVES` now.
|
||||
|
||||
**And the Office told the truth about itself.** `baseOf` returned zeros for a passenger facility
|
||||
despite a comment claiming it read the Office tier, so a plain Depot displayed `out 1 +1` — crediting
|
||||
a Modifier that was never played. The card tooltip had the mirror-image bug, reading the printed tier
|
||||
rather than the Office as it stands, so a Modifier's effect never showed there either.
|
||||
|
||||
### Undo, and the chrome around it
|
||||
|
||||
**Undo, as far back as you like.** The save is the seed plus the intents, so undo is a replay without
|
||||
the last one: no undo stack, no inverse of each action, and no way to reach a position the rules could
|
||||
not have produced. Solitaire only. It is a deliberate take-back rather than a rewind — the RNG
|
||||
advances with the replay, so a train card re-rolls the same Stage, but you can see that roll and then
|
||||
spend the turn differently. `TODO.md` carries the question of whether a Stage boundary should become
|
||||
a commit point.
|
||||
|
||||
**Both Division end labels were clipped.** They hung off the outside of the end cells and needed
|
||||
padding wider than the caption to survive, which the board did not have — and giving it to them would
|
||||
have spent that width on two captions instead of on the map. Centred under their own Division Point
|
||||
they cost nothing, and `PAD` drops from 92 to 22.
|
||||
|
||||
**The turn chart scrolls away no more.** The title bar was sticky and the row under it — Day, Stage,
|
||||
phase, waiting-on — was not, so scrolling the board took away the thing most worth glancing at. They
|
||||
are one sticky block now.
|
||||
|
||||
**The timetable has a key.** Blue is a train due, violet is the Stage you are in, dimmed is gone, and
|
||||
the green is a *flash* marking the slot the die just filled — which is exactly why it needed saying.
|
||||
|
||||
**And the rule that shapes every district is finally written down.** A district only grows outwards:
|
||||
the Limits signs are the growth point on the Running Track and move outward with the card, and no
|
||||
card anywhere can be inserted between two cards already down. Enforced since the beginning, stated
|
||||
nowhere. It now sits beside the district, and a legal square that already carries a card says what
|
||||
playing there would do — `EXTEND THE RUNNING TRACK HERE`, `ATTACH TO THIS CARD` — instead of lighting
|
||||
up blue and saying nothing.
|
||||
|
||||
### Measurements
|
||||
|
||||
The two engine fixes take things away, so both were measured rather than assumed: 200 paired games
|
||||
before and after come to **revenue 1.4 either way**, cards played 24.4 → 24.2. Neither was paying for
|
||||
anything the bot relied on.
|
||||
|
||||
Worth stating plainly, since the changelog above still carries older numbers: the developer bot now
|
||||
averages **1.4 revenue against a target of 20**, with 1 win in 200. That is not a regression from this
|
||||
work — it is the same at `f4c0f49` — and it is consistent with what `TODO.md` already records about
|
||||
barring curves from the Running Track and making industries stub-only. The bot has not been retaught
|
||||
since those rules tightened.
|
||||
|
||||
Tests 386 → 402.
|
||||
|
||||
### A second playtest pass
|
||||
|
||||
**The arrival message told you to do work you could not do.** Train 4 is the *Express*, which carries
|
||||
|
||||
Reference in New Issue
Block a user