@bloopjs/engine 0.0.88 → 0.0.90

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.
@@ -1,3 +1,24 @@
1
+ import {
2
+ MAX_PLAYERS,
3
+ NET_CTX_PEERS_OFFSET,
4
+ NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET,
5
+ NET_CTX_TOTAL_ROLLBACKS_OFFSET,
6
+ NET_CTX_FRAMES_RESIMULATED_OFFSET,
7
+ NET_CTX_PEER_COUNT_OFFSET,
8
+ NET_CTX_LOCAL_PEER_ID_OFFSET,
9
+ NET_CTX_IN_SESSION_OFFSET,
10
+ NET_CTX_STATUS_OFFSET,
11
+ NET_CTX_MATCH_FRAME_OFFSET,
12
+ NET_CTX_SESSION_START_FRAME_OFFSET,
13
+ NET_CTX_ROOM_CODE_OFFSET,
14
+ NET_CTX_WANTS_ROOM_CODE_OFFSET,
15
+ NET_CTX_WANTS_DISCONNECT_OFFSET,
16
+ PEER_CTX_SIZE,
17
+ PEER_CTX_CONNECTED_OFFSET,
18
+ PEER_CTX_SEQ_OFFSET,
19
+ PEER_CTX_ACK_OFFSET,
20
+ } from "../codegen/offsets";
21
+
1
22
  /**
2
23
  * Network status values matching Zig NetStatus enum
3
24
  */
@@ -28,22 +49,6 @@ export type PeerInfo = {
28
49
  ack: number; // -1 if no packets received
29
50
  };
30
51
 
31
- const MAX_PEERS = 12;
32
- const PEERS_ARRAY_OFFSET = 32; // After _pad at offset 29-31
33
- const PEER_CTX_SIZE = 8; // connected(1) + packet_count(1) + seq(2) + ack(2) + ack_count(1) + pad(1)
34
-
35
- // Rollback stats offsets (after peers array at 32 + 12*8 = 128)
36
- const LAST_ROLLBACK_DEPTH_OFFSET = 128;
37
- const TOTAL_ROLLBACKS_OFFSET = 132;
38
- const FRAMES_RESIMULATED_OFFSET = 136;
39
-
40
- // Offsets within PeerCtx struct
41
- const PEER_CONNECTED_OFFSET = 0;
42
- const PEER_PACKET_COUNT_OFFSET = 1;
43
- const PEER_SEQ_OFFSET = 2;
44
- const PEER_ACK_OFFSET = 4;
45
- const PEER_ACK_COUNT_OFFSET = 6;
46
-
47
52
  const STATUS_MAP: Record<number, NetStatus> = {
48
53
  0: "offline",
49
54
  1: "local",
@@ -53,14 +58,8 @@ const STATUS_MAP: Record<number, NetStatus> = {
53
58
  };
54
59
 
55
60
  /**
56
- * NetCtx struct layout (from context.zig):
57
- * - peer_count: u8 (offset 0)
58
- * - local_peer_id: u8 (offset 1)
59
- * - in_session: u8 (offset 2)
60
- * - status: u8 (offset 3)
61
- * - match_frame: u32 (offset 4)
62
- * - session_start_frame: u32 (offset 8)
63
- * - room_code: [8]u8 (offset 12)
61
+ * NetCtx struct layout is defined in context.zig.
62
+ * Field offsets are generated in codegen/offsets.ts.
64
63
  *
65
64
  * All getters read directly from the engine's memory via DataView.
66
65
  * State is managed by the Zig engine, not TypeScript.
@@ -69,7 +68,7 @@ export class NetContext {
69
68
  dataView?: DataView;
70
69
 
71
70
  // Pre-allocated peer objects to avoid GC pressure
72
- #peers: PeerInfo[] = Array.from({ length: MAX_PEERS }, () => ({
71
+ #peers: PeerInfo[] = Array.from({ length: MAX_PLAYERS }, () => ({
73
72
  isLocal: false,
74
73
  seq: -1,
75
74
  ack: -1,
@@ -92,7 +91,7 @@ export class NetContext {
92
91
  if (!this.#hasValidBuffer()) {
93
92
  throw new Error("NetContext dataView is not valid");
94
93
  }
95
- return this.dataView!.getUint8(0);
94
+ return this.dataView!.getUint8(NET_CTX_PEER_COUNT_OFFSET);
96
95
  }
97
96
 
98
97
  /** Local peer ID in the session */
@@ -100,7 +99,7 @@ export class NetContext {
100
99
  if (!this.#hasValidBuffer()) {
101
100
  throw new Error("NetContext dataView is not valid");
102
101
  }
103
- return this.dataView!.getUint8(1);
102
+ return this.dataView!.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET);
104
103
  }
