blueprintGame.md

The full design and implementation blueprint for Griddie Golf.

Docs/blueprintGame.md
# Griddie Golf - Game Design Blueprint

**Version:** 1.32 – Web build scripts + Next.js integration; iOS portrait builder
**Last Updated:** 11/10/2025
See full change history in `changelog.md`.

---

## 1. Game Overview

### 1.1. Game Description & Core Appeal
**Griddie Golf** is a turn-based, tactical dice golf game designed for mobile (portrait orientation). Players navigate a series of grid-based golf holes, aiming to achieve the lowest score relative to par. Griddie Golf is built in Unity with C# for scripting.

**Design Pillars:**
*   **Tactical Dice Management:** The core loop revolves around managing a "Hand of Dice" and making strategic choices about which die to use for each shot.
*   **Meaningful Choices:** The interaction between the chosen die, the selected club, and terrain creates a rich decision space each turn.
*   **Simplified Golf, Strategic Depth:** Abstracts complex golf physics into a clear grid-based system but retains strategic elements like hazard avoidance, shot planning, and risk/reward.
*   **Session-Based Replayability:** Designed for satisfying short sessions with clear goals, with multiple game modes to provide deep replayability.
*   **"One More Hole" Feel:** Aims to provide a satisfying loop of challenge and reward that encourages continued play.

---

## 2. Core Gameplay Mechanics

### 2.1. Core Rules & Turn State Machine
*   **Objective:** Complete each hole in as few strokes as possible. Achieve the best score vs. Par over a round.
*   **Grid-Based Movement:** The game is played on a 2D tilemap. Movement is in 8 orthogonal/diagonal directions. Holes are a fixed size of 16x24 tiles.
*   **Dice & Club Interaction:** At the start of a turn, a die is rolled for each equipped club that is usable on the current terrain. The resulting value animates into place on the club's UI element in the Golf Bag.
*   **Action Sequence:**
    1.  Dice are rolled and their values are displayed directly on the corresponding club UIs in the Golf Bag. Unusable clubs are visually greyed out and are not tappable.
    2.  Player taps one of the usable clubs in their Golf Bag to select it for the shot.
    3.  The value of the associated die becomes the `EffectiveDiceRoll`, modified by club, terrain, and badge rules.
    4.  Player is presented with an aiming interface to select a direction. An animated line previews the shot path and distance.
    5.  Ball moves the `EffectiveDiceRoll` number of tiles in the chosen direction.
*   **Dice UI modifiers:** Certain effects can adjust the bag dice before they are shown (e.g., Steady Hand via a dedicated pre‑display hook). Aiming‑phase effects like Heavy Hit and Man Mountain modify only `effectiveDiceRoll` and do not change the dice values shown on clubs.
*   **Hole Completion:**
    *   **Tap-In Rule:** If the ball lands on a Green tile directly adjacent (1 tile away) to the Hole tile, a "Tap-In" button appears to automatically complete the hole.
    *   Landing directly on the Hole tile also completes the hole.
*   **Technical Note: Turn State Machine**
    *   The game loop is managed by a state machine in `GameManager.cs` using the `E_OverallTurnState` enum. This controls the flow of a turn through key states:
        *   `WaitingDiceChoice`: Player selects a die and club.
        *   `WaitingForDieSelectionForReroll`: Awaiting a club tap to reroll a single die (Lucky Clover).
        *   `WaitingForDieSelectionForDieFlip`: Awaiting a club tap to flip a single die (Die Flip).
        *   `WaitingDirectionInput`: Player aims the shot.
        *   `BallMoving`: Ball is in motion; player input is locked.
        *   `HoleCompleteDisplay`: Shows the score panel for the completed hole.
        *   `ShopOpen`: The inter-hole shop is active.
        *   `GameOver`: The round has ended.

### 2.2. Clubs & The Golf Bag
*   **The Golf Bag:** A grid-based inventory (e.g., 2x4) where players equip clubs. Each club has a specific size (e.g., Driver is 1x2), requiring spatial arrangement.
*   **ClubData:** Clubs are defined by `ClubData` ScriptableObjects. These act as **read-only templates** containing a club's type, name, size, color, and the number of available `badgeSlots`.
    *   **Technical Note on Persistence:** To prevent equipped badges from persisting between runs, `GameManager.cs` creates temporary instances of these `ClubData` assets at the start of a run (`ScriptableObject.Instantiate`). All in-game modifications are made to these copies, which are discarded at the end of the run.
*   **Club Rules:**
    *   **Dice Sizes:** Driver = D8 (1–8), Iron = D6 (1–6), Putter = D4 (1–4).
*   **DRIVER:** Usable from Tee/Fairway. Ignores Trees during path check (but cannot land *on* a Tree).
    *   **IRON:** Usable anywhere except the Green. If its path hits a Tree, the ball **bounces off** to the nearest safe preceding tile.
    *   **PUTTER:** Usable **only on the Green**. Its distance equals the chosen die value, ignoring all terrain modifiers.

### 2.3. The Energy System (Per-Hole Badge Budget, 0–10)
Energy is the sole per-hole resource that powers badges. It does not end a run; rounds end by hole count and are scored against par.
*   **Scale:** 0–10, displayed as pips.
*   **Start-of-hole refill:** 3 Energy at the start of each hole, subject to the floor and start cap.
*   **Floor:** Minimum 3 Energy at hole start (refill never drops below this).
*   **Start cap:** Cannot start a hole above 10 Energy.
*   **Rollover cap:** Up to 2 unused Energy carries to the next hole; any excess is lost.
*   **Costs & toggles:**
    *   One‑shot/persistent badges spend immediately.
    *   Toggle badges reserve Energy up‑front (rightmost pips tint). On shot commit, the reservation is consumed (no refund). Deactivating or going Back before commit refunds.
