14 KiB
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.iois the main browser board-game framework, but it would expand scope too much for this refactor.- If formal phase handling becomes painful later,
XStateis 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:
- Determine round initiative order.
- Reveal
playerCount + 2public cards. - In initiative order, each player takes exactly one action:
- draft one card for the round, or
- ban one card from the row
- Drafted cards become active global rules for the round.
- Banned cards are removed.
- 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
+1energy. - Branching Season: Each branch beyond your first gives
+1energy. - West Light: Left third columns give
+1energy. - East Light: Right third columns give
+1energy. - High Noon: Center third columns give
+1energy. - Sun Ladder: Straight-up growth costs
-1once per turn. - Storehouse: Banking is enabled this round.
- Shade Cloth: Your covered nodes still count for
1energy.
Conflict / Interaction Cards
- Sap Theft: When your branch crosses an opponent branch, gain
+1energy. - Hedge Trimmer: Branches above the lowest player height are cut down before scoring.
- Disease Sweep: Before each turn, that player loses
1energy. - 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
0this round. - Still Soil: Root shifts are disabled this round.
- Thin Air: Diagonal growth costs
+1. - Tailwind: Diagonal growth costs
-1once per turn. - Canopy Tax: The tallest branch of each player costs
+1to extend. - Undergrowth: The first growth each turn costs
0. - Dry Season: Banking loses
1energy when used. - Tall Reward: Your tallest leaf gains
+2energy. - Wide Reward: If you control the most columns, gain
+2energy. - Twig Bloom: New leaves created this round gain
+1energy when scored. - Heavy Shade: Columns with 2 or more players present count as contested.
- Sunbreak: Empty columns next to your branch give
+1energy.
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
+1energy. - Branching Season: Each branch beyond your first gives
+1energy. - West Light: Left third columns give
+1energy. - East Light: Right third columns give
+1energy. - High Noon: Center third columns give
+1energy. - Sun Ladder: Straight-up growth costs
-1once 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
lifetimeGrowthIncomestat 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.
Storehouseenables 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 childrenbranch: a root-to-leaf path, or more simply for scoring, each leaf represents one branch
Recommended implementation simplification:
- Use
leaf countinstead 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, considerLeaf 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.idis treated as both a stable identity and an array index.
Plan:
- Keep
playersin stable id order. - Introduce
turnOrder: PlayerId[]for seats and active order. - Update turn flow and render helpers to use
turnOrderwhere 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_bidweather_draftturnround_endgame_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_endincome as the stable default. - Add
per_turnas 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.tssrc/constants.tssrc/state/types.tssrc/state/createGame.tssrc/state/reducer.tssrc/rules/board.tssrc/rules/moves.tssrc/rules/scoring.tssrc/rules/initiative.tssrc/rules/weather.tssrc/rules/turnFlow.tssrc/ui/renderBoard.tssrc/ui/renderSidebar.tssrc/ui/renderScoreboard.tssrc/ui/renderModals.tssrc/app/events.ts
Proposed Type Model
Core types to introduce early:
PlayerIdNodeKeySetupGameStatePlayerPhaseTurnMoveWeatherCardIdWeatherCardDefinitionActiveRoundEffect
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:
phaseturnOrderseatChoicesinitiativeAnchorPlayerIdweatherRowdraftedWeatherbannedWeatheractiveRoundEffects
Execution Order
Milestone 1: TypeScript conversion and safe refactor
- Add TypeScript and config.
- Convert
src/main.jstosrc/main.tswithout changing behavior. - Extract constants and types.
- Extract pure board, move, and scoring helpers.
- Keep current gameplay working exactly as-is.
Milestone 2: Turn-order refactor
- Separate stable player identity from seat order.
- Introduce
turnOrder. - Update active-player logic to walk
turnOrder. - Add explicit
phasehandling.
Milestone 3: Initiative draft phase
- Add setup controls for initiative mode and bidding-order rule.
- Add lifetime growth income tracking.
- Implement rotating bid order.
- Implement lowest-growth-income bid order.
- Add public seat selection modal.
- Apply seat growth bonuses.
Milestone 4: Weather draft-ban phase
- Define weather card data in
rules/weather.ts. - Add setup toggle for weather phase.
- Implement weather row generation.
- Implement draft-or-ban action in seat order.
- Render active effects clearly.
- Apply phase 1 weather cards in scoring and move cost logic.
Milestone 5: Per-turn income mode
- Add setup control for income timing.
- Implement payout to the active player immediately after their turn.
- Keep round-end mode intact.
- Verify banking and pass logic still make sense.
Milestone 6: Distance-based balancing
- Add root-distance surcharge support.
- Start with a simple formula such as:
extraCost = floor((abs(dx) + abs(dy)) / 4)
- Apply only in
per_turnmode at first. - Tune after playtesting.
Milestone 7: Phase 2 weather cards
- Add crossing-based cards if still desired.
- Add hedge-trimming/pruning cards.
- Add disease drain cards.
- 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