Show the whole Division mainline. district now has option for auto hiding on phases where not needed. scaffold for multi-player geometry showing division. regions on mainline cards implemented. bug fix on laying rail cards. optimized replay (carried links forward). Add sounds, first pass
This commit is contained in:
+137
@@ -10,6 +10,143 @@ The target is 20 Revenue over 5 Days.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### A legal move you could not click
|
||||
|
||||
Reported from playtesting: laying a straight offered three places in the list and highlighted one on
|
||||
the board.
|
||||
|
||||
Empty squares were drawn by a separate `ghostSvg` and spliced into the SVG afterwards. It computed
|
||||
its origin over cells PLUS the offered spots, while `officeSvg` sized itself over cells alone — so
|
||||
the two disagreed the moment a legal square lay outside the played cards, which is every square that
|
||||
would EXTEND the district. Reproduced on seed 555: three placements offered, the empty one drawn at
|
||||
y=99 on a canvas 103 tall, i.e. off it entirely.
|
||||
|
||||
`officeSvg` now takes the ghosts and sizes itself over both, so there is one origin and one canvas,
|
||||
and `ghostSvg` is deleted rather than fixed — a second coordinate system was the bug, not a detail
|
||||
of it. It was also imported by the replay and never called there.
|
||||
|
||||
A test walks six seeds and asserts every square the menu offers has a target drawn inside the
|
||||
canvas: 129 squares across 47 placeable subjects. It fails on the old code with the exact
|
||||
coordinates above.
|
||||
|
||||
### The railroad, heard
|
||||
|
||||
A whistle at the end of each Stage, the grade-crossing bell at the end of each Day, and the
|
||||
conductor when a train is built. **Off by default** — everything here is synthesised rather than
|
||||
recorded, so it is a placeholder for real audio and a playtester who did not ask for noise should
|
||||
not get any. One click in the title bar turns it on, and that click doubles as the gesture browsers
|
||||
require before audio may start.
|
||||
|
||||
**Synthesised, not sampled**, and that is a constraint rather than a preference: the site is a static
|
||||
folder that fetches nothing — there is a test asserting no page reaches an external host — so audio
|
||||
would have to be committed to the repo, and a plausible whistle is not something to invent. A real
|
||||
steam whistle is a CHORD of several chambers slightly out of tune with each other plus the breath of
|
||||
the steam, which three detuned partials and a band of filtered noise get most of the way to. The
|
||||
bell is inharmonic partials struck twice, which is what separates a bell from a beep.
|
||||
|
||||
**"All aboard" is speech, and speech cannot be faked with oscillators.** `speechSynthesis` is built
|
||||
into the browser, needs no asset and works offline, so it says the words; where the platform has no
|
||||
voice installed a two-note conductor's call takes its place rather than nothing happening.
|
||||
|
||||
The model names WHAT happened — a Stage ended, a Day turned, a train was made up — and the page
|
||||
decides what that sounds like. A Day boundary rings the bell only: both would collide, and the bell
|
||||
is the bigger event. The first Stage of the game announces nothing, because a Stage beginning is the
|
||||
previous one ending and there is no previous one.
|
||||
|
||||
Counted against the clock rather than trusted: over a full game, 60 Stage boundaries produced 55
|
||||
whistles and 5 bells, and 5 Days produced 5 bells.
|
||||
|
||||
### Regions, drawn from what the crossing already cost
|
||||
|
||||
§2.1 divides a Mainline card into two regions and §8.2 moves a train one region per Stage. The
|
||||
engine had replaced that with `crossingStages()` — `implications.md` records it plainly: "The
|
||||
`Region` model is gone" — because ten card types with real speeds cannot be expressed by one region
|
||||
a Stage. Both `REGIONS_PER_MAINLINE_CARD = 2` and `entryPoints` survived as dead constants.
|
||||
|
||||
The map draws regions again without reinstating the mechanic, because the printed cards say how:
|
||||
they carry **Start positions**, so a train with Brakemen *enters further along the card*. That is
|
||||
the same fact as "takes a Stage off the crossing", in different coordinates. So position falls out
|
||||
of what the engine already knows:
|
||||
|
||||
entry = REGIONS - stagesTotal position = clamp(entry + elapsed)
|
||||
|
||||
| crossing | drawn as | |
|
||||
| --- | --- | --- |
|
||||
| 1 Stage (a 60 card) | enters at region 2, gone next Stage | the printed Start position |
|
||||
| 2 Stages (a 30 card) | region 1 → region 2 | **§8.2 exactly** |
|
||||
| 3 Stages (slow train) | region 1 → region 1 → region 2 | fixed distance, slow train |
|
||||
|
||||
`entry` is deliberately allowed to go negative and only the final position is clamped: that keeps a
|
||||
slow train's extra Stage at the START, where being slow shows. Clamping the entry instead parked it
|
||||
at the exit, reading as a train that raced across and then waited — which is what the first version
|
||||
did, and what the test now forbids.
|
||||
|
||||
`Transit` gains `stagesTotal`, set on entry. State, not rules: nothing reads it to decide anything,
|
||||
so the bot and every balance figure are untouched, and a save is a seed plus intents so there is
|
||||
nothing to migrate. It cannot be recomputed later — a modifier played onto the card mid-crossing
|
||||
would change the answer and make the train jump backwards.
|
||||
|
||||
Division Points draw one region, the queue. Running Track cards inside a district draw none: a crew
|
||||
moves there by Moves, not Stages, so it occupies a card outright.
|
||||
|
||||
### The Division map shows the whole route
|
||||
|
||||
The map drew one box per node, which collapsed each player's entire district into a single "Office"
|
||||
tile — so the track a train actually runs along was invisible on the only view that shows where
|
||||
trains are.
|
||||
|
||||
An Office now expands into its **Running Track, Limits to Limits**. That is the right cut rather than
|
||||
a compromise: the through route IS the Running Track, and everything hanging beneath it is secondary
|
||||
track a crossing train never touches. Sidings, industries and the load pipeline stay in the district
|
||||
view, which is where they can be read.
|
||||
|
||||
West DP · Mainline · [Limits … Office … Limits] · Mainline · [ … ] · Mainline · East DP
|
||||
|
||||
**Trains are shown wherever they are.** On a Running Track card, on a Mainline card, queued at a
|
||||
Division Point. A crew working BELOW the Running Track has no position on the through route, so it
|
||||
is reported against the district — "2 switching below" — rather than drawn somewhere it is not.
|
||||
Projecting a siding onto the through line would be a lie the map cannot support.
|
||||
|
||||
**Seating.** Players sit around a table, so the route is laid out the way they do: one row alone,
|
||||
two rows facing, a horseshoe of three, a square of four. The Division is a LINE and not a loop —
|
||||
trains enter at one Division Point and leave at the other — so the shape is deliberately left open,
|
||||
with buffer stops at both ends and the gap labelled "trains enter" and "trains leave". Closing it
|
||||
into a ring would promise a connection the rules do not have.
|
||||
|
||||
Layout is geometry with no visual feedback loop, so it is tested rather than eyeballed: for 1, 2, 3
|
||||
and 4 players no two cells may overlap and none may fall outside the canvas.
|
||||
|
||||
### The district folds itself away
|
||||
|
||||
Auto-focus. The district is worth its vertical space during the phases that change it — Local
|
||||
Operations and Cargo — and not during New Train, Mainline or Supervisor Shift, where the Division
|
||||
map is what matters. Folded, it leaves a summary line rather than vanishing, because a panel that
|
||||
disappears entirely reads as broken.
|
||||
|
||||
A manual toggle overrides it and stays put. This is DISPLAY state and never game state: two players
|
||||
at the same table may reasonably want it set differently.
|
||||
|
||||
### The replay was storing the same things over and over
|
||||
|
||||
Halved, near enough: **3415 KB → 1877 KB** on a 735-frame game.
|
||||
|
||||
The TODO said to carry `links` forward. Measured first, that would have bought 5% of the `cells`
|
||||
payload — `cells` is 62% of the file, and inside it the cost is elsewhere:
|
||||
|
||||
what 32% of cells long prose, identical on every turnout in the district
|
||||
facility 24% of cells the SAME object already serialised in the frame's `facilities`
|
||||
identity 26% of cells row/col/kind/label/running/links, fixed once the card is laid
|
||||
|
||||
So all three are interned. A card's identity and its description are written once for the whole
|
||||
recording and referenced by integer, and a cell points at its facility instead of carrying a second
|
||||
copy of it. What stays per-frame is what genuinely changes: the cars standing there, the crew, and
|
||||
any Enhancement laid on the card.
|
||||
|
||||
`rehydrateCells` is exported and emitted into the page by `toString()`, the same trick the two board
|
||||
renderers use — a second copy inside the page's inline script could drift from the packing and the
|
||||
symptom would be a board drawing the wrong cards rather than an error. The round-trip test runs that
|
||||
exact function over every frame, resolving nulls the way the page does.
|
||||
|
||||
### Rolling stock was leaving the game
|
||||
|
||||
Four things were tried against bot revenue. **One of them was worth more than everything else in
|
||||
|
||||
Reference in New Issue
Block a user