Edge Case Protocols β€” Gamepad-First Design

Status: Draft v1.0 Agent: sandshrew (via opencode) Timestamp: 2026-05-11 Platform: Anbernic RG40XXV β€” 640Γ—480, D-pad + A/B/X/Y Game: Turn-based strategy, 36-hex grid, LangGraph-driven AI agent units


1. NODE PRODUCED INCOMPLETE OUTPUT

Trigger

Unit began work at a hex node but the LangGraph execution terminated before completion: timeout, LLM error, process interruption, or partial output with missing required sections.

What the Player Sees

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ⚠ INCOMPLETE OUTPUT                    Unit: HERON [3/4]   β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Node: Hex 14  "Market Analysis"                        β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ βœ“ SUMMARY       ...................................... β”‚  β”‚
β”‚  β”‚ βœ“ FINDINGS      ...................................... β”‚  β”‚
β”‚  β”‚ βœ— METHODOLOGY   [─── MISSING ───]                     β”‚  β”‚
β”‚  β”‚ βœ— DATA TABLE    [─── MISSING ───]                     β”‚  β”‚
β”‚  β”‚ βœ“ CITATIONS     ...................................... β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ Error: timeout after 30s β€” LLM connection lost         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  β–Ά Retry                    (cost: 1⚑)                     β”‚
β”‚    Salvage partial output                                   β”‚
β”‚    Skip node                                                β”‚
β”‚    Mark as blocked                                          β”‚
β”‚                                                              β”‚
β”‚  [A:Select] [B:Preview] [X:ErrorLog] [Y:QuickRetry]        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping

Button Action
D-pad β–²/β–Ό Navigate decision options
A Confirm selected option
B Toggle between summary view and full partial output
X Open error log / traceback (scrollable with D-pad, B to close)
Y Quick Retry β€” skips menu, immediately retries (shortcut)

State Changes

game_state.interrupts[].resolved = true
game_state.interrupt_type = null

IF retry:
  unit_state[unit_id].energy -= 1
  node_outputs[hex_id].status = "executing"
  node_outputs[hex_id].attempt_count += 1
  └─ triggers LangGraph re-execution

IF salvage:
  node_outputs[hex_id].status = "salvaged"
  node_outputs[hex_id].missing_sections = ["methodology", "data_table"]
  game_state.backlog.append({hex_id, missing_sections, unit_id})
  unit_state[unit_id].status = "moving"

IF skip:
  node_outputs[hex_id].status = "skipped"
  unit_state[unit_id].status = "moving"
  unit_state[unit_id].path.pop(0)  # remove this node from path

IF blocked:
  node_outputs[hex_id].status = "blocked"
  unit_state[unit_id].status = "idle"
  game_state.blocked_nodes.append(hex_id)

Decision Tree

