Fix parking trains, freight deadlock, and Whistle Post lock-in
This commit is contained in:
+163
-8
@@ -76,14 +76,171 @@ Crossing time never falls below one Stage — a train cannot cross in no time.
|
||||
there is nothing to build. A test asserts it remains TBD, to stop anyone "fixing" it by inventing
|
||||
an effect; a silent no-op would be worse than a rejection.
|
||||
|
||||
> **Open question (Q11).** The sheet says Brakeman and Airbrakes give "faster passage downhill" and
|
||||
> Helpers "faster passage uphill", but never says **which way a Heavy Grade climbs**. Modelled as
|
||||
> climbing **eastward** — eastbound is uphill, westbound downhill. If grades instead carry their own
|
||||
> direction, this becomes a property of the Mainline node rather than a constant.
|
||||
**Q11, answered from the source.** The Heavy Grade card prints **"(Up)"** and **"Player sets
|
||||
orientation"**, so which way it climbs is a property of the placed card, not a fixed compass
|
||||
direction. `DivisionNode.gradeUp` records the direction a train travels when **climbing**; a train
|
||||
going the other way is descending. Brakeman and Airbrakes apply to the descent, Helpers to the
|
||||
climb, so turning the card around swaps which trains each card helps.
|
||||
|
||||
One gap remains, and it is a **setup** gap rather than a rules one: `createGame` is synchronous and
|
||||
returns a ready state, so there is no point at which a player can be asked. Orientation is currently
|
||||
**rolled** — deterministic from the seed, and roughly even (51/49 east/west across 400 games) — and
|
||||
should become a real player choice when setup gains an interactive phase.
|
||||
|
||||
**Still not implemented**: the Action (10) and Space-use (12) cards, which are genuinely
|
||||
multiplayer-only. Playing them is rejected with `NOT_IMPLEMENTED`.
|
||||
|
||||
### Parking trains — two engine bugs and a bot defect
|
||||
|
||||
Trains reached an Office and never left: **668 arrivals against 515 departures**. A train may only
|
||||
highball from the Office square itself (§8.1, Gap 2b); from anywhere else `moveTrain` returns
|
||||
`held`, permanently. 77 of 93 leftover crews were sitting somewhere they could never depart from.
|
||||
|
||||
Three separate causes, found by following the stranded crews:
|
||||
|
||||
1. **Reverse was hardcoded to east/west.** `destinationsFor` computed the backing-out port as
|
||||
`facing === 'e' ? 'w' : 'e'`. A crew facing *south* reversed to *east* — a port a north-south
|
||||
card does not have — so it could never back out of a district spur. Now `opposite(facing)`.
|
||||
2. **Facing was derived from `direction`, which only carries east and west.** A crew standing on a
|
||||
north-south spur was given an exit port its card did not have, leaving it with no legal moves at
|
||||
all. `CrewTray.facing` now records the actual port, updated on every move from the port the crew
|
||||
entered through.
|
||||
3. **The bot never went home,** and its "no useful work" fallback took an *arbitrary* legal move —
|
||||
the same shuttling bug as before, re-entered by a different door. It now heads for the Office
|
||||
whenever nothing productive remains, and a crew stranded away from the Office can claim a whole
|
||||
Local Operations turn to walk back.
|
||||
|
||||
Flying Switch was also mis-modelled: it tested **orthogonal adjacency** when the cut *rolls along
|
||||
the track*, which both allowed a cut to cross to a cell with no rail between and refused a siding
|
||||
two cards along the same track. It made the card nearly unplayable — held for 1,400 turns across 60
|
||||
games and legal on 2. Now the target must be track-connected.
|
||||
|
||||
| | before | after |
|
||||
| --- | --- | --- |
|
||||
| arrivals / departures | 668 / 515 (77%) | **795 / 793 (99.7%)** |
|
||||
| leftover crews stranded off the Office | 77 of 93 | 24 of 53 |
|
||||
| collisions per game | 0.2 | **0.7** |
|
||||
| revenue per player | 1.0 | **−1.1** |
|
||||
|
||||
**Revenue got worse, and that is the honest result.** Parked trains were not colliding. With trains
|
||||
circulating properly, every collision is now `no free A/D track` — 35 of 35 across 60 games — and
|
||||
collisions cost **−3.3** of the −1.1 net. The previous figure was flattered by a bug.
|
||||
|
||||
### A/D congestion is not congestion — it is the Whistle Post
|
||||
|
||||
Chasing it found no congestion at all. The Office is **empty at 88% of Stage starts** (occupancy 0
|
||||
at 3,136 of 3,600) and mean dwell on an A/D track is **1.4 Stages**. Collisions are bursts, and they
|
||||
are concentrated in one place:
|
||||
|
||||
- **25 of 26 collisions happen at Whistle Post**, which has **one** A/D track, so any second arrival
|
||||
is an automatic collision (§8.3, Gap 2a). One collision occurred at Depot; none above it.
|
||||
- **No collision was ever recorded in a district holding an Interlocking.** The enhancement is a
|
||||
complete cure — it just arrives too late and too rarely.
|
||||
- **25 of 100 games never escape Whistle Post at all**, and those are exactly the games that never
|
||||
drew a Depot card. Mean first upgrade is Stage **9.3 of 60**.
|
||||
|
||||
The damage is concentrated, not spread:
|
||||
|
||||
| | games | mean revenue | worst |
|
||||
| --- | --- | --- | --- |
|
||||
| never upgraded past Whistle Post | 25 | **−6.0** | −54 |
|
||||
| upgraded at least once | 75 | **−0.4** | +9 (best) |
|
||||
|
||||
That is what makes the median **+1.0** while the mean is **−1.0**: a quarter of games are ruined by
|
||||
a draw that never comes. The bot now plays an Office upgrade ahead of everything else and prefers
|
||||
Interlocking over other Enhancements; both were previously unranked. Neither moves the mean, because
|
||||
the constraint is **draw luck, not policy** — escaping Whistle Post requires one of **4 Depot cards
|
||||
in 133**.
|
||||
|
||||
### Q12 answered — office density doubled
|
||||
|
||||
**Players always start at a Whistle Post**; that is settled and not up for change. The lever is
|
||||
therefore card density. Office `copiesInDeck` is **doubled**: Depot 4→8, Station 2→4, Terminal 1→2.
|
||||
Offices go 7 → 14 and the deck 133 → **140** (solitaire 111 → **118**).
|
||||
|
||||
Station and Terminal were doubled alongside Depot rather than lifting Depot alone, because upgrades
|
||||
are strictly sequential (Gap 3b): the ladder has to stay a pyramid or players reach a rung whose
|
||||
next card is scarcer than the one that got them there. The test now asserts that **property**
|
||||
instead of the literal counts, so it survives the next density change.
|
||||
|
||||
| | 7 offices / 133 | 14 offices / 140 |
|
||||
| --- | --- | --- |
|
||||
| revenue per player | −1.0 | **0.7** |
|
||||
| games never leaving Whistle Post | 25 | **13** |
|
||||
| mean revenue, games that did upgrade | −0.4 | **1.9** |
|
||||
| collisions per game | 0.7 | **0.3** |
|
||||
| lost to collisions | −3.3 | **−1.6** |
|
||||
| worst game | −48 | −28 |
|
||||
|
||||
All 37 remaining collisions are still at Whistle Post, which confirms the diagnosis rather than
|
||||
undermining it. Note the games that *still* never upgrade got worse (−6.0 → −10.5): they are now a
|
||||
smaller, unluckier group, so the mean is both more extreme and noisier. **Still 0 wins.**
|
||||
|
||||
> **PROVISIONAL — re-evaluate later.** These counts were chosen to remove a 25% chance of an
|
||||
> unwinnable opening deal, not taken from the recovered design, and the instrument is blunt: it
|
||||
> lifts the whole office ladder and dilutes every other category slightly. Revisit once the victory
|
||||
> target is settled and freight carries its intended share. The better answer may turn out to be
|
||||
> fewer Terminals, a cheaper first upgrade, or more A/D capacity at the Whistle Post itself.
|
||||
|
||||
### Measuring, after getting it wrong
|
||||
|
||||
A throwaway probe reported "60 of 60 games never upgraded past Whistle Post" while the Office-tier
|
||||
histogram from the same run plainly showed Depots, Stations and Terminals. Events reach the log from
|
||||
**two** sources — the phase driver (`pump`) and player intents (`applyIntent`) — and the probe
|
||||
watched only the first. Office upgrades arrive through the second.
|
||||
|
||||
The cause was hand-rolling the drive loop. `playGame` already tallied both sources correctly; it was
|
||||
re-implemented because it returns the event log but not the *state at the moment an event fired*,
|
||||
which is what questions like "what tier was the Office at this collision" need.
|
||||
|
||||
`playGame` now takes an optional `PlayObserver` — `(event, state) => void`, called from the single
|
||||
point both sources funnel through. Two tests guard it: one asserts the observer sees **exactly** the
|
||||
events the log records, one asserts office upgrades are visible in the log across 25 games.
|
||||
Reintroducing the original mistake fails both.
|
||||
|
||||
**A wrong measurement is worse than a missing one, because it gets believed and acted on.**
|
||||
|
||||
### Freight throughput — a self-inflicted deadlock
|
||||
|
||||
Freight was 10% of gross revenue with 27 industries on the board, so the Gap 12 tripling had bought
|
||||
facilities but not freight. The pipeline said why: **415 loads started, 413 unjams, 13 completions**
|
||||
across 60 games — a **3%** completion rate, with the Freight Agent spending more than half its
|
||||
actions clearing jams it had caused.
|
||||
|
||||
A load leaves MEN|AT|WORK only onto a **spotted empty car of its own type** (§9.3). Starting one with
|
||||
no car spotted parks it on WORK — and a load on WORK **locks the industry track**, which blocks the
|
||||
very car that would clear it. The bot started a load whenever the intent was legal, so it jammed its
|
||||
own facilities and then spent its turns unjamming them.
|
||||
|
||||
Two fixes, both in the bot; the engine was right throughout.
|
||||
|
||||
- **Only start a load that can finish.** `loadCanFinish` requires the receiving car to be spotted
|
||||
already. Worth recording that the first attempt kept a last-resort `startLoad` "rather than idle a
|
||||
Laborer" and measured *identical* to having no gate at all — the fallback was taken every time. An
|
||||
idle Laborer is strictly better than a jam, which costs an action to clear and locks the track
|
||||
meanwhile.
|
||||
- **Weigh the whole consist, not just the back car.** 744 switch turns produced 135 Moves and 741
|
||||
immediate ends: a crew holding wanted cars behind one unwanted car concluded there was nothing to
|
||||
do. §A.3 makes the back car the only *droppable* one; it does not make it the only one worth
|
||||
travelling for. The crew now sets out dead weight on a plain spur **before** travelling — ordering
|
||||
that matters, because doing it after produced the shuttling loop for the third time.
|
||||
|
||||
| | before | after |
|
||||
| --- | --- | --- |
|
||||
| loads started / completed | 415 / 13 (**3%**) | 44 / 38 (**86%**) |
|
||||
| unjams | 413 | 281 |
|
||||
| cars dropped | 151 | 197 |
|
||||
| cars coupled | 14 | 25 |
|
||||
| unloads completed | 40 | 74 |
|
||||
| **revenue per player** | 0.7 | **2.2** |
|
||||
| cards played | 12.4 | 16.9 |
|
||||
| trains scheduled | 2.0 | 2.7 |
|
||||
| passenger revenue | 1.3 | 2.7 |
|
||||
|
||||
Freeing the Laborers freed the whole turn economy — passenger revenue doubled without a single
|
||||
passenger rule changing. **Still 0 wins against a target of 20**, and freight is still only 13% of
|
||||
gross, so the loop runs now but does not yet carry the game.
|
||||
|
||||
### Track was unplayable (found while wiring up Enhancements)
|
||||
|
||||
Moving track out of the deck into a per-player supply (§12.2) left **no way to play it**. Districts
|
||||
@@ -99,10 +256,8 @@ the grid.
|
||||
Fixed by adding a `track.lay` intent drawing from `OfficeArea.trackSupply`, and by giving
|
||||
Enhancements the occupied cells as candidates. Enhancement placements went 21 → 98 per 40 games.
|
||||
|
||||
> **Open question (Q10).** The design moves track to a per-player supply but never says *when* a
|
||||
> piece may be laid. It is currently treated as a play available during the "draw a card" option,
|
||||
> limited to **one piece a turn** — which is how track behaved when it *was* a card. This is an
|
||||
> assumption, flagged in `intents.ts`, and needs confirming.
|
||||
**Q10, answered.** Track is laid during the "draw a card" option, **one piece a turn** — which is
|
||||
how track behaved when it *was* a card.
|
||||
|
||||
### Measurement after the answers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user