user card Hand becomes the action surface, the board shows the train, and a reverse stops turning it around, other bug fixes too.

This commit is contained in:
Jesse
2026-08-05 19:18:26 -04:00
parent 1c321d4cea
commit 543584a2ca
20 changed files with 1851 additions and 320 deletions
+14 -2
View File
@@ -13,6 +13,7 @@
*/
import type { CarType, Hand, TrackGeometry } from './content.ts';
import { enhancementRule } from './content.ts';
import { check, areaOf, destinationsFor } from './apply.ts';
import type { Intent } from './intents.ts';
import type { GameState, GridCoord, PlayerIndex } from './state.ts';
@@ -140,8 +141,19 @@ function localOpsCandidates(s: GameState, player: PlayerIndex): Intent[] {
for (const cardId of s.decks.hands.get(player) ?? []) {
out.push({ type: 'card.play', cardId });
const kind = s.cards.get(cardId)?.kind;
// Enhancements ATTACH to a card already down, so their candidates are the occupied cells.
const targets = kind?.kind === 'enhancement' ? attachments : placements;
/**
* Enhancements ATTACH to a card already down, so their candidates are the occupied cells — with
* one exception. ABS Signals goes on a MAINLINE card, and `checkEnhancementPlacement` reads
* `placement.col` as a Division node index for it. Offering it grid squares meant the engine
* accepted "(0, 1)" because node 1 happened to be a Mainline card: the label said one thing and
* the placement meant another, so the card appeared to be playable all over the Office Area.
*/
const onMainline = kind?.kind === 'enhancement' && enhancementRule(kind.key)?.placement === 'mainlineCard';
const targets = onMainline
? s.division.nodes.map((_, node) => ({ row: -1, col: node }))
: kind?.kind === 'enhancement'
? attachments
: placements;
// Orientation is chosen on placement, and a printed card turns but never flips, so the widest
// variant set is TWO. Anything that is not track has a single orientation and needs one entry.
const rotations = kind?.kind === 'track' ? variantsFor(kind.geometry, kind.hand).length : 1;