105
104
 
106
105
  /** Whether we're in an active multiplayer session */
@@ -108,7 +107,7 @@ export class NetContext {
108
107
  if (!this.#hasValidBuffer()) {
109
108
  throw new Error("NetContext dataView is not valid");
110
109
  }
111
- return this.dataView!.getUint8(2) !== 0;
110
+ return this.dataView!.getUint8(NET_CTX_IN_SESSION_OFFSET) !== 0;
112
111
  }
113
112
 
114
113
  /** Current network status */
@@ -116,7 +115,7 @@ export class NetContext {
116
115
  if (!this.#hasValidBuffer()) {
117
116
  throw new Error("NetContext dataView is not valid");
118
117
  }
119
- const statusByte = this.dataView!.getUint8(3);
118
+ const statusByte = this.dataView!.getUint8(NET_CTX_STATUS_OFFSET);
120
119
  return STATUS_MAP[statusByte] ?? "local";
121
120
  }
122
121
 
@@ -125,7 +124,7 @@ export class NetContext {
125
124
  if (!this.#hasValidBuffer()) {
126
125
  throw new Error("NetContext dataView is not valid");
127
126
  }
128
- return this.dataView!.getUint32(4, true);
127
+ return this.dataView!.getUint32(NET_CTX_MATCH_FRAME_OFFSET, true);
129
128
  }
130
129
 
131
130
  /**
@@ -136,7 +135,7 @@ export class NetContext {
136
135
  if (!this.#hasValidBuffer()) {
137
136
  throw new Error("NetContext dataView is not valid");
138
137
  }
139
- return this.dataView!.getUint32(8, true);
138
+ return this.dataView!.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET, true);
140
139
  }
141
140
 
142
141
  /** Current room code (empty string if not in a room) */
@@ -144,10 +143,10 @@ export class NetContext {
144
143
  if (!this.#hasValidBuffer()) {
145
144
  throw new Error("NetContext dataView is not valid");
146
145
  }
147
- // Read 8 bytes starting at offset 12, convert to string until null terminator
146
+ // Read 8 bytes starting at room_code offset, convert to string until null terminator
148
147
  const bytes: number[] = [];
149
148
  for (let i = 0; i < 8; i++) {
150
- const byte = this.dataView!.getUint8(12 + i);
149
+ const byte = this.dataView!.getUint8(NET_CTX_ROOM_CODE_OFFSET + i);
151
150
  if (byte === 0) break;
152
151
  bytes.push(byte);
153
152
  }
@@ -159,10 +158,10 @@ export class NetContext {
159
158
  if (!this.#hasValidBuffer()) {
160
159
  return undefined;
161
160
  }
162
- // Read 8 bytes starting at offset 20, convert to string until null terminator
161
+ // Read 8 bytes starting at wants_room_code offset, convert to string until null terminator
163
162
  const bytes: number[] = [];
164
163
  for (let i = 0; i < 8; i++) {
165
- const byte = this.dataView!.getUint8(20 + i);
164
+ const byte = this.dataView!.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i);
166
165
  if (byte === 0) break;
167
166
  bytes.push(byte);
168
167
  }
@@ -175,12 +174,12 @@ export class NetContext {
175
174
  }
176
175
  // Clear first
177
176
  for (let i = 0; i < 8; i++) {
178
- this.dataView!.setUint8(20 + i, 0);
177
+ this.dataView!.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, 0);
179
178
  }
180
179
  if (code) {
181
180
  // Write up to 7 chars (leave room for null terminator)
182
181
  for (let i = 0; i < Math.min(code.length, 7); i++) {
183
- this.dataView!.setUint8(20 + i, code.charCodeAt(i));
182
+ this.dataView!.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, code.charCodeAt(i));
184
183
  }
