35 KiB
Node System (节点系统)
Status: Approved — Fantasy Into-the-Breach reference corrected; completedLoopCount cap description corrected; all 6th-review blocking items resolved; loss-gold AC added (C-W2 resolved) Author: SepComet + agents Last Updated: 2026-04-30 (post-review: Fantasy line 37 corrected to reflect opaque level variants; completedLoopCount cap description corrected from 2^31 to 2^30 × BaseHp) Implements Pillar: Core game loop navigation — players drive their own path through the run
Overview
The Node System is the run-level navigation layer that structures a complete playthrough. A run consists of exactly 10 sequential nodes. The player advances through nodes one at a time, choosing which available node to tackle next. After each node resolves, the player enters a brief assembly phase to reconfigure their towers before committing to the next node.
Node types:
- Combat Node: Triggers a wave-based tower defense battle via
CombatNodeComponent. The player earns Gold (persistent run currency) and component drops based on performance. - Event Node: Presents a branching choice with risk/reward outcomes. No combat; purely decision-based.
- Shop Node: Opens the component store for purchasing upgrades between battles.
- Boss Node (Node 10): A combat node with higher difficulty and guaranteed valuable drops — the run's climax.
Currency note: This GDD distinguishes two currencies. Gold is the persistent run-level currency earned from combat nodes and spent at Shop nodes. Coin is a combat-internal currency earned per combat round and spent within a single combat encounter on tower building and other intra-combat actions — Coin does not persist between nodes and is exclusive to the CombatNode domain. The Coin sink is defined in the CombatNode design; Coin has no interaction with Gold or the shop system.
Node graph structure: The node graph is a linear track with one branch per node. At each node entry, the player is shown the available outgoing edge(s) and must choose which node to enter next. There is no convergence/merging of paths within a run — the player advances linearly, not across branching tracks. The two edges at each junction lead to the same node type but offer different level variants (distinct map layouts, enemy compositions, or environmental conditions) — creating strategic divergence through topology and threat profile rather than through node-type variety.
Node type generation: Node types follow a fixed sequence (not randomized) per run. The sequence for the default (Plain theme) is: Combat → Combat → Combat → Shop → Combat → Event → Combat → Shop → Combat → BossCombat. Events are restricted to positions 4–8 only. Future themes may define their own fixed sequences. Players cannot choose or reroll node types. The two outgoing edges from each node lead to distinct level variants of the next node type, not to different node types.
Player Fantasy
"I feel like a tactician executing a plan through hostile terrain — the route is set, but how I prepare and when I commit my forces determines whether I survive."
The Node System delivers the fantasy of tactical navigation under pressure. The player is a tactician with a fixed route ahead — they know what kinds of challenges await (the full node track and boss are visible at run start), but at each junction they choose which of two paths to commit to. The core feeling is the weight of tactical commitment: selecting a path means locking in your approach. You can see the Boss at Node 10 glowing at the end, and you know the full track from the start — but the question is whether the resources and tower builds you've chosen will be sufficient to reach it intact.
The player should feel:
- Preparing and adapting — using Assembly Phases to optimize tower builds based on known upcoming challenges; choosing path segments that complement the components in hand
- The weight of commitment — once you enter a node, the choice is locked; there's no undoing or backtracking within a run
- Building toward a climax — each node brings the player closer to the Boss; the 10-node arc creates mounting tension toward the run's inevitable crescendo
- Satisfaction when the plan holds — the run "reads" as a coherent story in retrospect: "I invested heavily in early towers, conserved resources mid-run, and deployed my best assembly for the Boss"
Reference: Into the Breach's "visible consequences of choices" feeling — the player can see the full run track from start, sees the Boss glowing at the end, and must prepare their tower builds accordingly. The Geometry TD node system achieves this through the fixed Boss at Node 10, the linear-but-choiced track structure where the two outgoing edges present different level variants of the next node (the specific variant is revealed at the node, not beforehand), and the Assembly Phase where the player configures their towers for the known upcoming challenge. Resource timing and build optimization matter more than node-type gambling.
Detailed Design
Core Rules
Run Structure
- A run consists of exactly 10 sequential nodes. Node 10 is always a Boss Combat node.
- Node types follow a fixed sequence per theme (see Node Type Generation above). The player cannot choose or reroll node types.
- The run graph is strictly forward-only: no backtracking, no skipping nodes, no retries of completed nodes.
Node Entry Flow 4. Player arrives at a node. The node resolves based on type:
- Combat / Boss:
CombatNodeComponent.StartCombat()is called by the Procedure layer. On victory, player receives Coin, component drops, and Gold. - Event: Branching choice presented. Player selects an option; risk/reward resolves immediately.
- Shop: Shop interface opens. Player buys/sells components. Player exits freely.
- Node resolves. Assembly Phase is automatically entered after every node resolves. Player interaction within it is optional — the "Ready" button can be clicked immediately to proceed, or the player may re-enter freely before selecting the next node. Assembly Phase is never skipped; it is a mandatory transit point, not a mandatory modification point.
- Assembly Phase: Player can swap any component on any tower, reorganize inventory, and review current stats. Player confirms "Ready" to proceed to node choice, or may re-enter Assembly Phase freely before selecting the next node.
- Assembly Phase ends (player-initiated or via "Ready" confirmation). The 2 outgoing edge destinations from the completed node are revealed (node types shown).
- Player selects one destination. The chosen edge is locked in. Player travels to the next node.
- Repeat steps 4–8 until Node 10 (Boss) is reached.
Combat Loss Rules
10. Any combat loss: Run ends in failure immediately. There is no continuation after a combat loss — the run concludes at the point of failure. This applies to both regular Combat nodes and the Boss node. CombatNodeComponent fires NodeCompleteEventArgs(CombatWon=false); the Procedure layer receives this and transitions to RunEnd(Failure).
Data Persistence 11. Within a run: Inventory, Repository, Gold, Coin, Tower configs, visited node history, and active buffs/debuffs persist across nodes. 12. Between runs: All of the above reset to starting values. Only permanent meta-progression (unlocks, permanent upgrades) persists.
NodeComponent — Ownership and Interface
15. There is no NodeComponent class. Run-level orchestration is handled by the Procedure layer via RunStateAdvanceService and RunState.
16. The Procedure layer calls CombatNodeComponent.StartCombat() only after the Assembly Phase is confirmed complete.
17. CombatNodeComponent fires NodeCompleteEventArgs (with CombatWon field) after combat resolves. The Procedure layer receives this event and drives state transitions via RunStateAdvanceService.TryCompleteCurrentNode.
States and Transitions
| State | Description | Exits |
|---|---|---|
RunIdle |
Pre-run, at main menu. Player not yet in a run. | → NodeReveal on "Start Run" |
NodeReveal |
Outgoing edges from current node are displayed. Player makes a choice. | → NodeTransition on choice confirmed |
NodeTransition |
Player travels to the chosen destination node. | → NodeEntry |
NodeEntry |
Player arrives at the node. Node-type logic triggers (Combat/Event/Shop/Boss). | → AssemblyPhase on node resolved |
AssemblyPhase |
Full tower assembly enabled. Player may enter/exit freely. Player confirms "Ready" to proceed to node choice. | → NodeReveal (next node) or RunEnd (Boss completed) |
RunEnd |
Victory or failure screen. Stats recorded. Return to main menu. | → RunIdle |
RunNodeStatus.Exception |
Entered only when a node encounters an unexpected error (e.g., data load failure, invalid state). This is not normal gameplay flow. Treated as RunEnd(Failure) for all downstream purposes. |
→ RunIdle |
Note: CombatNodeComponent manages its own internal Loading → RunningPhase → ... → Settlement state machine (per CombatNodeArchitecture.md). From the Node System's perspective, a Combat node entry is a single atomic transition: NodeEntry → AssemblyPhase on receiving the CombatVictory or CombatDefeat event.
Interactions with Other Systems
| System | Direction | Interface |
|---|---|---|
| CombatNodeComponent | Delegates to | StartCombat(CombatData), receives OnCombatVictory / OnCombatDefeat events |
| ShopSystem | Delegates to | ShopFormController.OpenShop(), receives OnShopClosed callback |
| EventSystem | Delegates to | EventNodeComponent.ProcessEvent(), receives OnEventResolved |
| TowerAssembly | Reads/writes | Tower config persists in NodeComponent run state; Assembly Phase reads current inventory |
| Inventory | Reads | Component drops from combat are added to inventory via InventoryComponent.AddItem |
| MapEntity / MapTopologyService | Reads | Combat nodes query MapTopologyService for path data to pass to CombatNodeComponent via MapData |
| Progression | Writes | On RunEnd, final stats (Gold, nodes completed, Boss killed) are written to Progression |
RunState (data container, owned by Procedure layer)
├── RunStateAdvanceService (state transition logic)
├── CombatNodeComponent (delegates combat entry)
├── ShopNodeComponent (opens Shop UI on Shop node)
├── EventNodeComponent (processes Event node choices)
└── TowerAssembly (read/written during Assembly Phase)
Note: There is no NodeComponent class. Orchestration is handled by the Procedure layer.
Formulas
Important note on completedLoopCount: This refers to the number of completed combat cycles within a single Boss node encounter — i.e., when a Boss fight loops (e.g., VictoryType requires surviving N rounds), each completed cycle increments the count. This is independent of the run's node count. The formula does NOT use nodesCompleted from the run state.
Gold earned per node completed, plus boss level reward and bonus:
TotalGold = Σ DRLevel.RewardGold(CompletedCombatNodes) + BossLevelGold + (HasDefeatedBoss ? BossBonus : 0)
Each Combat node's gold reward is determined by its linked level's DRLevel.RewardGold value. Event and Shop nodes do not award gold directly. The Boss node's own reward comes from its linked level (DRLevel.RewardGold at level index for Boss) plus the BossBonus if defeated. HasDefeatedBoss is determined by whether the player won the Boss encounter; losing at the Boss does not set this flag.
Note on impossible states: HasDefeatedBoss = truewithn = 0is unreachable — the Boss cannot be reached without completing at least one combat node.Combat nodes cleared n int 0–6 Number of non-boss combat nodes cleared (nodes 1, 2, 3, 5, 7, 9). Event and Shop nodes award 0 gold and are excluded from the sum. Boss defeated flag HasDefeatedBoss bool — True if the Boss was successfully defeated in this run. Boss bonus BossBonus int 200 Flat bonus for defeating Boss (applied only if HasDefeatedBoss = true)Boss level gold BossLevelGold int ≥ 0 Gold from DRLevel.RewardGoldof the Boss level (Node 10). Added regardless ofHasDefeatedBosswhen Boss is reached.
Per-node gold (REVISED — economy rebalanced): After rebalancing, the illustrative values for the Plain theme sequence (Combat at L1, L2, L3, L1; Boss at L4) are:
| Node | Type | Level | Gold (illustrative) |
|---|---|---|---|
| 1 | Combat | Level 1 | 90 |
| 2 | Combat | Level 2 | 90 |
| 3 | Combat | Level 3 | 120 |
| 4 | Shop | — | 0 |
| 5 | Combat | Level 1 | 90 |
| 6 | Event | — | 0 |
| 7 | Combat | Level 2 | 90 |
| 8 | Shop | — | 0 |
| 9 | Combat | Level 3 | 120 |
| 10 | BossCombat | Level 4 | 300 + BossBonus(200) |
Output Range: 0 to 1100 (6×Combat=600 + BossBonus=200 + BossLevelGold=300) depending on how far the player progressed and whether Boss was defeated. Constraint: Plain theme places exactly one Event node at position 6. Events may only occupy positions 4–8. Combat nodes 1, 2, 3, 5, 7, 9 use the Plain cycle L1, L2, L3, L1, L2, L3. Shop tiering: The first shop (Node 4) offers only White and Green rarity components; Blue and above appear only from Node 8 onward.
2. Boss Difficulty Scaling
Boss difficulty scales with the Boss node's loop/round count.
BossEffectiveHp = DRLevel.BaseHp × 2^completedLoopCount
| Variable | Symbol | Type | Range | Description |
|---|---|---|---|---|
| Boss base HP | DRLevel.BaseHp | int | ≥ 1 | Fixed HP from level config (note: DRLevel only has BaseHp, not a separate BossBaseHp field). Floor of 1 applied at data load time. |
| Completed loop count | completedLoopCount | int | 0–30 | Number of combat rounds/cycles completed within the current Boss encounter — NOT the count of nodes completed in the run. When a Boss fight loops (e.g., VictoryType requires surviving N rounds), each completed cycle increments the count. Resets when a new Boss fight begins. Hard cap of 30 loops — at 30, 2^30 × BaseHp reaches ~2×10^9; implementation returns int.MaxValue at the 30th cap to prevent overflow. |
| Boss effective HP | BossEffectiveHp | int | ≥ 1 | Final boss HP |
Note: Formula matches EnemyConfigProvider.ResolveScaledEnemyBaseHp. The DRLevel fields used are: Id, LevelThemeType, BaseHp, StartCoin, VictoryType, VictoryParam, RewardGold.
Edge Cases
-
If Player loses Combat at any node: Run ends in failure immediately. No partial rewards are awarded. The run is complete at the point of failure.
-
If Player has 0 components entering Assembly Phase: Player cannot assemble or modify any towers. Assembly phase is effectively a no-op pass-through. Player proceeds to the next node with existing tower state unchanged.
-
If Player encounters two consecutive Shop nodes: Player has back-to-back purchase opportunities. If Gold is insufficient at Shop 1, no mitigation occurs — Shop 2 may also be unaffordable. No rule forces spending or guarantees affordability.
-
If Player encounters Shop Node 4 (first shop): Only White and Green rarity components are available. Blue and higher rarities are excluded from this shop. If the player's gold is insufficient for all available items, no additional items appear — the player may proceed with insufficient purchases.
-
If Player encounters Shop Node 8 (second shop): All rarity tiers (White through Red) are available. There is no tier restriction on the second shop.
-
If Player loses at Boss node: Run ends in failure immediately with no partial rewards. The Boss node awards no rewards on loss.
Dependencies
Upstream Dependencies (what Node System depends on)
| System | Type | Interface | Status |
|---|---|---|---|
| CombatNodeComponent | Hard | Fires NodeCompleteEventArgs with CombatWon field after combat resolves. Calls to CombatNodeComponent.StartCombat() enter combat. |
Implemented (Assets\GameMain\Scripts\CustomComponent\CombatNode\CombatNodeComponent.cs) |
| ShopSystem | Hard | Calls ShopNodeComponent.StartShop(); receives OnShopClosed callback. |
Designed (design/gdd/shop.md). ShopContext contract and buy/sell behavior are defined and consistent with Assembly Phase timing. |
| EventSystem | Hard | Calls EventNodeComponent.ProcessEvent(); receives OnEventResolved. |
Designed (design/gdd/event-system.md). EventContext contract and risk/reward resolution flow are defined and consistent with NodeEntry → AssemblyPhase atomic transition model. |
| TowerAssembly | Soft | Reads/writes tower configs during Assembly Phase. Tower state persists in NodeComponent run context. |
Designed (design/gdd/tower-assembly.md). TryAssembleTower() and TryDisassembleTower() interfaces are defined; Assembly Phase timing and inventory access patterns are consistent with this GDD. |
| Inventory | Soft | Reads component drops from combat; adds items via PlayerInventoryComponent.MergeInventory. |
Implemented |
| MapEntity / MapTopologyService | Hard | Reads path/topology data for combat map setup; assembles MapData passed to CombatNodeComponent. |
Implemented (see Assets\GameMain\Scripts\CustomComponent\Map\) |
| Progression | Soft | Writes final run stats (Gold, nodes completed, Boss killed) on RunEnd. |
Designed (design/gdd/progression.md). RecordRunEnd() interface is defined; all acceptance criteria involving Progression are now implementable. |
Downstream Dependents (what depends on Node System)
| System | Type | Interface | Status |
|---|---|---|---|
| Progression | Hard | Reads run completion data (Gold, nodes cleared, Boss defeat) from RunState on RunEnd. |
Designed (design/gdd/progression.md). Interface contract is defined. |
Bidirectional Consistency Check
CombatNodeComponent→ listed as upstream (Node System receives its events) ✅Progression→ downstream only; Node System writes to it ✅ShopSystem→ GDD exists; interface contract aligned ✅EventSystem→ GDD exists; interface contract aligned ✅TowerAssembly→ GDD exists; interface contract defined ✅
Provisional Assumptions
ShopSystemreceives aShopContextfrom the orchestrating component containing current Gold/Coin and run node indexEventSystemreceives anEventContextcontaining run state (node index)TowerAssemblyis called during Assembly Phase and writes back toRunState's inventory snapshot- There is no
NodeComponent— orchestration is handled by the Procedure layer viaRunStateAdvanceService
Tuning Knobs
All designer-adjustable values for the Node System. Changing these does not require code changes.
Run Structure
| Knob | Default | Safe Range | Extreme: Too Low | Extreme: Too High |
|---|---|---|---|---|
TotalNodesPerRun |
10 | 5–20 | Run feels too short; boss arrives too quickly | Run feels repetitive; pacing drags |
BossNodeIndex |
10 | = TotalNodesPerRun |
N/A | N/A |
OutgoingEdgesPerNode |
2 | 2–3 | Fewer choices reduces strategic depth | More choices may overwhelm UI/decision-making |
Boss Scaling
| Knob | Default | Safe Range | Extreme: Too Low | Extreme: Too High |
|---|---|---|---|---|
BossBonusGold |
200 | 100–500 | Boss reward feels trivial | Boss trivializes economy |
Assembly Phase
| Knob | Default | Safe Range | Extreme: Too Low | Extreme: Too High |
|---|---|---|---|---|
AssemblyPhaseIsMandatory |
true | true/false | Player skips assembly (reduces strategy depth) | N/A |
AssemblyPhaseHasTimeLimit |
false | false or 30–120s | N/A | Time pressure reduces quality of decisions |
Visual/Audio Requirements
VFX Event Specifications
| Event | Visual Effect | Audio Cue | Duration |
|---|---|---|---|
| Node Completion | Node pulses with type-color glow (1.0x → 1.1x → 1.0x), emits 8–12 geometric particles (triangles/diamonds) in radial burst | Soft ascending chime (C5-E5-G5), low volume | ~600ms |
| Choice Made | Selected edge animates from dashed to solid (300ms); unselected edge fades to 20% opacity | Subtle "lock-in" percussive click | ~400ms |
| Loss Suffered | Screen flashes red at 30% opacity for 150ms; failed node icon cracks/dims permanently; screen transitions to Run Failure screen | Low thud + dissonant minor-2nd tone | ~300ms |
| Boss Defeated | Full-screen golden particle shower (64+ hexagonal particles); Boss node explodes into geometric shards reforming as victory badge | Triumphant rising major chord fanfare (1.5s) | ~2000ms |
| Run Victory | All past nodes illuminate sequentially bottom-to-top (80ms each) forming a completed-path glow; Boss transforms into trophy/star icon; golden vignette | Extended C-E-G-C chord sustain + chime cascade | ~2500ms |
| Run Failure | Screen desaturates over 500ms; node track cracks along failed node; fade to dark with red tinge | Descending minor tone + deep bell | ~1500ms |
Color Palette
| Node Type | Color | Hex | Icon |
|---|---|---|---|
| Combat | Crimson Red | #FF4A4A |
Sword |
| Event | Royal Purple | #9B59B6 |
Question mark |
| Shop | Gold | #FFD700 |
Coin |
| Boss | Golden Crown | #FF8C00 |
Crown |
| Locked/Future | Slate Gray | #4A5568 |
— |
| Completed | Dimmed type color | 50% brightness | Checkmark |
| Failed | Desaturated + Red X | — | Broken node |
Node Visual States
| State | Treatment |
|---|---|
| Current | Full opacity, type color, white 3px border, pulse animation (1.0x–1.05x, 1.5s loop), outer glow ring |
| Completed | 40% opacity, grayscale tint, no glow, checkmark overlay |
| Failed | 30% opacity, desaturated, crack texture, red X overlay |
| Future/Locked | 25% opacity, no edges visible |
| Future/Revealed | 70% opacity, type color, dashed interactive edges |
| Boss | Full opacity, 1.3x scale, crown icon, amber/gold particle aura (#FF8C00) |
Track Layout
- Vertical orientation, Boss at top (fixed beacon glow), Node 1 at bottom
- Current node centered in viewport; past nodes slide up and compress (0.9x scale per node above)
- Edges: 2px type-colored lines, dashed when active, solid when locked
- All particle shapes: triangles, diamonds, hexagons only — no organic curves
Audio Style
- Sounds: clean sine/triangle waves — digital-mathematical, not organic
- SFX duration: 100–400ms typical
- Ambient: C major chord drone/pad during track exploration
- Boss entry: dedicated boss music stinger
Accessibility
- Color + iconography always paired (color alone never conveys type)
- Loss flash 150ms at 30% opacity — accompanied by a brief screen shake; an accessibility toggle in Settings allows this flash to be replaced with a slow fade (500ms) for players with photosensitive concerns
- Boss Defeated particle shower (64+ hexagonal particles) — an accessibility toggle in Settings reduces particle count to 16 for players with visual sensitivity
- Audio cues have visual alternatives (state changes, screen flashes)
- High-contrast mode: node border brightens to 4px white
- Colorblind differentiation: Failed state uses a distinct shape treatment (jagged/broken frame outline) in addition to desaturated color + red X, so it is distinguishable from Completed without relying on color perception. Boss node aura uses golden-orange (#FF8C00) rather than crimson to differentiate from Combat node red (#FF4A4A); crown icon and particle aura provide additional differentiation.
- Node state opacity minimum: Future/Locked state uses minimum 25% opacity (not 15%) so it remains visible rather than appearing as blank space.
- Inventory access: Player can view (but not modify) inventory and Gold/Coin balance from the Node Map Screen at any time. Modification is restricted to Assembly Phase only. This enables informed node choice decisions without violating assembly-phase-only build modification.
- Ready button pulse: The pulsing animation on the "Ready" button until confirmed may trigger photosensitivity. An accessibility toggle in Settings allows the pulse animation to be replaced with a static highlighted state for players with photosensitive concerns.
UI Requirements
Node Map Screen
Layout: Full-screen vertical scrollable node track.
- Boss node fixed at top with persistent beacon glow.
- Current node centered vertically.
- Past nodes stacked above (dimmed, compressed 0.9x per node).
- Future nodes hidden below fold.
Node Card (per node):
- Size: ~80×80px base, Boss 1.3x (104×104px)
- Content: type icon (sword/question/coin/crown), node index number
- Border: 3px white on current, type-colored on revealed, none on locked
- Background: type color fill
Edge Display:
- Lines connecting current node to 2 revealed destinations
- Dashed while unchosen, solid after selection
- Type-colored
Run Progress HUD:
- Top-left: Run index badge ("Run #3")
- Top-right: Gold counter and Coin counter (displayed separately with distinct icons; tooltip on hover explains each)
Node Choice Overlay
Trigger: Appears when entering NodeReveal state after Assembly Phase.
Content:
- Title: "Choose Your Path" (or equivalent)
- Two node cards displayed horizontally, each showing node type + type icon
- Cards highlight on hover; selection locks on click
- "Locked in" confirmation animation on selection
Constraints:
- No third option visible
- Player cannot advance without choosing
- ESC/Cancel not supported — choice is mandatory
Assembly Phase Screen
Trigger: Auto-enters after any node resolves.
Content:
- Tower slots (current tower configs, 3 component slots each)
- Inventory grid (all owned components)
- Repository grid (all stored components)
- Next Node Preview: The 2 outgoing edge destinations from the completed node are displayed on-screen during Assembly Phase, showing each destination's node type and index. This allows the player to optimize their tower build based on known upcoming challenges — matching the "preparing and adapting" Player Fantasy.
- "Ready" button (bottom-right, large, pulsing until confirmed)
Interactions:
- Drag components between inventory and tower slots
- Click "Ready" to confirm and advance
- No time limit in default configuration (tunable)
Empty State:
- When inventory is empty: tower slots show placeholder silhouettes (dotted outline) with "Empty" label
- "Ready" button is still present and functional when inventory is empty — it pulses to indicate action is available
- Repository grid shows empty state with "No stored components" message
Run End Screen
Trigger: Appears on RunEnd state.
Variants:
- Victory: Golden theme, Boss defeated badge, Gold total, nodes cleared, "Return to Menu" button
- Failure: Desaturated/red theme, "Run Failed" message, furthest node reached, "Return to Menu" button
No retry button — runs are single-attempt
Interaction Constraints
- No back button during node choice
- No undo after node selection is confirmed
- Mandatory commitment confirmation: A "This path cannot be undone." message must appear in the Node Choice Overlay before the player can confirm. This is not optional flavor text.
- ESC does not cancel Assembly Phase (must click "Ready")
- Player can view inventory and Gold/Coin balance from the Node Map Screen at any time; player can only modify inventory and tower builds during Assembly Phase
- Next node types visible during Assembly Phase: The 2 outgoing edge destinations are displayed on the Assembly Phase screen before the player clicks Ready, enabling informed build optimization
Acceptance Criteria
Run Structure
- GIVEN a new run, WHEN the player starts, THEN a 10-node track is loaded from the active theme's fixed node-type sequence (Plain theme: Combat, Combat, Combat, Shop, Combat, Event, Combat, Shop, Combat, BossCombat), with Node 10 as BossCombat.
- GIVEN the player is at NodeReveal, WHEN they see the outgoing edges, THEN exactly 2 destination nodes are displayed with their
RunNodeTypeenum values visible. The full track (all 10 nodes) is visible from the run start. - GIVEN the player has selected a node edge, WHEN they confirm, THEN a modal dialog displays the text "This Path Cannot Be Undone" with only a "Confirm" button (ESC and clicking outside the modal have no effect); clicking Confirm locks the choice and transitions to
NodeTransition. Previously visited nodes remain in Completed state. - GIVEN the player has completed Node N (N < 10), WHEN they are on the Node Choice Overlay or any subsequent screen, THEN no UI element (back button, swipe gesture, keyboard shortcut) and no sequence of menu traversals allows navigation to Node N-1 or any previously completed node.
- GIVEN the player completes Node 10 (Boss), WHEN the run end resolves, THEN the run enters
RunEndstate and does not return to the node track or generate additional nodes.
Node Resolution
- GIVEN the player completes Combat node n, WHEN they win, THEN they receive Gold equal to
DRLevel.RewardGoldfor the linked level, plus component drops as defined by the level's component drop table. - GIVEN the player completes Event node, WHEN the event resolves, THEN the event's outcome modifiers (Gold delta, HP delta, buffs/debuffs) are reflected in the player's run state immediately, the Event UI is dismissed, and the state machine transitions to
AssemblyPhase. Transition timing is implementation-defined. - GIVEN the player completes Shop node, WHEN they exit the shop, THEN all purchases and sales are committed to the player's inventory and the state machine transitions to
AssemblyPhase. Transition timing is implementation-defined. - GIVEN the player opens a Shop node, WHEN they click "Leave" without making any purchases or sales, THEN no changes are made to the player's inventory or Gold, and the UI transitions to Assembly Phase.
- GIVEN the player completes Shop node 4, WHEN they later reach Shop node 8, THEN Shop node 8 functions normally; there is no special mitigation for consecutive shops. The second shop differs from the first only in its rarity tier offering (all tiers available vs. White/Green only).
Combat Loss
- GIVEN the player loses Combat at any node (including Node 10), WHEN the loss is recorded, THEN the run ends immediately in failure. The run's Gold, Coin, Inventory, and TowerConfig are not written to Progression; the in-memory run state is cleared; and the player is placed on the RunEnd (Failure) screen.
- GIVEN the player loses at the Boss (Node 10), WHEN Node 10 resolves, THEN
CombatNodeComponentfiresNodeCompleteEventArgswithCombatWon = false; noDRLevel.RewardGoldorBossBonusis added to run state; and the Procedure layer routes this toRunEnd(Failure). - GIVEN the player loses Combat at any node, WHEN
RecordRunEndis called withbossDefeated=false, THENrunStats.goldEarned == 0andrunStats.coinsEarnedreflects combat-internal Coin earned this run (Coin is not persisted to Progression — it is reset each combat).
Boss
- GIVEN the player defeats the Boss, WHEN Node 10 resolves, THEN they receive the Boss level's
DRLevel.RewardGoldplusBossBonus = 200gold. - GIVEN the player faces the Boss, WHEN the Boss spawns, THEN
BossEffectiveHp = DRLevel.BaseHp × 2^completedLoopCount, wherecompletedLoopCountis the number of completed boss cycles within the current Boss encounter.
Assembly Phase
- GIVEN the player is in Assembly Phase, WHEN the screen is displayed, THEN the Assembly Phase scene hierarchy contains two UI elements representing the outgoing edge destinations, each accessible via the UI framework's content API (not requiring screenshot comparison) and showing its node type and index.
- GIVEN the player is in Assembly Phase, WHEN they click the "Ready" button, THEN the Assembly Phase ends, the Node Choice Overlay appears, and the player selects from the 2 already-revealed destinations.
- GIVEN the player has 0 components in inventory, WHEN they enter Assembly Phase, THEN the "Ready" button is enabled and clicking it proceeds to the next node without modification.
- GIVEN a node resolves (Combat victory, Event completed, Shop exited), WHEN the resolution completes, THEN the Procedure layer receives the completion signal (via
NodeCompleteEventArgs,OnShopClosed, orOnEventResolved) and transitions the state machine toAssemblyPhasewithout requiring any player action. - GIVEN the player is in Assembly Phase, WHEN they have not yet clicked "Ready", THEN the player may re-enter and modify tower configurations freely. The "Ready" button is the only mandatory action to proceed.
Data Persistence
- GIVEN a completed run ending in victory, WHEN the run ends, THEN a subsequent read of
Progression.GetRunHistory()returns an entry containing the Gold total, nodesCompleted count, and BossDefeated flag for this run, and the player is returned to main menu. - GIVEN a new run starts, WHEN the player clicks "Start Run", THEN Gold is set to
DRRunConfig.StartGold, Coin is set toDRRunConfig.StartCoin, Inventory is cleared, and TowerConfig is set to default — all before the state machine transitions toNodeRevealfor Node 1.
Cross-System
- GIVEN the player completes a Combat node, WHEN they win, THEN
InventoryComponent.AddItemis called for each drop beforeRunStateAdvanceService.CompleteCurrentNode()is called, such that the inventory reflects the drops by the timeAssemblyPhasestate is entered. - GIVEN the player completes a Combat node, WHEN
NodeCompleteEventArgswithCombatWon = trueis dispatched, THEN the state machine transitions toAssemblyPhase. Transition timing is implementation-defined; the Assembly Phase state must be entered after the event is dispatched.
Open Questions
1. Should Event Nodes Appear in the First 3 Nodes?
Status: ✅ RESOLVED — Option [C] adopted: Events restricted to positions 4–8. Node sequence updated accordingly (Events only at node 6 in Plain theme).
2. Does the Player See the Full Track at Run Start?
Status: ✅ RESOLVED — Option [A] adopted: Full track visible from run start. Player Fantasy updated to reflect this. All nodes visible, current node highlighted, past nodes dimmed.
3. Blocked Systems Before Node System Implementation
Status: ✅ RESOLVED — All blocking GDDs have been completed:
- Shop System GDD (
design/gdd/shop.md) — Status: Designed.ShopContextcontract and buy/sell behavior are defined. - Event System GDD (
design/gdd/event-system.md) — Status: Designed.EventContextcontract and risk/reward resolution flow are defined. - Progression GDD (
design/gdd/progression.md) — Status: Designed.RecordRunEnd()andGetLifetimeStats()interfaces are defined.