bloody-engine 1.0.10 → 1.1.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.
package/README.md CHANGED
@@ -4,9 +4,11 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
4
4
 
5
5
  ## Features
6
6
 
7
+ - **Structure of Arrays (SoA)** - Entity storage with typed arrays for zero-copy GPU transfers and cache-friendly access
7
8
  - **2.5D Rendering** - Optimized for isometric and dimetric projections with depth sorting
8
9
  - **Server-Side Rendering** - Headless WebGL rendering on Node.js using `gl` and `@kmamal/sdl`
9
10
  - **Batch Rendering** - Efficient sprite batching with GPU-accelerated transformations
11
+ - **Persistent Buffer Mapping** - WebGL2 zero-copy GPU transfers for maximum performance
10
12
  - **Resource Management** - Unified asset loading pipeline for textures and shaders
11
13
  - **Input System** - Command queue pattern supporting SDL and network input sources
12
14
  - **Networking** - Client-side prediction, server reconciliation, and state synchronization
@@ -15,6 +17,7 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
15
17
  - **TypeScript** - Fully typed for excellent developer experience
16
18
  - **Object Pooling** - Memory-efficient object reuse patterns
17
19
  - **Window Management** - SDL-based window creation for interactive applications
20
+ - **Custom Properties** - Opt-in extensible system for game-specific entity properties
18
21
 
19
22
  ## Installation
20
23
 
@@ -22,6 +25,38 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
22
25
  npm install bloody-engine
23
26
  ```
24
27
 
28
+ ## Understanding Coordinate Systems
29
+
30
+ **⚠️ IMPORTANT**: Before building your game, understand the coordinate systems to avoid inverted controls!
31
+
32
+ Bloody Engine uses different coordinate systems for different purposes. Mixing these up is the #1 cause of inverted controls.
33
+
34
+ ### Quick Summary
35
+
36
+ | System | Used For | Y-Axis | Example |
37
+ |--------|----------|--------|---------|
38
+ | **Grid Space** | Game logic, entity positions | Y-UP (↓ Y = North/Up) | `entity.move(0, -1, 0)` moves up on screen |
39
+ | **Screen Space** | Rendering, camera, mouse | Y-DOWN (↓ Y = Down) | `camera.y += 10` moves camera down |
40
+
41
+ **Golden Rule**: Use grid space for game logic, transform to screen space only for rendering.
42
+
43
+ ### Common Mistake
44
+
45
+ ❌ **Wrong**: `camera.y += 1` for "up" movement (moves down on screen!)
46
+ ✅ **Right**: Use direction deltas: `entity.move(0, -1, 0)` for North
47
+
48
+ ### WASD Controls
49
+
50
+ | Key | Direction | Delta | Screen Effect |
51
+ |-----|-----------|-------|---------------|
52
+ | **W** / ↑ | North | `{dx: 0, dy: -1}` | ✅ Up |
53
+ | **S** / ↓ | South | `{dx: 0, dy: 1}` | ✅ Down |
54
+ | **A** / ← | West | `{dx: -1, dy: 0}` | ✅ Left |
55
+ | **D** / → | East | `{dx: 1, dy: 0}` | ✅ Right |
56
+
57
+ 📖 **Full Guide**: [docs/COORDINATE_SYSTEMS.md](docs/COORDINATE_SYSTEMS.md)
58
+ 🚀 **Interactive Demo**: Run `npm run demo:coordinates` after building
59
+
25
60
  ## API Overview
26
61
 
27
62
  ### Core Graphics
@@ -64,9 +99,12 @@ npm install bloody-engine
64
99
 
65
100
  | Class | Description |
66
101
  |-------|-------------|
67
- | [Ticker](src/core/ticker.ts) | Fixed timestep game loop |
68
- | [Entity](src/simulation/entity.ts) / [EntityManager](src/simulation/entity-manager.ts) | Entity component system |
102
+ | [EntityStorage](src/simulation/entity-storage.ts) | SoA storage with typed arrays for high-performance entity data |
103
+ | [EntityHandle](src/simulation/entity-handle.ts) | Opaque handles for safe entity references |
104
+ | [EntityTypeRegistry](src/simulation/entity-type-registry.ts) | Type string to ID mapping for storage efficiency |
105
+ | [Entity](src/simulation/entity.ts) / [EntityManager](src/simulation/entity-manager.ts) | Entity component system (now uses SoA storage internally) |
69
106
  | [SimulationLoop](src/simulation/simulation-loop.ts) | Deterministic game logic simulation |
107
+ | [SoaWebGLRenderer](src/rendering/soa-webgl-renderer.ts) | WebGL2 renderer with persistent buffer mapping |
70
108
  | [ClientPredictor](src/networking/client-predictor.ts) | Client-side prediction for lag compensation |
71
109
  | [ServerReconciler](src/networking/server-reconciler.ts) | Server-side reconciliation |
72
110
  | [StateSnapshot](src/networking/state-snapshot.ts) | World state serialization |
@@ -226,32 +264,46 @@ const metrics = ticker.getMetrics();
226
264
  console.log(`FPS: ${metrics.fps}, Delta Time: ${metrics.deltaTime}s`);
227
265
  ```