*   **Hazard drains (this hole):**
    *   **Sand:** −1 Energy once per stroke if the ball lands in Sand during that stroke.
    *   **Tree bounce:** −1 Energy once per stroke when a Tree bounce occurs.
    *   **Reserve pip rule:** Hazard drains cannot reduce Energy below 1 within the current hole. Badge costs may still spend to 0 on commit.
    *   **No‑op at 0/1:** Hazards never increase Energy. Drains only apply when `current ≥ 2`; at `current ≤ 1` hazards are a no‑op. Hazard toasts are suppressed when no net change occurs.
*   **Toasts:** Start-of-hole refill, badge spends (e.g., “Vacuum −1”), toggles (“Rage −2” / “Rage +2”), and hazards (“Sand −1”, “Tree −1”) appear briefly near the pip bar. Shop purchases that affect next-hole Energy show an immediate toast (e.g., “Next hole +2”).

### 2.4. The Badge System
Badges provide active, player-triggered abilities. They are the core of run-based customization and are integrated with the Golf Bag system.
*   **Badge Types:**
    *   **Persistent Badges:** Always available regardless of equipped clubs (e.g., Mulligan, Lucky Clover). Persistent badges no longer track per-round uses; they consume Energy.
    *   **Club Badges:** Only available when their associated club is equipped. Club badges have unlimited availability; they consume Energy when applicable.
*   **Badge Categories:**
    *   **Toggle Badges:** Can be activated/deactivated (e.g., `Precision Shot`). Show a glow effect when active.
    *   **One-Shot Badges:** Instant effects that apply immediately (e.g., `Vacuum`).
*   **Availability & Timing:**
    *   **Dice/Club Selection (`WaitingDiceChoice`):** Only persistent badges are usable (e.g., Mulligan, Lucky Clover reroll). Club badges are hidden.
    *   **Direction Choice (`WaitingDirectionInput`):** Only the badges attached to the selected club are shown and usable.
    *   **One Active Club Badge Rule:** Only one club badge can be active per shot. Activating a different club badge will automatically deactivate the previous one. This prevents degenerate stacking while preserving strategic choice.
    *   **Back/Reset Behavior:** When returning to club selection from the aiming phase, all club badge toggle states are reset without consuming resources. Energy costs are only paid when the shot is committed. Reset refunds reserved Energy once and clears toggle flags without re-executing actions (prevents double refunds).
    *   These rules are enforced by UI container gating and validated in the turn state machine.
    *   Passive persistent badges are always visible during DiceChoice even if their data `activationWindow` is not `DiceChoice`. Hazard Pro dims after its once-per-hole waiver is consumed.
*   **Implemented Badges (Energy costs):**
*   **Mulligan (Persistent):** Re-rolls the entire dice hand. Cost 2 Energy. Max once per hole (enforced).
    *   **Lucky Clover (Persistent):** Re-rolls a single selected die in the current hand. Cost 1 Energy. Max 1 reroll per turn.
    *   **Die Flip (Persistent):** Flip a single selected die to its opposite face (Driver D8: 1↔8; Iron D6: 1↔6; Putter D4: 1↔4). Cost 1 Energy. OneShot, window: DiceChoice. Flow mirrors Lucky Clover (tap badge → select club die).
*   **Precision Shot (Club):** Enables 16-way aiming and opens new angles of opportunity. Significant power, but Cost 0 Energy to keep it as a club identity buff. Toggle. Auto-deactivates on shot commit and when backing out; state is per-club/per-shot.
    *   **Rage (Club):** Adds +3 distance, restricts to cardinal directions. Cost 2 Energy. Toggle.
    *   **Vacuum (Club):** Collects all pickups within a 4-tile radius. Cost 1 Energy. One-shot (usable every turn).
    *   **Delicate Touch (Club):** Reduces shot distance by 1 (minimum of 1). Cost 0 Energy. Toggle.
*   **Heavy Hit (Club):** Increases shot distance by 1. Cost 1 Energy. Toggle. Usable only during direction input; modifies only `effectiveDiceRoll` and does not change the dice values shown on clubs.
*   **Man Mountain (Club):** Adds +5 distance; on commit the final direction is chosen randomly among center (weight 2), left (1), and right (1) after dropping any illegal off‑targets (renormalized weights). Cost 3 Energy. Toggle. Triple preview shows as yellow off‑target lines alongside the normal center aim line, and appears only while dragging to aim; it hides on toggle off, Back, shot commit, and turn start. Auto‑deactivates after a shot.
*   **Green Reader (Club):** Extends the 'Tap-In' range on the Green to 2 tiles. Passive, unlimited uses.
    *   **Frugal (Persistent):** The first Energy‑costing badge you use each hole is free. Passive; implemented via `EnergyModel` waiver and per‑hole flag in `GameManager`.
