@skewedaspect/sage 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +53 -0
  3. package/dist/classes/bindings/toggle.d.ts +122 -0
  4. package/dist/classes/bindings/trigger.d.ts +79 -0
  5. package/dist/classes/bindings/value.d.ts +104 -0
  6. package/dist/classes/entity.d.ts +83 -0
  7. package/dist/classes/eventBus.d.ts +94 -0
  8. package/dist/classes/gameEngine.d.ts +57 -0
  9. package/dist/classes/input/gamepad.d.ts +94 -0
  10. package/dist/classes/input/keyboard.d.ts +66 -0
  11. package/dist/classes/input/mouse.d.ts +80 -0
  12. package/dist/classes/input/readers/gamepad.d.ts +77 -0
  13. package/dist/classes/input/readers/keyboard.d.ts +60 -0
  14. package/dist/classes/input/readers/mouse.d.ts +45 -0
  15. package/dist/classes/loggers/consoleBackend.d.ts +29 -0
  16. package/dist/classes/loggers/nullBackend.d.ts +14 -0
  17. package/dist/engines/scene.d.ts +11 -0
  18. package/dist/interfaces/action.d.ts +20 -0
  19. package/dist/interfaces/binding.d.ts +144 -0
  20. package/dist/interfaces/entity.d.ts +9 -0
  21. package/dist/interfaces/game.d.ts +26 -0
  22. package/dist/interfaces/input.d.ts +181 -0
  23. package/dist/interfaces/logger.d.ts +88 -0
  24. package/dist/managers/binding.d.ts +185 -0
  25. package/dist/managers/entity.d.ts +70 -0
  26. package/dist/managers/game.d.ts +20 -0
  27. package/dist/managers/input.d.ts +56 -0
  28. package/dist/managers/level.d.ts +55 -0
  29. package/dist/sage.d.ts +20 -0
  30. package/dist/sage.es.js +2208 -0
  31. package/dist/sage.es.js.map +1 -0
  32. package/dist/sage.umd.js +2 -0
  33. package/dist/sage.umd.js.map +1 -0
  34. package/dist/utils/capabilities.d.ts +2 -0
  35. package/dist/utils/graphics.d.ts +10 -0
  36. package/dist/utils/logger.d.ts +66 -0
  37. package/dist/utils/physics.d.ts +2 -0
  38. package/dist/utils/version.d.ts +5 -0
  39. package/docs/architecture.md +129 -0
  40. package/docs/behaviors.md +706 -0
  41. package/docs/binding_system.md +820 -0
  42. package/docs/design/input.md +86 -0
  43. package/docs/entity_system.md +538 -0
  44. package/docs/eventbus.md +225 -0
  45. package/docs/getting_started.md +264 -0
  46. package/docs/images/sage_logo.png +0 -0
  47. package/docs/images/sage_logo_shape.png +0 -0
  48. package/docs/overview.md +38 -0
  49. package/docs/physics_system.md +686 -0
  50. package/docs/scene_system.md +513 -0
  51. package/package.json +69 -0
  52. package/src/classes/bindings/toggle.ts +261 -0
  53. package/src/classes/bindings/trigger.ts +211 -0
  54. package/src/classes/bindings/value.ts +227 -0
  55. package/src/classes/entity.ts +256 -0
  56. package/src/classes/eventBus.ts +259 -0
  57. package/src/classes/gameEngine.ts +125 -0
  58. package/src/classes/input/gamepad.ts +388 -0
  59. package/src/classes/input/keyboard.ts +189 -0
  60. package/src/classes/input/mouse.ts +276 -0
  61. package/src/classes/input/readers/gamepad.ts +179 -0
  62. package/src/classes/input/readers/keyboard.ts +123 -0
  63. package/src/classes/input/readers/mouse.ts +133 -0
  64. package/src/classes/loggers/consoleBackend.ts +135 -0
  65. package/src/classes/loggers/nullBackend.ts +51 -0
  66. package/src/engines/scene.ts +112 -0
  67. package/src/images/sage_logo.svg +172 -0
  68. package/src/images/sage_logo_shape.svg +146 -0
  69. package/src/interfaces/action.ts +30 -0
  70. package/src/interfaces/binding.ts +191 -0
  71. package/src/interfaces/entity.ts +21 -0
  72. package/src/interfaces/game.ts +44 -0
  73. package/src/interfaces/input.ts +221 -0
  74. package/src/interfaces/logger.ts +118 -0
  75. package/src/managers/binding.ts +729 -0
  76. package/src/managers/entity.ts +252 -0
  77. package/src/managers/game.ts +111 -0
  78. package/src/managers/input.ts +233 -0
  79. package/src/managers/level.ts +261 -0
  80. package/src/sage.ts +119 -0
  81. package/src/types/global.d.ts +11 -0
  82. package/src/utils/capabilities.ts +16 -0
  83. package/src/utils/graphics.ts +148 -0
  84. package/src/utils/logger.ts +225 -0
  85. package/src/utils/physics.ts +16 -0
  86. package/src/utils/version.ts +11 -0
