Files
canopy-game/plan.md
2026-04-08 16:29:50 -04:00

505 lines
14 KiB
Markdown

# Canopy Refactor And Rules Expansion Plan
## Goals
- Convert the game from JavaScript to TypeScript.
- Refactor the single-file implementation into small rules and UI modules.
- Fix the turn-order leverage problem with a public initiative seat draft.
- Add a public weather/effect card draft-ban phase to break symmetry.
- Add an alternate per-turn income mode with extra positional costs for balance.
## Recommended Foundation
- Use plain TypeScript with typed domain models and pure rules modules.
- Do not migrate to a board-game framework during this feature pass.
- Keep rendering simple and browser-native for now.
- Treat the game as a deterministic rules engine with DOM rendering layered on top.
Why this approach:
- The game is turn-based and rules-heavy, not entity-heavy.
- ECS-style libraries are a poor fit for this problem.
- `boardgame.io` is the main browser board-game framework, but it would expand scope too much for this refactor.
- If formal phase handling becomes painful later, `XState` is the best follow-up addition.
## Card System Goals
The weather/effect cards should:
- be readable from a TV screen
- be resolved with perfect information
- create asymmetric incentives each round
- be strong enough to disrupt mirror play
- avoid too much arithmetic or long text
- mostly modify incentives, not replace the core game
## Card Market Structure
Recommended round flow:
1. Determine round initiative order.
2. Reveal `playerCount + 2` public cards.
3. In initiative order, each player takes exactly one action:
- draft one card for the round, or
- ban one card from the row
4. Drafted cards become active global rules for the round.
5. Banned cards are removed.
6. Undrafted cards are discarded.
Recommended limits:
- Start with 8 to 14 total cards.
- Keep active cards per round low, ideally 1 to 3.
- Prefer cards with one sentence and one number.
## Candidate Weather / Effect Cards
These are written to fit a large shared screen. The title should be the main thing players read; the rule text should stay short.
### Economy / Position Cards
- **Leaf Surge**: Each leaf gains `+1` energy.
- **Branching Season**: Each branch beyond your first gives `+1` energy.
- **West Light**: Left third columns give `+1` energy.
- **East Light**: Right third columns give `+1` energy.
- **High Noon**: Center third columns give `+1` energy.
- **Sun Ladder**: Straight-up growth costs `-1` once per turn.
- **Storehouse**: Banking is enabled this round.
- **Shade Cloth**: Your covered nodes still count for `1` energy.
### Conflict / Interaction Cards
- **Sap Theft**: When your branch crosses an opponent branch, gain `+1` energy.
- **Hedge Trimmer**: Branches above the lowest player height are cut down before scoring.
- **Disease Sweep**: Before each turn, that player loses `1` energy.
- **Stalemate**: Contested columns give no energy.
- **Split Light**: Contested columns give half energy to each tied player.
- **Shared Light**: Contested columns give full energy to every tied player.
### Additional Cards Worth Considering
- **Root Pulse**: Root shifts cost `0` this round.
- **Still Soil**: Root shifts are disabled this round.
- **Thin Air**: Diagonal growth costs `+1`.
- **Tailwind**: Diagonal growth costs `-1` once per turn.
- **Canopy Tax**: The tallest branch of each player costs `+1` to extend.
- **Undergrowth**: The first growth each turn costs `0`.
- **Dry Season**: Banking loses `1` energy when used.
- **Tall Reward**: Your tallest leaf gains `+2` energy.
- **Wide Reward**: If you control the most columns, gain `+2` energy.
- **Twig Bloom**: New leaves created this round gain `+1` energy when scored.
- **Heavy Shade**: Columns with 2 or more players present count as contested.
- **Sunbreak**: Empty columns next to your branch give `+1` energy.
## Best Initial Card Set
For the first implementation, avoid the cards that require the most new geometry or crossing logic. Start with cards that reuse existing board evaluation.
### Phase 1 card set
- **Leaf Surge**: Each leaf gains `+1` energy.
- **Branching Season**: Each branch beyond your first gives `+1` energy.
- **West Light**: Left third columns give `+1` energy.
- **East Light**: Right third columns give `+1` energy.
- **High Noon**: Center third columns give `+1` energy.
- **Sun Ladder**: Straight-up growth costs `-1` once per turn.
- **Storehouse**: Banking is enabled this round.
- **Stalemate**: Contested columns give no energy.
- **Split Light**: Contested columns give half energy to each tied player.
- **Shared Light**: Contested columns give full energy to every tied player.
### Phase 2 card set
Add later after the rules core is stable:
- **Shade Cloth**
- **Sap Theft**
- **Hedge Trimmer**
- **Disease Sweep**
- **Root Pulse**
- **Still Soil**
Why split it this way:
- Phase 1 mostly changes scoring and move costs.
- Phase 2 needs new concepts like coverage protection, branch crossing checks, pruning, or per-turn drain.
## Rules Clarifications To Lock Down
### Initiative seat draft
- Players use a pure seat-selection token, not growth bidding.
- Players choose specific seat numbers.
- Seats are drafted publicly in bidding order, so no seat conflict resolution is needed.
- Bidding order is determined by setup rule:
- rotating from a random starting anchor, or
- lowest lifetime growth income first
### Lifetime growth income
Track all growth ever gained.
This should include:
- starting growth at game start
- round-end sunlight income
- per-turn sunlight income
- seat bonus growth
- card-generated growth
- random event growth like sunbeam bonuses
- recovered or protected growth if the game records that as gain
This should not double-count:
- banked energy carried into a later round after it was already counted when first gained
Implementation note:
- Add a dedicated `lifetimeGrowthIncome` stat to each player.
- Update it only at the moment growth is awarded.
### Banking
Current open issue:
- Banking should likely become conditional on a round card rather than always-on.
Recommended rule:
- By default, banking is disabled.
- `Storehouse` enables banking for that round.
Why:
- This helps reduce the economy snowball.
- It gives the weather deck a meaningful strategic lever.
### Contested columns
The current game reads column ownership by the top intercepting node. Weather cards that talk about contested columns require a clear definition.
Recommended definition:
- A column is contested if 2 or more players have a node in that column and they are tied under the active contest rule.
Open design task:
- Decide whether contest is based on equal top row, any shared presence, or special card-defined presence.
Recommended default:
- Contest means two or more players share the best topmost position for that column.
This keeps the rule closest to current sunlight logic.
### Leaves and branches
These terms need firm technical definitions before implementation.
Recommended definitions:
- `leaf`: a node with no children
- `branch`: a root-to-leaf path, or more simply for scoring, each leaf represents one branch
Recommended implementation simplification:
- Use `leaf count` instead of true branch count in the first pass.
- Rename card text if needed to match the actual rule.
Example:
- Instead of `Branching Season: Each branch beyond your first gives +1 energy`, consider `Leaf Burst: Each leaf after your first gives +1 energy`.
This is much easier to explain and compute.
### Branch crossing
`Sap Theft` needs a precise crossing rule.
Risk:
- The current data model stores branch segments, but crossing detection between diagonal edges is new logic.
Recommendation:
- Do not include crossing-based cards in phase 1.
- If added later, count only actual geometric edge intersections between different players' segments.
### Hedge Trimmer
This card is flavorful but potentially destructive and swingy.
Risks:
- Requires pruning nodes and edges from the board.
- Can invalidate pending move assumptions.
- Needs a clear “same level” definition.
Recommendation:
- Delay to phase 2.
- Define it as: `Before scoring, all nodes above the shortest tallest-player height are removed.`
- Only apply between turns or at round end, never mid-turn.
### Disease Sweep
This can be implemented in more than one way.
Choices:
- lose growth before each turn
- lose sunlight before end-of-round tally
- kill twigs as the current disease event does
Recommendation:
- For a card, use the simple economy version: `Before each turn, that player loses 1 energy.`
- Keep the existing random disease event separate.
## Implementation Risks
### Player identity and turn order are currently coupled
Current risk:
- `player.id` is treated as both a stable identity and an array index.
Plan:
- Keep `players` in stable id order.
- Introduce `turnOrder: PlayerId[]` for seats and active order.
- Update turn flow and render helpers to use `turnOrder` where needed.
### The game needs explicit phases
Current risk:
- The code uses ad hoc flow control plus animation locks.
Plan:
- Add a typed phase model:
- `initiative_bid`
- `weather_draft`
- `turn`
- `round_end`
- `game_over`
### The code is currently one large file
Current risk:
- Rules, state, rendering, and DOM events are tightly mixed.
Plan:
- Convert to TypeScript first.
- Extract pure rules before feature work.
### Per-turn income changes the economy deeply
Current risk:
- Existing logic assumes income is only awarded between rounds.
Plan:
- Keep `round_end` income as the stable default.
- Add `per_turn` as an alternate rules path after the initiative and weather phases are working.
- Add positional surcharges to slow runaway chaining.
### TV readability
Current risk:
- Too many active modifiers will be hard to track.
Plan:
- Cap active cards per round.
- Keep card titles short.
- Show active effects in one compact panel with icons or one-line summaries.
### Banking may conflict with weather cards
Current risk:
- If banking is always available and also appears on a card, the card has no value.
Plan:
- Make banking conditional on round effects, or convert the card into a banking bonus instead of availability.
### Randomness vs strategy
Current risk:
- Too much randomness in the weather row can obscure good play.
Plan:
- Start with a small curated deck.
- Reveal a modest row each round.
- Consider a rotating deck instead of full shuffle if needed after testing.
## Proposed TypeScript Module Structure
- `src/main.ts`
- `src/constants.ts`
- `src/state/types.ts`
- `src/state/createGame.ts`
- `src/state/reducer.ts`
- `src/rules/board.ts`
- `src/rules/moves.ts`
- `src/rules/scoring.ts`
- `src/rules/initiative.ts`
- `src/rules/weather.ts`
- `src/rules/turnFlow.ts`
- `src/ui/renderBoard.ts`
- `src/ui/renderSidebar.ts`
- `src/ui/renderScoreboard.ts`
- `src/ui/renderModals.ts`
- `src/app/events.ts`
## Proposed Type Model
Core types to introduce early:
- `PlayerId`
- `NodeKey`
- `Setup`
- `GameState`
- `Player`
- `Phase`
- `TurnMove`
- `WeatherCardId`
- `WeatherCardDefinition`
- `ActiveRoundEffect`
New setup fields:
- `initiativeMode: "fixed" | "bid"`
- `biddingOrderRule: "rotating" | "lowest_growth_income"`
- `incomeTiming: "round_end" | "per_turn"`
- `weatherDraftEnabled: boolean`
New player fields:
- `lifetimeGrowthIncome: number`
New game state fields:
- `phase`
- `turnOrder`
- `seatChoices`
- `initiativeAnchorPlayerId`
- `weatherRow`
- `draftedWeather`
- `bannedWeather`
- `activeRoundEffects`
## Execution Order
### Milestone 1: TypeScript conversion and safe refactor
1. Add TypeScript and config.
2. Convert `src/main.js` to `src/main.ts` without changing behavior.
3. Extract constants and types.
4. Extract pure board, move, and scoring helpers.
5. Keep current gameplay working exactly as-is.
### Milestone 2: Turn-order refactor
1. Separate stable player identity from seat order.
2. Introduce `turnOrder`.
3. Update active-player logic to walk `turnOrder`.
4. Add explicit `phase` handling.
### Milestone 3: Initiative draft phase
1. Add setup controls for initiative mode and bidding-order rule.
2. Add lifetime growth income tracking.
3. Implement rotating bid order.
4. Implement lowest-growth-income bid order.
5. Add public seat selection modal.
6. Apply seat growth bonuses.
### Milestone 4: Weather draft-ban phase
1. Define weather card data in `rules/weather.ts`.
2. Add setup toggle for weather phase.
3. Implement weather row generation.
4. Implement draft-or-ban action in seat order.
5. Render active effects clearly.
6. Apply phase 1 weather cards in scoring and move cost logic.
### Milestone 5: Per-turn income mode
1. Add setup control for income timing.
2. Implement payout to the active player immediately after their turn.
3. Keep round-end mode intact.
4. Verify banking and pass logic still make sense.
### Milestone 6: Distance-based balancing
1. Add root-distance surcharge support.
2. Start with a simple formula such as:
- `extraCost = floor((abs(dx) + abs(dy)) / 4)`
3. Apply only in `per_turn` mode at first.
4. Tune after playtesting.
### Milestone 7: Phase 2 weather cards
1. Add crossing-based cards if still desired.
2. Add hedge-trimming/pruning cards.
3. Add disease drain cards.
4. Rebalance text and values based on playtests.
## Testing Plan
Add rule-level tests as soon as the core logic is modularized.
Priority coverage:
- legal growth moves
- root shift legality
- scoring by column
- contested column resolution modes
- seat draft ordering
- lifetime growth income tracking
- weather draft resolution
- weather effect application
- per-turn payout behavior
- distance surcharge cost calculation
## Recommended First Shipping Configuration
- initiative mode: `bid`
- bidding order rule: `rotating`
- income timing: `round_end`
- weather draft: `enabled`
- banking: enabled only through `Storehouse`
- weather deck: only phase 1 cards
Why this first:
- It directly attacks the symmetry problem.
- It fixes turn-order leverage without also changing all economy timing at once.
- It keeps the new concepts readable for group play.
## Summary
The most important design shift is this:
- turn order becomes a public seat draft
- each round gains a public asymmetric card market
- mirror play is no longer automatically safe because players can deny and reshape incentives before growth begins
The best implementation strategy is:
- TypeScript first
- modular rules second
- initiative phase third
- weather card system fourth
- per-turn economy mode after the game flow is stable