*   **Hazard Pro (Persistent):** The first hazard (Sand or Tree) each hole is ignored. Passive; waives the first hazard Energy drain each hole. Distance modifiers and tree pathing are unchanged.
*   **Bombs Away (Club):** If this Driver's D8 shows an 8 (including after rerolls or flips) it counts as a 10. Passive, unlimited uses.
*   **Sand Specialist (Club):** Irons ignore the −1 distance penalty when starting a shot from Sand. Passive, unlimited uses. Implemented in the terrain step; sets `wasTriggeredThisShot` when it applies.
*   **Steady Hand (Club):** For Putters, a natural D4 roll of 4 becomes 3 before display. Passive, unlimited uses. Always renders at full opacity when equipped on a Putter.
*   **Technical Note: Badge Architecture**
    *   Badges are implemented using a flexible ScriptableObject pattern:
        *   **`BadgeData.cs`:** A ScriptableObject that defines a badge's static properties (name, description, icon).
        *   **`BadgeAction.cs`:** An abstract ScriptableObject that defines the badge's core logic. Concrete classes (e.g., `MulliganAction.cs`) inherit from this to implement the `Execute()` method. This makes creating new badges data-driven and requires minimal new code.
        *   Toggle actions should deterministically set game state from the badge instance's state (e.g., `PrecisionShotAction` calls `GameManager.SetPrecisionAim(!caller.isActive)`), rather than flipping global flags. This prevents toggle state from persisting across clubs.
        *   `HeavyHitAction` follows this deterministic pattern, is gated to `WaitingDirectionInput`, adjusts `effectiveDiceRoll` by ±1, and must not call `ApplyDiceModifiers()` (to avoid mutating bag dice UI).
        *   `BombsAwayAction` is a passive club action. `GameManager.HandleClubTap(...)` detects Driver at 8 (including after rerolls or flips) and applies +2 to `calculatedShotDistance` (bag dice UI unchanged). The owning badge instance sets `wasTriggeredThisShot=true` so UI shows full alpha for that shot.
        *   `SandSpecialistAction` is a passive club marker. In the terrain‑modifier step, `GameManager` negates Sand’s −1 when the selected club is Iron and this badge is equipped on that club; sets `wasTriggeredThisShot=true` for the shot.
        *   `SteadyHandAction` is a passive club marker. During dice generation it applies via `GameManager.ApplyPreDisplayClubRollModifiers(club, value)` so a Putter’s natural 4 becomes 3 before the value is shown, and the same rule applies to Lucky Clover rerolls. This does not use `wasTriggeredThisShot`.
        *   `ManMountainAction` is a club toggle. It deterministically calls `GameManager.SetManMountainActive(!caller.isActive)`. While active:
            *   Aiming adds +5 to `effectiveDiceRoll`.
            *   During aim updates (drag), `GameManager` computes neighbor directions from the active direction set (4/8/16) and calls `HighlightManager.ShowOffTargetLines(start,left,right,...)` to render two yellow off‑target lines; no per‑tile path painting is used here.
            *   On commit, `GameManager` evaluates legality for left/right and chooses the final direction with weights [center:2, left:1?, right:1?] after dropping illegal options; then executes the shot. The toggle auto‑deactivates post‑shot.
            *   Lines and toggle state are cleared on Back, toggle off, shot commit, and new turn.
        *   UI dimming: `BadgeSlot_UI` keeps passive club badges at 0.5 alpha by default, forces full alpha when `wasTriggeredThisShot` is true (e.g., Bombs Away/Sand Specialist on a triggered shot), and always shows full alpha for Steady Hand when equipped on a Putter.

### 2.5. Terrain & Pathing
*   **Terrain Effects:** Modifiers applied to the die value *before* club modifiers. The result cannot be less than 1.
    *   **Fairway and Tee:** Adds +1 to shot distance.
    *   **Sand:** Subtracts −1 from shot distance (negated when using an Iron with the Sand Specialist badge).
    *   **Green, Rough:** No distance modifier.
*   **Path Checking:**
    *   The aiming line preview color indicates path validity (Green for clear, Yellow for deflected, Red for blocked).
    *   A path is blocked if it goes through an Out-of-Bounds tile or lands in Water.
    *   Trees block shots: if an Iron's path goes *through* a tree or any shot *lands on* a tree, the ball bounces back to the nearest safe tile.

---

## 3. Game Modes & Content

### 3.1. Classic Mode
*   **Description:** A standard 9-hole round of golf.
*   **Content Source:** Uses pre-designed hole layouts defined in `HoleData` ScriptableObjects. The `GameManager` loads these from the `courseHoles` list.
*   **Goal:** Achieve the best score versus Par.

### 3.2. Daily Challenge Mode (Implemented)
*   **Description:** A competitive, seeded daily 18-hole course. All players face the exact same holes, dice rolls, and shop offerings.
*   **Content Source:** Holes are procedurally generated by `HoleGenerator.cs`. All randomness is derived from a daily seed generated in `GameManager.GetDailySeed()`.
*   **Core Rules:**
    *   Starts with a fixed 2x2 Golf Bag and default clubs.
    *   Energy fuels badge usage each hole; Energy does not end the run.
    *   The Inter-Hole Shop is available after each hole.
    *   On completion, the final score is submitted to a UGS Leaderboard (`daily-challenge`).

### 3.3. Infinite Mode (Conceptual)
*   **Description:** A continuous mode where the player plays as many procedurally generated holes as desired.
*   **Content Source:** Uses `HoleGenerator.cs` to create holes, but with a random seed at the start of each run (instead of a fixed daily seed).
*   **Goal:** Pursue the best streaks and low scores over time; Energy remains a per-hole badge budget and never ends the run.

---

## 4. Technical Implementation Guide

### 4.1. Project Setup
*   **Main Camera:** Orthographic, Size 18.
*   **UI Canvas:**
    *   Render Mode: `Screen Space - Camera`.
    *   Canvas Scaler: `Scale With Screen Size`, Reference `1080x1920`, Match `0.5`.
*   **Build Targets (Mobile-first Portrait):**
    *   Web (Stable): WebGL 2.
    *   Web (Experimental): WebGPU (Unity 6.1+); falls back to defaults if unavailable.
    *   iOS: Portrait‑only.
    *   Web build notes:
        *   Brotli compression + hashed filenames + IndexedDB caching. Requires correct `Content-Encoding: br` server headers (see `Docs/WebBuild.md`).
        *   Threads off by default. Enable only when COOP/COEP headers are configured.
        *   During Web build, quality overrides apply (MSAA 0, shadows off, vSync off); settings are restored after build.

### 4.2. Scene & Game Mode Flow (`GameModeManager`)
*   The `GameModeManager.cs` singleton persists between scenes and holds the selected game mode (`E_GameMode`).
*   **`Classic` & `Daily Challenge`:** Load directly into the `GameScene`. The `GameManager` then configures the game based on the selected mode.
*   **`Infinite`:** Loads the `CaddieScene` first, allowing the player to customize their Golf Bag. The chosen loadout is carried to the `GameScene` via the static `SelectedLoadoutCarrier`.

