geometry-tower-defense/design/gdd/gdd-cross-review-2026-04-29...

208 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Cross-GDD Review Report
> **Date**: 2026-04-29
> **Reviewer**: Consistency Agent + Game Design Holism Agent
> **GDDs Reviewed**: 5 (node-system.md, shop.md, tower-assembly.md, event-system.md, progression.md)
> **Systems Covered**: Node System, Shop System, Tower Assembly, Event System, Progression
---
## Consistency Issues
### Blocking (must resolve before architecture)
🔴 **[C1] Rule contradiction: "no partial rewards on loss" vs. Progression accepting gold on loss**
- **Documents**: `node-system.md` vs `progression.md`
- **Node System** (Edge Cases): "Any combat loss: Run ends in failure immediately. No partial rewards are awarded."
- **Progression** (SR2 + AC): `RecordRunEnd()` is called on every run end (win or loss). On loss: `totalRunsStarted++`, `totalGoldEarned += goldEarned`, `furthestNodeReached` updates, but NO unlock evaluation.
- **Contradiction**: "No partial rewards" implies gold is NOT awarded/recorded on loss. Progression explicitly expects `goldEarned` on loss runs.
- **Resolution needed**: Clarify the intended design. Two options:
- (A) Loss runs do NOT record gold in LifetimeStats → remove `totalGoldEarned += goldEarned` from loss path in Progression AC
- (B) Loss runs DO record gold stats (only unlocks are withheld) → Node System's "no partial rewards" must be clarified to mean "no unlocks" not "no gold tracking"
---
🔴 **[C2] One-directional dependency: Tower Assembly ↔ Progression**
- **Documents**: `tower-assembly.md` vs `progression.md`
- **Tower Assembly** (Dependencies): "Progression — May read aggregate tower assembly stats across runs. Pending Progression GDD."
- **Progression** (Dependencies): Lists Node System and Shop System as upstream. Does NOT list Tower Assembly.
- **Impact**: If Tower Assembly wanted to persist aggregate stats (e.g., "total towers assembled lifetime"), no interface contract exists. Progression has no `RecordTowerAssemblyStats()` method.
- **Resolution needed**: Either (A) Progression adds Tower Assembly as a soft upstream dependency with a defined read interface, or (B) Tower Assembly's reference to Progression is removed/clarified as speculative.
---
### Warnings
⚠️ **[C3] Bidirectionality gap: Node System → Tower Assembly (soft vs. hard mismatch)**
- **Documents**: `node-system.md` vs `tower-assembly.md`
- Node System marks Tower Assembly as **Soft/provisional** in its Dependencies.
- Tower Assembly marks Node System as **Hard** upstream.
- This is a directionality mismatch — the softer designation should flow one way consistently.
- **Not blocking**: Tower Assembly's treatment of Node System as hard is likely correct; Node System's soft marking may be residual uncertainty from before Tower Assembly was designed.
⚠️ **[C4] Shop System GDD status in Node System is stale**
- **Documents**: `node-system.md` (Open Questions #3)
- Node System marks Shop System GDD as "blocking — interface contract pending alignment."
- However, `shop.md` is now **Designed** (complete design exists).
- Node System's Open Question #3 flag is stale.
- **Resolution**: Node System Open Question #3 should be resolved now that Shop GDD exists.
⚠️ **[C5] Tuning Knob ownership: MaxPlayerGold shared across 3 GDDs**
- **Documents**: `shop.md`, `event-system.md`, `progression.md`
- All three reference `MaxPlayerGold = 9999` consistently — no value conflict.
- However, no single GDD formally declares itself the **owner** of this constant.
- Entity registry correctly sources it from `shop.md`.
- **Recommendation**: Document in Shop GDD that it is the authoritative owner of `MaxPlayerGold`.
---
## Game Design Issues
### Blocking
🔴 **[G1] Exponential boss HP vs. linear player power — indefinite gap creates a hard wall**
- **Documents**: `node-system.md` + `tower-assembly.md`
- `BossEffectiveHp = BaseHp × 2^(completedLoopCount)` — exponential in boss cycles survived
- Player tower stats: `statValue[i] = baseValue + perLevel × i` — linear, capped at 5 levels
- After ~57 boss cycles, boss HP doubles so fast that even max-rarity max-level towers cannot deal enough damage before the boss outscales
- The game becomes mathematically unbeatable at high loop counts regardless of build quality
- **This is not a difficulty curve — it is a hard wall**
- **Resolution needed**: Add a boss cycle cap, or add a player power catch-up mechanic (e.g., each boss cycle also grants a temporary attack buff, or boss HP scaling changes to logarithmic rather than exponential). Alternatively, redefine "VictoryType" to not involve looping, removing the exponential scaling trigger.
---
🔴 **[G2] Run End Screen cannot display gold earned this run — undefined data flow**
- **Documents**: `node-system.md` + `progression.md`
- Node System fires `RunEnd` → calls `Progression.RecordRunEnd(runStats)`
- Progression updates `LifetimeStats.totalGoldEarned += goldEarned`
- But `OnUnlockedEventArgs` carries only `UnlockResult[]` — NOT the run's gold amount
- Run End Victory screen should show "Gold earned this run" — but no interface provides this data
- **Resolution needed**: Either (A) `UnlockedEventArgs` carries a `goldEarnedThisRun` field, or (B) Node System provides gold data directly to Run End screen (bypass Progression), or (C) Run End screen queries `Progression.GetLastRunGold()` (add this method)
---
🔴 **[G3] Undefined behavior: does RecordRunEnd fire on loss runs?**
- **Documents**: `node-system.md` vs `progression.md`
- Progression SR2: "`RecordRunEnd()` is called on every run end (win or loss)"
- Node System Edge Case: "Any combat loss: Run ends in failure immediately" — no mention of calling `RecordRunEnd`
- Progression AC explicitly covers loss runs (stats update, no unlocks)
- But if Node System never calls `RecordRunEnd` on loss, Progression's loss-path AC is unreachable
- **Resolution needed**: Clarify: does Node System call `RecordRunEnd` on loss? If yes — Node System GDD must document this. If no — Progression's loss-run AC is unreachable and must be revised.
---
### Warnings
⚠️ **[G4] Unbounded gold accumulation — no sink between runs**
- **Documents**: `node-system.md` + `shop.md`
- Per full winning run: ~600 (combat) + 300 (boss level) + 200 (boss bonus) = ~1100 gold
- With `MaxPlayerGold = 9999`, a player needs ~9 wins to cap out
- Once at cap, excess gold from combat is discarded — additional survival is not rewarded
- No sink between runs: no repair, no permanent upgrades, no sacrifice mechanic
- **Risk**: Short optimized runs may be more rewarding than long successful ones; endgame economy becomes meaningless
- **Recommendation**: Consider a meta-gold sink (permanent repair station, unlock purchases, or cosmetic unlocks) to recycle late-game gold
⚠️ **[G5] Multiple systems claim to be the primary progression loop**
- **Documents**: All 5 GDDs
- Node System: "tactician executing a plan" — navigates the run
- Tower Assembly: "puzzle solver, cleverness" — builds power
- Shop: "tactical urgency, deliberate investment" — resource decisions
- Event: "narrative surprise, meaningful stakes" — variety
- Progression: "relentless collection" — meta motivation
- **Risk**: Without a declared primary loop, players optimize the wrong thing
- **Recommendation**: Explicitly designate Tower Assembly as the core tactical loop; Node System as the structural frame; Shop/Event as the decision points; Progression as the meta-reward
⚠️ **[G6] Component Tag stacking — potential dominant strategy**
- **Documents**: `tower-assembly.md`
- R7: No compatibility constraints between component types
- Tags aggregate with stack counts merging across 3 components
- Optimal strategy: collect 3 copies of the best tag, stack it
- **Risk**: The "puzzle" of tower assembly may have a trivially discoverable dominant solution
- **Recommendation**: Consider adding anti-synergy rules (e.g., same tag on 3 components reduces effectiveness) or explicit trade-offs to maintain build diversity
⚠️ **[G7] Lossy economy (sell ~50% of buy) discourages experimentation**
- **Documents**: `shop.md`
- Sell price = midpoint of `[MinPrice, MaxPrice]` ≈ 50% of average buy
- Repeated buy-sell cycles destroy gold
- Players hoard components rather than experiment
- **Risk**: Reduces strategic depth; players avoid build diversity
- **Recommendation**: Consider a higher sell ratio (e.g., 6070%) or a component enhancement sink to make exploration more affordable
⚠️ **[G8] Event rewards use same rarity budget as shop — no distinct reward tier**
- **Documents**: `event-system.md` + `shop.md` (OQ5)
- `AddRandomComps` uses `InventoryGenerationComponent` with identical rarity budget to shop
- Events offer no higher-tier rewards than shop can provide
- **Risk**: Events feel mathematically equivalent to additional shop visits; "genuine dilemma" fantasy may be undermined
- **Recommendation**: Consider a separate event rarity budget (e.g., events drop 1 tier lower than equivalent shop purchase) to give events a distinct identity
⚠️ **[G9] Assembly Phase has 5 simultaneous information panels**
- **Documents**: `tower-assembly.md`
- During Assembly Phase: Inventory Grid + Tower Slots + Assembled Towers Panel + Combat Roster + Next Node Preview
- **Risk**: Cognitive overload for new players
- **Recommendation**: Consider a tabbed or sequenced interface rather than all panels visible simultaneously
---
## Cross-System Scenario Issues
**Scenario: Player completes a winning run — full RunEnd chain**
### Steps:
1. **Node System**: Combat victory at Boss node → fires `NodeCompleteEventArgs` with `CombatWon=true`
2. **Node System**: Calls `Progression.RecordRunEnd(runStats)` with `{goldEarned, nodesCompleted=10, bossDefeated=true, ...}`
3. **Progression**: Updates `LifetimeStats` → evaluates unlocks → fires `UnlockedEventArgs` with `UnlockResult[]`
4. **UI / Run End Screen**: Receives `OnUnlockedEventArgs` → shows toast for new unlocks
### Issues found:
🔴 **G2 (already flagged above)**: Run End screen cannot display gold earned this run
- Step 3: `UnlockedEventArgs` carries no `goldEarned` field
- Run End Victory screen should show gold earned — no data path defined
🔴 **G3 (already flagged above)**: Ambiguous whether loss runs call `RecordRunEnd`
- If loss path does not call `RecordRunEnd`, steps 23 never occur on loss
⚠️ **Scenario: Player reaches Boss but loses**
- Node System fires `RunEnd` with `bossDefeated=false`
- Does `RecordRunEnd` fire? (G3 ambiguity)
- If yes: LifetimeStats records `furthestNodeReached=9`, no unlocks
- If no: Partial run progress is lost, player gets no credit for reaching node 9
⚠️ **Scenario: Multiple unlocks fire simultaneously**
- Step 3: `UnlockResult[]` can contain multiple items
- Step 4: Toast popup shows all — good
- But no priority ordering if unlocks are from different categories (e.g., Difficulty + Theme simultaneously)
- **Info**: Not a blocker; worth documenting expected ordering
---
## GDDs Flagged for Revision
| GDD | Reason | Type | Priority |
|-----|--------|------|----------|
| `node-system.md` | C1/C3/C4: Stale Open Question #3, ambiguous loss behavior, "no partial rewards" contradicts Progression's loss-path AC | Consistency + Design Theory | **High** |
| `progression.md` | C1: Loss-run gold recording contradicts Node System's "no partial rewards"; G3: loss run behavior undefined | Consistency + Design Theory | **High** |
| `tower-assembly.md` | C2: Progression dependency is orphaned (one-directional); G6: Tag stacking dominant strategy risk | Consistency + Design Theory | Medium |
---
## Verdict: **CONCERNS**
Three blocking issues must be resolved before architecture begins:
1. **C1**: "No partial rewards" vs. Progression loss-path gold — design decision required
2. **G1**: Exponential boss HP vs. linear player power — hard wall makes boss unbeatable after ~cycle 57
3. **G2/G3**: Undefined data flow for Run End screen gold display + ambiguous loss run behavior
Warnings (G4G9) are advisory and should be addressed before implementation but do not block architecture.
---
## Recommended Actions
1. **C1**: Decide: do loss runs record gold in LifetimeStats? Update both Node System and Progression GDDs accordingly
2. **G1**: Redesign boss scaling formula — change from exponential to logarithmic, or add player catch-up mechanic, or cap boss cycles
3. **G2**: Add `goldEarnedThisRun` to `UnlockedEventArgs`, OR have Node System provide gold directly to Run End screen
4. **G3**: Clarify whether Node System calls `RecordRunEnd` on loss. Update Progression SR2 and AC to match.
5. **C2**: Either add Tower Assembly as soft upstream to Progression (with interface), or remove the speculative reference from Tower Assembly GDD
6. **C4**: Resolve Node System Open Question #3 now that Shop GDD is complete