INCOMPLETE OUTPUT
β”‚
β”œβ”€ RETRY ─────────────────────────────────────────────────────
β”‚  β”‚ cost: 1⚑
β”‚  β”‚
β”‚  β”œβ”€ SUCCESS β†’ normal output review flow
β”‚  β”‚
β”‚  └─ FAILURE AGAIN β†’ escalate to "CRITICAL FAILURE"
β”‚     β”‚ node_outputs[hex_id].status = "critical_failure"
β”‚     β”‚
β”‚     β”œβ”€ Force Retry (cost: 2⚑, higher timeout)
β”‚     β”œβ”€ Salvage (as above)
β”‚     β”œβ”€ Abandon (unit recalled, hex marked unreachable)
β”‚     └─ Delegate (flag for another unit type to attempt)
β”‚
β”œβ”€ SALVAGE ───────────────────────────────────────────────────
β”‚  Partial output accepted. Missing sections added to backlog.
β”‚  Backlog visible from pause menu or end-of-turn summary.
β”‚  Another unit can later fill missing sections via revisit (Case 5).
β”‚
β”œβ”€ SKIP ──────────────────────────────────────────────────────
β”‚  Node marked skipped. Zero output stored.
β”‚  Unit advances to next hex in path.
β”‚  Skipped nodes can be revisited later.
β”‚
└─ BLOCK ─────────────────────────────────────────────────────
   Node marked blocked. Unit stays on hex.
   Player must resolve from elsewhere (external resource, 
   another unit's context, or manual override).
   Blocked nodes visible as red outline on main map.

2. NODE PRODUCED OUTPUT BUT UNIT IS STUCK

Trigger

Unit completed work at current hex but cannot path to its next destination. Causes: edge blocked by obstacle, prerequisite hex not yet completed by another unit, unit type incompatible with next node type, path cost exceeds remaining energy.

What the Player Sees

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β›” UNIT STUCK                       Unit: ACE [7/8]        β”‚
β”‚                                                              β”‚
β”‚  Current: Hex 21 βœ“             Target: Hex 27              β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ Grid Preview (minimap) ──────────────────────────────┐  β”‚
β”‚  β”‚  21──22──23──24                                      β”‚  β”‚
β”‚  β”‚   β•² β•± β•² β•± β•²                                        β”‚  β”‚
β”‚  β”‚    25──26══27  ◄══ BLOCKED EDGE                      β”‚  β”‚
β”‚  β”‚   β•± β•² β•± β•² β•±    Reason: Hex 26 not completed          β”‚  β”‚
β”‚  β”‚  28──29──30                                          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  Reason: Path blocked β€” Hex 26 must be completed first       β”‚
β”‚  Alt path via 25β†’28β†’... cost: 4⚑ (unit has 3⚑)       β”‚
β”‚                                                              β”‚
β”‚  β–Ά Reroute manually                                         β”‚
β”‚    Auto-find alternate path      (cost: 1⚑)                β”‚
β”‚    Wait here (check next turn)                              β”‚
β”‚    Force through                 (cost: 2⚑, -1 integrity) β”‚
β”‚    Recall unit to base                                      β”‚
β”‚                                                              β”‚
β”‚  [A:Select] [B:Map] [X:Config] [Y:AutoPath]                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping

Button Action
D-pad β–²/β–Ό Navigate decision options
D-pad β—„/β–Ί When "Reroute manually" selected: cycle through alternate hex targets
A Confirm selected option
B Full grid map mode β€” player uses D-pad to explore grid freely, A selects destination
X View unit config: type, energy, special abilities, prerequisites needed
Y Quick Auto-Find β€” immediately runs pathfinding (shortcut)

State Changes

IF reroute:
  game_state.mode = "path_select"
  └─ player selects target hex on grid
  └─ on confirm: unit_state[unit_id].path[] = new_path
  └─ unit_state[unit_id].status = "moving"

IF auto_find:
  unit_state[unit_id].energy -= 1
  └─ A* search with unit constraints
  └─ SUCCESS: unit_state[unit_id].path[] = found_path
  └─ FAILURE: offer manual reroute or wait
  └─ unit_state[unit_id].status = "moving" or "waiting"

IF wait:
  unit_state[unit_id].status = "waiting"
  unit_state[unit_id].wait_reason = "path_blocked"
  game_state.waiting_units.append(unit_id)
  └─ end-of-turn: check each waiting unit
  └─ if blocker resolved: auto-proceed
  └─ if still blocked: increment wait_count

IF force:
  unit_state[unit_id].energy -= 2
  unit_state[unit_id].integrity -= 1
  hex_edges[blocked_edge].status = "bypassed"
  unit_state[unit_id].path[] = direct_to_target
  unit_state[unit_id].status = "moving"

IF recall:
  unit_state[unit_id].position = null
  unit_state[unit_id].status = "recalled"
  game_state.available_units.append(unit_id)
  game_state.units_on_grid.remove(unit_id)

Decision Tree

UNIT STUCK
β”‚
β”œβ”€ REROUTE MANUALLY ─────────────────────────────────────────
β”‚  Player enters grid map mode:
β”‚  1. D-pad to navigate hex grid
β”‚  2. Valid targets pulse green; invalid targets gray
β”‚  3. Path cost shown on hover: "Via 25β†’28β†’... (3⚑)"
β”‚  4. A to confirm destination
β”‚  5. If path valid β†’ new path set. If invalid β†’ error beep.
β”‚
β”œβ”€ AUTO-FIND ────────────────────────────────────────────────
β”‚  β”‚ cost: 1⚑
β”‚  β”‚
β”‚  β”œβ”€ PATH FOUND β†’ displayed as dotted line on grid, cost shown
β”‚  β”‚  Player confirms (A) or rejects (B) and retries manually
β”‚  β”‚
β”‚  └─ NO PATH FOUND β†’ message "No valid path exists"
β”‚     β”œβ”€ Wait (blockers may resolve)
β”‚     β”œβ”€ Force (pay premium)
β”‚     └─ Recall
β”‚
β”œβ”€ WAIT ─────────────────────────────────────────────────────
β”‚  Unit stays on current hex. Status: "waiting (path_blocked)".
β”‚  Each turn end: recheck edge conditions.
β”‚  Max wait: 3 turns. After 3 turns of no resolution β†’ 
β”‚  auto-escalate to force/recall prompt.
β”‚
β”œβ”€ FORCE THROUGH ────────────────────────────────────────────
β”‚  β”‚ cost: 2⚑ + -1 integrity
β”‚  β”‚ Bypassed edge marked with yellow outline on grid
β”‚  β”‚ If integrity drops to 0 β†’ unit breaks, must be repaired at base
β”‚  β”‚
β”‚  └─ Use sparingly. Forced edges can cause negative events.
β”‚
└─ RECALL ────────────────────────────────────────────────────
   Unit removed from grid entirely. Returns to base.
   Can be redeployed next turn.
   No penalty beyond lost turn.

3. PLAYER DISAGREES WITH OVERALL DIRECTION (RESTRUCTURE)

Trigger

Player reviews unit output and determines the entire approach is wrong β€” not fixable with line-item corrections. Unit needs a new directive to take a fundamentally different approach.

What the Player Sees

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ”„ RESTRUCTURE                     Unit: HERON [3/4]       β”‚
β”‚                                                              β”‚
β”‚  Current output summary:                                     β”‚
β”‚  "AI alignment risks analyzed through regulatory lens..."     β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Choose new approach:                                   β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ β–Ά [ANALYTICAL]  Systematic breakdown, correctness       β”‚  β”‚
β”‚  β”‚   [CREATIVE]    Unconventional, novel angles           β”‚  β”‚
β”‚  β”‚   [CONCISE]     Shortest useful output                 β”‚  β”‚
β”‚  β”‚   [THOROUGH]    Exhaustive, all edge cases             β”‚  β”‚
β”‚  β”‚   [SKEPTICAL]   Challenge assumptions, find gaps       β”‚  β”‚
β”‚  β”‚   [SYNTHETIC]   Cross-reference other nodes            β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ [X: Modifiers] [B: Keep current]                       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  Restructure history: 2 previous attempts for this node      β”‚
β”‚                                                              β”‚
β”‚  [A:Select] [B:Cancel] [X:Modifiers] [Y:PrevAttempts]      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Modifier Sub-Screen (X button)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  + MODIFIERS                        Unit: HERON [3/4]       β”‚
β”‚                                                              β”‚
β”‚  β–Ά Focus on: [security] [ethics] [long-term] [none]          β”‚
β”‚    Constrain: [budget < $10M] [time < 6mo] [none]            β”‚
β”‚    Ignore:    [regulations] [competitors] [none]              β”‚
β”‚                                                              β”‚
β”‚  Focus keywords change based on neighboring nodes' topics     β”‚
β”‚                                                              β”‚
β”‚  [A:Toggle] [β—„β–Ί:Cycle] [B:Done]                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping

Button Action
D-pad β–²/β–Ό Navigate approach archetypes
A Select highlighted approach
B Cancel β€” keep current output, dismiss
X Open modifier sub-screen
Y Cycle through previous restructure attempts for this node

Modifier sub-screen: | Button | Action | |--------|--------| | D-pad β–²/β–Ό | Navigate modifier categories | | D-pad β—„/β–Ί | Cycle through options within category | | A | Toggle modifier on/off | | B | Confirm modifiers and return to approach selection |

State Changes

node_outputs[hex_id].restructure_count += 1
node_outputs[hex_id].approach_directive = {
  archetype: "analytical" | "creative" | "concise" | "thorough" | "skeptical" | "synthetic",
  modifiers: {
    focus_on: ["security"],
    constrain: ["budget < 10M"],
    ignore: ["regulations"]
  }
}
node_outputs[hex_id].status = "restructuring"
node_outputs[hex_id].previous_attempts[] = {
  output: <prior_output>,
  directive: <prior_directive>,
  result: "rejected_by_player"
}
unit_state[unit_id].status = "rethinking"

IF energy spent on restructure:
  unit_state[unit_id].energy -= 1

Decision Tree

RESTRUCTURE
β”‚
β”œβ”€ SELECT ARCHETYPE (no modifiers) ──────────────────────────
β”‚  Directive written to node state.
β”‚  Unit re-executes node with archetype as system prompt prefix.
β”‚  Example prompt injection:
β”‚    "APPROACH: ANALYTICAL. Break down systematically. 
β”‚     Prioritize rigor and correctness. Original task: ..."
β”‚  Output replaces (or appends, player choice) previous output.
β”‚
β”œβ”€ SELECT ARCHETYPE + MODIFIERS ──────────────────────────────
β”‚  Both folded into directive.
β”‚  Example:
β”‚    "APPROACH: SKEPTICAL. Challenge every assumption.
β”‚     FOCUS ON: security. IGNORE: regulations.
β”‚     Original task: ..."
β”‚
β”œβ”€ CANCEL ────────────────────────────────────────────────────
β”‚  Unit keeps current output. Proceeds normally.
β”‚  No state changes except interrupt resolved.
β”‚
β”œβ”€ AFTER RESTRUCTURE EXECUTION ───────────────────────────────
β”‚  β”‚
β”‚  β”œβ”€ PLAYER ACCEPTS β†’ normal flow, node completed
β”‚  β”‚
β”‚  β”œβ”€ PLAYER STILL DISAGREES β†’ increment restructure_count
β”‚  β”‚  β”‚
β”‚  β”‚  β”œβ”€ restructure_count < 3 β†’ offer restructure again
β”‚  β”‚  β”‚
β”‚  β”‚  └─ restructure_count >= 3 β†’ offer escalation:
β”‚  β”‚     β”œβ”€ Mark for human review (external, off-grid)
β”‚  β”‚     β”œβ”€ Delegate to different unit type
β”‚  β”‚     β”œβ”€ Accept best attempt and move on
β”‚  β”‚     └─ Skip node entirely
β”‚  β”‚
β”‚  └─ PLAYER WANTS HYBRID β†’ combine best parts of multiple attempts
β”‚     (triggers a merge UI, D-pad to select sections from each attempt)

Archetype Definitions (injected into LangGraph prompt)

Archetype System Prompt Prefix
ANALYTICAL "Take a systematic, step-by-step approach. Break the problem into components. Prioritize correctness and logical rigor. Number your steps."
CREATIVE "Explore unconventional angles and novel connections. Prioritize originality and unexpected insights. Do not follow standard templates."
CONCISE "Produce the shortest useful output. One paragraph summary, max 5 bullet points. Eliminate all redundancy."
THOROUGH "Be exhaustive. Cover every edge case, every sub-topic, every implication. Prefer completeness over brevity."
SKEPTICAL "Challenge every assumption in the given context. Identify weaknesses, gaps, and failure modes. Play devil's advocate."
SYNTHETIC "Look for connections to other nodes' outputs. Combine and reconcile ideas from different sources. Seek unifying themes."

4. TWO UNITS COLLIDE ON SAME HEX

Trigger

Two units attempt to enter the same hex coordinate in the same turn. Detected during movement phase resolution.

What the Player Sees

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ⚑ COLLISION β€” Hex 18                  Turn 12              β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚  RANGER [Unit A]    β”‚  β”‚  TRIDENT [Unit B]   β”‚           β”‚
β”‚  β”‚  ⚑⚑⚑⚑⚑ 5/7      β”‚  β”‚  ⚑⚑⚑ 3/6        β”‚           β”‚
β”‚  β”‚  Priority: HIGH     β”‚  β”‚  Priority: MEDIUM   β”‚           β”‚
β”‚  β”‚  Arrived: 1st       β”‚  β”‚  Arrived: 2nd       β”‚           β”‚
β”‚  β”‚  Mission: Scout     β”‚  β”‚  Mission: Survey    β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β”‚                                                              β”‚
β”‚         β•²         β•±                                         β”‚
β”‚          β•²   ⚑   β•±     ◄── Both targeting Hex 18            β”‚
β”‚           β•²     β•±                                           β”‚
β”‚            HEX 18                                            β”‚
β”‚                                                              β”‚
β”‚  β–Ά Ranger enters β€” Trident redirected                        β”‚
β”‚    Trident enters β€” Ranger redirected                        β”‚
β”‚    Coexist (both enter β€” compatible unit types)              β”‚
β”‚    Ranger delays 1 turn                                      β”‚
β”‚    Trident delays 1 turn                                     β”‚
β”‚                                                              β”‚
β”‚  [A:Confirm] [B:Auto] [X:Stats] [Y:SwapSelection]           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping

Button Action
D-pad β–²/β–Ό Navigate resolution options
D-pad β—„/β–Ί Toggle preview: shows hex grid with proposed resolution outcome animated
A Confirm selected resolution
B Auto-resolve using priority rules
X Toggle detailed stat view for selected unit
Y Swap which unit is displayed as primary/secondary

State Changes

game_state.collisions.append({
  hex_id: 18,
  turn: 12,
  units: [unit_a_id, unit_b_id],
  resolution: <chosen_option>,
  timestamp: <game_clock>
})

IF unit A enters:
  hexes[18].occupant = unit_a_id
  unit_state[unit_a_id].position = 18
  unit_state[unit_a_id].status = "working"

  // Redirect Unit B
  nearest_free = find_nearest_adjacent_hex(18)
  unit_state[unit_b_id].position = nearest_free
  unit_state[unit_b_id].energy -= 1  # redirect cost
  unit_state[unit_b_id].status = "moving"
  unit_state[unit_b_id].redirected = true
  unit_state[unit_b_id].redirect_source = "collision_hex18"

IF coexist:
  hexes[18].occupants = [unit_a_id, unit_b_id]
  hexes[18].is_shared = true
  unit_state[unit_a_id].position = 18
  unit_state[unit_b_id].position = 18
  // Both units work on same node β€” outputs may interact
  node_outputs[18].collaborators = [unit_a_id, unit_b_id]

IF delay (e.g., Ranger delays):
  unit_state[unit_a_id].status = "waiting"
  unit_state[unit_a_id].wait_reason = "collision_delay"
  unit_state[unit_a_id].resume_position = 18
  unit_state[unit_b_id].position = 18
  unit_state[unit_b_id].status = "working"

Auto-Resolve Priority Rules

1. Player-directed unit > AI-autonomous unit
2. Higher mission priority > lower
3. Higher remaining energy > lower
4. Earlier arrival in turn order > later
5. Higher unit level > lower
6. Random coin flip (if all equal)

Decision Tree

COLLISION DETECTED
β”‚
β”œβ”€ PRIORITY: UNIT A ENTERS ──────────────────────────────────
β”‚  Unit B redirected to nearest adjacent unoccupied hex.
β”‚  If no adjacent hex free: search radius 2 hexes.
β”‚  If still no free hex (grid congested):
β”‚  └─ Unit B forced to wait in place (no movement this turn)
β”‚     status = "waiting_collision_overflow"
β”‚     Auto-resolves next turn when grid opens
β”‚
β”œβ”€ COEXIST ───────────────────────────────────────────────────
β”‚  β”‚ Only available if: both units share a compatible type pair
β”‚  β”‚ Compatible pairs defined in unit_type_compatibility table:
β”‚  β”‚   SCOUT + SURVEY = compatible (survey needs scout data)
β”‚  β”‚   SUPPORT + any = compatible (support unit augments)
β”‚  β”‚   CARRIER + LIGHT = compatible (carrier transports light)
β”‚  β”‚   Same triad (LAND/SEA/AIR) = generally compatible
β”‚  β”‚
β”‚  β”œβ”€ Compatible β†’ both enter, shared hex
β”‚  β”‚  Output: if both produce output, they are merged
β”‚  β”‚  or stacked with labels [Unit A] [Unit B]
β”‚  β”‚
β”‚  └─ Not compatible β†’ option grayed out, tooltip shows why
β”‚
β”œβ”€ DELAY ─────────────────────────────────────────────────────
β”‚  Selected unit stays at current position for 1 turn.
β”‚  Status: "waiting (collision)". 
β”‚  On next turn: automatically attempts hex entry again.
β”‚  If collision persists next turn (another unit beats it):
β”‚  └─ Escalate to priority resolution with penalty:
β”‚     delayed unit gets +1 priority boost per turn delayed
β”‚
└─ AUTO-RESOLVE (B button) ─────────────────────────────────
   System applies priority rules and selects resolution.
   Result displayed for 2 seconds with "Auto: Ranger enters" message.
   Player can override with B again within 2 seconds.

Redirect Logic Pseudocode

function resolve_collision(winner, loser, hex_id):
    adjacent = get_ring(hex_id, radius=1) - occupied_hexes
    if adjacent.not_empty:
        loser.position = nearest(adjacent, loser.current)
        loser.energy -= 1
    else:
        extended = get_ring(hex_id, radius=2) - occupied_hexes
        if extended.not_empty:
            loser.position = nearest(extended, loser.current)
            loser.energy -= 2
        else:
            loser.status = "waiting_collision_overflow"
            loser.stored_target = hex_id

5. UNIT NEEDS TO REVISIT A COMPLETED NODE

Trigger

Player discovers that a previously completed node should be re-examined. New context (another unit's output, a player insight, a game event) makes the original output incomplete, outdated, or contradictory.

What the Player Sees

Trigger From Context Review Screen

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“‹ UNIT OUTPUT β€” HEX 22              Unit: HERON [3/4]     β”‚
β”‚                                                              β”‚
β”‚  "Market segmentation shows 3 tiers...                       β”‚
β”‚   However, this contradicts Hex 14's finding that..."        β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ Cross-Reference Alert ────────────────────────────────┐  β”‚
β”‚  β”‚ ⚠ Hex 14 output conflicts with this finding.           β”‚  β”‚
β”‚  β”‚    Hex 14 completed Turn 7 by ACE.                     β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ [Y: Flag for Revisit]  [β—„β–Ί: See Hex 14 output]       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  [A:Accept] [B:Back] [X:Correct] [Y:FlagRevisit]           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Revisit Confirmation Modal

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ” REVISIT HEX 14?                   Unit: RANGER [2/4]    β”‚
β”‚                                                              β”‚
β”‚  Original output (Turn 7, ACE):                              β”‚
β”‚  "Market is bifurcated into premium and budget tiers..."      β”‚
β”‚                                                              β”‚
β”‚  New context (Turn 12, HERON Hex 22):                        β”‚
β”‚  "Three distinct tiers identified: premium, mid, budget..."   β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ Revisit Mode ─────────────────────────────────────────┐ β”‚
β”‚  β”‚                                                        β”‚ β”‚
β”‚  β”‚ β–Ά APPEND β€” Add new output, keep original               β”‚ β”‚
β”‚  β”‚   OVERWRITE β€” Replace entire output (old archived)     β”‚ β”‚
β”‚  β”‚                                                        β”‚ β”‚
β”‚  β”‚ Cost: 1⚑  |  Distance from unit: 3 hexes              β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                              β”‚
β”‚  [A:Confirm] [B:Cancel] [X:FullOriginal] [Y:FullContext]   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping (Review Screen)

Button Action
D-pad Scroll output text
A Accept output and proceed
B Back to grid
X Enter correction mode (for line-item corrections β€” not this case)
Y Flag current cross-reference for revisit

Button Mapping (Revisit Modal)

Button Action
D-pad β–²/β–Ό Select mode: Append vs Overwrite
A Confirm revisit with selected mode
B Cancel revisit
X View full original output (scrollable, B to close)
Y View full new context that triggered the revisit

State Changes

// Revisit queued
game_state.revisit_queue.append({
  hex_id: 14,
  reason: "conflict_with_hex_22",
  source_unit: "HERON",
  source_hex: 22,
  turn_triggered: 12,
  mode: "append" | "overwrite"
})

// Unit dispatched for revisit
unit_state[revisit_unit_id].status = "revisiting"
unit_state[revisit_unit_id].path[] = calculate_path(current, hex_id=14)

// After revisit execution
node_outputs[14].revision_count += 1
node_outputs[14].status = "revisited"

IF append:
  node_outputs[14].appended_sections.append({
    turn: 12,
    source_unit: "RANGER",
    trigger_context: "Hex 22 conflict",
    content: <new_output>,
    original_preserved: true
  })

IF overwrite:
  node_history[14].append({
    turn: 7,
    author: "ACE",
    content: <old_output>,
    overwritten_by: "RANGER",
    overwrite_turn: 12
  })
  node_outputs[14].output = <new_output>
  node_outputs[14].status = "completed_revised"

Decision Tree

REVISIT TRIGGERED
β”‚
β”œβ”€ APPEND MODE ──────────────────────────────────────────────
β”‚  β”‚
β”‚  β”œβ”€ Unit routes to hex, executes with context injection:
β”‚  β”‚  "Previous finding: [old output]. New context: [new info].
β”‚  β”‚   Reconcile or extend. Do NOT repeat what was already found."
β”‚  β”‚
β”‚  β”œβ”€ Output appended with header:
β”‚  β”‚  "═══ REVISITED: Turn 12 ═══
β”‚  β”‚   Source: Hex 22 (HERON)
β”‚  β”‚   Reason: Contradiction in market tier count
β”‚  β”‚   ═══════════════════════════"
β”‚  β”‚
β”‚  └─ Node stays marked "completed" with revision badge
β”‚     Display: Hex 14 [βœ“Β²] β€” checkmark with superscript revision count
β”‚
β”œβ”€ OVERWRITE MODE ───────────────────────────────────────────
β”‚  β”‚
β”‚  β”œβ”€ Old output archived to node_history.
β”‚  β”‚
β”‚  β”œβ”€ Unit executes fresh, with full new context.
β”‚  β”‚  System prompt: "This node was previously completed 
β”‚  β”‚   but is being revised. New directive: [new context].
β”‚  β”‚   Produce a complete replacement output."
β”‚  β”‚
β”‚  └─ Node status β†’ "completed_revised"
β”‚     Display: Hex 14 [↻] β€” circular arrow icon
β”‚
β”œβ”€ CANCEL ───────────────────────────────────────────────────
β”‚  Revisit flag removed from queue.
β”‚  No state changes. Player can revisit later manually.
β”‚
└─ UNIT CAN'T PATH TO HEX ───────────────────────────────────
   Triggers Edge Case 2 (stuck protocol).
   Player can assign a different unit that's closer.

Revisit Queue UI (Pause Menu > Revisit Queue)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ” REVISIT QUEUE                      1 pending            β”‚
β”‚                                                              β”‚
β”‚  β–Ά Hex 14  "Market Analysis"                                β”‚
β”‚     Conflict with Hex 22. Triggered Turn 12.                 β”‚
β”‚     Assign unit: [RANGER] [HERON] [any available]            β”‚
β”‚                                                              β”‚
β”‚  [A:Assign] [B:Back] [X:Remove] [Y:ViewContext]            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

6. CONTEXT ROUTING FAILS

Trigger

The curation agent (which routes relevant context from one unit's output to another unit's node) produces nothing useful. Either: no relevant context found, context is too vague, pointer destination is invalid, or context is empty after filtering.

What the Player Sees

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“‘ ROUTING FAILURE                   Unit: TRIDENT [5/6]   β”‚
β”‚                                                              β”‚
β”‚  Source: Hex 14 (HERON) β”€β”€βœ•β”€β”€ ?                              β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ Failure Details ──────────────────────────────────────┐  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ Agent: CURATOR v2.1                                    β”‚  β”‚
β”‚  β”‚ Error: No relevant context found                       β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ Raw output: "The market analysis from Hex 14 does not  β”‚  β”‚
β”‚  β”‚ contain information relevant to the technical          β”‚  β”‚
β”‚  β”‚ architecture task at Hex 22."                          β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ Filter threshold: 0.6  |  Results returned: 0          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  β–Ά Route manually (pick destination)                         β”‚
β”‚    Broaden search              (cost: 1⚑)                  β”‚
β”‚    Store as orphan context                                   β”‚
β”‚    Discard β€” proceed without routing                         β”‚
β”‚    Apply to self (TRIDENT gains insight)                     β”‚
β”‚                                                              β”‚
β”‚  [A:Select] [B:Dismiss] [X:RawOutput] [Y:ManualRoute]      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Button Mapping

Button Action
D-pad β–²/β–Ό Navigate options
A Confirm selected option
B Dismiss β€” leave unit idle, routing unresolved (can revisit)
X View full raw curation agent output (scrollable, B to close)
Y Quick manual route β€” immediately enter grid selection mode

State Changes

curation_state[unit_id].status = "failed"
curation_state[unit_id].failure_count += 1
curation_state[unit_id].last_failure = {
  turn: <current>,
  source_hex: 14,
  reason: "no_relevant_context",
  raw_output: <curator_response>,
  threshold: 0.6,
  results_count: 0
}

IF manual_route:
  game_state.mode = "manual_routing"
  └─ player selects hex on grid
  └─ context_routes.append({from: 14, to: <selected>, context: <payload>})

IF broaden_search:
  unit_state[unit_id].energy -= 1
  curation_state[unit_id].search_params.threshold -= 0.2
  curation_state[unit_id].search_params.k += 3
  └─ re-run curation with relaxed params
  └─ if still fails β†’ escalate

IF store_orphan:
  game_state.orphan_contexts.append({
    source_hex: 14,
    source_unit: "HERON",
    context_payload: <output_summary>,
    created_turn: <current>,
    tags: ["market", "analysis"],
    status: "unrouted"
  })

IF discard:
  curation_state[unit_id].status = "skipped"
  unit_state[unit_id].status = "working"  # proceeds without routing

IF self_apply:
  unit_state[unit_id].insight_tokens += 1
  unit_state[unit_id].status = "integrating"
  curation_state[unit_id].status = "self_applied"

Decision Tree

ROUTING FAILURE
β”‚
β”œβ”€ ROUTE MANUALLY ──────────────────────────────────────────
β”‚  β”‚
β”‚  β”œβ”€ Grid map mode: valid hexes pulse blue, invalid gray
β”‚  β”‚  Tooltip on hover: "Hex 18: Technical Design (ACE)" 
β”‚  β”‚
β”‚  β”œβ”€ Player selects destination hex
β”‚  β”‚  β”‚
β”‚  β”‚  β”œβ”€ HEX EXISTS AND IS VALID β†’ context routed. 
β”‚  β”‚  β”‚  Unit proceeds. Route logged.
β”‚  β”‚  β”‚
β”‚  β”‚  └─ HEX IS INVALID (unreachable, wrong type) β†’ 
β”‚  β”‚     error beep + brief reason. Retry selection.
β”‚  β”‚
β”‚  └─ Player presses B β†’ back to options
β”‚
β”œβ”€ BROADEN SEARCH ──────────────────────────────────────────
β”‚  β”‚ cost: 1⚑
β”‚  β”‚ Curation agent re-runs with:
β”‚  β”‚   threshold -= 0.2 (more permissive)
β”‚  β”‚   k += 3 (more results)
β”‚  β”‚
β”‚  β”œβ”€ RESULTS FOUND β†’ display top 3 destinations
β”‚  β”‚  Player picks one (D-pad + A) or selects "none of these"
β”‚  β”‚
β”‚  └─ STILL NO RESULTS β†’ escalate:
β”‚     curation_state[unit_id].status = "degraded"
β”‚     β”‚
β”‚     β”œβ”€ Force route (player picks any hex, context routed 
β”‚     β”‚  even if irrelevant β€” flagged "low_confidence")
β”‚     β”‚
β”‚     β”œβ”€ Store as orphan (see below)
β”‚     β”‚
β”‚     └─ Discard (see below)
β”‚
β”œβ”€ STORE AS ORPHAN ─────────────────────────────────────────
β”‚  Context saved with metadata.
β”‚  Viewable from: Pause Menu > Dangling Contexts
β”‚  Orphan contexts list:
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β”‚ 🧩 DANGLING CONTEXTS          2 total               β”‚
β”‚  β”‚                                                     β”‚
β”‚  β”‚ β–Ά Hex 14β†’?  "Market tiers"      Turn 12  [Route]   β”‚
β”‚  β”‚   Hex 8β†’?   "SWOT analysis"     Turn 9   [Route]   β”‚
β”‚  β”‚                                                     β”‚
β”‚  β”‚ [A:Route] [X:View] [Y:Discard] [B:Back]            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  Player can route orphan contexts at any time.
β”‚  Max orphans: 10. Exceeding β†’ oldest auto-discarded.
β”‚
β”œβ”€ DISCARD ─────────────────────────────────────────────────
β”‚  Context discarded. Unit proceeds as if no routing needed.
β”‚  curation_state[unit_id].status = "skipped"
β”‚  No penalty. But lost opportunity for cross-pollination.
β”‚
└─ APPLY TO SELF ──────────────────────────────────────────
   Context absorbed by current unit instead of routed.
   Unit gains +1 insight_token (temporary stat boost).
   Effect: next output gets quality bonus.
   Useful when no destination makes sense but context is valuable.

7. SAVE/LOAD WITH ACTIVE INTERRUPTS

Trigger

Player saves the game while one or more interrupts are active (units paused, waiting for player decisions from Cases 1-6). On reload, the game must resume exactly at the interrupt point with full context.

What the Player Sees

Save Screen (with active interrupts)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ’Ύ SAVE GAME                            Turn 14             β”‚
β”‚                                                              β”‚
β”‚  ⚠ 2 ACTIVE INTERRUPTS β€” will be saved with game state       β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ Interrupt Queue ──────────────────────────────────────┐  β”‚
β”‚  β”‚ 1. HERON - Incomplete Output (Hex 8)     [awaiting]    β”‚  β”‚
β”‚  β”‚ 2. ACE - Collision Resolution (Hex 18)   [awaiting]    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  β–Ά Slot 1: Turn 14 β€” Hex 8/36  β€” 3 units active  [!2]       β”‚
β”‚    Slot 2: Turn 12 β€” Hex 6/36  β€” 2 units active             β”‚
β”‚    Slot 3: Turn 8  β€” Hex 4/36  β€” 2 units active             β”‚
β”‚    Slot 4: EMPTY                                             β”‚
β”‚                                                              β”‚
β”‚  [A:Save] [B:Back] [X:Details]                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Load Screen

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“‚ LOAD GAME                                                 β”‚
β”‚                                                              β”‚
β”‚  β–Ά Slot 1: Turn 14 β€” 3 units β€” [!] 2 interrupts              β”‚
β”‚    Slot 2: Turn 12 β€” 2 units                                 β”‚
β”‚    Slot 3: Turn 8  β€” 2 units                                 β”‚
β”‚    Slot 4: EMPTY                                             β”‚
β”‚                                                              β”‚
β”‚  [A:Load] [B:Back] [X:SlotDetails] [Y:Delete]               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Load Splash / Resume

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                                              β”‚
β”‚                                                              β”‚
β”‚               ═══════════════════════════                    β”‚
β”‚               TURN 14 β€” RESUMING                             β”‚
β”‚               ═══════════════════════════                    β”‚
β”‚                                                              β”‚
β”‚              β–‘β–‘β–‘β–‘ Loading grid... β–‘β–‘β–‘β–‘                       β”‚
β”‚              β–‘β–‘β–‘β–‘ Restoring units... β–‘β–‘β–‘β–‘                    β”‚
β”‚              β–‘β–‘β–‘β–‘ Resolving interrupt queue... β–‘β–‘β–‘β–‘          β”‚
β”‚                                                              β”‚
β”‚              [!] 2 pending decisions                         β”‚
β”‚                                                              β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Camera pans to: HERON @ Hex 8                          β”‚  β”‚
β”‚  β”‚ Interrupt: INCOMPLETE OUTPUT                           β”‚  β”‚
β”‚  β”‚ (Press A to begin / B to overview map)                 β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Resuming the Interrupt (identical to original interrupt UI)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ⚠ INCOMPLETE OUTPUT         Interrupt 1/2   Unit: HERON    β”‚
β”‚                                                              β”‚
β”‚  [Same UI as Case 1 β€” fully restored state]                  β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Node: Hex 8  "Competitor Analysis"                     β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ βœ“ EXECUTIVE SUMMARY .................................. β”‚  β”‚
β”‚  β”‚ βœ— COMPETITOR MATRIX  [─── MISSING ───]                β”‚  β”‚
β”‚  β”‚ βœ— THREAT ASSESSMENT  [─── MISSING ───]                β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ Error: LLM timeout after 30s                           β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  [A:Select] [B:Preview] [X:ErrorLog] [Y:Context]           β”‚
β”‚                                                              β”‚
β”‚  ═══════════════════════════════════════════════════════════  β”‚
β”‚  [!] 1 more interrupt after this one                         β”‚
β”‚  (B for map overview / skip to next)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

HUD Elements During Interrupt Resolution

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Turn 14    [!2]    ⚑12/18    🏴3 units                     β”‚
β”‚                                                              β”‚
β”‚  [Main game area / interrupt modal]                          β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The [!2] badge in the HUD shows remaining unresolved interrupts. Pulses gently when all interrupts resolved to signal "ready to advance turn."

Button Mapping (Save Screen)

Button Action
D-pad β–²/β–Ό Select save slot
A Save to selected slot
B Back to game
X View slot details: turn, hex progress, unit count

Button Mapping (Load Screen)

Button Action
D-pad β–²/β–Ό Select save slot
A Load selected slot
B Back to title
X View slot details
Y Delete selected slot (with confirmation prompt)

Button Mapping (Resume Interrupt β€” same as original + one addition)

Same as the original edge case's button mapping, plus:

Button Action
Y Toggle context recap β€” shows "What happened before this interrupt" summary for memory refresh

Context Recap Screen (Y during interrupt resume)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“‹ SESSION RECAP                      Turn 14, Hex 8        β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€ What happened this turn ──────────────────────────────┐  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ β€’ HERON moved Hex 5β†’Hex 8, began "Competitor Analysis" β”‚  β”‚
β”‚  β”‚ β€’ ACE completed Hex 12, output routed to RANGER        β”‚  β”‚
β”‚  β”‚ β€’ RANGER moved Hex 10β†’Hex 14                           β”‚  β”‚
β”‚  β”‚ β€’ CURATOR attempted routing Hex 14β†’Hex 22 (failed)     β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ ═══════════════════════════════════════════════════════ β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚ β€’ Hex 8: HERON started Competitor Analysis             β”‚  β”‚
β”‚  β”‚   └─ TIMEOUT after 30s. Now awaiting your decision.    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  [B:Close] [D-pad:Scroll]                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

State Changes (Save)

save_data = {
  version: "1.0",
  turn: 14,
  timestamp: <iso8601>,

  // Full game state snapshot
  game_state: { ...entire_game_state },
  unit_states: { ...all_unit_states },
  node_outputs: { ...all_node_outputs },
  hexes: { ...all_hex_states },
  curation_state: { ...all_curation_states },

  // INTERRUPT STACK β€” critical for resume
  interrupt_stack: [
    {
      interrupt_id: "int_001",
      interrupt_type: "incomplete_output",    // matches Case 1-6
      unit_id: "HERON",
      hex_id: 8,
      turn_triggered: 14,

      // Full UI state restoration
      ui_state: {
        modal_type: "incomplete_output",
        selected_option_index: 0,             // which option was highlighted
        scroll_position: 0,                   // if output was scrolled
        subview: null,                        // if modifier screen / detail view was open
        preview_open: false                   // B-toggled preview state
      },

      // Case-specific payload
      payload: {
        completed_sections: ["executive_summary"],
        missing_sections: ["competitor_matrix", "threat_assessment"],
        error_type: "timeout",
        error_detail: "LLM connection lost after 30s",
        attempt_count: 1,
        output_so_far: <partial_output_text>
      }
    },
    {
      interrupt_id: "int_002",
      interrupt_type: "collision",
      unit_id: "ACE",
      hex_id: 18,
      turn_triggered: 14,
      ui_state: { ... },
      payload: {
        colliding_units: ["ACE", "TRIDENT"],
        unit_a_stats: { ... },
        unit_b_stats: { ... }
      }
    }
  ],

  // For save slot display
  interrupt_count: 2,
  units_active: 3,
  hexes_completed: 8,
  total_hexes: 36
}

State Changes (Load)

// 1. Restore game state
game_state ← save_data.game_state
unit_states ← save_data.unit_states
node_outputs ← save_data.node_outputs
hexes ← save_data.hexes
curation_state ← save_data.curation_state

// 2. Restore interrupt stack
interrupt_stack ← save_data.interrupt_stack

// 3. If interrupt stack is non-empty:
game_state.mode = "interrupt_resolution"
current_interrupt ← interrupt_stack[0]

// 4. Restore UI to exact saved state
ui.modal_type = current_interrupt.ui_state.modal_type
ui.selected_option_index = current_interrupt.ui_state.selected_option_index
ui.scroll_position = current_interrupt.ui_state.scroll_position
ui.subview = current_interrupt.ui_state.subview

// 5. Camera pan to interrupt unit's hex
camera.target = hexes[current_interrupt.hex_id].position
camera.animate(1.5s)

// 6. Display interrupt resolution UI

Decision Tree

SAVE WITH ACTIVE INTERRUPTS
β”‚
β”œβ”€ Interrupts serialized in interrupt_stack exactly as-is
β”‚  Save file tagged with interrupt_count
β”‚  Save slot shows [!N] badge
β”‚
└─ No special handling needed β€” interrupts are just state

LOAD WITH ACTIVE INTERRUPTS
β”‚
β”œβ”€ NO INTERRUPTS β†’ normal load, game resumes in default state
β”‚
β”œβ”€ INTERRUPTS PRESENT β†’ enter "interrupt resolution mode"
β”‚  β”‚
β”‚  β”œβ”€ CAMERA PANS to first interrupted unit
β”‚  β”œβ”€ INTERRUPT MODAL appears exactly as saved
β”‚  β”œβ”€ PLAYER RESOLVES first interrupt
β”‚  β”‚  β”‚
β”‚  β”‚  β”œβ”€ Resolution may spawn NEW interrupts β†’ added to end of stack
β”‚  β”‚  └─ interrupt_stack.shift() β€” remove resolved interrupt
β”‚  β”‚
β”‚  β”œβ”€ IF MORE INTERRUPTS β†’ camera pans to next
β”‚  β”‚  "Interrupt 2/2" indicator updates
β”‚  β”‚
β”‚  └─ STACK EMPTY β†’ game_state.mode = "normal"
β”‚     "Ready to advance" message + turn advance button enabled
β”‚
β”œβ”€ PLAYER WANTS MAP OVERVIEW (B during interrupt) ───────────
β”‚  Interrupt stays queued. Player can:
β”‚  β€’ Browse full grid, read other nodes' outputs
β”‚  β€’ Check unit states, energy, paths
β”‚  β€’ Cannot advance turn until interrupts resolved
β”‚  β€’ "Resolve Interrupts" prompt remains in HUD
β”‚  β€’ Press Y to jump back to current interrupt
β”‚
└─ CRASH DURING INTERRUPT ───────────────────────────────────
   Auto-save triggered on every interrupt spawn.
   On next load: last autosave with interrupt stack intact.
   Autosave file: separate slot, marked "[AUTO] Turn 14 β€” [!2]"
   Max 3 autosave slots, rotating.

CROSS-CUTTING CONCERNS

Interrupt Priority Order

When multiple interrupts are queued, they resolve in this order:

  1. Collisions (Case 4) β€” resolve first, they affect unit positions
  2. Stuck Units (Case 2) β€” resolve before units can move
  3. Incomplete Output (Case 1) β€” resolve before output can be used
  4. Routing Failures (Case 6) β€” resolve before context can flow
  5. Restructure (Case 3) β€” unit can't proceed until approach settled
  6. Revisit Queue (Case 5) β€” lowest priority, can be deferred

This order ensures dependencies are resolved: positions β†’ movement β†’ output β†’ routing β†’ quality.

Energy Economy

Action Energy Cost
Retry (Case 1) 1⚑
Auto-find path (Case 2) 1⚑
Force through (Case 2) 2⚑ + -1 integrity
Restructure (Case 3) 1⚑
Redirect from collision (Case 4) 1-2⚑ (based on distance)
Revisit node (Case 5) 1⚑ + travel
Broaden search (Case 6) 1⚑
Recall unit (Case 2) 0⚑ (lost turn only)
Self-apply context (Case 6) 0⚑ (gain instead)
Skip node (Case 1) 0⚑

Visual Language Conventions

Symbol Meaning
⚠ Incomplete/warning
β›” Blocked/stuck
πŸ”„ Restructure
⚑ Collision / Energy
πŸ” Revisit
πŸ“‘ Routing
πŸ’Ύ Save
[!N] N pending interrupts
[βœ“] Completed node
[βœ“Β²] Completed node with revisions
[↻] Revised (overwritten) node
[βœ—] Skipped node
[🚫] Blocked node

LangGraph State Key Reference

game_state
β”œβ”€β”€ mode: "normal" | "interrupt_resolution" | "path_select" | "manual_routing"
β”œβ”€β”€ turn: int
β”œβ”€β”€ interrupts[]: list of pending interrupt references
β”œβ”€β”€ interrupt_stack[]: serialized interrupt states (for save/load)
β”œβ”€β”€ collisions[]: collision event log
β”œβ”€β”€ routing_failures[]: routing failure log
β”œβ”€β”€ blocked_nodes[]: hex ids of blocked nodes
β”œβ”€β”€ blocked_edges[]: edge ids of blocked edges
β”œβ”€β”€ waiting_units[]: unit ids waiting on conditions
β”œβ”€β”€ revisit_queue[]: pending revisit tasks
β”œβ”€β”€ orphan_contexts[]: unrouted context payloads
β”œβ”€β”€ available_units[]: recalled idle units
└── backlog[]: incomplete sections needing attention

unit_state[unit_id]
β”œβ”€β”€ position: hex_id | null
β”œβ”€β”€ status: "idle" | "moving" | "working" | "waiting" | "rethinking" 
β”‚          | "revisiting" | "recalled" | "integrating"
β”œβ”€β”€ energy: int
β”œβ”€β”€ integrity: int
β”œβ”€β”€ insight_tokens: int
β”œβ”€β”€ path[]: hex_id sequence
β”œβ”€β”€ redirected: bool
β”œβ”€β”€ redirect_source: string
β”œβ”€β”€ wait_reason: string
└── resume_position: hex_id

node_outputs[hex_id]
β”œβ”€β”€ status: "empty" | "executing" | "completed" | "salvaged" | "skipped"
β”‚          | "blocked" | "restructuring" | "revisiting" | "completed_revised"
β”‚          | "critical_failure"
β”œβ”€β”€ output: string
β”œβ”€β”€ missing_sections: string[]
β”œβ”€β”€ attempt_count: int
β”œβ”€β”€ restructure_count: int
β”œβ”€β”€ revision_count: int
β”œβ”€β”€ approach_directive: {archetype, modifiers}
β”œβ”€β”€ previous_attempts[]: {output, directive, result}
β”œβ”€β”€ appended_sections[]: {turn, source_unit, content}
β”œβ”€β”€ collaborators: unit_id[]
└── status (legacy, use top-level status)

node_history[hex_id][]
β”œβ”€β”€ turn: int
β”œβ”€β”€ author: unit_id
β”œβ”€β”€ content: string
β”œβ”€β”€ overwritten_by: unit_id
└── overwrite_turn: int

curation_state[unit_id]
β”œβ”€β”€ status: "idle" | "running" | "completed" | "failed" | "degraded"
β”‚          | "skipped" | "self_applied"
β”œβ”€β”€ failure_count: int
β”œβ”€β”€ search_params: {threshold, k}
└── last_failure: {turn, source_hex, reason, raw_output}

save_data
β”œβ”€β”€ version: string
β”œβ”€β”€ turn: int
β”œβ”€β”€ timestamp: iso8601
β”œβ”€β”€ game_state: {...}
β”œβ”€β”€ unit_states: {...}
β”œβ”€β”€ node_outputs: {...}
β”œβ”€β”€ hexes: {...}
β”œβ”€β”€ curation_state: {...}
β”œβ”€β”€ interrupt_stack: [{interrupt_id, type, unit_id, hex_id, ui_state, payload}]
└── interrupt_count: int

IMPLEMENTATION NOTES

Interrupt Dispatch Pattern

When any edge case trigger fires:

function trigger_interrupt(type, unit_id, hex_id, payload):
    interrupt = {
        interrupt_id: generate_id(),
        interrupt_type: type,
        unit_id: unit_id,
        hex_id: hex_id,
        turn_triggered: game_state.turn,
        ui_state: capture_ui_state(),
        payload: payload
    }

    game_state.interrupt_stack.push(interrupt)
    game_state.mode = "interrupt_resolution"

    // Autosave on interrupt trigger
    autosave()

    // Camera pan to affected hex
    camera.pan_to(hexes[hex_id].position)

    // Render appropriate modal based on interrupt_type
    render_interrupt_modal(interrupt)

Controller Input Buffer

During interrupt resolution, all non-interrupt inputs are buffered: - Turn advance button (Start) is disabled - Unit selection is disabled (except for map overview mode) - Only interrupt modal inputs + map overview (B) are processed

Accessibility


Document version 1.0. Sandshrew via opencode. Last updated: 2026-05-11. Target container: d3-tui-pi-teams-proto @ /workcell/llm-wiki/wiki/research/edge-case-protocols.md