### 4.3. The Caddie Scene
This scene allows the player to assemble their Golf Bag before an Infinite Mode run.
*   **Data:** The player's bag layout is stored in a `GolfBag` data component.
*   **UI:** The `GolfBagUI` script manages the visual grid and drop targets. `DraggableClub` handles drag-and-drop logic.
*   **Scene-Gated Dragging:** `DraggableClub` is enabled only in `CaddieScene` and automatically disables itself elsewhere. In `GameScene`, clubs are tap-only (no dragging).
*   **Club Loading:** All available `ClubData` assets must be placed in `Assets/Resources/ClubData/` to be loaded correctly in a build.
*   **Layout Features:** The available clubs list supports vertical grouping by type (Drivers, Irons, Putters) with optional sticky headers and filtering, controlled by `CaddieSceneController.cs`.

### 4.4. Inter-Hole Shop: "The Caddie's Shack"
The shop is a UI overlay within the `GameScene` that appears between holes in `Daily Challenge` and `Infinite` modes. It is a dynamic, data-driven system.
*   **Flow & State:** `ShopController.cs` manages a state machine for `Browsing`, `EquippingBadge`, and `EquippingPersistent`.
    *   Badge purchase of a club badge enters `EquippingBadge` (player chooses a club slot).
    *   Badge purchase of a persistent badge enters `EquippingPersistent` (player chooses one of the three persistent slots).
    *   If a target slot is already occupied, a confirmation dialog is presented before replacement.
*   **Data-Driven UI:**
    *   All items are defined by `ShopItemData` ScriptableObjects, which include rarity, price, weight for random generation, and an effect category (e.g., `Energy`, `Badge`).
    *   The static `RewardGenerator.cs` class handles tiered, weighted random reward generation for the "Hot Sheet" (free reward choice). It includes smart filtering (e.g., lower/zero weight for Energy Packs when projected next‑hole Energy is at start cap) and an upward rarity fallback to fill the configured number of choices when possible.
    *   Persistent badge refills are removed; persistent badges now consume the shared Energy budget.
    *   Free Reward De-duplication: When a badge is claimed from the free reward options, `ShopController` removes that `ShopItemData` from `freeRewardPool` for the rest of the run to prevent duplicate offerings. Applies only to badges chosen via the free reward UI; non-badge free rewards (Energy/Golf Bucks/etc.) are unaffected and remain in the pool.
    *   **Single-Claim Enforcement (Hot Sheet):** After a free reward is claimed (including equipping a badge), the remaining Hot Sheet buttons are disabled for the rest of the shop visit. Implemented by setting `_claimedReward` when `IsFreeRewardItem(...)` is true in both `EquipBadge(...)` and `CommitPersistentEquip(...)`.
    *   The `ShopItemUI.cs` prefab populates itself based on a given `ShopItemData`. It dynamically adjusts text color for readability against different rarity backgrounds, which are mapped in `RarityColorMapper.cs`.
    *   Badge-specific card: `Shop-Button-BadgeItem-Prefab.prefab` is a taller variant used for Badge items (especially Free Rewards) to support longer descriptions. `ShopController` selects this prefab when `badgeItemUIPrefab` is assigned.
    *   Badge Energy Pill (card): The tall badge card shows the badge's Energy cost on use inside a compact pill (top‑right). Format: plain number (e.g., "2"; "0" when free). Implemented via `ShopItemUI` fields `energyCostPill`/`energyCostText`/`energyCostBackground`; text autosizes and centers. Colors contrast against card background using the same luminance heuristic.
    *   Free Reward Price Override: Hot Sheet rewards display “FREE” and cost 0 to claim without mutating their asset `basePrice`. Implemented via `ShopItemUI.IsFreeOverride` (display) and `ShopController.IsFreeRewardItem(...)` (purchase flow). Standard wares still charge their asset price.
    *   Cost Source of Truth: Cost is read from `BadgeData.energyCost` via `BadgeEnergyHelper.GetEnergyCost(BadgeData)`, which falls back to the action type mapping (e.g., Heavy Hit = 1) if an asset omits the field. We intentionally do not duplicate cost on `ShopItemData`.
*   **Replacement & Legality:**
    *   Occupied slot taps trigger a confirmation via `UIManager.Instance.ShowConfirmation(message, onConfirm, onCancel)`.
    *   Duplicate-prevention per club: a badge cannot be equipped twice on the same club. Incompatible clubs are disabled in the selection UI.
    *   Club gating: When a badge’s `allowedClubTypes` is specific (Driver/Iron/Putter), the pipeline auto‑sets `requiresSpecificClubType=true` and `requiredClubType` accordingly. Equip flow enforces gating in `ShopController` and in the club selection UI (`ShopGolfBagUI`).
    *   Cancel behavior for Free Rewards: If the player cancels while equipping a free reward, the shop exits equip mode and returns to `Browsing` (free reward buttons re-enable).
    *   Confirm behavior: Replaces the badge and refreshes bag/badge UI; free reward is marked claimed where applicable.
*   **In-Shop Golf Bag:**
    *   The `ShopGolfBagUI.cs` script manages the display of the player's current bag. To handle clubs of varying sizes (e.g., a 1x2 Driver), it manually calculates the position and size of each `ShopClubUI` instance, bypassing standard layout groups. It also dynamically resizes its main container via a `LayoutElement` component to correctly inform its parent layout controller of its desired size.
    *   `ShopClubUI.cs` is responsible for displaying a single club and dynamically creating the correct number of badge slots based on the `badgeSlots` property in the club's `ClubData`.
