Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ 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
Expand Down Expand Up @@ -464,25 +464,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." }}
]
}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,8 +1008,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) {
Expand All @@ -1025,12 +1025,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.'
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down