185
184
  }
186
185
  }
@@ -190,14 +189,14 @@ export class NetContext {
190
189
  if (!this.#hasValidBuffer()) {
191
190
  return false;
192
191
  }
193
- return this.dataView!.getUint8(28) !== 0;
192
+ return this.dataView!.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET) !== 0;
194
193
  }
195
194
 
196
195
  set wantsDisconnect(value: boolean) {
197
196
  if (!this.#hasValidBuffer()) {
198
197
  throw new Error("NetContext dataView is not valid");
199
198
  }
200
- this.dataView!.setUint8(28, value ? 1 : 0);
199
+ this.dataView!.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET, value ? 1 : 0);
201
200
  }
202
201
 
203
202
  /**
@@ -223,12 +222,12 @@ export class NetContext {
223
222
 
224
223
  // Calculate local peer ack = min(seq) across connected remotes with data
225
224
  let minRemoteSeq = -1;
226
- for (let i = 0; i < MAX_PEERS; i++) {
225
+ for (let i = 0; i < MAX_PLAYERS; i++) {
227
226
  if (i === localPeerId) continue;
228
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
229
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1) continue;
227
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
228
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1) continue;
230
229
  // seq is now i16 with -1 meaning "no data yet"
231
- const seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET, true);
230
+ const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
232
231
  if (seq < 0) continue; // No data from this peer yet
233
232
  if (minRemoteSeq === -1 || seq < minRemoteSeq) {
234
233
  minRemoteSeq = seq;
@@ -237,9 +236,9 @@ export class NetContext {
237
236
 
238
237
  // Update pre-allocated peer objects and build result array
239
238
  this.#peersResult.length = 0;
240
- for (let i = 0; i < MAX_PEERS; i++) {
241
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
242
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1) continue;
239
+ for (let i = 0; i < MAX_PLAYERS; i++) {
240
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
241
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1) continue;
243
242
 
244
243
  const peer = this.#peers[i];
245
244
  if (!peer) {
@@ -253,8 +252,8 @@ export class NetContext {
253
252
  peer.ack = minRemoteSeq;
254
253
  } else {
255
254
  // seq and ack are now i16 with -1 meaning "no data yet"
256
- peer.seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET, true);
257
- peer.ack = dv.getInt16(peerOffset + PEER_ACK_OFFSET, true);
255
+ peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
256
+ peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET, true);
258
257
  }
259
258
  this.#peersResult.push(peer);
260
259
  }
@@ -266,7 +265,7 @@ export class NetContext {
266
265
  if (!this.#hasValidBuffer()) {
267
266
  throw new Error("NetContext dataView is not valid");
268
267
  }
269
- return this.dataView!.getUint32(LAST_ROLLBACK_DEPTH_OFFSET, true);
268
+ return this.dataView!.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET, true);
270
269
  }
271
270
 
272
271
  /** Total number of rollbacks during this session */
@@ -274,7 +273,7 @@ export class NetContext {
274
273
  if (!this.#hasValidBuffer()) {
275
274
  throw new Error("NetContext dataView is not valid");
276
275
  }
277
- return this.dataView!.getUint32(TOTAL_ROLLBACKS_OFFSET, true);
276
+ return this.dataView!.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET, true);
278
277
  }
279
278
 
280
279
  /** Total frames resimulated during this session */
@@ -283,6 +282,6 @@ export class NetContext {
283
282
  throw new Error("NetContext dataView is not valid");
284
283
  }
285
284
  // Read u64 as BigInt then convert to number (safe for reasonable frame counts)
286
- return Number(this.dataView!.getBigUint64(FRAMES_RESIMULATED_OFFSET, true));
285
+ return Number(this.dataView!.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET, true));
287
286
  }
288
287
  }