*   **State Reset:** The `UIManager.ShowShop()` method is intentionally ordered to first activate the shop panel `GameObject` (triggering the `OnEnable()` state reset in `ShopController`) *before* calling `RefreshShop()` to populate it with new items. This prevents state from incorrectly carrying over between holes. As an additional safeguard, `ShowShop(true)` hides any lingering `ConfirmationDialog` instance before the shop is rendered.

*   **Standard Wares: Energy Pack:**
    *   Buying the "Energy Pack" grants +2 Energy at the start of the next hole.
    *   Repeatable; the next-hole Energy cannot exceed the start cap (10). Disabled when next-hole Energy is already at the cap.
    *   Persistence: Standard wares remain purchasable after purchase (repeat buys allowed). Only free reward items are hidden once claimed.

*   **Shop Frequency & Free Rewards:**
    *   **Standard Wares Frequency:** The purchasable wares ("Buttons-Shop-StandardWares") are only visible every N holes. Default N = 3, yielding full shops on holes 3, 6, 9, ... Logic uses `currentHoleIndex % fullShopFrequency == 0`.
    *   **Free Rewards Every Hole:** The Hot Sheet (free rewards) is shown on every hole, independent of the full shop frequency. Players choose from `freeRewardChoices` options (default 3).
    *   **Editor Tuning:** These values are configurable on `ShopController` via `fullShopFrequency` and `freeRewardChoices` for testing/balancing.
    *   **Gating Implementation:** On non-full-shop holes, the standard wares container is hidden and its instantiated items cleared. Free rewards are unaffected.
    *   **Rarity Fallback & Logging:** The generator will fall back upward across rarities when needed to fill the configured number of free reward choices. If the pool is truly exhausted, a warning is logged ("Free rewards underfilled: requested N, got M.").

*   **Free-Reward: Energy Pack Variants:**
    *   A rarity-scaled set of free Energy Pack items is included in the Hot Sheet reward pool.
    *   Rarity → Next-hole Energy: Uncommon = +1, Rare = +2, Legendary = +3, Exotic = +4 (capped by start cap).
    *   Behavior: These are free (`basePrice=0`) and apply to next-hole Energy immediately when claimed; they are not removed from the pool and can reappear later in the run (badge-only de-dup).

*   **Labeling:** Items render their asset `displayName`/`description`. Recommended naming for Energy items: “Energy Pack (+X next hole)”.

*   **Persistent Rail (Always Visible):**
    *   Visual container: `Canvas/ShopPanel/PersistentRail` with `HorizontalLayoutGroup` (spacing 16). Each child is a slot `Button` named `Slot-0`, `Slot-1`, `Slot-2`.
    *   Sizing: Each slot has a `LayoutElement` with `preferred` and `min` size 72×72 to guarantee a circular footprint. The rail does not force-expand children.
    *   Icons: Each slot contains a centered child `Image` named `Icon` (52×52, `preserveAspect=true`, non-raycasting). No per-badge use counters are displayed; badges consume the shared Energy budget.
    *   Controller: `ShopPersistentSlotsUI` (on `ShopPanel`) manages visuals and interactivity for the rail.
        *   `slotButtons[0..2]` map to `Slot-0..2`.
        *   `slotBackgroundSprite` provides a circular socket sprite (default: `Assets/ModularGameUIKit/Common/Sprites/Shapes/Circle.png`); backgrounds use `preserveAspect=true`.
        *   Idle tints: `emptySlotBackgroundColor` and `occupiedSlotBackgroundColor` both default to low-alpha white (0.25) for consistent idle appearance.
        *   Equip-mode highlights: `equipModeEmptyColor` (green) and `equipModeOccupiedColor` (yellow). `ShopController` calls `SetEquipModeHighlight(true/false, holder)` on enter/exit of persistent equip mode.
        *   Interactivity gating: A `CanvasGroup` lives on `PersistentRail` and is referenced by `ShopPersistentSlotsUI.canvasGroup`. This gates only the rail, avoiding whole-shop blocking.
    *   Behavior: The rail is always visible. It is view-only while browsing and becomes fully interactive in `EquippingPersistent`.

### 4.5. Unity Gaming Services (Auth, Cloud Save, Player Name)
*   **Services:** Authentication, Unity Player Accounts (UPA), and Cloud Save are used.
*   **Boot Flow:** The game attempts to sign in with a linked UPA account if available; otherwise, it signs in anonymously. `UGSInitializationManager.cs` handles this flow.
*   **Account Linking:** The UI allows players to link their anonymous session to a UPA account. If the account is already linked elsewhere, the system gracefully signs the player out and back in with the UPA account to unify the session.
*   **Save System (`SaveManager.cs`):**
    *   Uses a single JSON payload in Cloud Save (`gg_save_v1`) with a local PlayerPrefs mirror for offline resilience.
    *   On load, it tries Cloud first, then local. On save, it writes to both.
    *   Handles data migration from an anonymous account to a linked UPA account.
    *   Energy shims: `UpdateEnergyShimsAsync(int energyCarry, int energyNextHoleBonus)` persists mid‑run Energy carry/bonus when applied and at hole complete.
*   **Player Display Name:**
    *   Players can set a display name via a UI input field. The name is saved to the UGS Authentication profile and mirrored in the Cloud Save payload.

### 4.6. Core UI Components
*   **Confirmation Dialog:** A generic, reusable confirmation dialog has been implemented.
    *   **Prefab:** `ConfirmationDialog.prefab`
    *   **Script:** `ConfirmationDialog.cs`
    *   **Access:** The dialog should be triggered via the `UIManager.Instance.ShowConfirmation(message, onConfirm, onCancel)` method to ensure it is managed correctly.

*   **Tap-In Button:**
    *   **Behavior:** Shown at the start of a turn when Tap-In is eligible (ball on Green and within the dynamic Tap-In range).
    *   **Control Flow:** `GameManager.StartNewTurnSequence()` calls `IsAdjacentToHole(...)`; if eligible, `UIManager.ShowTapInButton(true)` is invoked and dice/aiming UI is hidden.
    *   **Execution:** On press, `GameManager.ExecuteTapIn()` validates eligibility again and moves the ball one tile into the hole.