228
266
 
229
- ### Entity System
267
+ ### Entity System (SoA Architecture)
268
+
269
+ The engine now uses Structure of Arrays (SoA) for entity storage, providing:
270
+ - **Zero-copy GPU transfers** - Direct typed array uploads to WebGL buffers
271
+ - **Better cache locality** - Sequential memory access patterns
272
+ - **SIMD-ready** - Data layout enables future vectorization
273
+ - **Extensible properties** - Add custom typed arrays for game-specific data
230
274
 
231
275
  ```typescript
232
- import { Entity, EntityManager, type EntityState } from 'bloody-engine';
276
+ import { EntityManager, type EntityState } from 'bloody-engine';
233
277
 
234
- // Create entity
235
- const entity = new Entity({
236
- id: 'player-1',
237
- x: 0, y: 0, z: 0,
238
- rotation: 0,
239
- type: 'player',
240
- health: 100
278
+ // Create entity manager (uses SoA storage internally)
279
+ const manager = new EntityManager();
280
+
281
+ // Create entity with initial state
282
+ const player = manager.createEntity("player", {
283
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 5 },
284
+ velocity: { x: 1, y: 0, z: 0 },
285
+ speed: 2.5
241
286
  });
242
287
 
243
- // Create entity manager
244
- const manager = new EntityManager();
245
- manager.add(entity);
288
+ // All existing methods work unchanged (full backward compatibility)
289
+ player.setGridPos(50, 60, 10);
290
+ player.move(5, 5, 0);
291
+ player.setVelocity(2, 1, 0);
246
292
 
247
293
  // Query entities
248
- const players = manager.query({ type: 'player' });
249
- const nearby = manager.queryInRegion(0, 0, 100);
294
+ const players = manager.getEntitiesByType("player");
295
+ const nearby = manager.getEntitiesInRange(50, 60, 100);
250
296
 
251
- // Update entity
252
- entity.x += 10;
253
- entity.updatedAt = Date.now();
254
- manager.update(entity);
297
+ // Register custom properties (opt-in extension)
298
+ manager.registerCustomProperty("health", Float32Array);
299
+ manager.registerCustomProperty("stamina", Uint32Array);
300
+
301
+ // Access SoA storage directly for advanced use
302
+ const storage = manager.getStorage();
303
+ const handle = (player as any).getHandle();
304
+
305
+ // Set custom property
306
+ storage.setCustomProperty(handle.index, "health", 100);
255
307
  ```
256
308
 
257
309
  ### Input System with Command Queue
@@ -404,6 +456,131 @@ batchRenderer.addQuad({
404
456
  });
