9a9e50b3c688feff9b5fe64a345bdec4b7c4a902
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9a9e50b3c6 |
v0.8.0.11 — sixteen fixes from the second multiplayer playtest
Arrivals name whose Office they reached, and no longer tell every seat they can work the train. The turn chart follows the animation queue, so being five behind looks five behind across the whole screen rather than half of it. Pause sits beside Skip and preserves the dwell a held step still owed. A one-render look at another player's Office Area. The district summary counts the board being shown. LIMITS is printed beneath its card instead of through its border. The Mainline region divider is visible. Only Hilly mentions FAST/SLOW, because it is the only card that reads it. A passenger Modifier on a Whistle Post reports itself dormant rather than claiming the facility "only receives". An automatic phase says what the Division is doing instead of answering by negation. The version appears once in the header rather than twice on every .s9pk. Save files carry the join code, the Stage and the date. The New Train phase, reviewed before being changed: the make-up panel now says what the train STILL needs rather than only what its card calls for, explains that a player adds one car before the round passes on, marks the train being loaded on the Division map, and gives an addable car in the yard the same amber every other clickable thing on the page wears. Reasoning, measurements and the reports behind each are in CHANGELOG.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nnuCv8UodHucFfx3LWEoX |
||
|
|
64e8ce584f |
v0.8.0.6 — your move waits its turn, the lit pile keeps asking to be looked at
Your actions are put away while the board is catching up. The board on screen is behind the game, so a move offered there is a move against a position that has already moved on — and the screen had grown to four things competing at once: the district, the history, the catching-up row, and a lit pile. Skip is one click away, so the wait stays voluntary. That could have locked a player out of their own game. Hiding actions behind busy() makes that flag the thing standing between a player and their turn, and without requestAnimationFrame nothing ever advances the queue — so busy() would never clear. Caught by the DOM-stub test that has been proving this page still starts since long before any of this existed. No rAF now means draw everything at once, which is what pace 0 does deliberately, and a queue that throws empties itself rather than stranding anyone. The lit pile was never brief: measured, it stays lit for 6997ms at 10x. It was a single flash over a dark fill, easy to miss while watching the district — a state that settles stops asking to be looked at. It pulses now for as long as the move is up. And the pace ceiling was not theoretical. 10x was the top of the ladder and was reported still a bit fast; it runs to 20 now. A control whose limit is reached in ordinary use has the wrong limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6 |
||
|
|
3fca325699 |
v0.8.0.5 — the Home Office deck, and lighting the pile a move touched
"Many operations still occurred too fast for me to see", at 10x — where an action already holds the screen for seven seconds. So it was never duration: a bot drawing a card changes one number in a panel nobody is watching, and the board sits unchanged. Raising the dwell was the wrong lever and it had been pulled three times. f.deck has carried the face-down count since the Frame existed and nothing drew it — the display gap test/display-gaps.test.ts sweeps for, surviving in the one panel that draws every other pile. It is a tile now, first in the row, face down, because that is the order a card travels and not knowing what is on top is the point. And the piles a move touched are lit for as long as that move is on screen. Derived from the frames either side of a step rather than sent, so nothing joins the protocol and the 0.8.1 board gets it free. What lights follows what is public, and was measured across four seeds rather than reasoned about: a Home Office draw lights the deck and never names the card; a Department draw lights that pile, and the deck too when it refills; a discard lights the Department it lands on; a played card lights the Salvage Yard. Switching and new trains light nothing here — they move the board, which the district panel already follows. A state rather than a flash: the timetable's fixed 1.5s animation would be over long before a seven-second pause. Not for your own moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6 |
||
|
|
c10f52791e |
v0.8.0.2 — the speed control that was only ever a URL parameter, and a Day-end
contradiction Two things found by playing v0.8.0.1, neither in the mechanism itself. ?pace= never worked. index.html's doors are play.html?lobby and play.html?solitaire, so arriving through the splash replaces the query string and the play page only ever saw ?lobby — a whole game was played at 1x while believing it was at 7x. v0.8.0 shipped that parameter as the only way to change speed and the game's own front door destroyed it. There is a control on the play screen now, beside zoom, persisted per viewer; the doors carry pace through as well, so the URL lever is honest for handing two playtesters different speeds. PACE_LEVELS moved to sim/pacing.ts with DWELL and MAX_PACE — the tuning surface in one file, and testable. The committed default is unchanged: what it should be is a question for a game played at a speed that took effect. And the Day-end dialog said "0 today, 2 in all". advance.ts increments the Day and then zeroes collisionsToday, and noteDayEnd() fires when the Day goes up — so the dialog reporting the Day that just finished was drawn from the very frame in which that Day's count was reset. Reproduced on four of five seeds before changing anything. The count is captured at the rollover now; it is not derivable on the client, because in multiplayer the push announcing the new Day is the same push that carries the reset. And "today" was the wrong word regardless: it names the Day instead — "Collisions: 2 on Day 1, 2 in all". Unrelated to v0.8.0 — that one has been wrong since the dialog was built for Gitea#10, and needed somebody to play a Day with a collision in it and then read the summary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6 |
||
|
|
e62ea54259 |
Four things the game counted and never said, and two it said wrong
Stays in the unshipped v0.7.9. Prompted by Jesse asking the general question after two v0.7.9 fixes turned out to be the same shape: actingPlayer existed and the Frame threw it away, and collisionsToday / collisionsTotal rode the Frame for three releases with nothing drawing them. So what else is computed, serialised and sent to nobody? THE AUDIT, done rather than guessed. Every one of Frame's 59 top-level fields grepped for a read across the seven renderers, then the same for Tally's 26 members. 55 of 59 are read. Four are not. tally.unloadsBegun was visible rather than merely unused. §9.1 makes loading and unloading the same shape — begun, then carried through — and the results screen printed "Loads still in the pipeline" for one side and nothing for the other, reporting half of a symmetric mechanism. tally.cardsDiscarded was counted by the engine and listed beside "Cards drawn" and "Cards played" without it, though Gitea#9 made throwing a Timetabled train away a deliberate move — a player CHOICE the game counted and never reported. Both are reported now. viewerSeat and overHandLimit are deferred by Jesse. The second is the fullest version of the shape: engine computes it, view.ts puts it on the Frame, session.ts declares it on the Session interface AND implements it twice, and the only caller in the repo is its own test. Four layers of plumbing, no consumer. The decision when it comes is delete-or-document, not a patch. Fixing the two turned up a third thing: resultsHtml draws tallyHtml(report?.tally ?? f.tally), and report is f.official, so a finished game reports the tally frozen at the official ending rather than the live one. The first attempt at a test overrode f.tally alone, changed nothing on screen, and failed for a reason unrelated to the fix. A SHOUTED KEYWORD IS NOT A SENTENCE. `EXTRA X18 started…` attributed to a player rendered as `Player Solitaire eXTRA X18 started…`, and the same happened to TRAIN 1 MADE UP and COLLISION. `record` folds a narration's opening word into the middle of a sentence and did it with a flat charAt(0).toLowerCase(). It now folds only a sentence-cased word — ^[A-Z][a-z], a capital followed by a lower-case letter — which also leaves X22 Pee-Dee alone, where a naive uppercase test gets it wrong because '2'.toUpperCase() is '2'. It had been filed under Play Balance, where it has no business being, which is how it survived a session that had ruled balance work out of scope. A REPLAYED SAVE NOW NARRATES WHAT THE LIVE GAME NARRATED. fromSave's loop called record(game, result.events) with no actor, so every restored save, every undo (which rebuilds through fromSave) and the replay viewer stripped the "Player X" prefix off every attributed line. submit attributes and fromMultiplayerSave attributes; this was the one path of three that did not. One argument, with actor already computed on the line above. Why it survived: nothing ever compared a fromSave-built log against a LIVE-played one. The single log-comparing test compares undo's rebuilt log against another fromSave-built log — and undo itself rebuilds through fromSave — so the gap cancelled out on both sides. The suite was green with the bug in and green with it out. The new test plays a game, saves it, restores it and asserts the two logs are identical: the missing direction, not a new requirement. All three fixes were confirmed to go RED with the fix reverted before being called done. 884 tests pass, seventeen new. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTaNBL1jVxNqgFdjHkHoo3 |
||
|
|
a70b7f88f3 |
Say who the game is waiting on, and move the Fedora; replay in words
Three items folded into the unshipped v0.7.9. WAITING ON (reported by Jesse from play). The status line said "nobody — the Division is running itself" while the game was stopped on the Superintendent. Frame.actor carried clock.currentActor, which is null for the whole Mainline Phase, so all three interruptions — §8.1's clearance ruling, Gitea#5's Yard Office offer, Gitea#19's Red Flag prompt — reported that nobody was holding it up. actingPlayer had the answer since the Gitea#5 refactor; the Frame threw it away. It carries actingPlayer now, plus a new `awaiting` field naming the question and the train: "waiting on Bob · a clearance ruling · Train 4". Naming the person alone is not enough when three different things can be pending. THE FEDORA (TODO #29) rides at the right-hand end of the phase row instead of a line of its own, and wraps under rather than squeezing the chips. THE DEVELOPER REPLAY (TODO #34) printed "loss — revenueFloor", the same defect Gitea#16 was filed about, still alive because nothing player-facing pointed at it. panels.ts's reasonSentence is exported and shared rather than reimplemented, fed the last recorded frame and stripped of markup. The drift test maps win/loss to won/lost so it still checks the two AGREE rather than that they are spelled alike. Also carries the previous, unsigned commit's work: the two setup screens worded the same section by section. 877 tests pass, ten new. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdG46Ja2PEDBkpqiDazMoX |
||
|
|
45580d8b61 |
v0.7.3 — a game that asks before it ends, and a results screen worth reading
Two issues off the tracker, and they are halves of one thing: the end of a game. Neither ships on the 0.4.9 line — Jesse's call, that line may be complete and these are not fixes people mid-playtest need. EXTENDED PLAY (#11). The official result is settled at the original game length and never changes: in a five-Day game extended to eight, the winner is whoever led at the end of Day 5. Extending grants exactly one Day and the question is put again at the end of it — solitaire the player decides alone, multiplayer it is unanimous and one refusal ends it there. Only days-based endings offer it; a §3.4 collision breach is final, during an extended Day exactly as during the scheduled game. It could not be a client-side change. `check` refused every intent once `status` left `active`; the server never loads a `finished` game back into memory; and a save is `{ seed, config, history }` replayed through the engine, so a "continue" the history does not record did not happen. Hence a fourth status, `awaitingExtension`, and a `game.extend` intent. `config.days` never moves — `extraDays` counts the borrowed Days and `official` freezes the outcome, the standings and the statistics at the first ending. THE RESULTS SCREEN (#16). `GAME OVER — revenueFloor` was `outcome.reason`, an internal enum interpolated into the page at the one moment the game has the player's whole attention. Every reason now has a sentence with the game's own numbers in it. Around it: the result and winner, standings, the rules the game was dealt under, a per-player breakdown, and the railroad — trains through the Division and how many worked en route, loads made up and broken, passengers, cars switched, trains destroyed. It shares the Day-end dialog's blocks rather than reimplementing them, and stays reopenable so continuing does not cost you the results. Statistics are folded, not recorded: `state.tally` counts what the event stream says happened, hooked at `applyIntent` and `advance` because `reduce` never sees the phase driver's events — and those are the interesting ones. Nothing in the rules reads it, and it rides the Frame, so multiplayer gets the same numbers as solitaire from one implementation. THREE BUGS FOUND IN TESTING, all of which would have shipped: - a saved game containing a vote could not be resumed (NO_ACTOR). A history is a flat Intent[] with no seat recorded; the replay derives who acted from the turn order, which cannot work for an intent every seat may send in any order. `game.extend` carries its voter, checked against the authenticated seat. - an all-bot game hung on the question for ever. `driveBots` loops on `currentActor`, null the moment the game stops, so it cannot cast a vote, and the bot-vote driver returned early with no humans to follow. - the balance harness became unbounded — `test/sim.test.ts` went from under a second to never finishing. `randomBot` took another Day about half the time, so every seeded game ran to playGame's 50,000-turn cap. Fixed in the driver, not in a policy, so it holds for bots not yet written. All three have regression tests. 832 tests pass, against 793 before this change. NOT BUILT, and a correction. #16's own comment said `trainStoodStill` "is emitted per Stage, so a run of them is exactly the sat-on-a-siding streak". It is not: reading advance.ts, it fires once per game and only for a train whose profile sets `stopEarnsPoint` — the X18 Circus — with `stopPointClaimed` preventing a second. The streak was built, rendered "1 Stage at (0,0)", and was taken out again. There is no per-Stage "this train did not move" signal in the engine, so "longest an engine sat on a siding" needs one first; TODO.md #36 records what it would take, and the Circus set-up is reported instead. Badges remain the second pass #16 asks for (TODO.md #33), and because the statistics are derived rather than recorded, that pass can add any of them retroactively to games already played and saved. Extended play has not yet been played at a real table (TODO.md #35): the multiplayer vote has only been driven through `session.intent`, never through two browsers. Closes #11 Closes #16 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAgJSmeV8zrMh55Mj85ESb |
||
|
|
441447648d |
v0.7.1 — a caboose is not a load, a Day that says it ended, and a train you may throw away
Four issues off the Gitea tracker, all of them things a player saw at the board. Reasoning for every item, and what was verified how: CHANGELOG.md. - Gitea#8: X22 Pee-Dee refused every caboose, including the one it was made up with, so setting it out stranded the train. All six cabooses are minted loaded because §2.2's "coloured is loaded, white is empty" doubles as a piece count in the supply table; one read of the flag took that literally. A caboose carries the crew, not freight, so it is never a load. - Gitea#10: a Day turns over inside the phases that run themselves, so it passes between one click and the next — and both transient signals fade before a player reading the board notices. A modal stops and waits, carrying the standings, the Days left and the combined target. Suppressed on the first frame, on Undo stepping back across a rollover, and on the Day the game ends. - Gitea#9, which SUPERSEDES Gitea#6 from three days ago: a Timetabled train may be tossed face-up to a Department slot, where a rival may pick it up — the second half of the ruling needed no code, since that is where every discard already goes. An Extra still may not. A New Game setting on this line (discardTimetabled, on by default), the plain rule on the 0.4.9 line. - Gitea#2 is not an engine bug: the rules are implemented exactly, and running the coach pool dry is Jesse's ruling to keep — "part of the strategy". What was wrong is that the game said nothing. A blocked platform now gives its reason, from the engine's own predicate, including how many coaches are stranded in Classification and what brings them back. The same four ship as v0.4.9g on the playtest line. Closes #2 Closes #8 Closes #9 Closes #10 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLnYR4XtXQNamYJXGYT8oC |
||
|
|
06db36e5b5 |
v0.7.0 — four game types, a lobby you can read and leave, and a multiplayer game that makes a sound
The multiplayer set-up, the lobby, the start of a game, and four signals a remote client had never been sent. Reasoning, the preset table and what was verified how: CHANGELOG.md. - Co-op, Competitive, Cutthroat, Solitaire and Custom, on both screens, from one shared block — they had drifted, and each was missing a question the other asked. - A player reads the whole rule set before taking a seat, may leave a lobby or a running game, and keeps a seat across a reload. The host may clear a chair. The browser remembers every game it is in, not just the last one. - The start of a game is drawn: a handoff beat, an announcement, the code and type in the header. - Sound, the timetable flash, announcements and the just-drawn badge now reach a remote client; justDrawn goes to the seat that drew it and nobody else. - Played on StartOS, which found the rest: an Extra belongs to the player who played it, the board never named the Superintendent, bot seats were reported as absent players, and rule section numbers are out of every string a player reads. Also carries the previous session's Heavy Grade documentation work — asked again, answer unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016JczK5i33ZNSf2PtzZqdhS |
||
|
|
7804756f11 |
v0.6.2 — an Extra starts where you put it, a train card is never discarded
Three more from the v0.4.9e gameplay-testing round, filed as Gitea issues, plus two bugs found underneath them. Gitea#2 is diagnosed but NOT fixed: it needs a ruling, and the reasoning is in TODO.md under Play Balance. GITEA#4 — AN EXTRA STARTS WHERE THE PLAYER PUTS IT. Only one Division Point was ever offered, chosen by number parity. The number no longer decides an Extra's direction — the start does, which supersedes the recorded ruling that "the number decides, like everything else on the timetable". The two cannot both hold: an odd, westbound Extra placed at the WEST end would leave the Division on its first move having crossed nothing, and be paid for the run. Either end now runs the train away from itself; at an Interchange or a Control Point the player picks the direction. The Interchange start is a YARD, off the running line, which is what makes the Superintendent clause work: placing it can never force a collision, a guaranteed one holds it there for another Stage, and a potential one is the Superintendent's to rule on — exactly evaluateClearance's `blocked` and `ask`, so nothing new decides collisions. Where an Extra may start is now a house rule (divisionPointsOnly / ownOffice / anyOffice, defaulting to what the engine already did). The legacy `atSeat` intent field still replays as it always meant. FOUND UNDERNEATH IT: an Extra started away from a Division Point ran empty. isBeingMadeUp tested position alone, so the Control Point start has been shipping since it was added with a train that could never be given a consist. Found by playing it, not by the tests, which had only asserted where the tray landed. FOUND UNDERNEATH IT: collide left the wrecks on the card. Destroyed trains kept their Transit entries, and evaluateClearance counts every transit as an occupant, so one rear-end collision permanently poisoned that Mainline card for every later train. THE MAINLINE CARDS WERE ROLLED, NOT DEALT — drawn from the nine types with replacement, so a Division could hold two Interchanges and Plains carried the weight of a card printed once. "An Extra may start at the Interchange if one is on the board" only reads as a rule if the board holds at most one. Now dealt from the printed deck without replacement, verified over 1600 deals. This re-deals every seed: the published replays were re-recorded, and the saved games in docs/ are retired too — two of those were already dead before this release and nobody had noticed. GITEA#6 — A TRAIN CARD IS NEVER DISCARDED, Timetabled and Extra alike. The forced play needed no mechanism: nothing discardable plus a hand over the limit leaves exactly one legal way to end the turn, and playing a train is unconditionally legal, so the corner cannot trap anyone. The bot needed no rule either. 400/400 games finished, revenue unmoved, trains scheduled 1.2 -> 1.3. The player is told on the card and on the button. GITEA#7 — COACH COUNTS. 1/2 Crack Limited 3 -> 2, 5/6 The Sparrow 2 -> 3. A change to the cards, so Trains3.pdf and the transcription keep the original numbers with a footnote while content.ts and the Home Deck reference carry what the game plays. CONTENT.TS COMMENT PASS — no data changed, only comments. Four were factually wrong, including an office table naming counts doubled long ago and a pointer to a DEALT_DECK_SIZE that has never existed. Every Enhancement row cited its implementation by line number and every citation had rotted; they name functions now. Card counts came out of the comments, since they move with play balance; source-sheet figures and dated measurements stayed. TODO.md gains an item for a card reference generated from content.ts, in six sections, so the documentation cannot disagree with the game. 715 tests pass, tsc clean, site builds. |
||
|
|
9f3b92d08e |
v0.4.7 — the switching game: track order, the cut on your own card, and four rules
Eight play reports and one design that had been written up and not built. The
through-line is switching: what a card can hold, which end of a train a cut comes
off, which way a train meets cars standing on the line, and what the board and the
log say about all of it.
TRACK ORDER FOR STANDING CARS, AND THE CUT ON YOUR OWN CARD
Two reports turned out to be one root cause. `TrackCard.standing` claimed "in track
order (§A.3)" and had no defined orientation at all, while `CrewTray.consist` does
(nose first, relative to facing) — so every transfer between them was a conversion
nothing performed. §A.3 says what it should be: cars occupy the track "in the same
order they originally held, left-to-right". Left-to-right is west-to-east, and that
is now the defined orientation of `standing` and of an industry track through
`carsOn`. It is the board's orientation, not the train's, so it does not change when
a different train touches the card.
- Setting out is batch-invariant. Four cars at once, four singles and two pairs
parked three different orders, one of them physically impossible. Successive
cuts off the same end stack up towards the engine, so the insertion point is the
train's own place in the row.
- Approaching a cut from either end now mirrors. `couples` is built nearest-first
along the direction of travel and reverses onto the nose, so the farthest car met
ends up nose-most — which is what makes a run-around worth its Move.
- A train no longer drives through its own cut. The walk began at the neighbour of
the start square and never read the start card, so a crew could set cars out and
pull straight away from them. Coupling is mandatory (§A.4) and your own square is
no exception; the cut counts against the four-car limit. Setting out off the end
you are not leaving by still works.
`CrewTray.standingWest` records where a train stands among the cars on its card — a
train may set out off both ends on one square, so which side a cut is on is not
recoverable from the array alone.
On the board, the cut is drawn split at the train — west cars left, east cars right,
engine in the gap — and each car's tooltip says whether it stands ahead of or behind
the engine. The history says which end a cut came off, and a move's button separates
"takes your own boxcar back off this card" from cars found standing on the line.
Decided: taking your own cut back on the square you are standing on is UNDOING the
drop. It is exempt from trains 3/4's per-location freight budget, X13's "drop but not
pick up" and X22's "empties only", and it refunds the budget the drop spent.
Otherwise a legal-looking drop becomes silently one-way.
Measured, 200 paired seeds, developer bot: -0.55 revenue (t = -3.63), freight revenue
1.11 -> 0.56. That cost is the bot's, not the rule's — its trains run engine-first,
so at a stub industry it sets a car out between itself and the only way out, and the
correct play is §A.5's facing-point move, which is the cross-turn planning TODO.md
already records as out of reach of any bot. Filtering self-recoupling moves out of its
options took recoupling from 625 of 1,029 set-outs to 101 of 677, and all 101 that
remain are that case. Read the number as a bot measurement, not a balance one.
THE SUPERINTENDENT'S RULING NAMES THE TRAINS IT IS ABOUT
Reported: the Superintendent could not tell which train he was clearing. The heading
asks the question now — "may Train 6 follow Train 4 onto the same Mainline card?" —
and the trains moved to the FRONT of each button, because the button splits its label
at the first em-dash and showed only the head.
AN INDUSTRY TRACK HOLDS FOUR CARS, LIKE EVERY OTHER CARD
Reported at undo 188: "we wanted to drop two cars, but were only allowed to drop one."
An industry track was built as long as its box count, so a one-box industry had room
for one car. Box count is how much WORK an industry can hold, not how much RAIL it
has. Ordinary track was the other exception, unbounded; both are gone and every card
holds four.
THE FREIGHT AGENT MAY STAGE A LOAD BEFORE THE CAR IS THERE
§6.3 asks nothing of the industry track — the empty car belongs to §9.3's Load the
car, which is the Laborer's action. The gate now lives only there, so cargo can wait
on the dock while the car to ship it in is still being switched in. Nothing can jam:
a load in a green box is waiting, not stuck.
THE TRUCK DOCK UNLOADS, AND BRINGS NOBODY
+1 inbound, no Laborer. It printed +1 outbound and +1 Laborer, which made it a
longer-host-list copy of Forklifts. Beside Packing Sheds it now does nothing at all,
and the hand tooltip says so before it is played.
Also in this release, from the days before: Mainline card tooltips computed from the
crossing rule; an Extra starts from the Division Point its number sends it to; a
modifier's suppressed grant comes back when a Whistle Post is upgraded; the Oil
Refinery and the Grocer's Warehouse ship as well as receive, per the card reference;
and the dormant defences name the attack they answer. `.claude/` is now gitignored —
it holds Claude Code's worktrees, i.e. a second checkout of this repository.
570 tests, typecheck clean. The three published replays were re-recorded twice —
legality changed, so bot play changed. Full detail in CHANGELOG.md.
|
||
|
|
08339effba | more info on train moves in history. train info added to tooltip on timetable and division map. support for multiple trains in area switching. more info on train7/8 wrt location of coach on initial train build. | ||
|
|
1bf1e95058 | v0.3.0 — playtest fixes, two new rules, and §7 enforced | ||
|
|
5d825b97d2 |
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 |
||
|
|
f4c0f49604 | More bug fixes. Show the timetable, and stop the arrival message contradicting itself. Fixed division point in labels. And highlighted discard as targets. | ||
|
|
543584a2ca | user card Hand becomes the action surface, the board shows the train, and a reverse stops turning it around, other bug fixes too. |