*   **Technical Notes (Tap-In Eligibility):**
*   **Hole Complete Continue Button:**
    *   **Label:** The Continue button on `HoleComplete_Panel` shows "Caddie's Shop" when the upcoming inter-hole shop is a full shop (standard wares visible) and "Claim Reward" when only free reward choices are presented.
    *   **Implementation:** `UIManager.ShowHoleCompletePanel(true, ...)` sets `holeComplete_ContinueButton_TMP.text` based on `(GameManager.currentHoleIndex % ShopController.fullShopFrequency) == 0`.
    *   **Editor Wiring:** Assign `UIManager.holeComplete_ContinueButton` and `UIManager.holeComplete_ContinueButton_TMP` to the button and its label.
    *   `GameManager.IsAdjacentToHole(Vector2Int fromCoord)` requires the ball to be on `Green` and compares Chebyshev distance to `_putterTapInRange`.
    *   `_putterTapInRange` defaults to 1 and is updated via `GameplayEvents.OnPutterTapInRangeChanged`.
    *   `GreenReaderAction` raises the event to set range to 2 on equip and back to 1 on unequip.

*   **HUD Hole Label:**
    *   **Behavior:** Displays "Hole: x/Total" to communicate round progress. Total depends on mode: Classic uses `GameManager.courseHoles.Count` (fallback 9), Daily Challenge uses 18, Infinite omits the denominator ("Hole: x").
    *   **Implementation:** `UIManager.UpdateGameplayHUD(...)` switches on `GameManager.currentGameMode` and formats `holeInfo_TMP.text` accordingly.

*   **Energy Meter (Pip Bar):**
    *   **Behavior:** 10‑segment meter representing Energy with baseline-only visibility at hole start (baseline pips only). Extra pips (rollover/bonus) appear only when present. Reserved Energy (active toggles) tints the rightmost filled pips.
    *   **Implementation:** `EnergyPipsUI.cs` on `EnergyPips_Panel`; `UIManager.UpdateEnergyDisplay(current, cap)`, `UIManager.SetEnergyReserved(int)`, and `UIManager.SetEnergyStartBreakdown(baseline,carry,bonus)` keep it in sync.
    *   **Prefab/Scene:** Under `/Canvas`, anchored bottom‑left; `ContentSizeFitter` (PreferredSize x/y) sizes from pip `LayoutElement` width/height and `HorizontalLayoutGroup` spacing. Children are `Pip-0 .. Pip-9` Images. Non‑blocking (`raycastTarget=false`).
    *   **Visuals:** `ModularGameUIKit` sprites; typical pip sizes ~45×45 (tune per design). Colors set on `EnergyPipsUI` (`filledColor`, `emptyColor`, `reservedColor`, `carryColor`, `bonusColor`).

*   **Energy Toast:**
    *   **Node:** `/Canvas/EnergyToast_TMP` (TMP + CanvasGroup), anchored bottom‑left, ~520×60, positioned just above the pip bar.
    *   **Usage:** `UIManager.ShowEnergyStartToast(current, carried)` on hole start. Badge/hazard/shop events use a centralized aggregator:
        *   `UIManager.BeginEnergyEvent(badgeInstance)` before a badge executes; `UIManager.EndEnergyEvent()` after success (or `CancelEnergyEvent()` on failure).
        *   Aggregator shows “Name -X/+X” by computing true deltas, and “Name Free” when the advertised cost > 0 but the effective delta is 0 (e.g., Frugal).
        *   Direct `ShowEnergyEventToast(...)` calls are suppressed while an aggregation is active to avoid duplicates.
        *   Hazard toasts are only shown when Energy actually decreases (no toast on waived or no‑op drains).
    *   Duration ~1.2s hold + 0.5s fade.
 

### 4.7. Shot & Path Highlighting (`HighlightManager`)
*   **Purpose:** To provide the player with clear, immediate visual feedback on the potential outcomes of their shot during the aiming phase.
*   **Core Component:** `HighlightManager.cs` is a singleton responsible for managing the highlight overlay.
*   **Technical Implementation:**
    *   The manager uses its own dedicated `Tilemap` to draw colored highlight tiles on the grid without interfering with the main gameplay tilemap.
    *   **Alignment:** The crucial `AlignTo(Tilemap referenceTilemap)` method ensures the highlight grid is perfectly synchronized with the reference gameplay grid. It robustly handles both World Space grids (Classic mode) and Screen Space - Camera grids (used in procedurally generated modes like Daily Challenge) by aligning the parent `Grid` transforms and the local `Tilemap` transforms.
    *   **Display Logic:** The `ShowHighlights(Dictionary<Vector2Int, PathResult> highlights)` method colors landing tiles for the general “potential landings” overlay. For Man Mountain’s triple preview, the manager uses auxiliary line renderers via `ShowOffTargetLines(start,left,right,color,width)` and `HideOffTargetLines()`; this draws only the two off‑target lines while the center line is provided by `GolfBall.DrawAimingLine`.
    *   **Aiming Timing:** Triple lines appear only while the player is dragging (aim update) and hide on Back/cancel, toggle off, shot commit, and turn start.
    *   **Direction Set:** `GameManager.GetAllPossibleDirections()` returns 16/8/4 directions based on `isPrecisionAimActive` and `isRageActive`. The joystick and triple preview neighbor sampling use this set.

### 4.8. Dice Reroll Animation (Golf Bag)
*   **Purpose:** Provide satisfying, readable feedback when dice values are (re)rolled for clubs.
*   **Visual Design:**
    *   Digit scramble (~0.3s), then a quick scale pop (~0.2s), with a brief color flash.
    *   Lucky Clover uses a green-tinted flash to distinguish targeted rerolls.
