bloody-engine 1.0.9 → 1.0.11

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
@@ -22,6 +22,38 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
22
22
  npm install bloody-engine
23
23
  ```
24
24
 
25
+ ## Understanding Coordinate Systems
26
+
27
+ **⚠️ IMPORTANT**: Before building your game, understand the coordinate systems to avoid inverted controls!
28
+
29
+ Bloody Engine uses different coordinate systems for different purposes. Mixing these up is the #1 cause of inverted controls.
30
+
31
+ ### Quick Summary
32
+
33
+ | System | Used For | Y-Axis | Example |
34
+ |--------|----------|--------|---------|
35
+ | **Grid Space** | Game logic, entity positions | Y-UP (↓ Y = North/Up) | `entity.move(0, -1, 0)` moves up on screen |
36
+ | **Screen Space** | Rendering, camera, mouse | Y-DOWN (↓ Y = Down) | `camera.y += 10` moves camera down |
37
+
38
+ **Golden Rule**: Use grid space for game logic, transform to screen space only for rendering.
39
+
40
+ ### Common Mistake
41
+
42
+ ❌ **Wrong**: `camera.y += 1` for "up" movement (moves down on screen!)
43
+ ✅ **Right**: Use direction deltas: `entity.move(0, -1, 0)` for North
44
+
45
+ ### WASD Controls
46
+
47
+ | Key | Direction | Delta | Screen Effect |
48
+ |-----|-----------|-------|---------------|
49
+ | **W** / ↑ | North | `{dx: 0, dy: -1}` | ✅ Up |
50
+ | **S** / ↓ | South | `{dx: 0, dy: 1}` | ✅ Down |
51
+ | **A** / ← | West | `{dx: -1, dy: 0}` | ✅ Left |
52
+ | **D** / → | East | `{dx: 1, dy: 0}` | ✅ Right |
53
+
54
+ 📖 **Full Guide**: [docs/COORDINATE_SYSTEMS.md](docs/COORDINATE_SYSTEMS.md)
55
+ 🚀 **Interactive Demo**: Run `npm run demo:coordinates` after building
56
+
25
57
  ## API Overview
26
58
 
27
59
  ### Core Graphics
@@ -3963,6 +3963,39 @@ class Entity {
3963
3963
  get previousState() {
3964
3964
  return this._previousState;
3965
3965
  }
3966
+ /**
3967
+ * Get position as floats (for smooth movement and rendering)
3968
+ * Returns a copy to prevent mutation
3969
+ */
3970
+ getPosition() {
3971
+ return {
3972
+ x: this._state.gridPos.xgrid,
3973
+ y: this._state.gridPos.ygrid,
3974
+ z: this._state.gridPos.zheight
3975
+ };
3976
+ }
3977
+ /**
3978
+ * Get grid position as integers (for logic that needs discrete cells)
3979
+ * Uses Math.floor for consistent cell mapping
3980
+ */
3981
+ getGridPos() {
3982
+ return {
3983
+ x: Math.floor(this._state.gridPos.xgrid),
3984
+ y: Math.floor(this._state.gridPos.ygrid),
3985
+ z: Math.floor(this._state.gridPos.zheight)
3986
+ };
3987
+ }
3988
+ /**
3989
+ * Get rounded grid position (nearest integer)
3990
+ * Uses Math.round for picking/clicking operations
3991
+ */
3992
+ getRoundedGridPos() {
3993
+ return {
3994
+ x: Math.round(this._state.gridPos.xgrid),
3995
+ y: Math.round(this._state.gridPos.ygrid),
3996
+ z: Math.round(this._state.gridPos.zheight)
3997
+ };
3998
+ }
3966
3999
  /**
3967
4000
  * Store current state as previous state before updating
3968
4001
  */
@@ -3975,10 +4008,18 @@ class Entity {
3975
4008
  }
3976
4009
  /**
3977
4010
  * Set grid position directly (instant movement)
4011
+ * Accepts floats for sub-grid positioning
3978
4012
  */
3979
4013
  setGridPos(x, y, z = 0) {
3980
4014
  this._state.gridPos = { xgrid: x, ygrid: y, zheight: z };
3981
4015
  }
4016
+ /**
4017
+ * Set position using integers (for discrete grid movement)
4018
+ * Convenience method for logic that works with integer coordinates
4019
+ */
4020
+ setGridPosInt(x, y, z = 0) {
4021
+ this._state.gridPos = { xgrid: x, ygrid: y, zheight: z };
4022
+ }
3982
4023
  /**
3983
4024
  * Set velocity for continuous movement
3984
4025
  */
@@ -3987,13 +4028,26 @@ class Entity {
3987
4028
  this._state.isMoving = x !== 0 || y !== 0 || z !== 0;
3988
4029
  }
3989
4030
  /**
3990
- * Move by grid coordinates
4031
+ * Move by relative amount
4032
+ * Accepts floats for smooth, sub-grid movement
4033
+ * @param dx Movement in X (can be fractional)
4034
+ * @param dy Movement in Y (can be fractional)
4035
+ * @param dz Movement in Z (can be fractional)
3991
4036
  */
3992
4037
  move(dx, dy, dz = 0) {
3993
4038
  this._state.gridPos.xgrid += dx;
3994
4039
  this._state.gridPos.ygrid += dy;
3995
4040
  this._state.gridPos.zheight += dz;
3996
4041
  }
4042
+ /**
4043
+ * Move by integer grid cells (for discrete movement)
4044
+ * Convenience method for logic that needs whole-cell movement
4045
+ */
4046
+ moveGridCells(dx, dy, dz = 0) {
4047
+ this._state.gridPos.xgrid += Math.floor(dx);
4048
+ this._state.gridPos.ygrid += Math.floor(dy);
4049
+ this._state.gridPos.zheight += Math.floor(dz);
4050
+ }
3997
4051
  /**
3998
4052
  * Set rotation angle (in radians)
3999
4053
  */
@@ -4008,7 +4062,8 @@ class Entity {
4008
4062
  }
4009
4063
  /**
4010
4064
  * Update entity based on velocity and delta time
4011
- * Returns true if position changed
4065
+ * Now properly tracks fractional movement for smooth animation
4066
+ * Returns true if position changed (by any amount)
4012
4067
  */
4013
4068
  updateVelocity(dt) {
4014
4069
  if (!this._state.isMoving) {
@@ -4017,15 +4072,10 @@ class Entity {
4017
4072
  const dx = this._state.velocity.x * this._state.speed * dt;
4018
4073
  const dy = this._state.velocity.y * this._state.speed * dt;
4019
4074
  const dz = this._state.velocity.z * this._state.speed * dt;
4020
- if (Math.abs(dx) >= 1 || Math.abs(dy) >= 1 || Math.abs(dz) >= 1) {
4021
- this.move(
4022
- Math.sign(dx),
4023
- Math.sign(dy),
4024
- Math.sign(dz)
4025
- );
4026
- return true;
4027
- }
4028
- return false;
4075
+ this._state.gridPos.xgrid += dx;
4076
+ this._state.gridPos.ygrid += dy;
4077
+ this._state.gridPos.zheight += dz;
4078
+ return dx !== 0 || dy !== 0 || dz !== 0;
4029
4079
  }
4030
4080
  /**
4031
4081
  * Clone this entity (for state snapshots)
@@ -4041,6 +4091,7 @@ class Entity {
4041
4091
  }
4042
4092
  /**
4043
4093
  * Serialize entity state for transmission/saving
4094
+ * Preserves floating-point positions for smooth movement
4044
4095
  */
4045
4096
  serialize() {
4046
4097
  return JSON.stringify({
@@ -2,6 +2,9 @@ import { GridCoord } from '../rendering/projection';
2
2
  /**
3
3
  * Entity state at a specific point in time
4
4
  * Used for state interpolation and deterministic replay
5
+ *
6
+ * Position is stored as floats for smooth, continuous movement.
7
+ * Use getGridPos() to get integer grid coordinates when needed.
5
8
  */
6
9
  export interface EntityState {
7
10
  gridPos: GridCoord;
@@ -32,22 +35,64 @@ export declare class Entity {
32
35
  * Get previous entity state (for interpolation)
33
36
  */
34
37
  get previousState(): Readonly<EntityState>;
38
+ /**
39
+ * Get position as floats (for smooth movement and rendering)
40
+ * Returns a copy to prevent mutation
41
+ */
42
+ getPosition(): {
43
+ x: number;
44
+ y: number;
45
+ z: number;
46
+ };
47
+ /**
48
+ * Get grid position as integers (for logic that needs discrete cells)
49
+ * Uses Math.floor for consistent cell mapping
50
+ */
51
+ getGridPos(): {
52
+ x: number;
53
+ y: number;
54
+ z: number;
55
+ };
56
+ /**
57
+ * Get rounded grid position (nearest integer)
58
+ * Uses Math.round for picking/clicking operations
59
+ */
60
+ getRoundedGridPos(): {
61
+ x: number;
62
+ y: number;
63
+ z: number;
64
+ };
35
65
  /**
36
66
  * Store current state as previous state before updating
37
67
  */
38
68
  saveState(): void;
39
69
  /**
40
70
  * Set grid position directly (instant movement)
71
+ * Accepts floats for sub-grid positioning
41
72
  */
42
73
  setGridPos(x: number, y: number, z?: number): void;
74
+ /**
75
+ * Set position using integers (for discrete grid movement)
76
+ * Convenience method for logic that works with integer coordinates
77
+ */
78
+ setGridPosInt(x: number, y: number, z?: number): void;
43
79
  /**
44
80
  * Set velocity for continuous movement
45
81
  */
46
82
  setVelocity(x: number, y: number, z?: number): void;
47
83
  /**
48
- * Move by grid coordinates
84
+ * Move by relative amount
85
+ * Accepts floats for smooth, sub-grid movement
86
+ * @param dx Movement in X (can be fractional)
87
+ * @param dy Movement in Y (can be fractional)
88
+ * @param dz Movement in Z (can be fractional)
49
89
  */
50
90
  move(dx: number, dy: number, dz?: number): void;
91
+ /**
92
+ * Move by integer grid cells (for discrete movement)
93
+ * Convenience method for logic that needs whole-cell movement
94
+ */
95
+ moveGridCells(dx: number, dy: number, dz?: number): void;
51
96
  /**
52
97
  * Set rotation angle (in radians)
53
98
  */
@@ -58,7 +103,8 @@ export declare class Entity {
58
103
  setSpeed(speed: number): void;
59
104
  /**
60
105
  * Update entity based on velocity and delta time
61
- * Returns true if position changed
106
+ * Now properly tracks fractional movement for smooth animation
107
+ * Returns true if position changed (by any amount)
62
108
  */
63
109
  updateVelocity(dt: number): boolean;
64
110
  /**
@@ -67,6 +113,7 @@ export declare class Entity {
67
113
  clone(): Entity;
68
114
  /**
69
115
  * Serialize entity state for transmission/saving
116
+ * Preserves floating-point positions for smooth movement
70
117
  */
71
118
  serialize(): string;
72
119
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/simulation/entity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,cAAc,CAAc;gBAExB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,OAAO,CAAC,WAAW,CAAM;IAkB7E;;OAEG;IACH,IAAI,KAAK,IAAI,QAAQ,CAAC,WAAW,CAAC,CAEjC;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,QAAQ,CAAC,WAAW,CAAC,CAEzC;IAED;;OAEG;IACH,SAAS,IAAI,IAAI;IAQjB;;OAEG;IACH,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IAIrD;;OAEG;IACH,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IAKtD;;OAEG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAE,MAAU,GAAG,IAAI;IAMlD;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;;OAGG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAuBnC;;OAEG;IACH,KAAK,IAAI,MAAM;IAUf;;OAEG;IACH,SAAS,IAAI,MAAM;IAQnB;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMxC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC;IAM5C;;;;OAIG;WACU,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAMjE;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAQtC;;;OAGG;IACH,YAAY,IAAI,WAAW;CAO5B"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/simulation/entity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,cAAc,CAAc;gBAExB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,OAAO,CAAC,WAAW,CAAM;IAkB7E;;OAEG;IACH,IAAI,KAAK,IAAI,QAAQ,CAAC,WAAW,CAAC,CAEjC;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,QAAQ,CAAC,WAAW,CAAC,CAEzC;IAED;;;OAGG;IACH,WAAW,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAQlD;;;OAGG;IACH,UAAU,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAQjD;;;OAGG;IACH,iBAAiB,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAQxD;;OAEG;IACH,SAAS,IAAI,IAAI;IAQjB;;;OAGG;IACH,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IAIrD;;;OAGG;IACH,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IAIxD;;OAEG;IACH,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IAKtD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAE,MAAU,GAAG,IAAI;IAMlD;;;OAGG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAE,MAAU,GAAG,IAAI;IAM3D;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAkBnC;;OAEG;IACH,KAAK,IAAI,MAAM;IAUf;;;OAGG;IACH,SAAS,IAAI,MAAM;IAQnB;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMxC;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC;IAM5C;;;;OAIG;WACU,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAMjE;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAQtC;;;OAGG;IACH,YAAY,IAAI,WAAW;CAO5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloody-engine",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "A WebGL-based 2.5D graphics engine for isometric rendering",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,6 +42,7 @@
42
42
  "build:node": "vite build --config vite.config.node.ts",
43
43
  "build:demo": "vite build --config vite.config.demo.ts",
44
44
  "demo": "npm run build:demo && node dist/demo/index.js",
45
+ "demo:coordinates": "npm run build && node dist/node/examples/coordinate-systems-demo.js",
45
46
  "test:visual": "node test-visual.js",
46
47
  "test:determinism": "node test-determinism.js",
47
48
  "test": "vitest run",