8a29de54ee1932c3b946dbeb05988b5f1858dd04
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e47cd3d400 |
v0.8.4 — the multiplayer transport: server and browser
The second release from the audit. Every fault here was invisible in solitaire, and four of the five server faults were in the one file no test had ever stood up; `http.ts` now has an end-to-end suite on a real port. CHANGELOG has the reasoning. SERVER. Leaving a lobby freed the chair and kept the token, so a leaver could stream and move for whoever took the seat next — revoked now, in memory and on disk. The browser numbered intents from 1 per page load while the server remembered the seat's last number, so the first move after a reload was swallowed as a resend — the connect push carries the count and the client continues from it. Nothing serialised moves within a game and every write shared one `.tmp` name, so two moves at once tore `game.json` (measured: 6 of 200), and the boot's bare `JSON.parse` then took every game down — per-path write queues, a per-game move queue, and a boot that skips one bad file. An error after the SSE head was sent crashed the process. Bodies were unbounded before any secret check. BROWSER. A double-click did the thing twice: one submit in flight at a time. A failed submit is `false`, not an unhandled rejection. The documentation renderer flattened nested bullets into a literal "- " mid-sentence on the published home-deck page. The make-up panel promised cars the engine refuses; it asks `acceptsCar` now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FrCWubm9GAftYCm2hWdKwK |
||
|
|
3befc420da |
v0.8.2 — every district opens on a Depot, and the docs are pages now
A second-digit bump for a playtest read back against the save file. Nine questions were asked of one three-Day game; three were bugs, three were the rules working and undocumented, three were decisions. Every save on the test server was replayed against this build BEFORE release, which is how the cost of each rule was known before it was chosen rather than discovered after. EVERY DISTRICT OPENS ON A DEPOT. A Whistle Post has one A/D track and is not a Passenger Facility, so the opening of every game was spent unable to work a passenger and one arrival away from a collision. Two A/D tracks and passengers from Stage 1 now; "Players start with Whistle Posts, not Depots" is the harder game, set when the game is created. The deck follows the choice — starting on Depots the four Depot upgrade cards are left out, because an upgrade must be to the next tier and a Depot card at a table of Depots is a dead draw. How much easier it is showed up as a test failure rather than an argument: the cue-coverage pool needed widening from 24 seeded games to 60 before it held one collision. NO SAVE WAS STRANDED BY IT, which took care. This is the one house rule that changes how a game is DEALT rather than how it plays, so replaying a save under the wrong opening is a different railroad from intent one — silently, with no error. `withSavedOpening` fills it on the replay paths ONLY. Putting it in the resolver instead made a fresh Cutthroat game deal Whistle Posts and read as Custom, which is how the distinction was found. THREE BUGS, ALL REPORTED FROM ONE GAME AND ALL CONFIRMED ON ITS SAVE. An Office held TWO TRAINS ON ONE A/D TRACK. The capacity test passed with nothing standing, the train the Interlocking had been holding at the Limits was moved into the free slot, and the arriving train was pushed in after it without anyone asking again whether there was room — so the collision §8.3 calls for never happened. The held train keeps priority; the newcomer now takes the consequence it would have met had the held train arrived first. THE HISTORY FROZE, permanently, and the log cap was not really the cause. Each seat's "what have I sent you" bookmark was an INDEX into an array the game trims, so once a seat's bookmark reached the limit the slice returned nothing for the rest of the game — at a different moment per seat, because each holds its own. That game's log ended at exactly the cap. Lines carry a sequence number now, which survives trimming; proven by pushing twice the cap through a simulated seat. §8.1 ASKED THE WRONG QUESTION TWICE. "Trains may pass" returned `clear` before the Subdivision was looked at, so a train entering a Double Track was released however busy the rest of it was — that, not anything about Control Points, is what let Train 8 out with no ruling. And a train standing at an Office was invisible to the scan, so one about to re-enter the very Subdivision being entered counted for nothing. Capacity is the test, not presence: a Depot with a track free is not in the way; a Whistle Post with its one track taken is. THINGS THAT HAPPENED SILENTLY NOW SAY SO — a train held against a facing one, a train released from the Limits (a side effect of somebody else's arrival, so it simply appeared at the Office), and the train an Interlocking is holding, whose explanatory tooltip has existed since #99 with NO renderer ever reading the flag. WHERE A MOVE IS REFUSED, AND WHY. `exploreMoves` decides where the rails go and the pick-up restrictions are enforced afterwards in `check`, so a square the rails reached and the card forbade was reachable, un-offered, and absent from the block list with nothing said. Those squares are blocked with the rule that blocks them now, and the reasons are got by ASKING `check` rather than re-deriving: a second implementation of the rules is exactly the failure the block list exists to avoid. A train may also always recover its own caboose — X13 prints "may drop but not pick up anything", and a train needs its caboose to be made up, so one that parted with it could never legally leave again. RULES DECIDED IN SEPTEMBER AND APPLIED HERE. A Modifier must sit square against its host, no diagonals. A passenger Modifier may not be played at a Whistle Post. Both were built, measured, held back for a fortnight so a playtest could finish, and applied now. A Second Section costs its card: `SECOND_SECTION` was declared in content.ts and never dealt, so the action was free and the bot ordered 26 accidental ones in a measured round. The card is dealt and spent — gating on a card the deck never holds would have deleted the mechanic rather than fixed it. THE DOCUMENTATION IS A SET OF PAGES, not five text files served as text/plain — a card reference is mostly tables, and as plain text a table is rows of pipes. Markdown is still the one copy; the build renders it, and publishes the .md beside each page. No Markdown library: this project has no runtime dependencies and one would be a poor first. The pages add what Markdown cannot carry without drifting — a nav across the set, a contents list built from the headings actually rendered, an anchor on every heading, a 70-character measure, and tables that are tables. They print as ink on paper. The references caught up with the rules, checked rather than assumed: two statements had gone from stale to misleading (the Quickstart told a new player to "get a Depot down as soon as one appears"), and four rules nobody could look up are written down — the Office tier table, §8.1 in practice, what the Circus Train pays for, and that a Realignment can be a card with no legal target. Adding one card to the deck reshuffles every seeded deal, which broke five fixtures. Each was a seed meaning "a game like this" — TODO #84, exactly — so seeds moved and pools widened rather than assertions weakening, and the clearance fixture pins its terrain the way `enhancements.test.ts` already does. The three published replays were re-recorded. Closes TODO #40, #42a, #108, #109 and #110. 1046 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUizFYCMHRWhbWwXhp7WPR |
||
|
|
b90c0413d2 |
v0.8.0.17 — four things the game knew and the screen did not say
All four reported from a table on Day 1 of v0.8.0.16, and all the same shape. ABS SIGNALS COULD ONLY BE PLAYED ON ONE MAINLINE CARD, while its tooltip said "any Mainline card". The engine was never wrong: check accepts any node whose kind is mainline and legalActions filters by check, so all of them were legal. The failure was the LABEL — describeIntent named i.placement and never i.node, so every placement described itself as plain "play ABS Signals", and the action list drops duplicate labels. All but the lowest-index node were discarded before the menu saw them. This is the THIRD time that trap has fired and the file documents the other two three lines apart: a turnout's two rotations, and three Department discards. Same fix — name what distinguishes them. The card is also called what the card face calls it. prettyKey rendered absSignals as "Abs Signals" beside a tooltip saying ABS, an acronym no key-splitter can recover, so the authored names now win. Three of those names were transcribed in sentence case and were CORRECTED rather than adopted: the repository says "Yard Office" 36 times against "Yard office" twice. A lookup that imports its own source's typos is the drift it exists to prevent. NOTHING ON A MAINLINE CARD SHOWED WHAT WAS STANDING ON IT. Played, ABS left no mark and you found out by hovering — the same complaint the Heavy Grade wedge answered, and it matters more here because ABS decides whether a second train on that card is safe. It draws a signal mast with a lit lamp now; a signal is the literal object and needs no room for words, which is what lets it sit clear of a name as long as "Uncontrolled Siding" on a 152px cell. The Mainline modifiers draw as BRK, AIR and HLP. Realignment is deliberately not among them: reduce takes the `became` branch and changes node.card, so a realigned Trestle IS an Uncontrolled Siding afterwards. Asserted, so the absence reads as a finding. A FREIGHT AGENT TURN SAID A CAR MOVED WHEN NONE HAD. Three faults behind one line. It asserted an outcome, where §6.3 requires no action and the bot declines deliberately — unjamming a healthy box destroys a load that cost a whole action to stock. An idle Agent was then silent, which read as a dropped turn; a new freightAgentIdled event says so and why, reducing to nothing exactly like switchingEnded. And the work named a coordinate rather than the industry, though a `place` helper has existed for precisely that since the switching lines moved to it. "Loaded a loaded boxcar INTO the green Outbound box at the Freight House", with the direction in capitals because to-or-from was the question asked. THE LOG AND THE ACTION MENU SPELLED THE SAME SQUARE DIFFERENTLY. view.ts wrote (col,row) — X,Y, east/west then north/south — with a comment saying why; narrate.ts wrote the internal storage order with no comment at all. So the menu offered a move to "(1,-1)" and the log reported it at "(-1,1)", side by side. Pinned by a test that renders one square through BOTH describers and compares them to each other: a test written against either file alone would have passed. THE DOCUMENTATION IS REACHABLE FROM A RUNNING GAME, AND ALL OF IT IS PUBLISHED. v0.8.0.16 published the Quickstart and nothing it points at — its §8 links five documents by relative path and every one 404'd on the package, verified against the running container. The build publishes the full set, and the test reads the links OUT OF the guide rather than listing them. They are linked from the This Game card, where reference already lives, rather than the header that must not wrap; no mode awareness is needed, because solitaire and multiplayer are the same page on the same origin. THE REFERENCES DROPPED THE VERSION FROM THEIR NAMES. Four described v0.8.0.16 and had since the v0.8.0.15 audit; the v0.4.5 was the prototype edition they were first written against, kept only because 36 citations pointed at it — and it read as documentation five minor versions stale. They are quickstart.md, rules.md, home-deck.md, mainline-deck.md and components.md now, kept current with each release rather than published as editions. Two errors surfaced while checking them against this release, which is the argument for doing it: home-deck.md filed ABS Signals under Enhancements "played into your district" that "change what a square does" — it does neither, this release's bug written down — and mainline-deck.md, which lists everything playable onto a Mainline card, never mentioned it at all. 1010 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUizFYCMHRWhbWwXhp7WPR |
||
|
|
dc31555625 |
v0.8.0.16 — the guide a tester can reach, and a flag that named the card instead of the gate
v0.8.0.15 wrote a Quickstart for a tester who has never played and then left it in docs/, where a tester does not look — reachable only by somebody who already has the repository. Nobody handed the box had it. build-web.ts now copies it to dist/quickstart.md and the splash page offers it under the three doors, as a line rather than a fourth door: reading the guide is not a way to play, and giving it equal weight in that grid would say it is. COPIED, NEVER RE-WRITTEN. The Markdown document stays the one copy. A hand-written HTML twin drifts from it on the first edit, which is the failure #15a was raised about and precisely what the v0.8.0.15 pass spent itself undoing. It is served as PLAIN TEXT, which is honest rather than good — tables render as pipes and the links do not click. Rendering it into a styled page wants a small Markdown converter and is filed as TODO #109; build-web.ts's comment names that number rather than gesturing at "the next step", so the file and the worklist cannot drift the way the references just did. Two things had to be true and tsc checks neither, so both are tests. The href on the splash page and the filename the build writes are two strings with nothing connecting them: rename the document and the build quietly publishes nothing while the page keeps offering a link that 404s. And a .md file must not arrive as a download — the server's MIME fallback is application/octet-stream, which a browser saves instead of displaying, so the link would have handed a tester a file to save rather than a page to read. '.md' is in http.ts's table now, and the test reads that table out of the source rather than asserting on a copy of it, which would pass while the real one was wrong. VERIFIED AGAINST A RUNNING SERVER, not only compiled: 200, text/plain; charset=utf-8, the guide's own first lines, and the splash link resolving. THE sortsCars COMMENT. Asked after v0.8.0.15 whether everything now agreed, and the audit turned up one place that did not — the field's own doc comment named the card's printed text as though it were the flag's meaning. Nothing reads it to permit a sort; its two readers, resolveExtraStart in apply.ts and the enumeration in legal.ts, both ask whether this is the one Mainline card with a Yard Limit and therefore the one an Extra may be made up and started on. Comment only, and worth the bump because of where it is: it is what a developer reads before using the flag, and it is the likeliest source of the sentence v0.8.0.15 had to correct off the board. The name is kept for its link to the card face and the comment now says outright that the name is not the meaning. The five references and docs/design.md read v0.8.0.16. They describe this build because the audit re-checked them against it, not because the number was swept forward — a stamp bumped without a reading is worth less than none. 1001 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUizFYCMHRWhbWwXhp7WPR |
||
|
|
f308a2d94d |
v0.8.0.15 — the reference documents, and a card that advertised what it cannot do
The four hand-written references brought up to the game as it actually runs, ahead of the next testing round, plus a Quickstart to hand a tester who has never played. They had not been touched since v0.6.2 — a month and two minor versions — and each now says at the top which build it describes. ONE LIVE BUG CAME OUT OF THE PASS. mainlineDescription told players "Cars may be sorted into any new order here" on the Interchange. It is the printed capability and has never been implemented: nothing reads sortsCars to permit a sort, and its one live use is marking the card an Extra may be made up on, because it is the Mainline card with a yard. That sentence is not only documentation — view.ts renders it as a Mainline card's `what`, so it is what a player reads on the board, and the generated reference printed a "Sorts cars: yes" column beside it. A card advertising a button that does not exist sends a player hunting for it and then concluding the game is broken. What the documents had wrong, all of it verified against the code rather than read for tone: the victory model in the Rules book (firstToTarget / highestAfterDays and the target-bearing length presets stopped existing in 2026-08 — it is a free days count and a combined floor of 3 x players x days); "there is no lobby, no server, no multiplayer"; crossing time in mph rather than regions; Extras launched automatically eastbound; the Uncontrolled Siding listed as a passing card; industry track length taken from the box count; and a "Sister Trains" optional rule that never existed. The Home deck's counts table came out under TODO #15a — Jesse's own ruling that counts move with balance — and it had been wrong for a month, which is the argument made twice. The Home and Mainline deck references are restructured to explain how a deck is USED and to defer every per-card table to rules/as-built.md. Duplicating it by hand is precisely the drift #15a was raised about: as-built needed no correction beyond the Interchange, because build:cards regenerates it and a test fails when the checked-in file disagrees. Everything hand-maintained around it had drifted; it had not. docs/design.md, the index everything starts from, said v0.4.3, "what is not: the server", and 493 tests. It now also lists the player-facing references, which it never has, so the Quickstart is findable at all. 999 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DmdqqCNoiqE7GBo6wthBnR |
||
|
|
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 |
||
|
|
e734481d65 |
v0.7.9.3 — the reference says what only the code knows, and saves get a rule
The generated card reference now carries the half TODO #15a said was the point of generating it: every Enhancement's `live` / `dormantSolo` / `unbuilt` status — whether its printed effect actually resolves yet — and the opponent-directed Action and Space-use cards, none of which is dealt in any deck. A transcription cannot carry either fact. Card counts are removed throughout, as ruled: they move with play balance so a document printing them is stale on the next retune. Where a count matters it is a yes/no "is this dealt at all", which is a fact about the design rather than the current tuning. #88 closes with it — it asked whether `card-reference.md`'s industry rows were stale, deliberately without rewriting them since Laborer counts are a balance decision. They are; nothing in the engine changed; that file is simply no longer where anyone looks. The balance question it guarded is #70. Save compatibility becomes a general rule in `README.md` § Design notes rather than a fact restated per version: a save is a list of moves and reopens by being re-played through the CURRENT rules, so any change that makes a once-legal move illegal stops an older one there — a deck change being the likeliest breaker. It fails safe every time. #40 generalised, #32's version-specific note dropped, #52 carries the ruling that versioned replays are a post-1.0 question. #94 opened: a Red Flag set out at an Office's Limits holds the next train from that side and is drawn nowhere. `DivisionView`'s office node has no `redFlag` field and `board-svg.ts` never mentions one, so after the single log line announcing it there is nothing on screen. Same class as Gitea#21 and #22, and already on the common board's step 1 list. No engine change. 897 tests pass, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3Qk7uresKCHksdZajXCLg |
||
|
|
819996faa2 |
v0.7.9.2 — two things the table could hear that only one seat should
Both leaks were found while planning the common board (Gitea#20 step 1), and both are live multiplayer bugs with or without that display, so they are fixed now rather than with 0.8.0. `game.log` is one shared list and `linesSince(seat)` slices it with no per-seat filter, so every line reaches every player. It carried the SEED in the opening line of each multiplayer game — the whole future of the deal — and the NAME OF A CARD DRAWN BLIND from the face-down Home Office deck. Solitaire deliberately keeps both: a one-seat table has nobody to leak to, the seed is what a bug report quotes, and a player's own history naming their own draw is the record. A Department slot is face up and stays named. The drawer still learns their card through `justDrawn`, which already goes to that seat alone. Neither was found by a test. Every test in `redaction.test.ts` passes an empty log, so the whole of narration has sat outside the redaction net since the net was built. Both now have tests there; TODO #91 carries what is still owed and supersedes #78, which described a gap that had already been closed and never mentioned this one. `docs/rules/` had no current description of the game, and `content.ts` named `card-reference.md` as the file that carries what the cards say — a file whose own banner says not to use its numbers, describing the v0.4.5 deck where 3/4 is a Mail-Express with three coaches. Every file in that directory is a deliberate historical record, so none of them is rewritten. `as-built.md` is new and GENERATED from the same catalogues the engine instantiates from, with a test that re-runs the generator and fails when the checked-in file disagrees. A hand-written replacement would have drifted the same way, for the same reason. TODO.md: #32 closed — the playtest migration note did its job and the jump is made; the durable fact it carried is kept. #78 retired in favour of #91. The "play it at a table" section now records that 0.7.4-0.7.9 were test-run without change requests, and that more testing comes at the end of the 0.7.9 series. 897 tests pass, up from 891. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3Qk7uresKCHksdZajXCLg |
||
|
|
af68aac78d |
v0.7.7 — two releases shipped to a browser that never received them
buildStamp()'s no-git fallback was the literal "nogit", and the .s9pk Dockerfile copies the tree in without .git — so git rev-parse fails on every packaged build. That string is also the cache-bust key every module URL carries, so v0.7.4, v0.7.5 and v0.7.6 all published ./web/main.js?v=nogit, byte-identical, and returning browsers refetched nothing. v0.7.5's setup screen and v0.7.6's door fix were both correct and neither arrived. The fallback is now the package version plus the build timestamp, always distinct. And serveStatic sent no Cache-Control at all, which is the other half — a cached play.html pins a player to the whole build it names. A request carrying ?v= is now immutable for a year; everything else is no-cache. ?v= rather than "not HTML" because build-web.ts tags the modules and nothing else. Neither half is sufficient alone. 864 tests pass, two new. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdG46Ja2PEDBkpqiDazMoX |
||
|
|
42adfda390 |
v0.6.3 — the deploy script follows the host to FileBrowser Quantum
The deploy script only. No rules change, no game change, and the built site is byte-for-byte what 0.6.2 produced — this repairs the path that publishes it. Found trying to publish the 0.4.9f playtest build: every deploy died with "login failed: 404 404 page not found". The File Browser host has been upgraded to FileBrowser Quantum, a fork whose API differs from the v2.63 one deploy-web.ts was written against. Three things moved at once, each fatal on its own: auth is a session COOKIE rather than a JWT sent back as X-Auth, so a deploy that ignored it would authenticate and then be rejected by every upload; the password is an X-Password header, URL-encoded, rather than a JSON body field; and the path is a query parameter, with every resource call also having to name a `source` — Quantum can serve several named stores and refuses any call that does not say which, a concept the v2.63 API did not have. Rewritten against the running instance's own bundle rather than guessed, the same discipline the v2.63 version was written with. Two things worth knowing next time: the bundle is gzip-compressed, so it needs gunzip before it can be grepped; and an endpoint that exists answers a bad password with 401 while a missing one answers 404, which is how each path was confirmed against the live host without holding a password. The source is discovered from GET /api/settings/sources — one configured source is used silently, several makes the script stop and list them rather than deploy into the wrong store. FB_SOURCE overrides it, FB_OTP carries a two-factor code. Verified by deploying with it rather than by reading: 0.4.9f went up this way. This is that same file byte-identical, brought across to the main line — both branches carried the same broken script, so deploying 0.6.x would have failed identically. Also removes dist-test/, an untracked hand-made copy of a v0.6.2 dist/ build that no script or test references. build-web.ts hardcodes dist and wipes it on every run, so nothing in the repo could have produced that directory or would ever read it. .gitignore is deliberately unchanged: the answer for a directory that should not exist is to delete it, not to hide it. 715 tests pass, tsc clean, site builds. |
||
|
|
c3c5cbfeec |
v0.5.0 — multiplayer Phases 2 and 3: a server that runs a game and survives being restarted
Phases 0-1 shipped in v0.4.0 (seat/identity split, per-player turn state, the Session boundary). This lands Phase 2 (server core, one game, no lobby) and Phase 3 (persistence and resumption) per docs/architecture/multiplayer.md §12. Phases 4-6 (lobby/reconnection, the 22 opponent-directed cards, StartOS packaging) are still ahead. Phase 2: src/server/session.ts hosts a game in pure logic (no sockets) on top of game.ts's existing Game/submit/currentActor/actionMenu; it verifies seat === currentActor(game) itself before calling submit, since submit() trusts its caller and a server can't. src/server/http.ts and index.ts add POST /api/game, GET /api/stream (SSE, per-seat), POST /api/intent, and static serving of dist/. src/sim/frame-delta.ts is a purpose-built per-seat board delta for one live push at a time. Found and fixed along the way: actionMenu(game, seat) only used seat for the hand field, so a server computing every connected seat's Menu would have handed the acting player's legal moves to a waiting seat. Verified with a live end-to-end smoke test (2-player game, two SSE streams, a rejected intent from the wrong seat, an idempotent resend) plus test/server/session.test.ts and test/redaction.test.ts. Not verified: an actual browser (none available in this environment). Phase 3: src/server/persistence.ts writes game.json and turn-timings.json, atomic-rewrite-then- rename. game.ts gained fromMultiplayerSave, fixing a narration-attribution bug found while testing it (fromSave's replay loop drops the actor argument, invisible in solitaire, unreadable the moment there's more than one seat — fromSave itself still has this gap, deliberately untouched). Verified live: server killed and restarted mid-game, both seats reconnected exactly where they left off. Two rules bugs found while building this: the New Train phase never implemented its car-placement round (every car of every train was placed by the Superintendent alone, in every mode, all along — now reads the round position off tray.consist.length); and victory conditions are now one shared, configurable GameConfig set across solitaire/competitive/coop instead of a fixed length lookup and a dead firstToTarget condition. Also folds in the three fixes already released on the patch line as v0.4.9b/c/d: a switching train's crew badge failing to draw once it left the Office square, an unload that always took the westmost car regardless of which was picked, and a legal decision that could render with zero buttons. docs/testing/0.5.0-test-plan.md and three reported-bug save files (docs/station-master-seed*.json) included for reproducibility. tools/jitsi-harness/ deliberately left untracked — unrelated side-project work, not part of this release. 635 tests, 0 failures. |
||
|
|
859817d173 |
v0.4.9 — X,Y coordinates, no-switching can still clear the mainline, Q3 corrected, a clearance bug, three sounds, and the box art
A playtest review of seed 58228926 (day 6), plus one long-standing display complaint and the first real audio beyond a placeholder. - Coordinate labels read X,Y everywhere shown to a player, not the internal Y,X storage order. Display-only. - "No switching" now means may not add or drop cars, not "never touch it" — these trains can still be moved onto Secondary Track to clear the mainline. - Q3 corrected: Expedite governs WHERE a train may be left standing, not WHEN it leaves. The forced same-Stage departure is gone; a new fault costs 1 Revenue if an expedited train is left off the station when a Mainline Phase begins. Resolves "3/4 Express prints a rule it can never use" as a side effect. - evaluateClearance now checks every occupant on a Mainline card before offering a judgment call, instead of returning on whichever it found first — found while explaining a playtest report, fixed with a regression test. - Three new synthesised sounds: arrive, depart, crash. - The splash page shows the box art. Full detail, measurements and reasoning in CHANGELOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FvU99NEakShRMg3nN3fHAZ |
||
|
|
ec792dfc15 |
Say who is playing, what you can press, and where you are in the Stage
Mostly presentation, plus one rules fix found on the way. |
||
|
|
f52ff0e9ac |
Draw the board as track, split the site into three pages, and fix curve geometry
Adds SVG board rendering shared by the game and the replay, a splash page with a shareable replay directory, hover tooltips for reference detail, and makes curves two-port arcs so sidings and run-arounds can finally be built. |
||
|
|
dd300ac154 | Fix caching, Limits placement, and explain the board better | ||
|
|
2eca9de09f | various fixes. added version#s and added a playable browser build of the solitaire game (including deploy to filebrowser script) |