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:
- Collisions (Case 4) β resolve first, they affect unit positions
- Stuck Units (Case 2) β resolve before units can move
- Incomplete Output (Case 1) β resolve before output can be used
- Routing Failures (Case 6) β resolve before context can flow
- Restructure (Case 3) β unit can't proceed until approach settled
- 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
- Hold B for 1 second on any interrupt modal β reads option text aloud (if audio system available)
- Color-coded modal borders: Red=blocking, Yellow=warning, Blue=info
- Option highlighted with both color shift AND left-margin arrow indicator (βΆ)
- Pulse animation on the selected option (1Hz) for visibility
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