@@ -0,0 +1,55 @@
1
+ import {
2
+ SCREEN_CTX_WIDTH_OFFSET,
3
+ SCREEN_CTX_HEIGHT_OFFSET,
4
+ SCREEN_CTX_PHYSICAL_WIDTH_OFFSET,
5
+ SCREEN_CTX_PHYSICAL_HEIGHT_OFFSET,
6
+ SCREEN_CTX_PIXEL_RATIO_OFFSET,
7
+ } from "../codegen/offsets";
8
+
9
+ export class ScreenContext {
10
+ dataView?: DataView;
11
+
12
+ constructor(dataView?: DataView) {
13
+ this.dataView = dataView;
14
+ }
15
+
16
+ /** Logical width in CSS pixels */
17
+ get width(): number {
18
+ if (!this.dataView) {
19
+ throw new Error("ScreenContext DataView is not initialized");
20
+ }
21
+ return this.dataView.getUint32(SCREEN_CTX_WIDTH_OFFSET, true);
22
+ }
23
+
24
+ /** Logical height in CSS pixels */
25
+ get height(): number {
26
+ if (!this.dataView) {
27
+ throw new Error("ScreenContext DataView is not initialized");
28
+ }
29
+ return this.dataView.getUint32(SCREEN_CTX_HEIGHT_OFFSET, true);
30
+ }
31
+
32
+ /** Physical width in device pixels */
33
+ get physicalWidth(): number {
34
+ if (!this.dataView) {
35
+ throw new Error("ScreenContext DataView is not initialized");
36
+ }
37
+ return this.dataView.getUint32(SCREEN_CTX_PHYSICAL_WIDTH_OFFSET, true);
38
+ }
39
+
40
+ /** Physical height in device pixels */
41
+ get physicalHeight(): number {
42
+ if (!this.dataView) {
43
+ throw new Error("ScreenContext DataView is not initialized");
44
+ }
45
+ return this.dataView.getUint32(SCREEN_CTX_PHYSICAL_HEIGHT_OFFSET, true);
46
+ }
47
+
48
+ /** Device pixel ratio (physical pixels / logical pixels) */
49
+ get pixelRatio(): number {
50
+ if (!this.dataView) {
51
+ throw new Error("ScreenContext DataView is not initialized");
52
+ }
53
+ return this.dataView.getFloat32(SCREEN_CTX_PIXEL_RATIO_OFFSET, true);
54
+ }
55
+ }
@@ -1,3 +1,9 @@
1
+ import {
2
+ TIME_CTX_FRAME_OFFSET,
3
+ TIME_CTX_DT_MS_OFFSET,
4
+ TIME_CTX_TOTAL_MS_OFFSET,
5
+ } from "../codegen/offsets";
6
+
1
7
  export class TimeContext {
2
8
  dataView?: DataView;
3
9
 
@@ -10,7 +16,7 @@ export class TimeContext {
10
16
  if (!this.dataView) {
11
17
  throw new Error("TimeContext DataView is not initialized");
12
18
  }
13
- return this.dataView.getUint32(0, true);
19
+ return this.dataView.getUint32(TIME_CTX_FRAME_OFFSET, true);
14
20
  }
15
21
 
16
22
  /** The number of seconds since the last frame */
@@ -18,7 +24,7 @@ export class TimeContext {
18
24
  if (!this.dataView) {
19
25
  throw new Error("TimeContext DataView is not initialized");
20
26
  }
21
- return this.dataView.getUint32(4, true) / 1000;
27
+ return this.dataView.getUint32(TIME_CTX_DT_MS_OFFSET, true) / 1000;
22
28
  }
23
29
 
24
30
  /** The total number of seconds since the engine started */
@@ -26,6 +32,6 @@ export class TimeContext {
26
32
  if (!this.dataView) {
27
33
  throw new Error("TimeContext DataView is not initialized");
28
34
  }
29
- return this.dataView.getUint32(8, true) / 1000;
35
+ return this.dataView.getUint32(TIME_CTX_TOTAL_MS_OFFSET, true) / 1000;
30
36
  }
31
37
  }
package/js/defaultUrl.ts CHANGED
@@ -1 +1 @@
1
- export const DEFAULT_WASM_URL: URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.88/wasm/bloop.wasm");
1
+ export const DEFAULT_WASM_URL: URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.90/wasm/bloop.wasm");
package/js/engine.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * as Enums from "./codegen/enums";
2
2
  export * from "./contexts/inputContext";
3
3
  export * from "./contexts/netContext";
