✅ Phase 3: Patrol Behavior COMPLETE
Test scenario with 9 NPCs, comprehensive documentation (800+ lines),
and Ink toggle testing for patrol behavior.
Includes:
- 9 test NPCs with varied configurations
- Speed tests (50-200 px/s)
- Bounds validation tests
- Stuck detection tests
- Narrow corridor tests
- Patrol + face player integration
- Complete test guide with debugging tools
18 KiB
Phase 3: Patrol Behavior - Test Guide
Overview
Phase 3 focuses on testing and verifying the Patrol behavior. This makes NPCs move randomly within defined bounds, creating dynamic, living environments.
Status: ✅ Implementation Complete, Ready for Testing
What Was Implemented
Core Functionality
-
Random Movement Within Bounds
- NPCs pick random target points within configured bounds
- Move toward target using velocity-based physics
- Pick new target when reached (< 8px distance)
-
Timed Direction Changes
- Configurable interval (default: 3000ms)
- New random target chosen at each interval
- Prevents NPCs from getting "stuck" in patterns
-
Stuck Detection & Recovery
- Detects when NPC is blocked by collision
- 500ms timeout before choosing new direction
- Prevents infinite collision loops
-
Walk Animations
- 8-way walk animations during movement
- Direction calculated based on velocity
- Smooth animation transitions
-
Collision Handling
- NPCs collide with walls, chairs, and other objects
- Physics-based collision response
- Automatic recovery from stuck states
-
Priority Integration
- Patrol is Priority 2 (overridden by higher priorities)
- Face Player (Priority 1) can interrupt patrol
- Personal Space (Priority 3) overrides patrol
Test Scenario
File: scenarios/test-npc-patrol.json
This scenario contains 9 NPCs testing various patrol configurations:
Test NPCs
| NPC ID | Position | Speed | Interval | Bounds | Test Purpose |
|---|---|---|---|---|---|
patrol_basic |
(3,3) | 100 | 3000ms | 6x6 tiles | Standard patrol |
patrol_fast |
(8,3) | 200 | 2000ms | 4x4 tiles | High speed |
patrol_slow |
(3,8) | 50 | 5000ms | 4x3 tiles | Low speed |
patrol_small |
(8,8) | 80 | 2000ms | 2x2 tiles | Tiny area |
patrol_with_face |
(5,5) | 100 | 4000ms | 4x4 tiles | Patrol + face player |
patrol_narrow_horizontal |
(1,1) | 100 | 3000ms | 8x1 tiles | Corridor test |
patrol_narrow_vertical |
(1,5) | 100 | 3000ms | 1x5 tiles | Corridor test |
patrol_initially_disabled |
(10,5) | 100 | 3000ms | 3x3 tiles | Toggle via Ink |
patrol_stuck_test |
(6,1) | 120 | 4000ms | 3x3 tiles | Collision test |
Visual Layout
Room: test_patrol (room_office)
1 2 3 4 5 6 7 8 9 10
1 [NarrowH] [NarrowH] [Stuck]
2
3 [Basic] [Fast]
4
5 [NarrowV] [WithFace] [Toggle]
6
7
8 [Slow] [Small]
9
How to Test
Setup
-
Load Test Scenario:
window.gameScenario = await fetch('scenarios/test-npc-patrol.json').then(r => r.json()); // Then reload game -
Verify Behavior Manager:
console.log('Behavior Manager:', window.npcBehaviorManager); console.log('Registered behaviors:', window.npcBehaviorManager.behaviors.size);
Test Procedures
Test 1: Basic Patrol Movement
NPC: patrol_basic (blue, position 3,3)
Configuration:
- Speed: 100px/s
- Interval: 3000ms (3 seconds)
- Bounds: 6x6 tiles (192x192px)
Procedure:
- Observe NPC from a distance (don't approach)
- Watch for 30 seconds
Expected Behavior:
- ✅ NPC should walk to random points within 6x6 area
- ✅ Changes direction every 3 seconds
- ✅ Uses walk animations (8 directions)
- ✅ Smooth movement, no jittering
- ✅ Stays within bounds (2-7 tiles from origin)
- ✅ Direction matches movement (walks forward, not sideways)
Measurements:
const behavior = window.npcBehaviorManager.getBehavior('patrol_basic');
console.log('Current target:', behavior.patrolTarget);
console.log('Direction:', behavior.direction);
console.log('Is moving:', behavior.isMoving);
console.log('Current state:', behavior.currentState); // Should be 'patrol'
Test 2: Speed Variations
NPCs: patrol_fast (200px/s) vs patrol_slow (50px/s)
Procedure:
- Observe both NPCs simultaneously
- Compare movement speeds visually
Expected Behavior:
- ✅
patrol_fastmoves noticeably faster (2x basic speed) - ✅
patrol_slowmoves noticeably slower (0.5x basic speed) - ✅ Both use correct walk animation frame rate (8 fps)
- ✅ Animation doesn't look sped up/slowed down (velocity changes, not animation)
- ✅ Fast NPC reaches targets quicker
- ✅ Slow NPC appears to "stroll"
Debug:
// Check velocities
const fast = window.npcManager.npcs.get('patrol_fast')._sprite;
const slow = window.npcManager.npcs.get('patrol_slow')._sprite;
console.log('Fast velocity:', Math.sqrt(fast.body.velocity.x**2 + fast.body.velocity.y**2));
console.log('Slow velocity:', Math.sqrt(slow.body.velocity.x**2 + slow.body.velocity.y**2));
// Fast should be ~200, Slow should be ~50
Test 3: Direction Change Intervals
NPCs: Various intervals
patrol_fast: 2000ms (2 seconds)patrol_basic: 3000ms (3 seconds)patrol_slow: 5000ms (5 seconds)
Procedure:
- Time direction changes with a stopwatch
- Observe for 30 seconds
- Count direction changes
Expected Results:
- ✅
patrol_fast: ~15 direction changes in 30s - ✅
patrol_basic: ~10 direction changes in 30s - ✅
patrol_slow: ~6 direction changes in 30s - ✅ Changes are roughly consistent (±10%)
- ✅ NPC picks different target each time (not same point)
Debug:
// Monitor direction changes
const behavior = window.npcBehaviorManager.getBehavior('patrol_basic');
let lastTarget = null;
setInterval(() => {
if (JSON.stringify(behavior.patrolTarget) !== lastTarget) {
console.log('Direction changed:', behavior.patrolTarget);
lastTarget = JSON.stringify(behavior.patrolTarget);
}
}, 100);
Test 4: Bounds Validation
NPC: patrol_basic (6x6 tile bounds)
Bounds Configuration:
{
"x": 64,
"y": 64,
"width": 192,
"height": 192
}
World Coordinates: (64, 64) to (256, 256)
Procedure:
- Observe NPC for 1 minute
- Note maximum/minimum X and Y positions reached
Expected Behavior:
- ✅ NPC X position: 64 ≤ X ≤ 256
- ✅ NPC Y position: 64 ≤ Y ≤ 256
- ✅ NPC never leaves bounds area
- ✅ Targets are distributed throughout bounds (not clustered)
Debug:
// Track bounds violations
const behavior = window.npcBehaviorManager.getBehavior('patrol_basic');
const sprite = window.npcManager.npcs.get('patrol_basic')._sprite;
const bounds = behavior.config.patrol.worldBounds;
setInterval(() => {
const x = sprite.x;
const y = sprite.y;
if (x < bounds.x || x > bounds.x + bounds.width ||
y < bounds.y || y > bounds.y + bounds.height) {
console.error('❌ BOUNDS VIOLATION:', {x, y, bounds});
}
}, 100);
Test 5: Stuck Detection & Recovery
NPC: patrol_stuck_test
Setup:
- Place obstacles in patrol area (if possible)
- Observe NPC encountering obstacles
Procedure:
- Watch NPC patrol
- Wait for NPC to hit a wall or obstacle
- Observe recovery behavior
Expected Behavior:
- ✅ NPC walks toward wall/obstacle
- ✅ NPC stops when colliding (blocked state)
- ✅ After ~500ms, NPC chooses new direction
- ✅ New direction avoids the obstacle
- ✅ NPC doesn't get permanently stuck
- ✅ No console errors
Debug:
// Monitor stuck states
const behavior = window.npcBehaviorManager.getBehavior('patrol_stuck_test');
const sprite = window.npcManager.npcs.get('patrol_stuck_test')._sprite;
setInterval(() => {
const isBlocked = sprite.body.blocked.none === false;
if (isBlocked) {
console.log('🚧 NPC stuck! Timer:', behavior.stuckTimer, 'ms');
}
}, 100);
Test 6: Narrow Area Patrol
NPCs:
patrol_narrow_horizontal(8x1 tiles - horizontal corridor)patrol_narrow_vertical(1x5 tiles - vertical corridor)
Procedure:
- Observe horizontal NPC - should mostly move left/right
- Observe vertical NPC - should mostly move up/down
- Check animations match movement direction
Expected Behavior:
Horizontal NPC:
- ✅ Primarily uses
walk-leftandwalk-rightanimations - ✅ Rarely uses vertical animations
- ✅ Stays within 8-tile wide corridor
- ✅ Smooth horizontal movement
Vertical NPC:
- ✅ Primarily uses
walk-upandwalk-downanimations - ✅ Rarely uses horizontal animations
- ✅ Stays within 1-tile wide corridor
- ✅ Smooth vertical movement
Test 7: Patrol + Face Player Interaction
NPC: patrol_with_face (center, red sprite)
Configuration:
- Patrol: enabled, 100px/s
- Face Player: enabled, 96px range
Procedure:
- Stay far from NPC (>3 tiles)
- Observe patrol behavior
- Approach within 3 tiles
- Walk away
Expected Behavior:
When Far (>3 tiles):
- ✅ NPC patrols normally
- ✅ Uses walk animations
- ✅ Changes direction every 4 seconds
- ✅ State:
'patrol'
When Near (<3 tiles):
- ✅ NPC stops patrolling
- ✅ NPC turns to face player
- ✅ Uses idle animation facing player
- ✅ Velocity becomes (0, 0)
- ✅ State:
'face_player'
When Leaving:
- ✅ NPC resumes patrol after player leaves range
- ✅ Picks new random target
- ✅ Resumes walk animations
- ✅ State returns to
'patrol'
Debug:
const behavior = window.npcBehaviorManager.getBehavior('patrol_with_face');
setInterval(() => {
console.log('State:', behavior.currentState,
'Is Moving:', behavior.isMoving,
'Direction:', behavior.direction);
}, 500);
Test 8: Small Area Patrol
NPC: patrol_small (2x2 tiles only)
Procedure:
- Observe NPC in tiny area
- Watch for 30 seconds
Expected Behavior:
- ✅ NPC moves within 2x2 tile area only
- ✅ Frequent direction changes (targets nearby)
- ✅ Reaches targets quickly (small distances)
- ✅ No getting stuck in corners
- ✅ Smooth transitions despite small space
Edge Case Check:
- Target point might be very close to current position
- Should still move smoothly, not jitter
Test 9: Patrol Toggle via Ink
NPC: patrol_initially_disabled
Procedure:
- Observe NPC initially - should be stationary
- Talk to NPC (E key when nearby)
- Select "Start patrolling"
- Exit conversation - NPC should start moving
- Talk again, select "Stop patrolling"
- Exit - NPC should stop
Expected Behavior:
Initial State:
- ✅ NPC is stationary (idle animation)
- ✅ NPC faces player when nearby
- ✅ State:
'face_player'or'idle' - ✅ Patrol enabled:
false
After "Start patrolling":
- ✅ Tag
#patrol_mode:onprocessed - ✅ NPC starts moving after conversation ends
- ✅ State changes to
'patrol' - ✅ Uses walk animations
- ✅ Patrol enabled:
true
After "Stop patrolling":
- ✅ Tag
#patrol_mode:offprocessed - ✅ NPC stops moving
- ✅ Returns to idle/face player behavior
- ✅ Patrol enabled:
false
Debug:
const behavior = window.npcBehaviorManager.getBehavior('patrol_initially_disabled');
console.log('Patrol enabled:', behavior.config.patrol.enabled);
console.log('Current state:', behavior.currentState);
Performance Testing
Test: Multiple Patrolling NPCs
Procedure:
- Load test scenario (9 NPCs, 8 patrolling)
- Let all NPCs patrol simultaneously
- Monitor FPS and performance
Expected Performance:
- ✅ Stable 60 FPS with 8 patrolling NPCs
- ✅ No visible lag or stuttering
- ✅ Smooth animations for all NPCs
- ✅ CPU usage reasonable (<20% spike)
Debug:
// Monitor FPS
let lastTime = performance.now();
let frames = 0;
setInterval(() => {
const now = performance.now();
const fps = frames / ((now - lastTime) / 1000);
console.log('FPS:', fps.toFixed(1));
frames = 0;
lastTime = now;
}, 1000);
window.game.scene.scenes[0].events.on('postupdate', () => frames++);
Animation Testing
Expected Animation States
While Patrolling:
- Animation:
npc-{npcId}-walk-{direction} - Direction: Matches movement vector
- Frame rate: 8 fps
- FlipX: true for left-facing directions
When Reaching Target:
- Brief moment at target (< 8px)
- May show idle frame for 1 frame
- Quickly picks new target and resumes walking
When Blocked:
- Walk animation continues briefly
- After 500ms stuck timeout, picks new direction
- Changes to new walk animation
Debug Animations
const sprite = window.npcManager.npcs.get('patrol_basic')._sprite;
console.log('Current animation:', sprite.anims.currentAnim?.key);
console.log('Is playing:', sprite.anims.isPlaying);
console.log('FlipX:', sprite.flipX);
Edge Cases
Edge Case 1: Target Point on Wall
Scenario: Random target is inside a wall
Expected:
- NPC walks toward target
- Hits wall, becomes blocked
- Stuck timer triggers after 500ms
- New target chosen (likely not in wall)
- ✅ No infinite loop
Edge Case 2: NPC Starts Outside Bounds
Scenario: NPC spawned outside configured patrol bounds
Handling:
- Bounds auto-expand to include starting position (implemented in parseConfig)
- ✅ NPC patrols normally
- ✅ Console warning logged
Test:
// Check if bounds were expanded
const behavior = window.npcBehaviorManager.getBehavior('patrol_basic');
const sprite = window.npcManager.npcs.get('patrol_basic')._sprite;
console.log('Start pos:', sprite.x, sprite.y);
console.log('Bounds:', behavior.config.patrol.worldBounds);
// Bounds should include start position
Edge Case 3: Very Small Bounds
Scenario: Bounds smaller than NPC sprite
Expected:
- NPC still picks targets within bounds
- May appear to jitter if bounds very tiny
- Should not crash
Edge Case 4: Reached Target Exactly
Scenario: NPC reaches within 8px of target
Expected:
- ✅ New target chosen immediately
- ✅ No stopping at target (seamless transition)
- ✅ Direction changes smoothly
Edge Case 5: Direction Change During Collision
Scenario: Direction interval expires while NPC is stuck
Expected:
- ✅ New target chosen
- ✅ Stuck timer resets
- ✅ NPC attempts to move to new target
- ✅ If still blocked, stuck timer continues
Common Issues
Issue 1: NPC Not Moving
Symptoms: NPC stationary, not patrolling
Possible Causes:
- Patrol disabled:
behavior.config.patrol.enabled === false - No bounds configured:
behavior.config.patrol.worldBounds === null - Higher priority behavior active (face player, personal space)
- NPC stuck permanently (rare)
Debug:
const behavior = window.npcBehaviorManager.getBehavior('npc_id');
console.log('Patrol enabled:', behavior.config.patrol.enabled);
console.log('Bounds:', behavior.config.patrol.worldBounds);
console.log('Current state:', behavior.currentState);
console.log('Patrol target:', behavior.patrolTarget);
Fix:
- Enable patrol:
window.npcGameBridge.setNPCPatrol('npc_id', true) - Check state priority
Issue 2: NPC Leaving Bounds
Symptoms: NPC wanders outside configured area
Possible Causes:
- Bounds in room coordinates, not world coordinates
- Bounds calculation error
- Collision pushing NPC out
Debug:
const behavior = window.npcBehaviorManager.getBehavior('npc_id');
const sprite = window.npcManager.npcs.get('npc_id')._sprite;
const bounds = behavior.config.patrol.worldBounds;
console.log('NPC pos:', sprite.x, sprite.y);
console.log('Bounds:', bounds);
console.log('In bounds?',
sprite.x >= bounds.x && sprite.x <= bounds.x + bounds.width &&
sprite.y >= bounds.y && sprite.y <= bounds.y + bounds.height
);
Note: Bounds are converted to world coordinates in parseConfig()
Issue 3: NPC Getting Stuck
Symptoms: NPC stops moving for >1 second
Possible Causes:
- Stuck in corner with bad target
- Collision not resolving properly
- Stuck detection not working
Debug:
const behavior = window.npcBehaviorManager.getBehavior('npc_id');
const sprite = window.npcManager.npcs.get('npc_id')._sprite;
console.log('Blocked:', sprite.body.blocked);
console.log('Stuck timer:', behavior.stuckTimer);
console.log('Target:', behavior.patrolTarget);
Expected: Stuck timer should reach 500ms and reset
Issue 4: Wrong Animation
Symptoms: Walk animation doesn't match direction
Possible Causes:
- Direction calculation error
- Animation not created (using fallback idle)
- FlipX not applied for left directions
Debug:
const behavior = window.npcBehaviorManager.getBehavior('npc_id');
const sprite = window.npcManager.npcs.get('npc_id')._sprite;
console.log('Direction:', behavior.direction);
console.log('Animation:', sprite.anims.currentAnim?.key);
console.log('FlipX:', sprite.flipX);
console.log('Velocity:', sprite.body.velocity);
Success Criteria
✅ Phase 3 Complete When:
- Basic patrol works (random movement in bounds)
- Speed variations work correctly (fast/slow)
- Direction changes occur at configured intervals
- NPCs stay within configured bounds
- Stuck detection recovers from collisions
- Narrow area patrols work (corridors)
- Patrol + face player interaction works
- Small area patrol works without jittering
- Patrol can be toggled via Ink tags
- Walk animations match movement direction
- Performance acceptable with 8+ patrolling NPCs
- No console errors during patrol
- Edge cases handled gracefully
Next Steps
After Phase 3:
- Phase 4: Personal Space behavior testing
- Phase 5: Ink integration testing
- Phase 6: Hostile visual feedback
Document Status: Test Guide v1.0 Last Updated: 2025-11-09 Phase: 3 - Patrol Behavior Testing