From 25b8f17268568df6577e7aea9c9be17514389ff5 Mon Sep 17 00:00:00 2001 From: ewood Date: Sun, 15 Feb 2026 16:11:53 -0500 Subject: [PATCH 1/2] Enhance validation and documentation for game winner logic in transitions and prompts --- .../nodes/extract-instructions/prompts.ts | 52 +++++++++++++------ .../nodes/extract-instructions/validators.ts | 14 ++--- .../nodes/extract-transitions/prompts.ts | 8 ++- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts index 4a4e45e..f7542d2 100644 --- a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts +++ b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts @@ -133,11 +133,16 @@ Rules & Guidance: - Runtime will automatically compute game.winningPlayers from these flags - Example stateChanges: * Single winner: "set winning player's isGameWinner to true" - * Tie/multiple winners: "set both/all winning players' isGameWinner to true" - * No winner/draw: "leave all isGameWinner flags false (default)" + * Tie/multiple winners: "set ALL players tied for the win isGameWinner to true" + * No winner/abandoned game: "explicitly set ALL players' isGameWinner to false" - Can be set before gameEnded (e.g., player meets win condition mid-game) - - MUST be set on all paths leading to the "finished" phase (except no-winner scenarios) - - ⚠️ Validation will fail if any path to "finished" doesn't set isGameWinner appropriately + - ⚠️ CRITICAL: MUST be set on ALL paths to "finished" (including no-winner scenarios) + - ⚠️ Validation requires at least ONE transition sets isGameWinner (even if setting to false) + * **DETECTION RULE**: For ANY transition with toPhase == "finished": + - Your stateChanges array MUST include determining winner and setting isGameWinner + - This applies REGARDLESS of what the humanSummary says + - Even if humanSummary doesn't mention isGameWinner, YOU MUST INCLUDE IT + - Check the transitions artifact: if a transition goes to "finished", it needs winner logic * If game has multiple ending scenarios, EACH ending transition must handle both fields 4. Mechanics Descriptions (Key Guidance): @@ -464,25 +469,42 @@ Common variable patterns: - Example: {{ "op": "set", "path": "game.gameEnded", "value": true }} - Missing this will cause validation failure: "No transition sets game.gameEnded=true" -**players.{{{{playerId}}}}.isGameWinner** (boolean) - Set to true for winning player(s): -- ⚠️ CRITICAL: MUST be set on ALL paths to the "finished" phase (except no-winner scenarios) -- Set to true for each player who won the game -- Leave as false (default) for players who didn't win or in draw scenarios +**players.{{{{playerId}}}}.isGameWinner** (boolean) - MUST be set in game-ending transitions: +- ⚠️ CRITICAL: At least ONE transition MUST set isGameWinner (validation will fail otherwise) +- ⚠️ CRITICAL: Set isGameWinner on ALL paths leading to "finished" phase (including no-winner + scenarios) - Runtime automatically computes game.winningPlayers array from these flags - Can be set in same transition as gameEnded OR in an earlier transition -- Examples: - * Single winner: {{ "op": "set", "path": "players.{{{{winnerId}}}}.isGameWinner", "value": true }} - * Multiple winners (tie): Two ops - {{ "op": "set", "path": "players.{{{{player1Id}}}}.isGameWinner", "value": true }} and {{ "op": "set", "path": "players.{{{{player2Id}}}}.isGameWinner", "value": true }} - * No winner (draw/abandoned): No operations needed - all flags remain false -- Missing this will cause validation failure: "Path [phases] does not set isGameWinner" +- **DO NOT** leave isGameWinner unset - validation requires explicit set operations - If game has multiple ending scenarios, EACH ending transition must set isGameWinner appropriately -**Example: Complete game-ending transition stateDelta (sets BOTH required fields)**: +**Complete Examples (game-ending transitions with BOTH required fields):** + +Single Winner: {{ "stateDelta": [ {{ "op": "set", "path": "players.{{{{winnerId}}}}.isGameWinner", "value": true }}, {{ "op": "set", "path": "game.gameEnded", "value": true }}, - {{ "op": "set", "path": "game.publicMessage", "value": "Game Over! {{{{winnerName}}}} wins!" }} + {{ "op": "set", "path": "game.publicMessage", "value": "{{{{winnerName}}}} wins!" }} + ] +}} + +Tie (Multiple Winners - ALL tied players set to true): +{{ + "stateDelta": [ + {{ "op": "set", "path": "players.{{{{player1Id}}}}.isGameWinner", "value": true }}, + {{ "op": "set", "path": "players.{{{{player2Id}}}}.isGameWinner", "value": true }}, + {{ "op": "set", "path": "game.gameEnded", "value": true }}, + {{ "op": "set", "path": "game.publicMessage", "value": "It's a tie! Both players win!" }} + ] +}} + +No Winner (Abandoned/Stalemate - explicitly set ALL to false): +{{ + "stateDelta": [ + {{ "op": "setForAllPlayers", "field": "isGameWinner", "value": false }}, + {{ "op": "set", "path": "game.gameEnded", "value": true }}, + {{ "op": "set", "path": "game.publicMessage", "value": "Game ended with no winner." }} ] }} diff --git a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/validators.ts b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/validators.ts index 0c0821f..8d56939 100644 --- a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/validators.ts +++ b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/validators.ts @@ -993,8 +993,8 @@ export async function validateGameCompletion( ); } - // Check 3: All terminal paths set isGameWinner somewhere along the path - // Note: We don't require isGameWinner to be set for draw/no-winner scenarios + // Check 3: At least one terminal path sets isGameWinner to true (winning scenario) + // Note: No-winner scenarios should explicitly set isGameWinner=false (not leave unset) const terminalPaths = graph.getTerminalPaths(); if (terminalPaths.length === 0) { @@ -1010,12 +1010,14 @@ export async function validateGameCompletion( } } - // Warn if no paths set isGameWinner (might be intentional for draw-only games) + // Check if at least one path sets isGameWinner to true (winning scenario) + // Note: Paths that set all players to false (no-winner scenarios) still count as setting the field if (!hasWinningPath) { errors.push( - 'No path to "finished" sets players.*.isGameWinner. ' + - 'If your game has winners, at least one ending path must set isGameWinner=true for winning players. ' + - 'If this is a draw-only game (no winners), you can ignore this warning.' + 'No path to "finished" sets players.*.isGameWinner to true. ' + + 'At least one ending path must set isGameWinner=true for winning players. ' + + 'For no-winner scenarios (abandoned/stalemate), explicitly set isGameWinner=false for all players. ' + + 'Do not leave isGameWinner unset - validation requires explicit set operations.' ); } } diff --git a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-transitions/prompts.ts b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-transitions/prompts.ts index 40b66c8..ef15226 100644 --- a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-transitions/prompts.ts +++ b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-transitions/prompts.ts @@ -62,7 +62,13 @@ Don't create phases that only exist to trigger one automatic transition. Merge t - ❌ Wrong: phase_a → [trivial transition] → phase_b → [does actual work] → phase_c - ✅ Right: phase_a → [does all work] → phase_c -### 5. Precondition Hints (for executor synthesis) +### 5. Game-Ending Transitions MUST Set Winner + +⚠️ **CRITICAL**: Every path to "finished" must explicitly set the game winner. Any transition that determines winners MUST include "set isGameWinner" guidance in its \`humanSummary\` or validation will fail. + +Example: "After round 3, determine winner by closest guess, set isGameWinner=true for winner and false for others, then end game" + +### 6. Precondition Hints (for executor synthesis) When writing \`explain\` text for preconditionHints, follow these rules so executor can synthesize valid JsonLogic: ✅ Use wildcard for player array checks: "players[*].actionRequired == false" From 7db87ef0144a7637b7c8ee08d114b1251cca33ef Mon Sep 17 00:00:00 2001 From: ewood Date: Sun, 15 Feb 2026 16:19:33 -0500 Subject: [PATCH 2/2] Refine validation rules for setting isGameWinner in finished game transitions --- .../nodes/extract-instructions/prompts.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts index f7542d2..0041138 100644 --- a/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts +++ b/src/ai/simulate/graphs/spec-processing-graph/nodes/extract-instructions/prompts.ts @@ -136,13 +136,8 @@ Rules & Guidance: * Tie/multiple winners: "set ALL players tied for the win isGameWinner to true" * No winner/abandoned game: "explicitly set ALL players' isGameWinner to false" - Can be set before gameEnded (e.g., player meets win condition mid-game) - - ⚠️ CRITICAL: MUST be set on ALL paths to "finished" (including no-winner scenarios) - - ⚠️ Validation requires at least ONE transition sets isGameWinner (even if setting to false) - * **DETECTION RULE**: For ANY transition with toPhase == "finished": - - Your stateChanges array MUST include determining winner and setting isGameWinner - - This applies REGARDLESS of what the humanSummary says - - Even if humanSummary doesn't mention isGameWinner, YOU MUST INCLUDE IT - - Check the transitions artifact: if a transition goes to "finished", it needs winner logic + - MUST be set on all paths leading to the "finished" phase (except no-winner scenarios) + - ⚠️ Validation will fail if any path to "finished" doesn't set isGameWinner appropriately * If game has multiple ending scenarios, EACH ending transition must handle both fields 4. Mechanics Descriptions (Key Guidance):