v0.4.8 — the Limits bound the whole district, the nine spots really are nine, and an action points at its square
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQAJ4dND7enLj54eLyiF2C
This commit is contained in:
+108
@@ -19,6 +19,114 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.4.8 — 2026-08-19
|
||||
|
||||
Two reports from the same game, both about squares: which ones a card may go on, and which one a
|
||||
button in the action list means.
|
||||
|
||||
### The Limits bound the district, not just the Running Track
|
||||
|
||||
**Reported:**
|
||||
|
||||
> "Sidings should not be allowed to be built outside the limits."
|
||||
|
||||
The Limits sign "denotes the limit of your control area" (§2.1) and §3 defines Secondary Track as
|
||||
"all tracks **in your limits** that are not the Running Track" — but the bound was only ever enforced
|
||||
on the Running Track row itself, with a comment in `canPlaceAt` stating outright that "the district
|
||||
below the Running Track is unbounded". So a siding could run east past a player's own sign, take
|
||||
industries with it, and put cars on track that §8.1 and §10 do not consider his territory at all: the
|
||||
rules reason about "the track between the train and the Limits", and running past another player's
|
||||
Limits is what makes a collision his fault.
|
||||
|
||||
`withinLimits` is now the district's rule at **every row**, and a Facility is bounded by it too —
|
||||
§11.2 is explicit that "Facility cards carry their own rails: placing a Facility places track". The
|
||||
refusal is its own code, `OUTSIDE_LIMITS`, rather than `NOT_CONNECTED`, for the same reason
|
||||
`BREAKS_RUNNING_TRACK` is: the card would join perfectly well, and is refused for a different reason.
|
||||
|
||||
**Inclusive of the sign's own column**, which is the half that keeps the game playable. Jesse's call.
|
||||
The sign stands *on* the boundary rather than beyond it, and a district opens with signs at ±1 around
|
||||
the Office — so the strict reading would leave exactly one buildable column and break §11.3's promise
|
||||
that both Secondary rows, and the nine-spot Modifier neighbourhood, are usable from the first Stage.
|
||||
A siding may run under the sign; nothing may go past it.
|
||||
|
||||
**What it costs the bot: nothing measurable.** Out-of-limits building was rare for it to begin with —
|
||||
5 cards across 100 games, in 4 of them, never more than two columns over — and paired over 200 seeds
|
||||
the bound moved 4 games and −0.015 Revenue (t = −0.26). It is a human building deliberately who hits
|
||||
this, which is exactly how it was found.
|
||||
|
||||
### The nine spots really are nine — the four diagonals were legal and unofferable
|
||||
|
||||
**Reported:**
|
||||
|
||||
> "Modifiers should be able to go on any diagonal — we were unable to place it to the south-east."
|
||||
|
||||
§9 places a Modifier "adjacent to a Facility, on any of the nine nearby spots", and `check` has
|
||||
always accepted all eight neighbours. The fault was upstream in `placementCandidates`, which walks
|
||||
north, south, east and west from every occupied square: a diagonal square with no *orthogonal*
|
||||
occupied neighbour was never generated, so it was never offered. Measured on a facility below the
|
||||
Office: `check` says legal at all four diagonals, the menu offered the three orthogonal ones. At a
|
||||
stub industry — the case in the report — every diagonal is in that position.
|
||||
|
||||
Generated in a **second pass, only around a Facility**. Track has to *join* and a diagonal shares no
|
||||
edge, so a Modifier is the only card those squares can ever take; generating diagonals around every
|
||||
card instead costs 54% more simulation time producing candidates `check` then rejects. Appending
|
||||
rather than interleaving leaves the existing candidate order untouched, which matters because the bot
|
||||
breaks ties by first-best — measured over 200 seeded games, **0 played differently**, so every figure
|
||||
in `TODO.md` still stands.
|
||||
|
||||
### A Modifier may hang outside the Limits — but not in the Running Track's row
|
||||
|
||||
Jesse's call, both halves. A Modifier is not track (§9), so unlike a siding it keeps all nine of its
|
||||
spots when its host stands at the limit: refusing the outer three would make the card unplayable
|
||||
exactly where the district ends. What it may not do is stand in the row the Running Track grows
|
||||
along. Inside the Limits that row is always full, so the rule bites only beyond the sign — and that
|
||||
is the ground the main extends onto, where a parked Modifier would block the player's own sign from
|
||||
moving outward (§2.1) with nothing on screen to warn him. Industries have been barred from the
|
||||
Running Track since the "Placed" column was read properly; this is the same rule for the same reason.
|
||||
|
||||
### The board draws where the district ends
|
||||
|
||||
Two signs on one row cannot say that nothing may be built outside their columns at *any* row: a
|
||||
player looking at open ground beyond a sign had no way to know it was unbuildable until the square
|
||||
failed to light up. `officeSvg` now draws a quiet dashed edge down the whole canvas at each limit,
|
||||
labelled, and just **outside** the sign's own column so the picture agrees with the rule — a siding
|
||||
may run under the sign. It moves outward on its own as the Running Track is extended, which is the
|
||||
reward for extending made visible. The Frame carries `limits` for it, resolved server-side like
|
||||
everything else a remote client cannot look up (`multiplayer.md` §5).
|
||||
|
||||
The margin on the viewBox is not cosmetic: the west sign is usually the leftmost card on the canvas,
|
||||
so the line outside its column lands at x = −1.5 and simply is not there on a canvas starting at 0.
|
||||
Caught by drawing one, not by reading the code.
|
||||
|
||||
### Hovering an action points at the square it means
|
||||
|
||||
**Reported:**
|
||||
|
||||
> "When I have my mouse over the action for a particular square, could the corresponding square in
|
||||
> the office area be highlighted to minimize my mistakes of selecting the wrong square — 1,3 versus
|
||||
> −1,3? In a solitaire game with Undo it is not fatal. In a multiplayer game that could be a major
|
||||
> disaster."
|
||||
|
||||
The action list is a column of near-identical sentences separated by a coordinate, and the board
|
||||
beside it said nothing about which was which. Every action that names a square now carries it as
|
||||
`data-square`, and hovering — or focusing, so the keyboard route is not a lesser one — lights that
|
||||
square on the board, ghost targets included, which is precisely the moment a player is choosing
|
||||
between coordinates.
|
||||
|
||||
The coordinate rides on the **menu**, resolved from the intent by `coordOf`, rather than being parsed
|
||||
back out of the label: a remote client holds no `GameState` and cannot look up where a tray is
|
||||
standing, which is the same reason the menu already carries card descriptions. Checked over 12 bot
|
||||
games: 870 direct actions name a square, and all 870 carry it.
|
||||
|
||||
### Also
|
||||
|
||||
- The three published replays were re-recorded. A save is a seed and its intents, so tightening a
|
||||
placement rule kills every replay containing one — `harness.test.ts` catches it rather than letting
|
||||
them go quietly dead, which has happened twice before. Old saves are not preserved across rule
|
||||
changes and are not meant to be yet.
|
||||
- `node_modules` was a **tracked symlink pointing at a path that does not exist**, so `tsc` was
|
||||
missing and 20 tests failed before any of this was written. Replaced with the real dependencies.
|
||||
|
||||
## 0.4.7 — 2026-08-19
|
||||
|
||||
Eight play reports and one design that had been written up and not built. The through-line is the
|
||||
|
||||
Reference in New Issue
Block a user