diff --git a/planning_notes/npc/npc_behaviour/PHASE2_TEST_GUIDE.md b/planning_notes/npc/npc_behaviour/PHASE2_TEST_GUIDE.md new file mode 100644 index 0000000..4b82604 --- /dev/null +++ b/planning_notes/npc/npc_behaviour/PHASE2_TEST_GUIDE.md @@ -0,0 +1,390 @@ +# Phase 2: Face Player Behavior - Test Guide + +## Overview + +Phase 2 focuses on testing and verifying the **Face Player** behavior. This is the foundational behavior that makes NPCs turn to face the player when they approach. + +**Status**: ✅ Implementation Complete, Ready for Testing + +--- + +## What Was Implemented + +### Core Functionality + +1. **8-Way Directional Facing** + - NPCs can face in 8 directions: up, down, left, right, up-left, up-right, down-left, down-right + - Direction calculated based on player position relative to NPC + - Uses 2x threshold for cardinal vs diagonal (prevents flickering) + +2. **Distance-Based Activation** + - Default range: 96px (3 tiles) + - Configurable via `facePlayerDistance` in scenario JSON + - NPCs only face player when within range + +3. **Animation Integration** + - Uses idle animations for the calculated direction + - Supports flipX for left-facing directions + - Graceful fallback if animations missing + +4. **State Priority** + - Face Player is Priority 1 (overridden by higher priority behaviors) + - Only activates when no patrol/personal space/hostile behaviors active + +--- + +## Test Scenario + +**File**: `scenarios/test-npc-face-player.json` + +This scenario contains 12 NPCs arranged to test all aspects of face player behavior: + +### Test Layout + +``` + 1 2 3 4 5 6 7 8 9 + 1 [FAR] [DISABLED] + 2 [NW] [N] [NE] + 3 + 4 + 5 [W] [CENTER] [E] + 6 + 7 + 8 [SW] [S] [SE] + 9 +``` + +### NPCs in Scenario + +| NPC ID | Position | Test Purpose | Expected Behavior | +|--------|----------|--------------|-------------------| +| `npc_center` | (5, 5) | Default behavior | Should face player in all 8 directions | +| `npc_north` | (5, 2) | Cardinal: North | Should face DOWN when player south | +| `npc_south` | (5, 8) | Cardinal: South | Should face UP when player north | +| `npc_east` | (8, 5) | Cardinal: East | Should face LEFT when player west | +| `npc_west` | (2, 5) | Cardinal: West | Should face RIGHT when player east | +| `npc_northeast` | (8, 2) | Diagonal: NE | Should face DOWN-LEFT when player approaches | +| `npc_northwest` | (2, 2) | Diagonal: NW | Should face DOWN-RIGHT when player approaches | +| `npc_southeast` | (8, 8) | Diagonal: SE | Should face UP-LEFT when player approaches | +| `npc_southwest` | (2, 8) | Diagonal: SW | Should face UP-RIGHT when player approaches | +| `npc_far` | (1, 1) | Short range | Should only face within 2 tiles (64px) | +| `npc_disabled` | (9, 1) | Disabled | Should NEVER face player | + +--- + +## How to Test + +### Setup + +1. Load the test scenario: + ```javascript + // In browser console or main.js + window.gameScenario = await fetch('scenarios/test-npc-face-player.json').then(r => r.json()); + ``` + +2. Start the game and observe NPCs + +### Test Procedure + +#### Test 1: Cardinal Directions + +1. **North NPC** (red, position 5,2) + - Approach from below (south) + - ✅ **Expected**: NPC should turn to face DOWN + - Animation: `npc-npc_north-idle-down` + +2. **South NPC** (blue, position 5,8) + - Approach from above (north) + - ✅ **Expected**: NPC should turn to face UP + - Animation: `npc-npc_south-idle-up` + +3. **East NPC** (red, position 8,5) + - Approach from left (west) + - ✅ **Expected**: NPC should turn to face LEFT + - Animation: `npc-npc_east-idle-left` (uses idle-right with flipX) + +4. **West NPC** (blue, position 2,5) + - Approach from right (east) + - ✅ **Expected**: NPC should turn to face RIGHT + - Animation: `npc-npc_west-idle-right` + +#### Test 2: Diagonal Directions + +5. **Northeast NPC** (red, position 8,2) + - Approach from southwest + - ✅ **Expected**: NPC should turn to face DOWN-LEFT + - Animation: `npc-npc_northeast-idle-down-left` + +6. **Northwest NPC** (blue, position 2,2) + - Approach from southeast + - ✅ **Expected**: NPC should turn to face DOWN-RIGHT + - Animation: `npc-npc_northwest-idle-down-right` + +7. **Southeast NPC** (red, position 8,8) + - Approach from northwest + - ✅ **Expected**: NPC should turn to face UP-LEFT + - Animation: `npc-npc_southeast-idle-up-left` + +8. **Southwest NPC** (blue, position 2,8) + - Approach from northeast + - ✅ **Expected**: NPC should turn to face UP-RIGHT + - Animation: `npc-npc_southwest-idle-up-right` + +#### Test 3: Range and Edge Cases + +9. **Center NPC** (position 5,5) + - Walk around NPC in a circle + - ✅ **Expected**: NPC should smoothly track player, updating direction + - Should face all 8 directions as player circles + +10. **Far NPC** (red, position 1,1) + - Default range: 64px (2 tiles) + - Walk past at 3+ tiles distance + - ✅ **Expected**: NPC should NOT turn + - Get within 2 tiles + - ✅ **Expected**: NPC should NOW turn to face player + +11. **Disabled NPC** (position 9,1) + - `facePlayer: false` + - Walk right up to NPC + - ✅ **Expected**: NPC should NEVER turn (stays facing default direction) + +#### Test 4: Direction Calculation Threshold + +12. **Threshold Test** (use center NPC) + - Position player at exactly 45° angle to NPC + - ✅ **Expected**: Should use diagonal direction (down-right, up-left, etc.) + - Position player at ~30° angle (more horizontal) + - ✅ **Expected**: Should snap to cardinal direction (left/right) + - Position player at ~60° angle (more vertical) + - ✅ **Expected**: Should snap to cardinal direction (up/down) + +--- + +## Debugging Tools + +### Console Commands + +Check NPC behavior state: +```javascript +// Get behavior instance +const behavior = window.npcBehaviorManager.getBehavior('npc_center'); + +// Check current state +console.log('State:', behavior.currentState); +console.log('Direction:', behavior.direction); +console.log('Config:', behavior.config.facePlayer, behavior.config.facePlayerDistance); + +// Check animation +console.log('Last animation:', behavior.lastAnimationKey); +``` + +### Visual Debug + +Enable behavior debug mode (if implemented in Phase 7): +```javascript +window.NPC_BEHAVIOR_DEBUG = true; +``` + +This should show: +- Green circles around NPCs showing face player range +- Direction indicators + +--- + +## Expected Behavior Summary + +### When Player Approaches (within range) + +1. NPC calculates dx/dy to player +2. Calls `calculateDirection(dx, dy)` to get 8-way direction +3. Sets `this.direction` to calculated direction +4. Calls `playAnimation('idle', direction)` +5. Animation system: + - Maps left directions to right with flipX + - Plays animation: `npc-{npcId}-idle-{direction}` + - Sets flipX if direction includes 'left' + +### When Player Leaves Range + +- NPC stays facing last direction (idle animation continues) +- State changes to 'idle' but direction unchanged +- This is intentional - NPC "remembers" where player was + +--- + +## Known Edge Cases + +### Edge Case 1: Player Exactly on Top of NPC +- **Behavior**: Distance = 0, direction calculation may be undefined +- **Handling**: Previous direction maintained (no crash) +- **Status**: ✅ Safe (direction only updates if dx/dy non-zero) + +### Edge Case 2: Multiple NPCs Overlapping +- **Behavior**: Each NPC independently faces player +- **Expected**: All NPCs face the same direction (towards player) +- **Status**: ✅ Working as intended + +### Edge Case 3: Direction Flickering at Threshold +- **Behavior**: Player moving along threshold boundary (e.g., 45° angle) +- **Mitigation**: 2x threshold prevents flickering + - Horizontal must be > 2x vertical for pure horizontal + - Vertical must be > 2x horizontal for pure vertical +- **Status**: ✅ Stable with 2x threshold + +### Edge Case 4: Animation Missing +- **Behavior**: Walk animation doesn't exist for direction +- **Fallback**: Uses idle animation with warning in console +- **Status**: ✅ Graceful degradation + +--- + +## Performance Metrics + +### Update Frequency +- **Throttled**: Updates every 50ms (20 Hz) +- **Frame Rate**: Should NOT impact 60 FPS +- **CPU Usage**: Minimal (simple calculations) + +### Test with 10 NPCs +- All NPCs should update independently +- No visible lag or stuttering +- Smooth direction transitions + +--- + +## Success Criteria + +✅ **Phase 2 Complete When**: + +1. [ ] All 8 cardinal/diagonal directions work correctly +2. [ ] Distance-based activation works (range configurable) +3. [ ] NPCs face player smoothly without flickering +4. [ ] Multiple NPCs can face player independently +5. [ ] Disabled NPCs do NOT face player +6. [ ] Short-range NPCs only activate within configured range +7. [ ] Animations play correctly (idle-{direction}) +8. [ ] FlipX works for left-facing directions +9. [ ] No console errors during testing +10. [ ] Performance acceptable with 10+ NPCs + +--- + +## Common Issues and Solutions + +### Issue 1: NPC Not Facing Player +**Symptoms**: NPC stays in default idle animation +**Possible Causes**: +- `facePlayer` disabled in config +- Player outside `facePlayerDistance` range +- Behavior not registered (check console for "🤖 Behavior registered") +- Higher priority behavior active (patrol, personal space) + +**Debug**: +```javascript +const behavior = window.npcBehaviorManager.getBehavior('npc_id'); +console.log('Face player enabled?', behavior.config.facePlayer); +console.log('Current state:', behavior.currentState); // Should be 'face_player' +``` + +### Issue 2: Wrong Direction +**Symptoms**: NPC faces wrong way +**Debug**: +```javascript +// Check direction calculation +const player = window.player; +const npc = window.npcManager.npcs.get('npc_id'); +const sprite = npc._sprite; +const dx = player.x - sprite.x; +const dy = player.y - sprite.y; +console.log('DX:', dx, 'DY:', dy); + +const behavior = window.npcBehaviorManager.getBehavior('npc_id'); +const direction = behavior.calculateDirection(dx, dy); +console.log('Calculated direction:', direction); +``` + +### Issue 3: Animation Not Playing +**Symptoms**: NPC doesn't change animation +**Possible Causes**: +- Animation key doesn't exist +- Animation not created in npc-sprites.js +- Sprite reference invalid + +**Debug**: +```javascript +const npcId = 'npc_id'; +const direction = 'down'; +const animKey = `npc-${npcId}-idle-${direction}`; +console.log('Animation exists?', window.game.scene.scenes[0].anims.exists(animKey)); +``` + +### Issue 4: FlipX Not Working +**Symptoms**: Left-facing NPCs face right +**Check**: +```javascript +const sprite = window.npcManager.npcs.get('npc_id')._sprite; +console.log('FlipX:', sprite.flipX); // Should be true for left directions +``` + +--- + +## Next Steps After Phase 2 + +Once Phase 2 tests pass: + +1. **Phase 3**: Patrol Behavior + - NPCs move randomly within bounds + - Test with moving NPCs + +2. **Phase 4**: Personal Space + - NPCs back away from player + - Test backing behavior + +3. **Phase 5**: Ink Integration + - Test behavior tags in dialogue + - Verify tag handlers work + +--- + +## Test Results Template + +```markdown +## Phase 2 Test Results + +**Date**: YYYY-MM-DD +**Tester**: [Name] +**Build**: [Commit hash] + +### Cardinal Directions +- [ ] North (DOWN) - PASS / FAIL / NOTES: +- [ ] South (UP) - PASS / FAIL / NOTES: +- [ ] East (LEFT) - PASS / FAIL / NOTES: +- [ ] West (RIGHT) - PASS / FAIL / NOTES: + +### Diagonal Directions +- [ ] Northeast (DOWN-LEFT) - PASS / FAIL / NOTES: +- [ ] Northwest (DOWN-RIGHT) - PASS / FAIL / NOTES: +- [ ] Southeast (UP-LEFT) - PASS / FAIL / NOTES: +- [ ] Southwest (UP-RIGHT) - PASS / FAIL / NOTES: + +### Edge Cases +- [ ] Range activation - PASS / FAIL / NOTES: +- [ ] Disabled NPC - PASS / FAIL / NOTES: +- [ ] Threshold stability - PASS / FAIL / NOTES: + +### Performance +- [ ] 10 NPCs smooth - PASS / FAIL / NOTES: +- [ ] No console errors - PASS / FAIL / NOTES: + +### Overall Status +- [ ] Phase 2 COMPLETE +- [ ] Issues found: [List] +- [ ] Ready for Phase 3: YES / NO +``` + +--- + +**Document Status**: Test Guide v1.0 +**Last Updated**: 2025-11-09 +**Phase**: 2 - Face Player Testing diff --git a/planning_notes/npc/npc_behaviour/phase2_direction_tests.js b/planning_notes/npc/npc_behaviour/phase2_direction_tests.js new file mode 100644 index 0000000..dbd00c1 --- /dev/null +++ b/planning_notes/npc/npc_behaviour/phase2_direction_tests.js @@ -0,0 +1,294 @@ +/** + * Phase 2: Direction Calculation Unit Tests + * + * These tests verify that the calculateDirection() function works correctly + * for all edge cases and boundary conditions. + * + * Run in browser console after game loads: + * > await import('./planning_notes/npc/npc_behaviour/phase2_direction_tests.js?v=1') + */ + +/** + * Test the calculateDirection logic + * (Copied from npc-behavior.js for testing) + */ +function calculateDirection(dx, dy) { + const absVX = Math.abs(dx); + const absVY = Math.abs(dy); + + // Threshold: if one axis is > 2x the other, consider it pure cardinal + if (absVX > absVY * 2) { + return dx > 0 ? 'right' : 'left'; + } + + if (absVY > absVX * 2) { + return dy > 0 ? 'down' : 'up'; + } + + // Diagonal + if (dy > 0) { + return dx > 0 ? 'down-right' : 'down-left'; + } else { + return dx > 0 ? 'up-right' : 'up-left'; + } +} + +/** + * Test suite + */ +const tests = [ + // Pure Cardinal Directions + { + name: 'Pure Right (player directly east)', + dx: 100, + dy: 0, + expected: 'right' + }, + { + name: 'Pure Left (player directly west)', + dx: -100, + dy: 0, + expected: 'left' + }, + { + name: 'Pure Down (player directly south)', + dx: 0, + dy: 100, + expected: 'down' + }, + { + name: 'Pure Up (player directly north)', + dx: 0, + dy: -100, + expected: 'up' + }, + + // Threshold Tests - Should snap to cardinal + { + name: 'Mostly Right (30° angle)', + dx: 100, + dy: 30, + expected: 'right', + note: 'absVX (100) > absVY * 2 (60), should be cardinal' + }, + { + name: 'Mostly Left (30° angle)', + dx: -100, + dy: 30, + expected: 'left', + note: 'absVX (100) > absVY * 2 (60), should be cardinal' + }, + { + name: 'Mostly Down (30° angle)', + dx: 30, + dy: 100, + expected: 'down', + note: 'absVY (100) > absVX * 2 (60), should be cardinal' + }, + { + name: 'Mostly Up (30° angle)', + dx: 30, + dy: -100, + expected: 'up', + note: 'absVY (100) > absVX * 2 (60), should be cardinal' + }, + + // Pure Diagonal Directions (45°) + { + name: 'Pure Down-Right (45° angle)', + dx: 100, + dy: 100, + expected: 'down-right', + note: 'Equal dx/dy = 45°, should be diagonal' + }, + { + name: 'Pure Down-Left (45° angle)', + dx: -100, + dy: 100, + expected: 'down-left', + note: 'Equal dx/dy = 45°, should be diagonal' + }, + { + name: 'Pure Up-Right (45° angle)', + dx: 100, + dy: -100, + expected: 'up-right', + note: 'Equal dx/dy = 45°, should be diagonal' + }, + { + name: 'Pure Up-Left (45° angle)', + dx: -100, + dy: -100, + expected: 'up-left', + note: 'Equal dx/dy = 45°, should be diagonal' + }, + + // Threshold Boundary Tests + { + name: 'Threshold Boundary - Barely Diagonal Right-Down (60° from horizontal)', + dx: 100, + dy: 51, + expected: 'down-right', + note: 'absVX (100) NOT > absVY * 2 (102), should be diagonal' + }, + { + name: 'Threshold Boundary - Barely Cardinal Right (30° from horizontal)', + dx: 100, + dy: 49, + expected: 'right', + note: 'absVX (100) > absVY * 2 (98), should be cardinal' + }, + { + name: 'Threshold Boundary - Barely Diagonal Down-Right (30° from vertical)', + dx: 51, + dy: 100, + expected: 'down-right', + note: 'absVY (100) NOT > absVX * 2 (102), should be diagonal' + }, + { + name: 'Threshold Boundary - Barely Cardinal Down (60° from vertical)', + dx: 49, + dy: 100, + expected: 'down', + note: 'absVY (100) > absVX * 2 (98), should be cardinal' + }, + + // Edge Cases + { + name: 'Zero Distance (player on top of NPC)', + dx: 0, + dy: 0, + expected: 'up-left', + note: 'When dx=0 dy=0: not cardinal (0 NOT > 0), goes to diagonal, dy <= 0 so up, dx <= 0 so left = up-left' + }, + + // Small movements + { + name: 'Small Right Movement', + dx: 1, + dy: 0, + expected: 'right' + }, + { + name: 'Small Diagonal Movement', + dx: 1, + dy: 1, + expected: 'down-right' + }, + + // Large movements + { + name: 'Large Right Movement', + dx: 1000, + dy: 0, + expected: 'right' + }, + + // Negative small movements + { + name: 'Small Up-Left Movement', + dx: -1, + dy: -1, + expected: 'up-left' + } +]; + +/** + * Run all tests + */ +export function runDirectionTests() { + console.log('🧪 Running Phase 2 Direction Calculation Tests...\n'); + + let passed = 0; + let failed = 0; + const failures = []; + + tests.forEach((test, index) => { + const result = calculateDirection(test.dx, test.dy); + const success = result === test.expected; + + if (success) { + passed++; + console.log(`✅ Test ${index + 1}: ${test.name}`); + if (test.note) { + console.log(` 📝 ${test.note}`); + } + } else { + failed++; + failures.push({ + ...test, + actual: result + }); + console.log(`❌ Test ${index + 1}: ${test.name}`); + console.log(` Expected: ${test.expected}, Got: ${result}`); + console.log(` Input: dx=${test.dx}, dy=${test.dy}`); + if (test.note) { + console.log(` 📝 ${test.note}`); + } + } + }); + + console.log(`\n${'='.repeat(50)}`); + console.log(`📊 Test Results: ${passed} passed, ${failed} failed`); + console.log(`${'='.repeat(50)}\n`); + + if (failures.length > 0) { + console.log('❌ Failed Tests:'); + failures.forEach(f => { + console.log(` - ${f.name}: Expected ${f.expected}, Got ${f.actual}`); + }); + } else { + console.log('✅ All tests passed!'); + } + + return { + passed, + failed, + total: tests.length, + failures + }; +} + +/** + * Test against actual NPC behavior (if behavior manager available) + */ +export function testWithActualBehavior(npcId = 'npc_center') { + if (!window.npcBehaviorManager) { + console.error('❌ NPCBehaviorManager not available. Load game first.'); + return; + } + + const behavior = window.npcBehaviorManager.getBehavior(npcId); + if (!behavior) { + console.error(`❌ NPC "${npcId}" not found or has no behavior.`); + return; + } + + console.log(`🧪 Testing with actual NPC behavior: ${npcId}\n`); + + // Test a few key directions + const testCases = [ + { dx: 100, dy: 0, expected: 'right' }, + { dx: -100, dy: 0, expected: 'left' }, + { dx: 0, dy: 100, expected: 'down' }, + { dx: 0, dy: -100, expected: 'up' }, + { dx: 100, dy: 100, expected: 'down-right' }, + { dx: -100, dy: -100, expected: 'up-left' } + ]; + + testCases.forEach(test => { + const result = behavior.calculateDirection(test.dx, test.dy); + const success = result === test.expected; + console.log(success ? '✅' : '❌', + `dx=${test.dx}, dy=${test.dy}: Expected ${test.expected}, Got ${result}`); + }); +} + +// Auto-run tests when imported +console.log('📦 Phase 2 Direction Tests Loaded'); +console.log('Run tests with: runDirectionTests()'); +console.log('Test with actual NPC: testWithActualBehavior("npc_id")'); + +// Export for use in console +window.runDirectionTests = runDirectionTests; +window.testWithActualBehavior = testWithActualBehavior; diff --git a/scenarios/test-npc-face-player.json b/scenarios/test-npc-face-player.json new file mode 100644 index 0000000..3a075fc --- /dev/null +++ b/scenarios/test-npc-face-player.json @@ -0,0 +1,228 @@ +{ + "scenario_brief": "Test scenario for NPC face player behavior - Phase 2", + "endGoal": "Test NPCs turning to face player in all 8 directions", + "startRoom": "test_face_player", + + "player": { + "id": "player", + "displayName": "Test Agent", + "spriteSheet": "hacker", + "spriteTalk": "assets/characters/hacker-talk.png", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + } + }, + + "rooms": { + "test_face_player": { + "type": "room_office", + "connections": {}, + "npcs": [ + { + "id": "npc_center", + "displayName": "Center NPC (Default Behavior)", + "npcType": "person", + "position": { "x": 5, "y": 5 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "_comment": "Default behavior - should face player when within 3 tiles (96px)" + }, + + { + "id": "npc_north", + "displayName": "North NPC", + "npcType": "person", + "position": { "x": 5, "y": 2 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned north of center - should face DOWN when player approaches from south" + }, + + { + "id": "npc_south", + "displayName": "South NPC", + "npcType": "person", + "position": { "x": 5, "y": 8 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned south of center - should face UP when player approaches from north" + }, + + { + "id": "npc_east", + "displayName": "East NPC", + "npcType": "person", + "position": { "x": 8, "y": 5 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned east of center - should face LEFT when player approaches from west" + }, + + { + "id": "npc_west", + "displayName": "West NPC", + "npcType": "person", + "position": { "x": 2, "y": 5 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned west of center - should face RIGHT when player approaches from east" + }, + + { + "id": "npc_northeast", + "displayName": "Northeast NPC", + "npcType": "person", + "position": { "x": 8, "y": 2 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned northeast - should face DOWN-LEFT when player approaches" + }, + + { + "id": "npc_northwest", + "displayName": "Northwest NPC", + "npcType": "person", + "position": { "x": 2, "y": 2 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned northwest - should face DOWN-RIGHT when player approaches" + }, + + { + "id": "npc_southeast", + "displayName": "Southeast NPC", + "npcType": "person", + "position": { "x": 8, "y": 8 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned southeast - should face UP-LEFT when player approaches" + }, + + { + "id": "npc_southwest", + "displayName": "Southwest NPC", + "npcType": "person", + "position": { "x": 2, "y": 8 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 96 + }, + "_comment": "Positioned southwest - should face UP-RIGHT when player approaches" + }, + + { + "id": "npc_far", + "displayName": "Far NPC (Out of Range)", + "npcType": "person", + "position": { "x": 1, "y": 1 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": true, + "facePlayerDistance": 64 + }, + "_comment": "Smaller range (2 tiles) - should NOT face player unless very close" + }, + + { + "id": "npc_disabled", + "displayName": "Disabled NPC (No Face Player)", + "npcType": "person", + "position": { "x": 9, "y": 1 }, + "spriteSheet": "hacker", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/test-npc.json", + "currentKnot": "start", + "behavior": { + "facePlayer": false + }, + "_comment": "Face player disabled - should NEVER turn to face player" + } + ] + } + } +}