*   **Triggers:**
    *   New Turn: animates all usable clubs with a brief intro delay (first hand ~0.35s, subsequent ~0.2s).
    *   Mulligan: animates all clubs.
    *   Lucky Clover: animates only the selected club.
    *   Reroll Consumable: animates only the affected club.
*   **Reliability:** The `GameManager` defers animation until `GolfBagUI` has spawned club UIs and any parent `CanvasGroup` fade-in completes.
*   **Implementation Notes:** Animation is implemented in `ClubUI` via coroutines; `GameManager.DisplayDiceOnClubs(...)` controls when to animate vs. set instantly.
*   **Face Range Note:** For D8 (Driver), scramble digits span 1–8 to match gameplay faces.

---

### 4.9. Quick Start for New Developers
This is a fast orientation to the moving parts you’ll touch most frequently.

*   **Entry Points**
    *   `Assets/Scripts/GameState/GameManager.cs` — Core loop, turn state machine, shot execution, energy/hazard application.
    *   `Assets/Scripts/GameState/EnergyModel.cs` — Energy start/refill, toggle reservation/refund, hazard drains and floor logic.
    *   `Assets/Scripts/UI/UIManager.cs` (+ `UIManager.Energy.cs`) — Shows/hides gameplay/shop UI; drives Energy pip bar and centralized Energy toasts (`BeginEnergyEvent/EndEnergyEvent`).
    *   `Assets/Scripts/Badges/BadgeHolder.cs` — Runtime badge instances, availability, toggle orchestration, reset logic.
    *   `Assets/Scripts/Badges/Actions/SandSpecialistAction.cs` — Passive club badge marker for Iron‑on‑Sand terrain exception.
    *   `Assets/Scripts/Badges/Actions/BombsAwayAction.cs` — Passive club; triggers when Driver shows 8 (including after rerolls or flips); handled in `GameManager.HandleClubTap(...)`; UI highlights the badge for that shot.
    *   `Assets/Scripts/Badges/Actions/DieFlipAction.cs` — Persistent OneShot; enters die selection and flips the chosen club’s die.
    *   `Assets/Scripts/Badges/PrecisionShotAction.cs` — Deterministically toggles 16‑way aiming via `SetPrecisionAim`.
    *   `Assets/Scripts/Badges/FrugalAction.cs` — Passive; enables per‑hole first‑cost waiver managed by `EnergyModel` + `GameManager`.
    *   `Assets/Scripts/Badges/HazardProAction.cs` — Passive; waives the first hazard Energy drain each hole; tracked by `GameManager.hazardProUsedThisHole`.
    *   `Assets/Scripts/Badges/HeavyHitAction.cs` — Deterministic toggle; aiming‑only; +1 to `effectiveDiceRoll`; Energy reserved/refunded on toggle; no bag dice mutation.
    *   `Assets/Scripts/Badges/ManMountainAction.cs` — Toggle; +5 to `effectiveDiceRoll`; triple off‑target line preview; weighted random direction on commit; clears on Back/commit/turn start.
    *   `Assets/Scripts/Badges/BadgeEnergyHelper.cs` — Centralizes Energy costs for badges for both runtime and UI (includes `GetEnergyCost(BadgeData)` overload for data‑only contexts).
    *   `Assets/Scripts/UI/EnergyPipsUI.cs` — Colors/tints the 10 pips; reserved section tinting.
    *   `Assets/Scripts/Shop/ShopController.cs` — Shop flow/state, item generation, equip flows; reward detection via `IsFreeRewardItem(...)` for free pricing and single‑claim gating via `_claimedReward`.
    *   `Assets/Scripts/Shop/ShopItemUI.cs` — Item card rendering; readability against rarity backgrounds; uses asset `displayName`/`description` verbatim; renders top‑right Energy cost pill on badge cards; `IsFreeOverride` shows “FREE” on rewards.
    *   `Assets/Scripts/Shop/RewardGenerator.cs` — Tiered weights, contextual gating, and upward rarity fallback to keep free reward choices filled when possible.
    *   `Assets/Scripts/Services/SaveManager.cs` — Single‑blob Cloud Save and energy shims helper.
    *   `Assets/Scripts/Clubs/DraggableClub.cs` — Drag-and-drop for CaddieScene; auto-disables in gameplay to prevent visual bugs.
    *   `Assets/Scripts/Input/JoystickInput.cs` — Drag‑aiming; snaps to 4/8/16 based on Rage/Precision.
    *   MCP (Editor Automation): 
        *   `Assets/Editor/MCP/Actions/CreateBadgeAction.cs` — Creates `BA_` and `BD_` assets in canonical folders; migrates legacy BadgeData into `Assets/Data/Badges/Data/`.
        *   `Assets/Editor/MCP/Actions/SetAssetReferenceAction.cs` — Sets `ObjectReference` fields; resolves Sprites by asset path (including Sprite sub‑assets).
        *   `.cursor/commands/NewBadgePipeline.md` — End‑to‑end pipeline with verification‑first steps and path‑based icon linking; now defaults to sync‑first writes and includes an idempotent docs append plus a `get_list_property` verification for shop pool registration. When `allowedClubTypes != Any`, the pipeline also sets `requiresSpecificClubType=true` and `requiredClubType=<allowedClubTypes>` to enforce equip gating.
    *   Builds (Editor Automation):
        *   `Assets/Editor/Build/BuildPipeline.cs` — Menu + CLI build entry points:
            *   Menu: `GG/Build/WebGL Stable`, `GG/Build/Web (WebGPU Experimental)`, `GG/Build/iOS (Portrait)`.
            *   CLI: `-executeMethod GG.Build.BuildWebStable`, `-executeMethod GG.Build.BuildWebWebGPU`, `-executeMethod GG.Build.BuildiOSPortrait`.
        *   Outputs: `Builds/Web/Stable` (WebGL2), `Builds/Web/WebGPU` (experimental), `Builds/iOS` (Xcode).
        *   See `Docs/WebBuild.md` for Next.js headers and embedding via `@react-unity/webgl` with hashed filenames.