405
457
  ```
406
458
 
459
+ ### SoA Deep Dive: Zero-Copy GPU Rendering
460
+
461
+ The Structure of Arrays (SoA) architecture enables direct GPU transfers without intermediate copying:
462
+
463
+ ```typescript
464
+ import { EntityStorage, SoaWebGLRenderer, Shader } from 'bloody-engine';
465
+
466
+ // Create SoA storage and populate with entities
467
+ const storage = new EntityStorage(10000);
468
+ // ... add entities ...
469
+
470
+ // Create WebGL2 renderer with persistent buffer mapping
471
+ const gl = device.getGLContext() as WebGL2RenderingContext;
472
+ const shader = device.createShader(vertexSource, fragmentSource);
473
+ const renderer = new SoaWebGLRenderer(gl, shader, 10000);
474
+
475
+ // Initialize persistent buffers (maps GPU memory to CPU arrays)
476
+ renderer.initialize(storage);
477
+
478
+ // In your render loop:
479
+ function render() {
480
+ // Zero-copy: Update GPU memory directly
481
+ renderer.render(storage);
482
+
483
+ // GPU sees changes immediately (coherent mapping)
484
+ device.present();
485
+ }
486
+ ```
487
+
488
+ **Performance Benefits:**
489
+ - **No bufferSubData overhead**: Direct CPU→GPU memory writes
490
+ - **Better cache locality**: Sequential access to entity data
491
+ - **SIMD-ready**: Data layout enables future vectorization
492
+ - **Memory efficient**: Typed arrays use 2-4x less memory than objects
493
+
494
+ ### Custom Properties Extension
495
+
496
+ Add game-specific properties without modifying core classes:
497
+
498
+ ```typescript
499
+ import { EntityManager, Float32Array, Uint32Array } from 'bloody-engine';
500
+
501
+ const manager = new EntityManager();
502
+
503
+ // Register custom properties (opt-in)
504
+ manager.registerCustomProperty('health', Float32Array); // Float values
505
+ manager.registerCustomProperty('mana', Uint32Array); // Integer values
506
+ manager.registerCustomProperty('xp', Float32Array); // Experience points
507
+
508
+ // Create entity
509
+ const player = manager.createEntity('player');
510
+
511
+ // Access storage to set custom properties
512
+ const storage = manager.getStorage();
513
+ const handle = (player as any).getHandle();
514
+
515
+ storage.setCustomProperty(handle.index, 'health', 100.0);
516
+ storage.setCustomProperty(handle.index, 'mana', 50);
517
+ storage.setCustomProperty(handle.index, 'xp', 0);
518
+
519
+ // Bulk update all entities (cache-efficient)
520
+ const allHealth = storage.getCustomPropertyArray('health');
521
+ for (let i = 0; i < storage.getCount(); i++) {
522
+ allHealth[i] += 10; // Regenerate health for all entities
523
+ }
524
+ ```
525
+
526
+ ### SoA Memory Layout
527
+
528
+ Understanding the SoA memory layout helps with performance optimization:
529
+
530
+ ```typescript
531
+ // Entity 0 data at indices 0-2
532
+ positions[0] = entity0.x
533
+ positions[1] = entity0.y
534
+ positions[2] = entity0.z
535
+
536
+ // Entity 1 data at indices 3-5
537
+ positions[3] = entity1.x
538
+ positions[4] = entity1.y
539
+ positions[5] = entity1.z
540
+
541
+ // Same pattern for all properties:
542
+ // - velocities: [vx0, vy0, vz0, vx1, vy1, vz1, ...]
543
+ // - colors: [r0, g0, b0, a0, r1, g1, b1, a1, ...]
544
+ // - rotations: [rot0, rot1, rot2, ...]
545
+ // - textureIds: [id0, id1, id2, ...]
546
+ ```
547
+
548
+ This layout enables:
549
+ - **Zero-copy views**: `positions.subarray(0, entityCount * 3)` → GPU
550
+ - **Bulk updates**: Loop through contiguous memory
551
+ - **Cache efficiency**: Predictable access patterns
552
+
553
+ ### Migration from AoS to SoA
554
+
555
+ If you have existing code using the old Array-of-Structures pattern:
556
+
557
+ **Before (AoS - deprecated):**
558
+ ```typescript
559
+ // This no longer works
560
+ const entity = new Entity("player1", "player", {
561
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 0 }
562
+ });
563
+ ```
564
+
565
+ **After (SoA - current):**
566
+ ```typescript
567
+ // Use EntityManager factory
568
+ const manager = new EntityManager();
569
+ const entity = manager.createEntity("player", {
570
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 0 }
571
+ });
572
+
573
+ // Everything else works the same!
574
+ entity.setGridPos(50, 60, 10);
575
+ entity.move(5, 5, 0);
576
+ entity.setVelocity(1, 0, 0);
577
+ ```
578
+
579
+ **Breaking Changes:**
580
+ - Direct `new Entity()` construction is no longer supported
581
+ - Use `EntityManager.createEntity()` for all entity creation
582
+ - Deserialization: Use `EntityManager.deserializeAll()` instead of `Entity.deserialize()`
583
+
407
584
  ## Advanced Examples
408
585
 
409
586
  ### Networked Game Architecture
@@ -483,7 +660,7 @@ console.log('Deterministic:', JSON.stringify(state1) === JSON.stringify(state2))
483
660
 
484
661
  ## Testing
485
662
 
486
- The engine includes comprehensive tests for determinism and visual regression:
663
+ The engine includes comprehensive tests for determinism, visual regression, and SoA functionality:
487
664
 
488
665
  ```bash
489
666
  # Run all tests
@@ -528,6 +705,7 @@ src/
528
705
  │ ├── batch-renderer.ts
529
706
  │ ├── camera.ts
530
707
  │ ├── projection.ts
708
+ │ ├── soa-webgl-renderer.ts # WebGL2 zero-copy renderer
531
709
  │ └── spatial-hash.ts
532
710
  ├── input/ # Input system (command queue)
533
711
  │ ├── command-queue.ts
@@ -536,6 +714,9 @@ src/
536
714
  ├── simulation/ # Game logic simulation
537
715
  │ ├── entity.ts
538
716
  │ ├── entity-manager.ts
717
+ │ ├── entity-storage.ts # SoA storage with typed arrays
718
+ │ ├── entity-handle.ts # Handle-based entity references
719
+ │ ├── entity-type-registry.ts # Type string to ID mapping
539
720
  │ └── simulation-loop.ts
540
721
  ├── networking/ # Networking for multiplayer
541
722
  │ ├── client-predictor.ts
@@ -552,6 +733,7 @@ src/
552
733
  ### Key Concepts
553
734
 
554
735
  - **Separation of Concerns**: Rendering, input, simulation, and networking are completely separate systems
736
+ - **Structure of Arrays (SoA)**: Entity storage uses typed arrays for zero-copy GPU transfers and cache efficiency
555
737
  - **Deterministic Simulation**: Game logic runs in fixed timestep for consistency across clients
556
738
  - **Command Pattern**: All input goes through a command queue for easy recording/replay
557
739
  - **Client-Side Prediction**: Reduces perceived lag in networked games