4
+ export * from "./contexts/screenContext";
4
5
  export * from "./contexts/timeContext";
5
6
  export * from "./inputs";
6
7
  export * from "./tape";
@@ -30,6 +31,7 @@ export const TIME_CTX_OFFSET = 0;
30
31
  export const INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
31
32
  export const EVENTS_OFFSET = INPUT_CTX_OFFSET + 4;
32
33
  export const NET_CTX_OFFSET = EVENTS_OFFSET + 4;
34
+ export const SCREEN_CTX_OFFSET = NET_CTX_OFFSET + 4;
33
35
 
34
36
  /**
35
37
  * Size of snapshot header in bytes
package/js/inputs.ts CHANGED
@@ -1,20 +1,24 @@
1
1
  import * as Enums from "./codegen/enums";
2
2
 
3
- // Constants for memory layout
4
- // TODO: move magic numbers to codegen
5
- export const MAX_PLAYERS = 12;
3
+ // Re-export layout constants from generated offsets
4
+ export {
5
+ MAX_PLAYERS,
6
+ KEY_CTX_SIZE,
7
+ MOUSE_CTX_SIZE,
8
+ PLAYER_INPUTS_SIZE,
9
+ INPUT_CTX_SIZE,
10
+ PLAYER_INPUTS_KEY_CTX_OFFSET,
11
+ PLAYER_INPUTS_MOUSE_CTX_OFFSET,
12
+ MOUSE_CTX_BUTTON_STATES_OFFSET,
13
+ } from "./codegen/offsets";
6
14
 
7
- // Per-player offsets (relative to start of PlayerInputs)
8
- export const KEYBOARD_OFFSET = 0;
9
- export const KEYBOARD_SIZE = 256;
10
- export const MOUSE_OFFSET = 256; // After keyboard
11
- export const MOUSE_BUTTONS_OFFSET = 16; // Within MouseCtx, after x, y, wheel_x, wheel_y (4 floats = 16 bytes)
12
-
13
- // PlayerInputs size = KeyCtx (256) + MouseCtx (24) = 280 bytes
14
- export const PLAYER_INPUTS_SIZE = 280;
15
-
16
- // InputCtx layout: players[12] = 12 * 280 = 3360 bytes
17
- export const INPUT_CTX_SIZE = MAX_PLAYERS * PLAYER_INPUTS_SIZE;
15
+ // Backwards compatibility aliases
16
+ export {
17
+ PLAYER_INPUTS_KEY_CTX_OFFSET as KEYBOARD_OFFSET,
18
+ KEY_CTX_SIZE as KEYBOARD_SIZE,
19
+ PLAYER_INPUTS_MOUSE_CTX_OFFSET as MOUSE_OFFSET,
20
+ MOUSE_CTX_BUTTON_STATES_OFFSET as MOUSE_BUTTONS_OFFSET,
21
+ } from "./codegen/offsets";
18
22
 
19
23
  export const EVENT_PAYLOAD_SIZE = 8;
20
24
  export const EVENT_PAYLOAD_ALIGN = 4;
package/js/wasmEngine.ts CHANGED
@@ -106,6 +106,27 @@ export type WasmEngine = {
106
106
  * Get pointer to net context struct
107
107
  */
108
108
  get_net_ctx: () => EnginePointer;
109
+ /**
110
+ * Get pointer to screen context struct
111
+ */
112
+ get_screen_ctx: () => EnginePointer;
113
+
114
+ // Screen / Viewport events
115
+ /**
116
+ * Emit resize event - updates screen context and fires resize callback
117
+ * @param width Logical width in CSS pixels
118
+ * @param height Logical height in CSS pixels
119
+ * @param physical_width Physical width in device pixels
120
+ * @param physical_height Physical height in device pixels
121
+ * @param pixel_ratio Device pixel ratio
122
+ */
123
+ emit_resize: (
124
+ width: number,
125
+ height: number,
126
+ physical_width: number,
127
+ physical_height: number,
128
+ pixel_ratio: number,
129
+ ) => void;
109
130
 
110
131
  // Network / Packets
111
132
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/engine",
3
- "version": "0.0.88",
3
+ "version": "0.0.90",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
package/wasm/bloop.wasm CHANGED
Binary file