*   **Scenes & Prefabs**
    *   Scenes: `GameScene` (gameplay + shop overlay), `CaddieScene` (pre‑run loadout for Infinite).
    *   Under `/Canvas` in `GameScene`:
        *   `EnergyPips_Panel` — HorizontalLayoutGroup + ContentSizeFitter; child `Pip-0..9` Images with `LayoutElement` width/height; bottom‑left anchored.
        *   `EnergyToast_TMP` — TextMeshProUGUI + CanvasGroup; bottom‑left anchored; positioned above the pip bar. Driven by `UIManager.ShowEnergyStartToast(...)` and `ShowEnergyEventToast(...)`.
        *   `ShopPanel` (root) — Holds `ShopController` and sub‑containers.

*   **Data & Assets**
    *   `Resources/ClubData/` — Driver, Iron, Putter templates (instantiated at runtime).
    *   Badges — `BadgeData` + `BadgeAction` ScriptableObjects; look in `Assets/Scripts/Badges/`.

*   **Toggles & Energy**
    *   Energy-only runtime. Toggle badges reserve energy on activate (pips tint) and are consumed on shot commit; refunds occur only if deactivated or when backing out before commit.
    *   Hazards drain per stroke: Sand −1, Tree deflect −1.
    *   Start‑of‑hole refill applies floor/start‑cap (3/10) and consumes carry/next‑hole bonus (rollover cap +2).
    *   Focus has been removed from code and UI; there are no Focus-based game-over conditions.

*   **Testing & Debug**
    *   Press `P` (debug mode) to force hole complete.
    *   Energy system is always on; Focus paths have been removed.
    *   MCP in‑Editor tooling: see `Assets/Editor/MCP/Docs/MCPServer.md` for commands to reposition UI, set properties, or create nodes on the fly.
    *   Manual testing policy: The developer/tester will perform manual playtesting. Assistants should request specific manual tests and not run tests themselves.
    *   Web smoke tests: See `Docs/WebBuild.md` for a quick checklist (load time, memory stability, audio gesture on iOS, resume after background).

*   **Where to Add New Content**
    *   New badges: create a `BadgeAction` class (inherits `BadgeAction`), a `BadgeData` asset, and optionally shop entries (`ShopItemData`).
    *   New shop items: add `ShopItemData` and wire into `ShopController` lists; use `RewardGenerator` weights/gating.
    *   UI tweaks: prefer Editor changes (MCP where helpful); use `UIManager.Energy` only for minimal runtime hooks.

---

## 5. UI/UX Overview

### 5.1. Target Platform & UI Layout
*   **Primary Target:** Mobile (iOS/Android), Portrait Orientation.
*   **Top HUD:**
    *   **Left:** Hole #, Strokes, Par.
        *   Hole label shows "Hole: x/Total" where Total depends on mode: Classic = `courseHoles.Count` (fallback 9), Daily = 18, Infinite = none (just "Hole: x"). Implemented in `UIManager.UpdateGameplayHUD(...)`.
    *   **Center:** Round score summary or other non-Energy UI.
    *   **Right:** Cumulative round score.
*   **Badge Display:** Badges are organized in two containers for clarity:
    *   **Persistent Badges (Right):** e.g., Mulligan, Lucky Clover. No per-badge use counters; badges consume Energy.
    *   **Club Badges (Left):** Contextual, based on equipped clubs.
*   **Core Interaction:** Single-tap system on clubs in the Golf Bag to select the club and its associated die roll for the shot.
*   **Drag Behavior:** Drag-and-drop is available only in `CaddieScene` for loadout editing. In `GameScene`, clubs are not draggable (scene‑gated `DraggableClub`).
*   **Visibility Rules:** During dice/club selection, only Persistent badges are visible; during direction choice, only badges attached to the selected club are visible.
*   **Badge Active State:** When a club badge is active during aiming, other club badges are visually disabled to indicate the one-active-per-shot rule. Active badges show a glow effect.
*   **Triggered Club Passives:** Passives attached to the selected club that trigger on a shot render at full opacity for that shot (e.g., Bombs Away on Driver + natural 8).
*   **Back/Reset Button:** When in the aiming phase, the Back button resets all club badge toggle states (but does not consume resources). Costs are only applied when the shot is committed.
*   **Energy Meter Placement:** The Energy meter (0–10 pips) lives under `Canvas` and is anchored bottom‑left by default. It may be repositioned near badge rails on specific screens if desired. Avoid the central score HUD cluster.
*   **Cost Chips:** Each badge button displays a small cost chip showing the Energy cost as a plain number (e.g., "1", "2", "3"). Buttons are disabled when insufficient Energy. On gameplay badge slots the chip is a bottom‑right pill (`BadgeSlot_UI`), and on the shop Persistent Rail the number is shown over a bottom‑right pill on `Slot-0..2`.
*   **Future Direction:** The Golf Bag UI is planned to evolve into a more thematic top-down view of a golf bag, with club heads in dividers and the die roll appearing where the club's number (e.g., "9 Iron") would be.

### 5.2. UI Mockup Diagram
An SVG mockup diagram can be placed here to visualize the main game screen layout.

---

## 6. Future Development Roadmap
*   Balancing dice RNG vs. player skill.
*   Designing a compelling meta-progression system.
*   Ensuring procedurally generated holes are consistently fun and fair.
*   **Challenge Modes (e.g., 'The Cut Line'):** For experienced players, introducing optional, run-modifying challenges like a max score threshold could provide long-term replayability.
*   **Custom Dice Faces:** The idea of balls or other items altering the faces of the dice remains a compelling future direction.

---