@@ -0,0 +1,706 @@
1
+ # Behavior System Guide
2
+
3
+ ## Introduction
4
+
5
+ In the SAGE engine, behaviors are the "personality modules" that give entities their abilities and define how they interact with the world. Think of behaviors like special power discs from Tron - each one gives your entity unique capabilities that can be combined in creative ways.
6
+
7
+ This guide will walk you through creating and using behaviors, from basic concepts to advanced techniques. It serves as a detailed complement to the [Entity System Guide](entity_system.md), diving deeper into the behavior implementation specifics.
8
+
9
+ ## Behaviors: The Building Blocks of Functionality
10
+
11
+ A behavior in SAGE is a reusable component that:
12
+ - Has a unique name
13
+ - Listens for specific events
14
+ - Processes those events to update entity state
15
+ - May perform updates each frame
16
+ - Can emit new events for others to react to
17
+
18
+ While the [Entity System Guide](entity_system.md) provides an overview of how behaviors fit into the larger picture, this guide focuses specifically on implementing and combining behaviors effectively.
19
+
20
+ ## Behavior Lifecycle
21
+
22
+ When a behavior is attached to an entity, it goes through a specific lifecycle:
23
+
24
+ 1. **Initialization**: The behavior is instantiated and receives a reference to its parent entity
25
+ 2. **Subscription**: The behavior subscribes to any events it's interested in
26
+ 3. **Active Phase**: The behavior responds to events and executes update methods each frame
27
+ 4. **Detachment/Cleanup**: When removed, the behavior unsubscribes from events and cleans up any resources
28
+
29
+ All behaviors automatically handle this lifecycle, but it's important to understand it when developing custom behaviors.
30
+
31
+ ## Creating Your First Behavior
32
+
33
+ Let's create a simple behavior that makes an entity take damage and respond to healing:
34
+
35
+ ```typescript
36
+ import { GameEntityBehavior, GameEvent } from '@skewedaspect/sage';
37
+
38
+ // Define what state properties this behavior needs
39
+ interface HealthState {
40
+ currentHealth: number;
41
+ maxHealth: number;
42
+ isAlive: boolean;
43
+ }
44
+
45
+ export class HealthBehavior extends GameEntityBehavior<HealthState> {
46
+ // The unique name of this behavior
47
+ name = 'HealthBehavior';
48
+
49
+ // The event types this behavior listens for
50
+ eventSubscriptions = ['entity:damage', 'entity:heal'];
51
+
52
+ // Handle incoming events
53
+ processEvent(event: GameEvent, state: HealthState): boolean {
54
+ if (event.type === 'entity:damage') {
55
+ const damage = event.payload?.amount || 0;
56
+
57
+ // Reduce health by damage amount
58
+ state.currentHealth = Math.max(0, state.currentHealth - damage);
59
+
60
+ // Check if entity is now dead
61
+ if (state.currentHealth === 0 && state.isAlive) {
62
+ state.isAlive = false;
63
+
64
+ // Emit a death event for others to respond to
65
+ this.$emit({
66
+ type: 'entity:died',
67
+ payload: {
68
+ causeOfDeath: 'damage',
69
+ finalBlow: event.payload // Include info about what killed it
70
+ }
71
+ });
72
+ }
73
+
74
+ return true; // We handled this event
75
+ }
76
+
77
+ if (event.type === 'entity:heal') {
78
+ const healAmount = event.payload?.amount || 0;
79
+
80
+ // Only heal if alive
81
+ if (state.isAlive) {
82
+ // Add health, but don't exceed max
83
+ state.currentHealth = Math.min(
84
+ state.maxHealth,
85
+ state.currentHealth + healAmount
86
+ );
87
+
88
+ // Let others know healing occurred
89
+ this.$emit({
90
+ type: 'entity:healed',
91
+ payload: { amount: healAmount }
92
+ });
93
+ }
94
+
95
+ return true; // We handled this event
96
+ }
97
+
98
+ return false; // We didn't handle this event
99
+ }
100
+
101
+ // Optional: update method runs each frame
102
+ update(dt: number, state: HealthState): void {
103
+ // We could add regeneration over time here if desired
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## Core Behavior Methods
109
+
110
+ Every behavior must implement these key methods or properties:
111
+
112
+ ### 1. The `name` Property
113
+
114
+ ```typescript
115
+ name = 'MovementBehavior';
116
+ ```
117
+
118
+ Each behavior needs a unique name that identifies it. This name is used when:
119
+ - Detaching behaviors from entities
120
+ - Debugging behavior issues
121
+ - Managing behavior interactions
122
+
123
+ ### 2. The `eventSubscriptions` Property
124
+
125
+ ```typescript
126
+ eventSubscriptions = ['input:jump', 'physics:collision'];
127
+ ```
128
+
129
+ This array tells the system what events your behavior wants to listen for. When any of these events occur, your `processEvent` method will be called.
130
+
131
+ ### 3. The `processEvent` Method
132
+
133
+ ```typescript
134
+ processEvent(event: GameEvent, state: any): boolean {
135
+ // Handle the event
136
+ if (event.type === 'input:jump') {
137
+ // Process jump input
138
+ state.velocity.y = state.jumpPower;
139
+ return true; // We handled this event
140
+ }
141
+
142
+ return false; // We didn't handle this event
143
+ }
144
+ ```
145
+
146
+ This method is where your behavior responds to events. It receives:
147
+ - The event object with type and payload
148
+ - The entity's state object
149
+ - Returns `true` if the event was handled, `false` otherwise
150
+
151
+ The return value is important - if you return `true`, no other behaviors will process this event. This allows for "event filtering" where one behavior can intercept events before they reach others.
152
+
153
+ ### 4. The `update` Method (Optional)
154
+
155
+ ```typescript
156
+ update(dt: number, state: any): void {
157
+ // Update entity state based on time
158
+ state.position.x += state.velocity.x * dt;
159
+ state.position.y += state.velocity.y * dt;
160
+
161
+ // Apply gravity
162
+ state.velocity.y -= 9.8 * dt;
163
+ }
164
+ ```
165
+
166
+ This method runs every frame and lets your behavior apply continuous changes to the entity. The `dt` parameter represents the time (in seconds) since the last update, which helps create smooth, frame-rate independent movement.
167
+
168
+ ## Using Your Behavior
169
+
170
+ To use this behavior, you need to:
171
+
172
+ 1. Include it in an entity definition
173
+ 2. Ensure the entity state includes the properties it needs
174
+
175
+ ```typescript
176
+ // Register an entity definition with our behavior
177
+ gameEngine.managers.entityManager.registerEntityDefinition({
178
+ type: 'character:hero',
179
+ defaultState: {
180
+ name: 'Frodo',
181
+ currentHealth: 100, // Required by HealthBehavior
182
+ maxHealth: 100, // Required by HealthBehavior
183
+ isAlive: true, // Required by HealthBehavior
184
+ position: { x: 0, y: 0, z: 0 }
185
+ },
186
+ behaviors: [
187
+ HealthBehavior, // Add our health behavior
188
+ MovementBehavior // Add other behaviors as needed
189
+ ]
190
+ });
191
+
192
+ // Create the entity with additional initial state
193
+ const hero = gameEngine.managers.entityManager.createEntity('character:hero', { currentHealth: 80 });
194
+ ```
195
+
196
+ ## Integrating with the Entity System
197
+
198
+ While we've seen how to create individual behaviors, the real power comes from how they integrate with the entity system. As described in the [Entity System Guide](entity_system.md), behaviors are the functional building blocks that power entities.
199
+
200
+ Here's how behaviors connect with the broader entity architecture:
201
+
202
+ 1. **Entity Definitions**: When you register an entity type, you specify which behaviors it should have
203
+
204
+ 2. **State Sharing**: All behaviors attached to the same entity share access to that entity's state object
205
+
206
+ 3. **Event Flow**: When an event targets an entity, it flows through all behaviors in the order they were attached
207
+
208
+ 4. **Behavior Dependencies**: Some behaviors may depend on others (like an animation behavior responding to a movement behavior's changes)
209
+
210
+ ## Behavior Composition
211
+
212
+ The true power of behaviors comes from combining them. Let's see how multiple behaviors can work together:
213
+
214
+ ```typescript
215
+ // A behavior for inventory management
216
+ class InventoryBehavior extends GameEntityBehavior<{ items: any[] }> {
217
+ name = 'InventoryBehavior';
218
+ eventSubscriptions = ['item:pickup', 'item:use'];
219
+
220
+ processEvent(event: GameEvent, state: any): boolean {
221
+ if (event.type === 'item:pickup') {
222
+ state.items.push(event.payload.item);
223
+ return true;
224
+ }
225
+
226
+ if (event.type === 'item:use' && event.payload.itemIndex < state.items.length) {
227
+ const item = state.items[event.payload.itemIndex];
228
+
229
+ // Emit an event that something else might listen for
230
+ this.$emit({
231
+ type: 'item:effect',
232
+ payload: { item }
233
+ });
234
+
235
+ // If it's a one-time use item, remove it
236
+ if (item.consumable) {
237
+ state.items.splice(event.payload.itemIndex, 1);
238
+ }
239
+
240
+ return true;
241
+ }
242
+
243
+ return false;
244
+ }
245
+ }
246
+
247
+ // A behavior to handle potion effects
248
+ class PotionEffectBehavior extends GameEntityBehavior {
249
+ name = 'PotionEffectBehavior';
250
+ eventSubscriptions = ['item:effect'];
251
+
252
+ processEvent(event: GameEvent, state: any): boolean {
253
+ if (event.type === 'item:effect' && event.payload.item.type === 'potion') {
254
+ const potion = event.payload.item;
255
+
256
+ if (potion.effect === 'healing') {
257
+ // Re-emit as a healing event that HealthBehavior will handle
258
+ this.$emit({
259
+ type: 'entity:heal',
260
+ payload: { amount: potion.power }
261
+ });
262
+
263
+ return true;
264
+ }
265
+ }
266
+
267
+ return false;
268
+ }
269
+ }
270
+ ```
271
+
272
+ Now, when a character uses a healing potion:
273
+ 1. `InventoryBehavior` handles the item use and emits an `item:effect` event
274
+ 2. `PotionEffectBehavior` receives that event, sees it's a healing potion, and emits `entity:heal`
275
+ 3. `HealthBehavior` receives the `entity:heal` event and increases health accordingly
276
+
277
+ All this happens without any of these behaviors needing to know about each other directly!
278
+
279
+ ## Advanced Behavior Techniques
280
+
281
+ ### Dynamic Behavior Attachment
282
+
283
+ Behaviors can be attached or detached at runtime:
284
+
285
+ ```typescript
286
+ // Give a character temporary invincibility
287
+ const invincibilityBehavior = new InvincibilityBehavior();
288
+ hero.attachBehavior(invincibilityBehavior);
289
+
290
+ // Later, remove the behavior
291
+ setTimeout(() => {
292
+ hero.detachBehavior('InvincibilityBehavior');
293
+ console.log("Your star power has worn off!");
294
+ }, 10000);
295
+ ```
296
+
297
+ This dynamic ability to modify entity capabilities allows for:
298
+ - Power-ups and temporary abilities
299
+ - Status effects (like being stunned or poisoned)
300
+ - Progressive character development (learning new skills)
301
+ - Context-sensitive behaviors (different abilities in water vs. land)
302
+
303
+ ### State Dependencies
304
+
305
+ When designing behaviors, be explicit about what state properties they need:
306
+
307
+ ```typescript
308
+ // This behavior needs very specific state structure
309
+ class AiBehavior extends GameEntityBehavior<{
310
+ target: { id: string; } | null;
311
+ aiState: 'idle' | 'chase' | 'attack' | 'flee';
312
+ lastDecisionTime: number;
313
+ position: { x: number; y: number; z: number; };
314
+ }> {
315
+ name = 'AiBehavior';
316
+ // ...implementation...
317
+ }
318
+ ```
319
+
320
+ Using TypeScript interfaces as shown above creates a contract that:
321
+ - Documents what state your behavior requires
322
+ - Provides type safety and autocomplete when accessing state properties
323
+ - Makes it clear to other developers what your behavior depends on
324
+
325
+ ### Behavior Initialization and Cleanup
326
+
327
+ For behaviors that need to set up resources or perform cleanup:
328
+
329
+ ```typescript
330
+ class NetworkBehavior extends GameEntityBehavior {
331
+ name = 'NetworkBehavior';
332
+ private connection: WebSocket | null = null;
333
+
334
+ // Called when attached to an entity
335
+ onAttached(): void {
336
+ super.onAttached();
337
+
338
+ // Set up network connection
339
+ this.connection = new WebSocket('wss://game-server.example.com');
340
+ this.connection.onmessage = this.handleMessage.bind(this);
341
+ }
342
+
343
+ // Called when detached from an entity
344
+ onDetached(): void {
345
+ // Clean up network resources
346
+ if (this.connection) {
347
+ this.connection.close();
348
+ this.connection = null;
349
+ }
350
+
351
+ super.onDetached();
352
+ }
353
+
354
+ private handleMessage(msg: MessageEvent): void {
355
+ // Process network messages
356
+ // ...
357
+ }
358
+ }
359
+ ```
360
+
361
+ Always remember to call the parent class methods (`super.onAttached()` and `super.onDetached()`) to ensure proper behavior lifecycle management.
362
+
363
+ ### Communication Patterns
364
+
365
+ Behaviors can communicate in several ways:
366
+
367
+ 1. **Direct State Modification**: Behaviors that share an entity can access the same state
368
+ 2. **Event Broadcasting**: Emit events that other behaviors respond to
369
+ 3. **Entity References**: Store references to other entities in state
370
+
371
+ Each approach has different tradeoffs:
372
+
373
+ ```typescript
374
+ // Direct state modification - simple but creates tight coupling
375
+ class MovementBehavior extends GameEntityBehavior {
376
+ update(dt: number, state: any): void {
377
+ state.position.x += state.velocity.x * dt;
378
+ // Animation system can read directly from state
379
+ state.isMoving = Math.abs(state.velocity.x) > 0.1;
380
+ }
381
+ }
382
+
383
+ class AnimationBehavior extends GameEntityBehavior {
384
+ update(dt: number, state: any): void {
385
+ // Directly reads state set by movement behavior
386
+ if (state.isMoving) {
387
+ playAnimation('walk');
388
+ } else {
389
+ playAnimation('idle');
390
+ }
391
+ }
392
+ }
393
+
394
+ // Event broadcasting - more decoupled but less direct
395
+ class MovementBehavior extends GameEntityBehavior {
396
+ update(dt: number, state: any): void {
397
+ const oldX = state.position.x;
398
+ state.position.x += state.velocity.x * dt;
399
+
400
+ // Emit event only when movement state changes
401
+ const isMovingNow = Math.abs(state.velocity.x) > 0.1;
402
+ if (isMovingNow !== state.isMoving) {
403
+ state.isMoving = isMovingNow;
404
+ this.$emit({
405
+ type: 'entity:movementChanged',
406
+ payload: { isMoving: state.isMoving }
407
+ });
408
+ }
409
+ }
410
+ }
411
+
412
+ class AnimationBehavior extends GameEntityBehavior {
413
+ eventSubscriptions = ['entity:movementChanged'];
414
+
415
+ processEvent(event: GameEvent, state: any): boolean {
416
+ if (event.type === 'entity:movementChanged') {
417
+ if (event.payload.isMoving) {
418
+ playAnimation('walk');
419
+ } else {
420
+ playAnimation('idle');
421
+ }
422
+ return true;
423
+ }
424
+ return false;
425
+ }
426
+ }
427
+ ```
428
+
429
+ ### Behavior Priorities
430
+
431
+ The order behaviors are attached matters! Events are processed in attachment order, and any behavior can stop further processing by returning `true` from `processEvent`.
432
+
433
+ ```typescript
434
+ // Shield behavior might intercept damage before it reaches health
435
+ const entityDef = {
436
+ type: 'character:tank',
437
+ initialState: { /* ... */ },
438
+ behaviors: [
439
+ ShieldBehavior, // First chance to handle damage
440
+ ArmorBehavior, // Second chance to reduce damage
441
+ HealthBehavior // Finally, apply any remaining damage
442
+ ]
443
+ };
444
+ ```
445
+
446
+ This ordering enables powerful patterns like:
447
+ - Damage reduction chains (shields → armor → health)
448
+ - Input filtering (disable player control when stunned)
449
+ - Event transformation (convert raw inputs to game commands)
450
+
451
+ ## Specialized Behavior Types
452
+
453
+ Beyond basic behaviors, you can create specialized types for common tasks:
454
+
455
+ ### Physics Behaviors
456
+
457
+ ```typescript
458
+ class PhysicsBodyBehavior extends GameEntityBehavior {
459
+ name = 'PhysicsBodyBehavior';
460
+ private body: PhysicsBody | null = null;
461
+
462
+ onAttached(): void {
463
+ super.onAttached();
464
+
465
+ // Create physics body in the physics engine
466
+ this.body = physicsEngine.createBody({
467
+ type: 'dynamic',
468
+ position: this.entity.state.position,
469
+ mass: this.entity.state.mass || 1
470
+ });
471
+ }
472
+
473
+ update(dt: number, state: any): void {
474
+ if (this.body) {
475
+ // Sync physics engine position to entity
476
+ const pos = this.body.getPosition();
477
+ state.position.x = pos.x;
478
+ state.position.y = pos.y;
479
+ }
480
+ }
481
+
482
+ onDetached(): void {
483
+ // Remove body from physics engine
484
+ if (this.body) {
485
+ physicsEngine.removeBody(this.body);
486
+ this.body = null;
487
+ }
488
+
489
+ super.onDetached();
490
+ }
491
+ }
492
+ ```
493
+
494
+ ### Input Behaviors
495
+
496
+ ```typescript
497
+ class PlayerInputBehavior extends GameEntityBehavior {
498
+ name = 'PlayerInputBehavior';
499
+ eventSubscriptions = ['input:keydown', 'input:keyup', 'input:gamepad'];
500
+
501
+ processEvent(event: GameEvent, state: any): boolean {
502
+ if (event.type === 'input:keydown') {
503
+ // Map keyboard input to game actions
504
+ switch (event.payload.key) {
505
+ case 'ArrowLeft':
506
+ state.movement = 'left';
507
+ this.$emit({ type: 'player:move', payload: { direction: 'left' } });
508
+ return true;
509
+ case 'ArrowRight':
510
+ state.movement = 'right';
511
+ this.$emit({ type: 'player:move', payload: { direction: 'right' } });
512
+ return true;
513
+ case 'Space':
514
+ this.$emit({ type: 'player:jump' });
515
+ return true;
516
+ }
517
+ }
518
+
519
+ if (event.type === 'input:keyup') {
520
+ // Handle key release
521
+ switch (event.payload.key) {
522
+ case 'ArrowLeft':
523
+ case 'ArrowRight':
524
+ state.movement = 'none';
525
+ this.$emit({ type: 'player:stopMove' });
526
+ return true;
527
+ }
528
+ }
529
+
530
+ return false;
531
+ }
532
+ }
533
+ ```
534
+
535
+ ### AI Behaviors
536
+
537
+ ```typescript
538
+ class SimpleEnemyAI extends GameEntityBehavior {
539
+ name = 'SimpleEnemyAI';
540
+ eventSubscriptions = ['entity:spotted', 'entity:lostTarget'];
541
+
542
+ processEvent(event: GameEvent, state: any): boolean {
543
+ if (event.type === 'entity:spotted' && event.payload.type === 'player') {
544
+ state.target = event.payload.id;
545
+ state.aiState = 'chase';
546
+ return true;
547
+ }
548
+
549
+ if (event.type === 'entity:lostTarget') {
550
+ state.target = null;
551
+ state.aiState = 'patrol';
552
+ return true;
553
+ }
554
+
555
+ return false;
556
+ }
557
+
558
+ update(dt: number, state: any): void {
559
+ switch (state.aiState) {
560
+ case 'idle':
561
+ // Just stand around
562
+ break;
563
+
564
+ case 'patrol':
565
+ // Move along patrol path
566
+ this.updatePatrolBehavior(dt, state);
567
+ break;
568
+
569
+ case 'chase':
570
+ // Chase target if we have one
571
+ if (state.target) {
572
+ this.chaseTarget(dt, state);
573
+ }
574
+ break;
575
+ }
576
+ }
577
+
578
+ private updatePatrolBehavior(dt: number, state: any): void {
579
+ // Patrol logic implementation
580
+ // ...
581
+ }
582
+
583
+ private chaseTarget(dt: number, state: any): void {
584
+ // Chase target logic
585
+ // ...
586
+ }
587
+ }
588
+ ```
589
+
590
+ ## Testing Behaviors
591
+
592
+ Behaviors are highly testable in isolation:
593
+
594
+ ```typescript
595
+ import { expect } from 'chai';
596
+ import { HealthBehavior } from './behaviors/health';
597
+
598
+ describe('HealthBehavior', () => {
599
+ it('should reduce health when damaged', () => {
600
+ const behavior = new HealthBehavior();
601
+ const state = { currentHealth: 100, maxHealth: 100, isAlive: true };
602
+
603
+ // Mock the $emit method
604
+ behavior.$emit = () => {};
605
+
606
+ // Process a damage event
607
+ behavior.processEvent({
608
+ type: 'entity:damage',
609
+ payload: { amount: 30 }
610
+ }, state);
611
+
612
+ expect(state.currentHealth).to.equal(70);
613
+ expect(state.isAlive).to.be.true;
614
+ });
615
+
616
+ it('should emit death event when health reaches zero', () => {
617
+ const behavior = new HealthBehavior();
618
+ const state = { currentHealth: 30, maxHealth: 100, isAlive: true };
619
+ let emittedEvent = null;
620
+
621
+ // Mock the $emit method
622
+ behavior.$emit = (event) => { emittedEvent = event; };
623
+
624
+ // Process a lethal damage event
625
+ behavior.processEvent({
626
+ type: 'entity:damage',
627
+ payload: { amount: 50 }
628
+ }, state);
629
+
630
+ expect(state.currentHealth).to.equal(0);
631
+ expect(state.isAlive).to.be.false;
632
+ expect(emittedEvent).to.not.be.null;
633
+ expect(emittedEvent.type).to.equal('entity:died');
634
+ });
635
+
636
+ // More tests...
637
+ });
638
+ ```
639
+
640
+ ## Designing Event-Driven Behavior Systems
641
+
642
+ When creating a complex game with many behaviors, consider these design patterns:
643
+
644
+ ### Event Vocabulary
645
+
646
+ Develop a consistent pattern for event naming:
647
+
648
+ ```
649
+ category:action
650
+ ```
651
+
652
+ Examples:
653
+ - `player:move` - Player initiated movement
654
+ - `entity:damage` - Entity received damage
655
+ - `item:pickup` - Item was picked up
656
+ - `scene:loaded` - Scene finished loading
657
+
658
+ This consistency makes it easier to understand event flows and predict event names.
659
+
660
+ ### Behavior Composition Patterns
661
+
662
+ Several patterns emerge when combining behaviors:
663
+
664
+ 1. **Filter Chain**: Behaviors process events in sequence, with each potentially modifying or blocking the event
665
+ - Example: Input → ActionValidation → ActionExecution
666
+
667
+ 2. **Observer Pattern**: Multiple independent behaviors respond to the same events
668
+ - Example: Achievement, Sound, and Animation behaviors all listening for "enemy:defeated"
669
+
670
+ 3. **Command Pattern**: Behaviors emit high-level command events that other behaviors execute
671
+ - Example: AI emits "movement:request" that MovementBehavior handles
672
+
673
+ 4. **State Machine**: Behaviors implement state transitions and state-specific logic
674
+ - Example: Character states like "idle", "walking", "jumping", "attacking"
675
+
676
+ ## Best Practices
677
+
678
+ 1. **Single Responsibility**: Each behavior should do one thing well
679
+ 2. **Clear Dependencies**: Be explicit about what state properties a behavior needs
680
+ 3. **Use TypeScript Interfaces**: Define interfaces for your state requirements
681
+ 4. **Document Event Types**: Keep a registry of event types and their payload formats
682
+ 5. **Behavior Composition**: Solve complex problems by combining simple behaviors
683
+ 6. **Avoid Direct References**: Prefer events over direct references between behaviors
684
+ 7. **Test in Isolation**: Write unit tests for individual behaviors
685
+ 8. **Watch Event Performance**: Monitor high-frequency events that might impact performance
686
+
687
+ ## Common Behavior Types
688
+
689
+ Some behaviors you might want to implement in your games:
690
+
691
+ - **MovementBehavior**: Handle entity position and velocity
692
+ - **HealthBehavior**: Manage damage and healing
693
+ - **InputBehavior**: Process player inputs
694
+ - **AiBehavior**: Control NPC decision making
695
+ - **CombatBehavior**: Handle attack and defense logic
696
+ - **PhysicsBehavior**: Interface with the physics engine
697
+ - **AnimationBehavior**: Control visual animations
698
+ - **SoundBehavior**: Manage entity sound effects
699
+ - **InteractionBehavior**: Handle entity interactions with the world
700
+ - **InventoryBehavior**: Manage collected items
701
+
702
+ ## Conclusion
703
+
704
+ Behaviors are the heart of the SAGE entity system. By creating small, focused behaviors and combining them in different ways, you can build complex game objects with minimal code duplication. Remember that the key to good behavior design is composition - build simple behaviors that do one thing well, then combine them to create rich, interactive entities.
705
+
706
+ For more information on how behaviors fit into the overall entity architecture, see the [Entity System Guide](entity_system.md). To understand the event system that powers behavior communication, check out the [Event Bus Guide](eventbus.md).