Move goal-scored logic to behavior system with faceoff positioning

- Add isGoalScored flag to GameState for behavior tree decision-making
- Update SkaterBehavior to skate to center ice (0,0) when goal scored
- Update GoalieBehavior to return to center of goal when goal scored
- Remove update loop freeze from GameScene, now handled by behaviors
- Update CLAUDE.md with comprehensive subsystem documentation

All player logic now contained within the behavior system instead of
GameScene update loop, improving separation of concerns and making
post-goal behavior more realistic (players skate to faceoff positions).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pierre Wessman 2025-10-06 07:24:35 +02:00
parent 29885d1992
commit 581574fa1c
5 changed files with 403 additions and 56 deletions

427
CLAUDE.md
View File

@ -37,74 +37,397 @@ pnpm run preview
``` ```
src/ src/
config/ config/
constants.ts # All game constants (rink dimensions, colors, speeds) constants.ts # All game constants (rink dimensions, colors, speeds, AI parameters)
game/ game/
main.ts # Phaser game initialization and config main.ts # Phaser game initialization and config
GameScene.ts # Main game scene (rendering, game loop) GameScene.ts # Main game scene (rendering, game loop, physics, event handling)
Goal.ts # Goal structure with physics bodies Goal.ts # Goal structure with physics bodies and scoring detection
entities/ # Player and Puck classes (planned) entities/
systems/ # BehaviorTree, PositioningSystem, PuckSystem (planned) Player.ts # Player entity with physics, movement, and detection systems
Puck.ts # Puck entity with physics and state management
systems/
BehaviorTree.ts # Main behavior tree system for AI decision-making
BehaviorNodes.ts # Base classes (Selector, Sequence, Condition, Action)
behaviors/
SkaterBehavior.ts # Top-level skater AI (context selector)
GoalieBehavior.ts # Goalie-specific AI
TransitionBehavior.ts # Loose puck / neutral zone behavior
offensive/
PuckCarrierBehavior.ts # Shoot/carry/evade decisions
OffensiveSupportBehavior.ts # Support when teammate has puck (planned)
defensive/
DefensiveBehavior.ts # Chase and pressure opponent
types/
game.ts # TypeScript types and interfaces
utils/
coordinates.ts # Game ↔ screen coordinate conversion
math.ts # Distance, angle utilities
``` ```
### Implementation Phases (from PLAN.md) ### Implementation Status
The project follows a phased approach: **✓ Completed Phases:**
1. **Phase 1** (✓): Environment setup, rink rendering - **Phase 1**: Environment setup, rink rendering
2. **Phase 2** (Next): Player entities (12 players: 10 skaters + 2 goalies) with smooth movement - **Phase 2**: Player entities with smooth acceleration/deceleration and rotation
3. **Phase 3**: Puck entity and possession mechanics - **Phase 3**: Puck physics and possession mechanics
4. **Phase 4**: Behavior tree for puck carrier decisions (shoot/pass/carry) - **Phase 4**: Puck carrier behavior (shoot/carry/evade)
5. **Phase 5**: Team offensive positioning system - **Phase 6**: Basic defensive behavior (chase puck carrier)
6. **Phase 6**: Defensive behaviors and pressure mechanics - **Phase 7**: Basic goalie AI (track puck position)
7. **Phase 7**: Goalie AI - **Phase 8**: Goal detection, scoring system, pause-aware reset timers
8. **Phase 8**: Game flow (goals, faceoffs, scoring)
9. **Phase 9**: Polish and tuning
### Key Systems (Planned) **🚧 In Progress / Partial:**
- **Phase 5**: Offensive support (placeholder only)
- Tackle mechanics (implemented but needs tuning)
- Puck reception (implemented with skill-based probability)
**Behavior Trees**: Decision-making engine that runs every tick **📋 Planned:**
- Evaluates game state (possession, positions, threats) - Passing AI (identify and execute passes)
- Weights actions by player attributes (Hockey IQ, Skill, Speed) - Advanced offensive positioning (heat maps, formations)
- Outputs: move, pass, shoot, or defensive actions - Faceoffs and game flow states
- Save mechanics for goalies
**Tactical Positioning**: Heat map-based positioning ---
- Different formations based on game situation (offense/defense)
- Players move toward ideal positions modified by:
- Puck location
- Teammate spacing
- Opponent positions
- Player stamina/speed
**Puck Movement**: ## Core Systems Documentation
- Pass success = f(passer skill, distance, pressure, receiver skill)
- Shots use trajectory calculation with goalie save probability
- Smooth interpolation for visual feedback
## Technical Details ### 1. Behavior Tree System
- **Framework**: Phaser 3.90.0 (with Arcade Physics) **Location:** [src/systems/BehaviorTree.ts](src/systems/BehaviorTree.ts), [src/systems/BehaviorNodes.ts](src/systems/BehaviorNodes.ts)
**Purpose:** AI decision-making engine that runs at 60 FPS for each player.
**Architecture:**
- **BehaviorNode**: Abstract base class for all nodes
- **Selector**: OR logic - tries children until one succeeds
- **Sequence**: AND logic - all children must succeed
- **Condition**: Boolean predicate functions
- **Action**: Leaf nodes that return PlayerAction
**Decision Tree Structure:**
```
Root Selector
├─ Goalie? → GoalieBehavior
└─ Skater? → SkaterBehavior
├─ Has puck? → PuckCarrierBehavior (shoot/carry/evade)
├─ Teammate has puck? → OffensiveSupportBehavior (planned)
├─ Opponent has puck? → DefensiveBehavior (chase)
└─ Loose puck → TransitionBehavior (chase loose puck)
```
**Key Files:**
- [BehaviorTree.ts:26-60](src/systems/BehaviorTree.ts#L26-L60): Main tree evaluation
- [SkaterBehavior.ts:21-39](src/systems/behaviors/SkaterBehavior.ts#L21-L39): Context-based selector
---
### 2. Player Entity System
**Location:** [src/entities/Player.ts](src/entities/Player.ts)
**Purpose:** Player physics, movement, rotation, and nearby player detection.
**Key Features:**
**Movement System** ([Player.ts:186-286](src/entities/Player.ts#L186-L286)):
- Smooth acceleration with ease-out curve (PLAYER_ACCELERATION)
- Speed-dependent rotation (faster = wider turns)
- Deceleration when near target or after goals
- Stop threshold to prevent jittering (MOVEMENT_STOP_THRESHOLD)
**Player Detection System** ([Player.ts:324-387](src/entities/Player.ts#L324-L387)):
- Physics-based circular sensor (NEAR_PLAYER_DETECTION_RADIUS = 4m)
- Field-of-view filtering (NEAR_PLAYER_DETECTION_ANGLE = 180°)
- Maintains sets: `nearbyPlayers`, `nearbyOpponents`, `nearbyTeammates`
- Methods: `getDetectedOpponents()`, `getDetectedTeammates()`
**Tackle System** ([Player.ts:290-312](src/entities/Player.ts#L290-L312)):
- Cooldown timer (TACKLE_COOLDOWN = 1000ms)
- Fall duration when tackled (TACKLE_FALL_DURATION = 500ms)
- Speed requirement (TACKLE_MIN_SPEED = 2 m/s)
- Angle-based effectiveness modifiers
**Player Attributes** ([types/game.ts:27-33](src/types/game.ts#L27-L33)):
- `speed`: Movement speed (0-100) → m/s via SPEED_SCALE_FACTOR
- `skill`: Pass/shot accuracy, decision quality
- `tackling`: Tackle success probability
- `balance`: Resist being tackled
- `handling`: Puck reception and control
---
### 3. Puck Entity System
**Location:** [src/entities/Puck.ts](src/entities/Puck.ts)
**Purpose:** Puck physics, state management, and possession tracking.
**Puck States** ([types/game.ts:22](src/types/game.ts#L22)):
- `loose`: Free on ice, physics-driven
- `carried`: Held by player (carrier ID tracked)
- `passing`: In flight between players (future)
- `shot`: Shot toward goal
**Physics Configuration** ([Puck.ts:36-56](src/entities/Puck.ts#L36-L56)):
- Circular collision body (PUCK_RADIUS = 0.2m)
- Max velocity: MAX_PUCK_VELOCITY = 50 m/s
- Drag (ice friction): PUCK_DRAG = 200 pixels/s²
- Bounce coefficient: PUCK_BOUNCE = 0.6 (loses energy)
- World bounds collision enabled
**Key Methods:**
- `setCarrier(playerId, team)`: Assign possession
- `setLoose()`: Release to physics simulation
- `update()`: Sync game coordinates with physics body
---
### 4. Puck Carrier Behavior
**Location:** [src/systems/behaviors/offensive/PuckCarrierBehavior.ts](src/systems/behaviors/offensive/PuckCarrierBehavior.ts)
**Purpose:** Tactical decisions when player has possession.
**Decision Priority:**
1. **Evade** if opponent threatens head-on ([PuckCarrierBehavior.ts:104-254](src/systems/behaviors/offensive/PuckCarrierBehavior.ts#L104-L254))
2. **Shoot** if good opportunity ([PuckCarrierBehavior.ts:262-308](src/systems/behaviors/offensive/PuckCarrierBehavior.ts#L262-L308))
3. **Carry** toward opponent's net (default)
**Evasion System:**
- Uses physics-based player detection (NEAR_PLAYER_DETECTION_RADIUS)
- Checks if opponent is within 60° of goal direction
- Maintains consistent evasion direction per opponent (stored in `activeEvasions` map)
- Evasion angle: ±60° (EVASION_ANGLE) from goal direction
- Clears evasion when opponent leaves detection zone
**Shooting Logic:**
- Must be in front of goal (not behind)
- Distance ≤ SHOOTING_RANGE (10m)
- Angle-based accuracy:
- Close range (≤3m): 120° shooting angle allowed
- Normal range: 60° shooting angle required
- Aims at center of net (targetY = 0)
**Debug Visualization** (when DEBUG = true):
- Green line to goal
- Red circle around threats being evaded
- Yellow arrow showing evasion direction
- Orange circles for detected opponents
---
### 5. GameScene System
**Location:** [src/game/GameScene.ts](src/game/GameScene.ts)
**Purpose:** Main game loop, physics management, collision detection, event handling.
**Key Responsibilities:**
**Physics Setup:**
- Player-player collisions (including tackle mechanics)
- Player-puck overlaps (pickup and reception)
- Puck-goal post collisions with bounce
- Detection sensor overlaps (nearby player tracking)
**Puck Management:**
- Carrier positioning: puck stays 1m in front of carrier
- Pickup radius: PUCK_PICKUP_RADIUS = 1.5m
- Reception skill checks (PUCK_RECEPTION_BASE_CHANCE)
- Speed-based reception difficulty
**Shooting System:**
- Shot speed: SHOT_SPEED = 30 m/s (or 0.6× for close range)
- Post bounce: reduces speed and adds random angle variation
- Goal detection via overlap with scoring zone
**Tackle System:**
- Cooldown enforcement (TACKLE_COOLDOWN)
- Angle-based effectiveness (head-on = 100%, side = 70%, etc.)
- Closing speed modifiers
- Puck loose probability (TACKLE_PUCK_LOOSE_CHANCE = 60%)
**Goal System:**
- Freeze game on goal event
- 3-second pause-aware timer
- Player deceleration (GOAL_DECELERATION_RATE)
- Reset positions to center ice
---
### 6. Coordinate System
**Location:** [src/utils/coordinates.ts](src/utils/coordinates.ts)
**Purpose:** Convert between game coordinates (meters) and screen coordinates (pixels).
**Critical Details:**
- **Game coordinates:** Origin (0, 0) at center of rink, meters as floats
- **Screen coordinates:** Origin at top-left, pixels
- **Scale factor:** 1 meter = 20 pixels (SCALE constant)
- **Y-axis inversion:** Game Y increases upward, screen Y increases downward
**Key Methods:**
- `gameToScreen(scene, gameX, gameY)`: Meters → pixels
- `screenToGame(scene, screenX, screenY)`: Pixels → meters
- `getScreenCenter(scene)`: Canvas center in pixels
**Rink Layout:**
- Rink: 60m × 30m
- Left goal: x = -26m (GOAL_LINE_OFFSET)
- Right goal: x = +26m
- Blue lines: x = ±9m (BLUE_LINE_OFFSET)
- Center ice: (0, 0)
---
### 7. Constants Configuration
**Location:** [src/config/constants.ts](src/config/constants.ts)
**Purpose:** Centralized configuration for all game parameters.
**Categories:**
**Game Settings:**
- DEBUG, FPS, UI_BOTTOM_PADDING
**Rink Dimensions:**
- RINK_LENGTH, RINK_WIDTH, SCALE, corner radius, zone lines
**Player Constants:**
- Radius (goalie vs skater), rotation speed, acceleration/deceleration
- Speed scale factor, movement threshold
**Puck Constants:**
- Radius, pickup range, carry distance, max velocity
- Drag, bounce, shot speed, reception parameters
**AI/Behavior:**
- Shooting range and angle thresholds (normal vs close-range)
- Goalie range, detection radius and angle
- Evasion angle
**Tackle Mechanics:**
- Success modifiers, cooldown, fall duration
- Angle thresholds and modifiers
- Closing speed thresholds
**Collision/Physics:**
- Post bounce reduction, puck loose chance
- Reception skill scaling
---
## Type Definitions
**Location:** [src/types/game.ts](src/types/game.ts)
**Key Types:**
```typescript
PlayerPosition: 'LW' | 'C' | 'RW' | 'LD' | 'RD' | 'G'
TeamSide: 'home' | 'away'
PlayerState: 'offensive' | 'defensive'
PuckState: 'loose' | 'carried' | 'passing' | 'shot'
PlayerAttributes: {
speed, skill, tackling, balance, handling
}
GameState: {
puck: Puck
allPlayers: Player[]
}
PlayerAction: {
type: 'move' | 'chase_puck' | 'skate_with_puck' | 'shoot' | 'idle'
targetX?, targetY?
}
```
---
## Technical Stack
- **Framework**: Phaser 3.90.0 (Arcade Physics engine)
- **TypeScript**: Strict mode enabled - **TypeScript**: Strict mode enabled
- **Build Tool**: Vite 5.4 - **Build Tool**: Vite 5.4
- **Target FPS**: 60 (constant in constants.ts) - **Target FPS**: 60 (configured in constants.ts)
- **Physics**: Arcade physics with zero gravity (top-down view) - **Physics**: Arcade physics with zero gravity (top-down view)
- **Coordinate System**: Centered origin (0,0), meters as float values
- **Rendering Scale**: 1 meter = 20 pixels
## Configuration ---
All magic numbers and game constants are centralized in `src/config/constants.ts`: ## Debug Features
- Rink dimensions and zone lines
- Goal dimensions
- Colors (ice, boards, lines)
- Scale factor (meters to pixels)
- Game settings (FPS)
Future constants will include: **Toggle:** Set `DEBUG = true` in [constants.ts](src/config/constants.ts)
- Player speeds, shot speeds, pass speeds
- AI decision probabilities
- Attribute ranges
## Development Notes **Visual Overlays:**
- **Player movement:** Line and marker showing target position
- **Player detection:** Magenta wedge showing field-of-view sensor
- **Puck carrier evasion:**
- Green line to goal
- Red circles around detected threats
- Yellow/green arrows showing evasion direction
- Orange circles for all nearby opponents
- **Incremental testing**: Test each phase thoroughly before proceeding **Console Logging:**
- **Debug visualization**: Add toggleable overlays for targets, zones, etc. - Goal events with team and goal location
- **Tuning over precision**: Numbers are starting points; tune based on feel - Shot attempts with player ID
- Refer to PLAN.md for detailed phase requirements and validation criteria - Tackle results and success calculations
- Refer to NOTES.md for architectural decisions and design notes
---
## Development Workflow
**Hot Reload Development:**
```bash
pnpm run dev # Starts on localhost:3000 with hot reload
```
**Testing Approach:**
- Test each subsystem incrementally before integration
- Use DEBUG mode to visualize AI decision-making
- Tune constants based on gameplay feel (see [constants.ts](src/config/constants.ts))
- Validate physics with edge cases (goal posts, rink boundaries)
**Key Tuning Parameters:**
- Player acceleration/deceleration curves → smooth movement feel
- Detection radius/angle → evasion responsiveness
- Shooting angles → goal frequency balance
- Tackle modifiers → physical gameplay intensity
- Puck drag/bounce → realistic puck physics
**Documentation:**
- **[PLAN.md](PLAN.md)**: Phase-by-phase implementation plan
- **[NOTES.md](NOTES.md)**: Architecture decisions and design rationale
- **[REVIEW.md](REVIEW.md)**: Progress reviews and lessons learned
- **[CLAUDE.md](CLAUDE.md)** (this file): System reference for AI assistant
---
## Implementation Patterns
**Adding New Behaviors:**
1. Create behavior class extending `BehaviorNode` in `src/systems/behaviors/`
2. Implement `tick(player, gameState)` method
3. Add to appropriate selector in `SkaterBehavior` or `GoalieBehavior`
4. Test in isolation before integration
**Adding New Constants:**
1. Add to appropriate section in [constants.ts](src/config/constants.ts)
2. Use semantic names (e.g., `SHOOTING_RANGE` not `MAX_DIST`)
3. Include units in comments (meters, m/s, radians, pixels)
4. Export for use across codebase
**Physics Bodies:**
- Players: Circular bodies (radius varies by position)
- Puck: Circular body with drag and bounce
- Goals: Static rectangular bodies for posts/bars
- Sensors: Physics zones with `overlap` not `collide`
**Coordinate Conversions:**
- Always use `CoordinateUtils.gameToScreen()` and `screenToGame()`
- Never mix coordinate systems in calculations
- Store positions in game coordinates (meters), convert for rendering

View File

@ -348,7 +348,8 @@ export class GameScene extends Phaser.Scene {
this.events.off('goal'); this.events.off('goal');
this.scene.restart(); this.scene.restart();
} }
return; // Don't update anything while waiting for reset // Continue updating with isGoalScored flag set
// This allows behavior trees to handle the freeze logic
} }
this.updatePuck(); this.updatePuck();
@ -362,10 +363,11 @@ export class GameScene extends Phaser.Scene {
} }
private updatePlayers(delta: number) { private updatePlayers(delta: number) {
// Build game state // Build game state with goal freeze status
const gameState = { const gameState = {
puck: this.puck, puck: this.puck,
allPlayers: this.players allPlayers: this.players,
isGoalScored: this.isWaitingForReset
}; };
// Update all players with behavior tree decisions // Update all players with behavior tree decisions

View File

@ -7,6 +7,7 @@ import { GOAL_LINE_OFFSET, GOALIE_RANGE } from '../../config/constants';
* Goalie-specific behavior tree * Goalie-specific behavior tree
* *
* Goalies have unique behaviors: * Goalies have unique behaviors:
* - Return to center of goal when goal scored (highest priority)
* - Stay near goal line * - Stay near goal line
* - Track puck Y position * - Track puck Y position
* - Challenge shooters (future) * - Challenge shooters (future)
@ -14,6 +15,16 @@ import { GOAL_LINE_OFFSET, GOALIE_RANGE } from '../../config/constants';
*/ */
export class GoalieBehavior extends BehaviorNode { export class GoalieBehavior extends BehaviorNode {
tick(player: Player, gameState: GameState): PlayerAction { tick(player: Player, gameState: GameState): PlayerAction {
// Priority check: If goal was scored, return to center of goal
if (gameState.isGoalScored) {
const goalX = player.team === 'home' ? -GOAL_LINE_OFFSET : GOAL_LINE_OFFSET;
return {
type: 'move',
targetX: goalX,
targetY: 0 // Center of goal
};
}
// Stay near goal line // Stay near goal line
const goalX = player.team === 'home' ? -GOAL_LINE_OFFSET : GOAL_LINE_OFFSET; const goalX = player.team === 'home' ? -GOAL_LINE_OFFSET : GOAL_LINE_OFFSET;

View File

@ -10,7 +10,8 @@ import { TransitionBehavior } from './TransitionBehavior';
* Skater behavior tree - Context-based decision making * Skater behavior tree - Context-based decision making
* *
* The selector evaluates children in priority order: * The selector evaluates children in priority order:
* 1. PuckCarrier - If this player has the puck (highest priority) * 0. Goal Scored - Return to center faceoff circle (highest priority)
* 1. PuckCarrier - If this player has the puck
* 2. OffensiveSupport - If teammate has puck (support offense) * 2. OffensiveSupport - If teammate has puck (support offense)
* 3. Defensive - If opponent has puck (play defense) * 3. Defensive - If opponent has puck (play defense)
* 4. Transition - Loose puck or neutral situations (fallback) * 4. Transition - Loose puck or neutral situations (fallback)
@ -34,6 +35,15 @@ export class SkaterBehavior extends BehaviorNode {
} }
tick(player: Player, gameState: GameState): PlayerAction { tick(player: Player, gameState: GameState): PlayerAction {
// Priority check: If goal was scored, skate to center faceoff circle
if (gameState.isGoalScored) {
return {
type: 'move',
targetX: 0, // Center ice
targetY: 0 // Center ice
};
}
return this.tree.tick(player, gameState) as PlayerAction; return this.tree.tick(player, gameState) as PlayerAction;
} }
} }

View File

@ -38,6 +38,7 @@ export interface PlayerAttributes {
export interface GameState { export interface GameState {
puck: Puck; puck: Puck;
allPlayers: Player[]; allPlayers: Player[];
isGoalScored: boolean; // True when goal scored and game frozen
} }
/** /**