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

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.
  • 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
  • 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