@bloopjs/web 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.
package/dist/mod.js CHANGED
@@ -77,21 +77,28 @@ __export2(exports_engine, {
77
77
  inputSourceCodeToInputSource: () => inputSourceCodeToInputSource,
78
78
  TimeContext: () => TimeContext,
79
79
  TIME_CTX_OFFSET: () => TIME_CTX_OFFSET,
80
+ ScreenContext: () => ScreenContext,
80
81
  SNAPSHOT_HEADER_USER_LEN_OFFSET: () => SNAPSHOT_HEADER_USER_LEN_OFFSET,
81
82
  SNAPSHOT_HEADER_LEN: () => SNAPSHOT_HEADER_LEN,
82
83
  SNAPSHOT_HEADER_ENGINE_LEN_OFFSET: () => SNAPSHOT_HEADER_ENGINE_LEN_OFFSET,
84
+ SCREEN_CTX_OFFSET: () => SCREEN_CTX_OFFSET,
83
85
  PlayerInputContext: () => PlayerInputContext,
84
86
  PLAYER_INPUTS_SIZE: () => PLAYER_INPUTS_SIZE,
87
+ PLAYER_INPUTS_MOUSE_CTX_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
88
+ PLAYER_INPUTS_KEY_CTX_OFFSET: () => PLAYER_INPUTS_KEY_CTX_OFFSET,
85
89
  NetContext: () => NetContext,
86
90
  NET_CTX_OFFSET: () => NET_CTX_OFFSET,
87
91
  MouseContext: () => MouseContext,
88
- MOUSE_OFFSET: () => MOUSE_OFFSET,
89
- MOUSE_BUTTONS_OFFSET: () => MOUSE_BUTTONS_OFFSET,
92
+ MOUSE_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
93
+ MOUSE_CTX_SIZE: () => MOUSE_CTX_SIZE,
94
+ MOUSE_CTX_BUTTON_STATES_OFFSET: () => MOUSE_CTX_BUTTON_STATES_OFFSET,
95
+ MOUSE_BUTTONS_OFFSET: () => MOUSE_CTX_BUTTON_STATES_OFFSET,
90
96
  MAX_ROLLBACK_FRAMES: () => MAX_ROLLBACK_FRAMES,
91
97
  MAX_PLAYERS: () => MAX_PLAYERS,
92
98
  KeyboardContext: () => KeyboardContext,
93
- KEYBOARD_SIZE: () => KEYBOARD_SIZE,
94
- KEYBOARD_OFFSET: () => KEYBOARD_OFFSET,
99
+ KEY_CTX_SIZE: () => KEY_CTX_SIZE,
100
+ KEYBOARD_SIZE: () => KEY_CTX_SIZE,
101
+ KEYBOARD_OFFSET: () => PLAYER_INPUTS_KEY_CTX_OFFSET,
95
102
  InputContext: () => InputContext,
96
103
  INPUT_CTX_SIZE: () => INPUT_CTX_SIZE,
97
104
  INPUT_CTX_OFFSET: () => INPUT_CTX_OFFSET,
@@ -136,6 +143,7 @@ var EventType;
136
143
  EventType2[EventType2["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
137
144
  EventType2[EventType2["NetPacketReceived"] = 13] = "NetPacketReceived";
138
145
  EventType2[EventType2["NetSessionInit"] = 14] = "NetSessionInit";
146
+ EventType2[EventType2["Resize"] = 15] = "Resize";
139
147
  })(EventType ||= {});
140
148
  var MouseButton;
141
149
  ((MouseButton2) => {
@@ -387,33 +395,43 @@ var NetJoinFailReason;
387
395
  NetJoinFailReason2[NetJoinFailReason2["room_not_found"] = 3] = "room_not_found";
388
396
  NetJoinFailReason2[NetJoinFailReason2["already_in_room"] = 4] = "already_in_room";
389
397
  })(NetJoinFailReason ||= {});
390
- var MAX_PLAYERS = 12;
391
- var KEYBOARD_OFFSET = 0;
392
- var KEYBOARD_SIZE = 256;
393
- var MOUSE_OFFSET = 256;
394
- var MOUSE_BUTTONS_OFFSET = 16;
398
+ var TIME_CTX_FRAME_OFFSET = 0;
399
+ var TIME_CTX_DT_MS_OFFSET = 4;
400
+ var TIME_CTX_TOTAL_MS_OFFSET = 8;
401
+ var PEER_CTX_CONNECTED_OFFSET = 0;
402
+ var PEER_CTX_SEQ_OFFSET = 2;
403
+ var PEER_CTX_ACK_OFFSET = 4;
404
+ var PEER_CTX_SIZE = 8;
405
+ var NET_CTX_PEER_COUNT_OFFSET = 0;
406
+ var NET_CTX_LOCAL_PEER_ID_OFFSET = 1;
407
+ var NET_CTX_IN_SESSION_OFFSET = 2;
408
+ var NET_CTX_STATUS_OFFSET = 3;
409
+ var NET_CTX_MATCH_FRAME_OFFSET = 4;
410
+ var NET_CTX_SESSION_START_FRAME_OFFSET = 8;
411
+ var NET_CTX_ROOM_CODE_OFFSET = 12;
412
+ var NET_CTX_WANTS_ROOM_CODE_OFFSET = 20;
413
+ var NET_CTX_WANTS_DISCONNECT_OFFSET = 28;
414
+ var NET_CTX_PEERS_OFFSET = 32;
415
+ var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET = 128;
416
+ var NET_CTX_TOTAL_ROLLBACKS_OFFSET = 132;
417
+ var NET_CTX_FRAMES_RESIMULATED_OFFSET = 136;
418
+ var SCREEN_CTX_WIDTH_OFFSET = 0;
419
+ var SCREEN_CTX_HEIGHT_OFFSET = 4;
420
+ var SCREEN_CTX_PHYSICAL_WIDTH_OFFSET = 8;
421
+ var SCREEN_CTX_PHYSICAL_HEIGHT_OFFSET = 12;
422
+ var SCREEN_CTX_PIXEL_RATIO_OFFSET = 16;
423
+ var MOUSE_CTX_X_OFFSET = 0;
424
+ var MOUSE_CTX_Y_OFFSET = 4;
425
+ var MOUSE_CTX_WHEEL_X_OFFSET = 8;
426
+ var MOUSE_CTX_WHEEL_Y_OFFSET = 12;
427
+ var MOUSE_CTX_BUTTON_STATES_OFFSET = 16;
428
+ var MOUSE_CTX_SIZE = 24;
429
+ var KEY_CTX_SIZE = 256;
430
+ var PLAYER_INPUTS_KEY_CTX_OFFSET = 0;
431
+ var PLAYER_INPUTS_MOUSE_CTX_OFFSET = 256;
395
432
  var PLAYER_INPUTS_SIZE = 280;
396
- var INPUT_CTX_SIZE = MAX_PLAYERS * PLAYER_INPUTS_SIZE;
397
- var EVENT_PAYLOAD_SIZE = 8;
398
- var EVENT_PAYLOAD_ALIGN = 4;
399
- function keyToKeyCode(key) {
400
- return Key[key];
401
- }
402
- function keyCodeToKey(code) {
403
- return Key[code];
404
- }
405
- function mouseButtonToMouseButtonCode(button) {
406
- return MouseButton[button];
407
- }
408
- function mouseButtonCodeToMouseButton(code) {
409
- return MouseButton[code];
410
- }
411
- function inputSourceToInputSourceCode(source) {
412
- return InputSource[source];
413
- }
414
- function inputSourceCodeToInputSource(code) {
415
- return InputSource[code];
416
- }
433
+ var INPUT_CTX_SIZE = 3360;
434
+ var MAX_PLAYERS = 12;
417
435
 
418
436
  class PlayerInputContext {
419
437
  #dataView;
@@ -424,13 +442,13 @@ class PlayerInputContext {
424
442
  }
425
443
  get keys() {
426
444
  if (!this.#keys) {
427
- this.#keys = new KeyboardContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + KEYBOARD_OFFSET));
445
+ this.#keys = new KeyboardContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_KEY_CTX_OFFSET));
428
446
  }
429
447
  return this.#keys;
430
448
  }
431
449
  get mouse() {
432
450
  if (!this.#mouse) {
433
- this.#mouse = new MouseContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + MOUSE_OFFSET));
451
+ this.#mouse = new MouseContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_MOUSE_CTX_OFFSET));
434
452
  }
435
453
  return this.#mouse;
436
454
  }
@@ -455,6 +473,9 @@ class InputContext {
455
473
  this.#dataView = dataView;
456
474
  this.#buildPlayers();
457
475
  }
476
+ hasDataView() {
477
+ return !!this.#dataView;
478
+ }
458
479
  #buildPlayers() {
459
480
  this.#players = [];
460
481
  for (let i = 0;i < MAX_PLAYERS; i++) {
@@ -487,19 +508,19 @@ class MouseContext {
487
508
  this.#dataView = dataView;
488
509
  }
489
510
  get x() {
490
- return this.#dataView.getFloat32(0, true);
511
+ return this.#dataView.getFloat32(MOUSE_CTX_X_OFFSET, true);
491
512
  }
492
513
  get y() {
493
- return this.#dataView.getFloat32(4, true);
514
+ return this.#dataView.getFloat32(MOUSE_CTX_Y_OFFSET, true);
494
515
  }
495
516
  get wheel() {
496
517
  return { x: this.wheelX, y: this.wheelY };
497
518
  }
498
519
  get wheelX() {
499
- return this.#dataView.getFloat32(8, true);
520
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_X_OFFSET, true);
500
521
  }
501
522
  get wheelY() {
502
- return this.#dataView.getFloat32(12, true);
523
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_Y_OFFSET, true);
503
524
  }
504
525
  get left() {
505
526
  return this.#buttonState(1);
@@ -518,7 +539,7 @@ class MouseContext {
518
539
  state = { down: false, held: false, up: false };
519
540
  this.#buttonStates.set(code, state);
520
541
  }
521
- const offset = MOUSE_BUTTONS_OFFSET;
542
+ const offset = MOUSE_CTX_BUTTON_STATES_OFFSET;
522
543
  state.held = !!(this.#dataView.getUint8(offset + code) & 1);
523
544
  state.down = state.held && !(this.#dataView.getUint8(offset + code) & 2);
524
545
  state.up = !state.held && !!(this.#dataView.getUint8(offset + code) & 2);
@@ -1140,15 +1161,6 @@ class KeyboardContext {
1140
1161
  return state;
1141
1162
  }
1142
1163
  }
1143
- var MAX_PEERS = 12;
1144
- var PEERS_ARRAY_OFFSET = 32;
1145
- var PEER_CTX_SIZE = 8;
1146
- var LAST_ROLLBACK_DEPTH_OFFSET = 128;
1147
- var TOTAL_ROLLBACKS_OFFSET = 132;
1148
- var FRAMES_RESIMULATED_OFFSET = 136;
1149
- var PEER_CONNECTED_OFFSET = 0;
1150
- var PEER_SEQ_OFFSET = 2;
1151
- var PEER_ACK_OFFSET = 4;
1152
1164
  var STATUS_MAP = {
1153
1165
  0: "offline",
1154
1166
  1: "local",
@@ -1159,7 +1171,7 @@ var STATUS_MAP = {
1159
1171
 
1160
1172
  class NetContext {
1161
1173
  dataView;
1162
- #peers = Array.from({ length: MAX_PEERS }, () => ({
1174
+ #peers = Array.from({ length: MAX_PLAYERS }, () => ({
1163
1175
  isLocal: false,
1164
1176
  seq: -1,
1165
1177
  ack: -1
@@ -1177,38 +1189,38 @@ class NetContext {
1177
1189
  if (!this.#hasValidBuffer()) {
1178
1190
  throw new Error("NetContext dataView is not valid");
1179
1191
  }
1180
- return this.dataView.getUint8(0);
1192
+ return this.dataView.getUint8(NET_CTX_PEER_COUNT_OFFSET);
1181
1193
  }
1182
1194
  get localPeerId() {
1183
1195
  if (!this.#hasValidBuffer()) {
1184
1196
  throw new Error("NetContext dataView is not valid");
1185
1197
  }
1186
- return this.dataView.getUint8(1);
1198
+ return this.dataView.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET);
1187
1199
  }
1188
1200
  get isInSession() {
1189
1201
  if (!this.#hasValidBuffer()) {
1190
1202
  throw new Error("NetContext dataView is not valid");
1191
1203
  }
1192
- return this.dataView.getUint8(2) !== 0;
1204
+ return this.dataView.getUint8(NET_CTX_IN_SESSION_OFFSET) !== 0;
1193
1205
  }
1194
1206
  get status() {
1195
1207
  if (!this.#hasValidBuffer()) {
1196
1208
  throw new Error("NetContext dataView is not valid");
1197
1209
  }
1198
- const statusByte = this.dataView.getUint8(3);
1210
+ const statusByte = this.dataView.getUint8(NET_CTX_STATUS_OFFSET);
1199
1211
  return STATUS_MAP[statusByte] ?? "local";
1200
1212
  }
1201
1213
  get matchFrame() {
1202
1214
  if (!this.#hasValidBuffer()) {
1203
1215
  throw new Error("NetContext dataView is not valid");
1204
1216
  }
1205
- return this.dataView.getUint32(4, true);
1217
+ return this.dataView.getUint32(NET_CTX_MATCH_FRAME_OFFSET, true);
1206
1218
  }
1207
1219
  get sessionStartFrame() {
1208
1220
  if (!this.#hasValidBuffer()) {
1209
1221
  throw new Error("NetContext dataView is not valid");
1210
1222
  }
1211
- return this.dataView.getUint32(8, true);
1223
+ return this.dataView.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET, true);
1212
1224
  }
1213
1225
  get roomCode() {
1214
1226
  if (!this.#hasValidBuffer()) {
@@ -1216,7 +1228,7 @@ class NetContext {
1216
1228
  }
1217
1229
  const bytes = [];
1218
1230
  for (let i = 0;i < 8; i++) {
1219
- const byte = this.dataView.getUint8(12 + i);
1231
+ const byte = this.dataView.getUint8(NET_CTX_ROOM_CODE_OFFSET + i);
1220
1232
  if (byte === 0)
1221
1233
  break;
1222
1234
  bytes.push(byte);
@@ -1229,7 +1241,7 @@ class NetContext {
1229
1241
  }
1230
1242
  const bytes = [];
1231
1243
  for (let i = 0;i < 8; i++) {
1232
- const byte = this.dataView.getUint8(20 + i);
1244
+ const byte = this.dataView.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i);
1233
1245
  if (byte === 0)
1234
1246
  break;
1235
1247
  bytes.push(byte);
@@ -1241,11 +1253,11 @@ class NetContext {
1241
1253
  throw new Error("NetContext dataView is not valid");
1242
1254
  }
1243
1255
  for (let i = 0;i < 8; i++) {
1244
- this.dataView.setUint8(20 + i, 0);
1256
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, 0);
1245
1257
  }
1246
1258
  if (code) {
1247
1259
  for (let i = 0;i < Math.min(code.length, 7); i++) {
1248
- this.dataView.setUint8(20 + i, code.charCodeAt(i));
1260
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, code.charCodeAt(i));
1249
1261
  }
1250
1262
  }
1251
1263
  }
@@ -1253,13 +1265,13 @@ class NetContext {
1253
1265
  if (!this.#hasValidBuffer()) {
1254
1266
  return false;
1255
1267
  }
1256
- return this.dataView.getUint8(28) !== 0;
1268
+ return this.dataView.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET) !== 0;
1257
1269
  }
1258
1270
  set wantsDisconnect(value) {
1259
1271
  if (!this.#hasValidBuffer()) {
1260
1272
  throw new Error("NetContext dataView is not valid");
1261
1273
  }
1262
- this.dataView.setUint8(28, value ? 1 : 0);
1274
+ this.dataView.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET, value ? 1 : 0);
1263
1275
  }
1264
1276
  get peers() {
1265
1277
  if (!this.#hasValidBuffer()) {
@@ -1269,13 +1281,13 @@ class NetContext {
1269
1281
  const localPeerId = this.localPeerId;
1270
1282
  const matchFrame = this.matchFrame;
1271
1283
  let minRemoteSeq = -1;
1272
- for (let i = 0;i < MAX_PEERS; i++) {
1284
+ for (let i = 0;i < MAX_PLAYERS; i++) {
1273
1285
  if (i === localPeerId)
1274
1286
  continue;
1275
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
1276
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1)
1287
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
1288
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1)
1277
1289
  continue;
1278
- const seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET, true);
1290
+ const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
1279
1291
  if (seq < 0)
1280
1292
  continue;
1281
1293
  if (minRemoteSeq === -1 || seq < minRemoteSeq) {
@@ -1283,9 +1295,9 @@ class NetContext {
1283
1295
  }
1284
1296
  }
1285
1297
  this.#peersResult.length = 0;
1286
- for (let i = 0;i < MAX_PEERS; i++) {
1287
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
1288
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1)
1298
+ for (let i = 0;i < MAX_PLAYERS; i++) {
1299
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
1300
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1)
1289
1301
  continue;
1290
1302
  const peer = this.#peers[i];
1291
1303
  if (!peer) {
@@ -1297,8 +1309,8 @@ class NetContext {
1297
1309
  peer.seq = matchFrame;
1298
1310
  peer.ack = minRemoteSeq;
1299
1311
  } else {
1300
- peer.seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET, true);
1301
- peer.ack = dv.getInt16(peerOffset + PEER_ACK_OFFSET, true);
1312
+ peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
1313
+ peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET, true);
1302
1314
  }
1303
1315
  this.#peersResult.push(peer);
1304
1316
  }
@@ -1308,19 +1320,56 @@ class NetContext {
1308
1320
  if (!this.#hasValidBuffer()) {
1309
1321
  throw new Error("NetContext dataView is not valid");
1310
1322
  }
1311
- return this.dataView.getUint32(LAST_ROLLBACK_DEPTH_OFFSET, true);
1323
+ return this.dataView.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET, true);
1312
1324
  }
1313
1325
  get totalRollbacks() {
1314
1326
  if (!this.#hasValidBuffer()) {
1315
1327
  throw new Error("NetContext dataView is not valid");
1316
1328
  }
1317
- return this.dataView.getUint32(TOTAL_ROLLBACKS_OFFSET, true);
1329
+ return this.dataView.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET, true);
1318
1330
  }
1319
1331
  get framesResimulated() {
1320
1332
  if (!this.#hasValidBuffer()) {
1321
1333
  throw new Error("NetContext dataView is not valid");
1322
1334
  }
1323
- return Number(this.dataView.getBigUint64(FRAMES_RESIMULATED_OFFSET, true));
1335
+ return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET, true));
1336
+ }
1337
+ }
1338
+
1339
+ class ScreenContext {
1340
+ dataView;
1341
+ constructor(dataView) {
1342
+ this.dataView = dataView;
1343
+ }
1344
+ get width() {
1345
+ if (!this.dataView) {
1346
+ throw new Error("ScreenContext DataView is not initialized");
1347
+ }
1348
+ return this.dataView.getUint32(SCREEN_CTX_WIDTH_OFFSET, true);
1349
+ }
1350
+ get height() {
1351
+ if (!this.dataView) {
1352
+ throw new Error("ScreenContext DataView is not initialized");
1353
+ }
1354
+ return this.dataView.getUint32(SCREEN_CTX_HEIGHT_OFFSET, true);
1355
+ }
1356
+ get physicalWidth() {
1357
+ if (!this.dataView) {
1358
+ throw new Error("ScreenContext DataView is not initialized");
1359
+ }
1360
+ return this.dataView.getUint32(SCREEN_CTX_PHYSICAL_WIDTH_OFFSET, true);
1361
+ }
1362
+ get physicalHeight() {
1363
+ if (!this.dataView) {
1364
+ throw new Error("ScreenContext DataView is not initialized");
1365
+ }
1366
+ return this.dataView.getUint32(SCREEN_CTX_PHYSICAL_HEIGHT_OFFSET, true);
1367
+ }
1368
+ get pixelRatio() {
1369
+ if (!this.dataView) {
1370
+ throw new Error("ScreenContext DataView is not initialized");
1371
+ }
1372
+ return this.dataView.getFloat32(SCREEN_CTX_PIXEL_RATIO_OFFSET, true);
1324
1373
  }
1325
1374
  }
1326
1375
 
@@ -1333,21 +1382,41 @@ class TimeContext {
1333
1382
  if (!this.dataView) {
1334
1383
  throw new Error("TimeContext DataView is not initialized");
1335
1384
  }
1336
- return this.dataView.getUint32(0, true);
1385
+ return this.dataView.getUint32(TIME_CTX_FRAME_OFFSET, true);
1337
1386
  }
1338
1387
  get dt() {
1339
1388
  if (!this.dataView) {
1340
1389
  throw new Error("TimeContext DataView is not initialized");
1341
1390
  }
1342
- return this.dataView.getUint32(4, true) / 1000;
1391
+ return this.dataView.getUint32(TIME_CTX_DT_MS_OFFSET, true) / 1000;
1343
1392
  }
1344
1393
  get time() {
1345
1394
  if (!this.dataView) {
1346
1395
  throw new Error("TimeContext DataView is not initialized");
1347
1396
  }
1348
- return this.dataView.getUint32(8, true) / 1000;
1397
+ return this.dataView.getUint32(TIME_CTX_TOTAL_MS_OFFSET, true) / 1000;
1349
1398
  }
1350
1399
  }
1400
+ var EVENT_PAYLOAD_SIZE = 8;
1401
+ var EVENT_PAYLOAD_ALIGN = 4;
1402
+ function keyToKeyCode(key) {
1403
+ return Key[key];
1404
+ }
1405
+ function keyCodeToKey(code) {
1406
+ return Key[code];
1407
+ }
1408
+ function mouseButtonToMouseButtonCode(button) {
1409
+ return MouseButton[button];
1410
+ }
1411
+ function mouseButtonCodeToMouseButton(code) {
1412
+ return MouseButton[code];
1413
+ }
1414
+ function inputSourceToInputSourceCode(source) {
1415
+ return InputSource[source];
1416
+ }
1417
+ function inputSourceCodeToInputSource(code) {
1418
+ return InputSource[code];
1419
+ }
1351
1420
  var TAPE_MAGIC = 1413566533;
1352
1421
  function readTapeHeader(tape) {
1353
1422
  const view = new DataView(tape.buffer, tape.byteOffset, tape.byteLength);
@@ -1363,12 +1432,13 @@ function readTapeHeader(tape) {
1363
1432
  eventCount: view.getUint16(14, true)
1364
1433
  };
1365
1434
  }
1366
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.88/wasm/bloop.wasm");
1435
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.90/wasm/bloop.wasm");
1367
1436
  var MAX_ROLLBACK_FRAMES = 500;
1368
1437
  var TIME_CTX_OFFSET = 0;
1369
1438
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
1370
1439
  var EVENTS_OFFSET = INPUT_CTX_OFFSET + 4;
1371
1440
  var NET_CTX_OFFSET = EVENTS_OFFSET + 4;
1441
+ var SCREEN_CTX_OFFSET = NET_CTX_OFFSET + 4;
1372
1442
  var SNAPSHOT_HEADER_LEN = 16;
1373
1443
  var SNAPSHOT_HEADER_USER_LEN_OFFSET = 4;
1374
1444
  var SNAPSHOT_HEADER_ENGINE_LEN_OFFSET = 8;
@@ -1390,6 +1460,7 @@ class Sim {
1390
1460
  #serialize;
1391
1461
  #isPaused = false;
1392
1462
  #net;
1463
+ #screen;
1393
1464
  onTapeFull;
1394
1465
  constructor(wasm, memory, opts) {
1395
1466
  this.wasm = wasm;
@@ -1398,6 +1469,7 @@ class Sim {
1398
1469
  this.id = `${Math.floor(Math.random() * 1e6)}`;
1399
1470
  this.#serialize = opts?.serialize;
1400
1471
  this.#net = new NetContext;
1472
+ this.#screen = new ScreenContext;
1401
1473
  }
1402
1474
  step(ms) {
1403
1475
  if (this.#isPaused) {
@@ -1510,6 +1582,12 @@ class Sim {
1510
1582
  }
1511
1583
  return this.#net;
1512
1584
  }
1585
+ get screen() {
1586
+ if (!this.#screen.dataView || this.#screen.dataView.buffer !== this.#memory.buffer) {
1587
+ this.#screen.dataView = new DataView(this.#memory.buffer, this.wasm.get_screen_ctx());
1588
+ }
1589
+ return this.#screen;
1590
+ }
1513
1591
  get buffer() {
1514
1592
  return this.#memory.buffer;
1515
1593
  }
@@ -1541,6 +1619,9 @@ class Sim {
1541
1619
  mousewheel: (x, y, peerId = 0) => {
1542
1620
  this.wasm.emit_mousewheel(x, y, peerId);
1543
1621
  },
1622
+ resize: (width, height, physicalWidth, physicalHeight, pixelRatio) => {
1623
+ this.wasm.emit_resize(width, height, physicalWidth, physicalHeight, pixelRatio);
1624
+ },
1544
1625
  network: (type, data) => {
1545
1626
  switch (type) {
1546
1627
  case "join:ok": {
@@ -1628,6 +1709,7 @@ async function mount(mountable, options) {
1628
1709
  memory,
1629
1710
  __systems: function(system_handle, ptr) {
1630
1711
  mountable.hooks.setBuffer(memory.buffer);
1712
+ mountable.hooks.setContext(ptr);
1631
1713
  mountable.hooks.systemsCallback(system_handle, ptr);
1632
1714
  },
1633
1715
  __before_frame: function(ptr, frame) {
@@ -1705,6 +1787,39 @@ function calculateTapeConfig(tape) {
1705
1787
  return { maxEvents, maxPacketBytes };
1706
1788
  }
1707
1789
 
1790
+ class Players {
1791
+ #inputs;
1792
+ #net;
1793
+ constructor(inputs, net) {
1794
+ this.#inputs = inputs;
1795
+ this.#net = net;
1796
+ }
1797
+ get(index) {
1798
+ const players = this.#inputs.players;
1799
+ if (index < 0 || index >= players.length) {
1800
+ throw new RangeError(`Player index ${index} out of bounds (0-${players.length - 1})`);
1801
+ }
1802
+ return players[index];
1803
+ }
1804
+ get count() {
1805
+ return this.#net.isInSession ? this.#net.peerCount : 1;
1806
+ }
1807
+ *[Symbol.iterator]() {
1808
+ const players = this.#inputs.players;
1809
+ const count = this.count;
1810
+ for (let i = 0;i < count; i++) {
1811
+ yield players[i];
1812
+ }
1813
+ }
1814
+ *entries() {
1815
+ const players = this.#inputs.players;
1816
+ const count = this.count;
1817
+ for (let i = 0;i < count; i++) {
1818
+ yield [i, players[i]];
1819
+ }
1820
+ }
1821
+ }
1822
+
1708
1823
  class Bloop {
1709
1824
  #systems = [];
1710
1825
  #context;
@@ -1717,15 +1832,16 @@ class Bloop {
1717
1832
  throw new Error("Bloop constructor is private. Use Bloop.create() to create a new game instance.");
1718
1833
  }
1719
1834
  const inputs = new InputContext;
1835
+ const net = new NetContext;
1836
+ const screen = new ScreenContext;
1720
1837
  this.#context = {
1721
1838
  bag: opts.bag ?? {},
1722
1839
  time: new TimeContext,
1723
1840
  inputs,
1724
- get players() {
1725
- return inputs.players;
1726
- },
1841
+ players: new Players(inputs, net),
1727
1842
  rawPointer: -1,
1728
- net: new NetContext
1843
+ net,
1844
+ screen
1729
1845
  };
1730
1846
  }
1731
1847
  get bag() {
@@ -1773,10 +1889,20 @@ class Bloop {
1773
1889
  const timeCtxPtr = dv.getUint32(TIME_CTX_OFFSET, true);
1774
1890
  const inputCtxPtr = dv.getUint32(INPUT_CTX_OFFSET, true);
1775
1891
  const netCtxPtr = dv.getUint32(NET_CTX_OFFSET, true);
1892
+ const screenCtxPtr = dv.getUint32(SCREEN_CTX_OFFSET, true);
1776
1893
  this.#context.rawPointer = ptr;
1777
- this.#context.inputs.dataView = new DataView(this.#engineBuffer, inputCtxPtr);
1778
- this.#context.time.dataView = new DataView(this.#engineBuffer, timeCtxPtr);
1779
- this.#context.net.dataView = new DataView(this.#engineBuffer, netCtxPtr);
1894
+ if (!this.#context.inputs.hasDataView() || this.#context.inputs.dataView.buffer !== this.#engineBuffer || this.#context.inputs.dataView.byteOffset !== inputCtxPtr) {
1895
+ this.#context.inputs.dataView = new DataView(this.#engineBuffer, inputCtxPtr);
1896
+ }
1897
+ if (this.#context.time.dataView?.buffer !== this.#engineBuffer || this.#context.time.dataView?.byteOffset !== timeCtxPtr) {
1898
+ this.#context.time.dataView = new DataView(this.#engineBuffer, timeCtxPtr);
1899
+ }
1900
+ if (this.#context.net.dataView?.buffer !== this.#engineBuffer || this.#context.net.dataView?.byteOffset !== netCtxPtr) {
1901
+ this.#context.net.dataView = new DataView(this.#engineBuffer, netCtxPtr);
1902
+ }
1903
+ if (this.#context.screen.dataView?.buffer !== this.#engineBuffer || this.#context.screen.dataView?.byteOffset !== screenCtxPtr) {
1904
+ this.#context.screen.dataView = new DataView(this.#engineBuffer, screenCtxPtr);
1905
+ }
1780
1906
  },
1781
1907
  systemsCallback: (system_handle, ptr) => {
1782
1908
  this.hooks.setContext(ptr);
@@ -1881,6 +2007,22 @@ class Bloop {
1881
2007
  system.netcode?.(this.#context);
1882
2008
  break;
1883
2009
  }
2010
+ case exports_enums.EventType.NetSessionInit:
2011
+ console.log("[bloop] NetSessionInit event received");
2012
+ this.#context.event = {
2013
+ type: "session:start"
2014
+ };
2015
+ system.netcode?.(this.#context);
2016
+ break;
2017
+ case exports_enums.EventType.Resize:
2018
+ system.resize?.(attachEvent(this.#context, {
2019
+ width: this.#context.screen.width,
2020
+ height: this.#context.screen.height,
2021
+ physicalWidth: this.#context.screen.physicalWidth,
2022
+ physicalHeight: this.#context.screen.physicalHeight,
2023
+ pixelRatio: this.#context.screen.pixelRatio
2024
+ }));
2025
+ break;
1884
2026
  default:
1885
2027
  break;
1886
2028
  }
@@ -1932,6 +2074,7 @@ var EventType2;
1932
2074
  EventType22[EventType22["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
1933
2075
  EventType22[EventType22["NetPacketReceived"] = 13] = "NetPacketReceived";
1934
2076
  EventType22[EventType22["NetSessionInit"] = 14] = "NetSessionInit";
2077
+ EventType22[EventType22["Resize"] = 15] = "Resize";
1935
2078
  })(EventType2 ||= {});
1936
2079
  var MouseButton2;
1937
2080
  ((MouseButton22) => {
@@ -2183,15 +2326,33 @@ var NetJoinFailReason2;
2183
2326
  NetJoinFailReason22[NetJoinFailReason22["room_not_found"] = 3] = "room_not_found";
2184
2327
  NetJoinFailReason22[NetJoinFailReason22["already_in_room"] = 4] = "already_in_room";
2185
2328
  })(NetJoinFailReason2 ||= {});
2186
- var MAX_PLAYERS2 = 12;
2187
- var KEYBOARD_OFFSET2 = 0;
2188
- var MOUSE_OFFSET2 = 256;
2189
- var MOUSE_BUTTONS_OFFSET2 = 16;
2329
+ var PEER_CTX_CONNECTED_OFFSET2 = 0;
2330
+ var PEER_CTX_SEQ_OFFSET2 = 2;
2331
+ var PEER_CTX_ACK_OFFSET2 = 4;
2332
+ var PEER_CTX_SIZE2 = 8;
2333
+ var NET_CTX_PEER_COUNT_OFFSET2 = 0;
2334
+ var NET_CTX_LOCAL_PEER_ID_OFFSET2 = 1;
2335
+ var NET_CTX_IN_SESSION_OFFSET2 = 2;
2336
+ var NET_CTX_STATUS_OFFSET2 = 3;
2337
+ var NET_CTX_MATCH_FRAME_OFFSET2 = 4;
2338
+ var NET_CTX_SESSION_START_FRAME_OFFSET2 = 8;
2339
+ var NET_CTX_ROOM_CODE_OFFSET2 = 12;
2340
+ var NET_CTX_WANTS_ROOM_CODE_OFFSET2 = 20;
2341
+ var NET_CTX_WANTS_DISCONNECT_OFFSET2 = 28;
2342
+ var NET_CTX_PEERS_OFFSET2 = 32;
2343
+ var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET2 = 128;
2344
+ var NET_CTX_TOTAL_ROLLBACKS_OFFSET2 = 132;
2345
+ var NET_CTX_FRAMES_RESIMULATED_OFFSET2 = 136;
2346
+ var MOUSE_CTX_X_OFFSET2 = 0;
2347
+ var MOUSE_CTX_Y_OFFSET2 = 4;
2348
+ var MOUSE_CTX_WHEEL_X_OFFSET2 = 8;
2349
+ var MOUSE_CTX_WHEEL_Y_OFFSET2 = 12;
2350
+ var MOUSE_CTX_BUTTON_STATES_OFFSET2 = 16;
2351
+ var PLAYER_INPUTS_KEY_CTX_OFFSET2 = 0;
2352
+ var PLAYER_INPUTS_MOUSE_CTX_OFFSET2 = 256;
2190
2353
  var PLAYER_INPUTS_SIZE2 = 280;
2191
- var INPUT_CTX_SIZE2 = MAX_PLAYERS2 * PLAYER_INPUTS_SIZE2;
2192
- function mouseButtonCodeToMouseButton2(code) {
2193
- return MouseButton2[code];
2194
- }
2354
+ var MAX_PLAYERS2 = 12;
2355
+
2195
2356
  class PlayerInputContext2 {
2196
2357
  #dataView;
2197
2358
  #keys;
@@ -2201,13 +2362,13 @@ class PlayerInputContext2 {
2201
2362
  }
2202
2363
  get keys() {
2203
2364
  if (!this.#keys) {
2204
- this.#keys = new KeyboardContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + KEYBOARD_OFFSET2));
2365
+ this.#keys = new KeyboardContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_KEY_CTX_OFFSET2));
2205
2366
  }
2206
2367
  return this.#keys;
2207
2368
  }
2208
2369
  get mouse() {
2209
2370
  if (!this.#mouse) {
2210
- this.#mouse = new MouseContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + MOUSE_OFFSET2));
2371
+ this.#mouse = new MouseContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_MOUSE_CTX_OFFSET2));
2211
2372
  }
2212
2373
  return this.#mouse;
2213
2374
  }
@@ -2232,6 +2393,9 @@ class InputContext2 {
2232
2393
  this.#dataView = dataView;
2233
2394
  this.#buildPlayers();
2234
2395
  }
2396
+ hasDataView() {
2397
+ return !!this.#dataView;
2398
+ }
2235
2399
  #buildPlayers() {
2236
2400
  this.#players = [];
2237
2401
  for (let i = 0;i < MAX_PLAYERS2; i++) {
@@ -2264,19 +2428,19 @@ class MouseContext2 {
2264
2428
  this.#dataView = dataView;
2265
2429
  }
2266
2430
  get x() {
2267
- return this.#dataView.getFloat32(0, true);
2431
+ return this.#dataView.getFloat32(MOUSE_CTX_X_OFFSET2, true);
2268
2432
  }
2269
2433
  get y() {
2270
- return this.#dataView.getFloat32(4, true);
2434
+ return this.#dataView.getFloat32(MOUSE_CTX_Y_OFFSET2, true);
2271
2435
  }
2272
2436
  get wheel() {
2273
2437
  return { x: this.wheelX, y: this.wheelY };
2274
2438
  }
2275
2439
  get wheelX() {
2276
- return this.#dataView.getFloat32(8, true);
2440
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_X_OFFSET2, true);
2277
2441
  }
2278
2442
  get wheelY() {
2279
- return this.#dataView.getFloat32(12, true);
2443
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_Y_OFFSET2, true);
2280
2444
  }
2281
2445
  get left() {
2282
2446
  return this.#buttonState(1);
@@ -2295,7 +2459,7 @@ class MouseContext2 {
2295
2459
  state = { down: false, held: false, up: false };
2296
2460
  this.#buttonStates.set(code, state);
2297
2461
  }
2298
- const offset = MOUSE_BUTTONS_OFFSET2;
2462
+ const offset = MOUSE_CTX_BUTTON_STATES_OFFSET2;
2299
2463
  state.held = !!(this.#dataView.getUint8(offset + code) & 1);
2300
2464
  state.down = state.held && !(this.#dataView.getUint8(offset + code) & 2);
2301
2465
  state.up = !state.held && !!(this.#dataView.getUint8(offset + code) & 2);
@@ -2917,15 +3081,6 @@ class KeyboardContext2 {
2917
3081
  return state;
2918
3082
  }
2919
3083
  }
2920
- var MAX_PEERS2 = 12;
2921
- var PEERS_ARRAY_OFFSET2 = 32;
2922
- var PEER_CTX_SIZE2 = 8;
2923
- var LAST_ROLLBACK_DEPTH_OFFSET2 = 128;
2924
- var TOTAL_ROLLBACKS_OFFSET2 = 132;
2925
- var FRAMES_RESIMULATED_OFFSET2 = 136;
2926
- var PEER_CONNECTED_OFFSET2 = 0;
2927
- var PEER_SEQ_OFFSET2 = 2;
2928
- var PEER_ACK_OFFSET2 = 4;
2929
3084
  var STATUS_MAP2 = {
2930
3085
  0: "offline",
2931
3086
  1: "local",
@@ -2936,7 +3091,7 @@ var STATUS_MAP2 = {
2936
3091
 
2937
3092
  class NetContext2 {
2938
3093
  dataView;
2939
- #peers = Array.from({ length: MAX_PEERS2 }, () => ({
3094
+ #peers = Array.from({ length: MAX_PLAYERS2 }, () => ({
2940
3095
  isLocal: false,
2941
3096
  seq: -1,
2942
3097
  ack: -1
@@ -2954,38 +3109,38 @@ class NetContext2 {
2954
3109
  if (!this.#hasValidBuffer()) {
2955
3110
  throw new Error("NetContext dataView is not valid");
2956
3111
  }
2957
- return this.dataView.getUint8(0);
3112
+ return this.dataView.getUint8(NET_CTX_PEER_COUNT_OFFSET2);
2958
3113
  }
2959
3114
  get localPeerId() {
2960
3115
  if (!this.#hasValidBuffer()) {
2961
3116
  throw new Error("NetContext dataView is not valid");
2962
3117
  }
2963
- return this.dataView.getUint8(1);
3118
+ return this.dataView.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET2);
2964
3119
  }
2965
3120
  get isInSession() {
2966
3121
  if (!this.#hasValidBuffer()) {
2967
3122
  throw new Error("NetContext dataView is not valid");
2968
3123
  }
2969
- return this.dataView.getUint8(2) !== 0;
3124
+ return this.dataView.getUint8(NET_CTX_IN_SESSION_OFFSET2) !== 0;
2970
3125
  }
2971
3126
  get status() {
2972
3127
  if (!this.#hasValidBuffer()) {
2973
3128
  throw new Error("NetContext dataView is not valid");
2974
3129
  }
2975
- const statusByte = this.dataView.getUint8(3);
3130
+ const statusByte = this.dataView.getUint8(NET_CTX_STATUS_OFFSET2);
2976
3131
  return STATUS_MAP2[statusByte] ?? "local";
2977
3132
  }
2978
3133
  get matchFrame() {
2979
3134
  if (!this.#hasValidBuffer()) {
2980
3135
  throw new Error("NetContext dataView is not valid");
2981
3136
  }
2982
- return this.dataView.getUint32(4, true);
3137
+ return this.dataView.getUint32(NET_CTX_MATCH_FRAME_OFFSET2, true);
2983
3138
  }
2984
3139
  get sessionStartFrame() {
2985
3140
  if (!this.#hasValidBuffer()) {
2986
3141
  throw new Error("NetContext dataView is not valid");
2987
3142
  }
2988
- return this.dataView.getUint32(8, true);
3143
+ return this.dataView.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET2, true);
2989
3144
  }
2990
3145
  get roomCode() {
2991
3146
  if (!this.#hasValidBuffer()) {
@@ -2993,7 +3148,7 @@ class NetContext2 {
2993
3148
  }
2994
3149
  const bytes = [];
2995
3150
  for (let i = 0;i < 8; i++) {
2996
- const byte = this.dataView.getUint8(12 + i);
3151
+ const byte = this.dataView.getUint8(NET_CTX_ROOM_CODE_OFFSET2 + i);
2997
3152
  if (byte === 0)
2998
3153
  break;
2999
3154
  bytes.push(byte);
@@ -3006,7 +3161,7 @@ class NetContext2 {
3006
3161
  }
3007
3162
  const bytes = [];
3008
3163
  for (let i = 0;i < 8; i++) {
3009
- const byte = this.dataView.getUint8(20 + i);
3164
+ const byte = this.dataView.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i);
3010
3165
  if (byte === 0)
3011
3166
  break;
3012
3167
  bytes.push(byte);
@@ -3018,11 +3173,11 @@ class NetContext2 {
3018
3173
  throw new Error("NetContext dataView is not valid");
3019
3174
  }
3020
3175
  for (let i = 0;i < 8; i++) {
3021
- this.dataView.setUint8(20 + i, 0);
3176
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i, 0);
3022
3177
  }
3023
3178
  if (code) {
3024
3179
  for (let i = 0;i < Math.min(code.length, 7); i++) {
3025
- this.dataView.setUint8(20 + i, code.charCodeAt(i));
3180
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i, code.charCodeAt(i));
3026
3181
  }
3027
3182
  }
3028
3183
  }
@@ -3030,13 +3185,13 @@ class NetContext2 {
3030
3185
  if (!this.#hasValidBuffer()) {
3031
3186
  return false;
3032
3187
  }
3033
- return this.dataView.getUint8(28) !== 0;
3188
+ return this.dataView.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET2) !== 0;
3034
3189
  }
3035
3190
  set wantsDisconnect(value) {
3036
3191
  if (!this.#hasValidBuffer()) {
3037
3192
  throw new Error("NetContext dataView is not valid");
3038
3193
  }
3039
- this.dataView.setUint8(28, value ? 1 : 0);
3194
+ this.dataView.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET2, value ? 1 : 0);
3040
3195
  }
3041
3196
  get peers() {
3042
3197
  if (!this.#hasValidBuffer()) {
@@ -3046,13 +3201,13 @@ class NetContext2 {
3046
3201
  const localPeerId = this.localPeerId;
3047
3202
  const matchFrame = this.matchFrame;
3048
3203
  let minRemoteSeq = -1;
3049
- for (let i = 0;i < MAX_PEERS2; i++) {
3204
+ for (let i = 0;i < MAX_PLAYERS2; i++) {
3050
3205
  if (i === localPeerId)
3051
3206
  continue;
3052
- const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
3053
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
3207
+ const peerOffset = NET_CTX_PEERS_OFFSET2 + i * PEER_CTX_SIZE2;
3208
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET2) !== 1)
3054
3209
  continue;
3055
- const seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET2, true);
3210
+ const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET2, true);
3056
3211
  if (seq < 0)
3057
3212
  continue;
3058
3213
  if (minRemoteSeq === -1 || seq < minRemoteSeq) {
@@ -3060,9 +3215,9 @@ class NetContext2 {
3060
3215
  }
3061
3216
  }
3062
3217
  this.#peersResult.length = 0;
3063
- for (let i = 0;i < MAX_PEERS2; i++) {
3064
- const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
3065
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
3218
+ for (let i = 0;i < MAX_PLAYERS2; i++) {
3219
+ const peerOffset = NET_CTX_PEERS_OFFSET2 + i * PEER_CTX_SIZE2;
3220
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET2) !== 1)
3066
3221
  continue;
3067
3222
  const peer = this.#peers[i];
3068
3223
  if (!peer) {
@@ -3074,8 +3229,8 @@ class NetContext2 {
3074
3229
  peer.seq = matchFrame;
3075
3230
  peer.ack = minRemoteSeq;
3076
3231
  } else {
3077
- peer.seq = dv.getInt16(peerOffset + PEER_SEQ_OFFSET2, true);
3078
- peer.ack = dv.getInt16(peerOffset + PEER_ACK_OFFSET2, true);
3232
+ peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET2, true);
3233
+ peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET2, true);
3079
3234
  }
3080
3235
  this.#peersResult.push(peer);
3081
3236
  }
@@ -3085,21 +3240,24 @@ class NetContext2 {
3085
3240
  if (!this.#hasValidBuffer()) {
3086
3241
  throw new Error("NetContext dataView is not valid");
3087
3242
  }
3088
- return this.dataView.getUint32(LAST_ROLLBACK_DEPTH_OFFSET2, true);
3243
+ return this.dataView.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET2, true);
3089
3244
  }
3090
3245
  get totalRollbacks() {
3091
3246
  if (!this.#hasValidBuffer()) {
3092
3247
  throw new Error("NetContext dataView is not valid");
3093
3248
  }
3094
- return this.dataView.getUint32(TOTAL_ROLLBACKS_OFFSET2, true);
3249
+ return this.dataView.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET2, true);
3095
3250
  }
3096
3251
  get framesResimulated() {
3097
3252
  if (!this.#hasValidBuffer()) {
3098
3253
  throw new Error("NetContext dataView is not valid");
3099
3254
  }
3100
- return Number(this.dataView.getBigUint64(FRAMES_RESIMULATED_OFFSET2, true));
3255
+ return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET2, true));
3101
3256
  }
3102
3257
  }
3258
+ function mouseButtonCodeToMouseButton2(code) {
3259
+ return MouseButton2[code];
3260
+ }
3103
3261
  var TAPE_MAGIC2 = 1413566533;
3104
3262
  function readTapeHeader2(tape) {
3105
3263
  const view = new DataView(tape.buffer, tape.byteOffset, tape.byteLength);
@@ -3115,11 +3273,12 @@ function readTapeHeader2(tape) {
3115
3273
  eventCount: view.getUint16(14, true)
3116
3274
  };
3117
3275
  }
3118
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.88/wasm/bloop.wasm");
3276
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.90/wasm/bloop.wasm");
3119
3277
  var TIME_CTX_OFFSET2 = 0;
3120
3278
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3121
3279
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
3122
3280
  var NET_CTX_OFFSET2 = EVENTS_OFFSET2 + 4;
3281
+ var SCREEN_CTX_OFFSET2 = NET_CTX_OFFSET2 + 4;
3123
3282
 
3124
3283
  // src/debugui/mod.ts
3125
3284
  var exports_mod = {};
@@ -6327,6 +6486,138 @@ function joinRoom(brokerUrl, _roomId, cbs) {
6327
6486
  }
6328
6487
  }
6329
6488
 
6489
+ // src/netcode/reconcile.ts
6490
+ var udp = null;
6491
+ var localPeerId = null;
6492
+ var remotePeerId = null;
6493
+ var localStringPeerId = null;
6494
+ var remoteStringPeerId = null;
6495
+ var incomingPackets = [];
6496
+ var actual = {
6497
+ roomCode: ""
6498
+ };
6499
+ async function reconcile(app, signal) {
6500
+ app.beforeFrame.subscribe((_frame) => {
6501
+ if (!app.game.context.net.isInSession) {
6502
+ return;
6503
+ }
6504
+ try {
6505
+ receivePackets(app);
6506
+ sendPacket(app);
6507
+ } catch (e4) {
6508
+ console.error("Error in beforeFrame:", e4);
6509
+ }
6510
+ });
6511
+ logger.onLog = (log) => {
6512
+ addLog(log);
6513
+ };
6514
+ while (!signal.aborted) {
6515
+ const { net } = app.game.context;
6516
+ if (net.wantsRoomCode && actual.roomCode !== net.wantsRoomCode) {
6517
+ console.log("[netcode] wants a room code", {
6518
+ actual: actual.roomCode,
6519
+ wants: net.wantsRoomCode
6520
+ });
6521
+ actual.roomCode = net.wantsRoomCode;
6522
+ joinRollbackRoom(net.wantsRoomCode, app);
6523
+ }
6524
+ await sleep(150);
6525
+ }
6526
+ }
6527
+ async function sleep(ms) {
6528
+ return new Promise((resolve) => setTimeout(resolve, ms));
6529
+ }
6530
+ function joinRollbackRoom(roomId, app) {
6531
+ app.joinRoom(roomId, {
6532
+ onPeerIdAssign: (peerId) => {
6533
+ localStringPeerId = peerId;
6534
+ },
6535
+ onBrokerMessage: (_message) => {},
6536
+ onMessage(_peerId, data, _reliable) {
6537
+ incomingPackets.push(new Uint8Array(data));
6538
+ },
6539
+ onDataChannelClose(_peerId, reliable) {
6540
+ if (!reliable && remotePeerId !== null) {
6541
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6542
+ }
6543
+ },
6544
+ onDataChannelOpen(peerId, reliable, channel) {
6545
+ if (!reliable) {
6546
+ udp = channel;
6547
+ if (localStringPeerId === null) {
6548
+ console.error("[netcode] Local peer ID not assigned yet!");
6549
+ return;
6550
+ }
6551
+ const ids = assignPeerIds(localStringPeerId, peerId);
6552
+ localPeerId = ids.local;
6553
+ setLocalId(localPeerId);
6554
+ remotePeerId = ids.remote;
6555
+ remoteStringPeerId = peerId;
6556
+ setRemoteId(remotePeerId);
6557
+ app.sim.emit.network("peer:join", { peerId: localPeerId });
6558
+ app.sim.emit.network("peer:join", { peerId: remotePeerId });
6559
+ app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
6560
+ app.sim.emit.network("session:start", {});
6561
+ }
6562
+ },
6563
+ onPeerConnected(peerId) {
6564
+ addPeer({
6565
+ id: peerId,
6566
+ nickname: peerId.substring(0, 6),
6567
+ ack: -1,
6568
+ seq: -1,
6569
+ lastPacketTime: performance.now()
6570
+ });
6571
+ console.log(`[netcode] Peer connected: ${peerId}. Total peers: ${debugState.netStatus.value.peers.length}`);
6572
+ },
6573
+ onPeerDisconnected(peerId) {
6574
+ removePeer(peerId);
6575
+ if (remotePeerId !== null && peerId === remoteStringPeerId) {
6576
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6577
+ app.sim.emit.network("session:end", {});
6578
+ }
6579
+ }
6580
+ });
6581
+ }
6582
+ function assignPeerIds(localId, remoteId) {
6583
+ if (localId < remoteId) {
6584
+ return { local: 0, remote: 1 };
6585
+ } else {
6586
+ return { local: 1, remote: 0 };
6587
+ }
6588
+ }
6589
+ function receivePackets(app) {
6590
+ for (const packetData of incomingPackets) {
6591
+ app.sim.emit.packet(packetData);
6592
+ if (remotePeerId == null) {
6593
+ return;
6594
+ }
6595
+ const peerState = unwrap(app.sim.net.peers[remotePeerId], `Remote peer state not found for peerId ${remotePeerId}`);
6596
+ updatePeer(remoteStringPeerId, {
6597
+ ack: peerState.ack,
6598
+ seq: peerState.seq,
6599
+ lastPacketTime: performance.now()
6600
+ });
6601
+ }
6602
+ incomingPackets.length = 0;
6603
+ }
6604
+ function sendPacket(app) {
6605
+ if (!udp || remotePeerId === null) {
6606
+ console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
6607
+ return;
6608
+ }
6609
+ if (udp.readyState !== "open") {
6610
+ console.warn("[netcode] Data channel not open, cannot send packet. readyState=", udp.readyState);
6611
+ return;
6612
+ }
6613
+ const packet = app.sim.getOutboundPacket(remotePeerId);
6614
+ if (!packet) {
6615
+ console.warn("[netcode] No packet to send");
6616
+ return;
6617
+ }
6618
+ udp.send(packet);
6619
+ }
6620
+
6330
6621
  // src/App.ts
6331
6622
  var DEFAULT_BROKER_URL = "wss://webrtc-divine-glade-8064.fly.dev/ws";
6332
6623
  async function start(opts) {
@@ -6350,6 +6641,7 @@ class App {
6350
6641
  #unsubscribe = null;
6351
6642
  #now = performance.now();
6352
6643
  #debugUi = null;
6644
+ #abortController = new AbortController;
6353
6645
  constructor(sim, game, brokerUrl, debugUiOpts) {
6354
6646
  this.#sim = sim;
6355
6647
  this.game = game;
@@ -6363,6 +6655,9 @@ class App {
6363
6655
  this.beforeFrame.notify(frame);
6364
6656
  };
6365
6657
  this.subscribe();
6658
+ reconcile(this, this.#abortController.signal).catch((err) => {
6659
+ console.error("Error in lemmyloop:", err);
6660
+ });
6366
6661
  }
6367
6662
  get sim() {
6368
6663
  return this.#sim;
@@ -6403,6 +6698,41 @@ class App {
6403
6698
  onHmr = createListener();
6404
6699
  subscribe() {
6405
6700
  const shouldEmitInputs = () => !this.sim.isReplaying;
6701
+ let resizeObserver = null;
6702
+ let cleanupPixelRatio = null;
6703
+ const emitResize = () => {
6704
+ const canvas2 = this.canvas;
6705
+ const pixelRatio = window.devicePixelRatio || 1;
6706
+ let width;
6707
+ let height;
6708
+ if (canvas2) {
6709
+ const rect = canvas2.getBoundingClientRect();
6710
+ width = Math.round(rect.width);
6711
+ height = Math.round(rect.height);
6712
+ } else {
6713
+ width = window.innerWidth;
6714
+ height = window.innerHeight;
6715
+ }
6716
+ this.sim.emit.resize(width, height, Math.round(width * pixelRatio), Math.round(height * pixelRatio), pixelRatio);
6717
+ };
6718
+ const canvas = this.canvas;
6719
+ if (canvas) {
6720
+ resizeObserver = new ResizeObserver(() => emitResize());
6721
+ resizeObserver.observe(canvas);
6722
+ } else {
6723
+ window.addEventListener("resize", emitResize);
6724
+ }
6725
+ const updatePixelRatioListener = () => {
6726
+ const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
6727
+ const handler = () => {
6728
+ emitResize();
6729
+ updatePixelRatioListener();
6730
+ };
6731
+ mediaQuery.addEventListener("change", handler, { once: true });
6732
+ return () => mediaQuery.removeEventListener("change", handler);
6733
+ };
6734
+ cleanupPixelRatio = updatePixelRatioListener();
6735
+ emitResize();
6406
6736
  const handleKeydown = (event) => {
6407
6737
  if (shouldEmitInputs())
6408
6738
  this.sim.emit.keydown(event.code);
@@ -6556,6 +6886,9 @@ class App {
6556
6886
  window.removeEventListener("touchstart", handleTouchstart);
6557
6887
  window.removeEventListener("touchend", handleTouchend);
6558
6888
  window.removeEventListener("touchmove", handleTouchmove);
6889
+ window.removeEventListener("resize", emitResize);
6890
+ resizeObserver?.disconnect();
6891
+ cleanupPixelRatio?.();
6559
6892
  if (this.#rafHandle != null) {
6560
6893
  cancelAnimationFrame(this.#rafHandle);
6561
6894
  }
@@ -6568,6 +6901,7 @@ class App {
6568
6901
  this.afterFrame.unsubscribeAll();
6569
6902
  this.onHmr.unsubscribeAll();
6570
6903
  this.#debugUi?.unmount();
6904
+ this.#abortController.abort();
6571
6905
  }
6572
6906
  async acceptHmr(module, opts) {
6573
6907
  const game = module.game ?? module;
@@ -6612,131 +6946,13 @@ var PacketType;
6612
6946
  PacketType2[PacketType2["None"] = 0] = "None";
6613
6947
  PacketType2[PacketType2["Inputs"] = 1] = "Inputs";
6614
6948
  })(PacketType ||= {});
6615
- // src/netcode/scaffold.ts
6616
- function joinRollbackRoom(roomId, app, opts) {
6617
- let udp = null;
6618
- let sessionActive = false;
6619
- let localPeerId = null;
6620
- let remotePeerId = null;
6621
- let localStringPeerId = null;
6622
- let remoteStringPeerId = null;
6623
- const incomingPackets = [];
6624
- function assignPeerIds(localId, remoteId) {
6625
- if (localId < remoteId) {
6626
- return { local: 0, remote: 1 };
6627
- } else {
6628
- return { local: 1, remote: 0 };
6629
- }
6630
- }
6631
- function receivePackets() {
6632
- for (const packetData of incomingPackets) {
6633
- app.sim.emit.packet(packetData);
6634
- if (remotePeerId == null) {
6635
- return;
6636
- }
6637
- const peerState = unwrap(app.sim.net.peers[remotePeerId], `Remote peer state not found for peerId ${remotePeerId}`);
6638
- updatePeer(remoteStringPeerId, {
6639
- ack: peerState.ack,
6640
- seq: peerState.seq,
6641
- lastPacketTime: performance.now()
6642
- });
6643
- }
6644
- incomingPackets.length = 0;
6645
- }
6646
- function sendPacket() {
6647
- if (!udp || remotePeerId === null) {
6648
- console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
6649
- return;
6650
- }
6651
- if (udp.readyState !== "open") {
6652
- console.warn("[netcode] Data channel not open, cannot send packet. readyState=", udp.readyState);
6653
- return;
6654
- }
6655
- const packet = app.sim.getOutboundPacket(remotePeerId);
6656
- if (!packet) {
6657
- console.warn("[netcode] No packet to send");
6658
- return;
6659
- }
6660
- udp.send(packet);
6661
- }
6662
- logger.onLog = (log) => {
6663
- addLog(log);
6664
- };
6665
- app.joinRoom(roomId, {
6666
- onPeerIdAssign: (peerId) => {
6667
- localStringPeerId = peerId;
6668
- },
6669
- onBrokerMessage: (_message) => {},
6670
- onMessage(_peerId, data, _reliable) {
6671
- incomingPackets.push(new Uint8Array(data));
6672
- },
6673
- onDataChannelClose(peerId, reliable) {
6674
- if (!reliable && remotePeerId !== null) {
6675
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6676
- sessionActive = false;
6677
- opts?.onSessionEnd?.();
6678
- }
6679
- },
6680
- onDataChannelOpen(peerId, reliable, channel) {
6681
- if (!reliable) {
6682
- udp = channel;
6683
- if (localStringPeerId === null) {
6684
- console.error("[netcode] Local peer ID not assigned yet!");
6685
- return;
6686
- }
6687
- const ids = assignPeerIds(localStringPeerId, peerId);
6688
- localPeerId = ids.local;
6689
- setLocalId(localPeerId);
6690
- remotePeerId = ids.remote;
6691
- remoteStringPeerId = peerId;
6692
- setRemoteId(remotePeerId);
6693
- app.sim.emit.network("peer:join", { peerId: localPeerId });
6694
- app.sim.emit.network("peer:join", { peerId: remotePeerId });
6695
- app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
6696
- app.sim.emit.network("session:start", {});
6697
- sessionActive = true;
6698
- opts?.onSessionStart?.();
6699
- }
6700
- },
6701
- onPeerConnected(peerId) {
6702
- addPeer({
6703
- id: peerId,
6704
- nickname: peerId.substring(0, 6),
6705
- ack: -1,
6706
- seq: -1,
6707
- lastPacketTime: performance.now()
6708
- });
6709
- console.log(`[netcode] Peer connected: ${peerId}. Total peers: ${debugState.netStatus.value.peers.length}`);
6710
- },
6711
- onPeerDisconnected(peerId) {
6712
- removePeer(peerId);
6713
- if (remotePeerId !== null && peerId === remoteStringPeerId) {
6714
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6715
- sessionActive = false;
6716
- opts?.onSessionEnd?.();
6717
- }
6718
- }
6719
- });
6720
- app.beforeFrame.subscribe((_frame) => {
6721
- if (!sessionActive || !udp || remotePeerId === null) {
6722
- return;
6723
- }
6724
- try {
6725
- receivePackets();
6726
- sendPacket();
6727
- } catch (e4) {
6728
- console.error("Error in beforeFrame:", e4);
6729
- }
6730
- });
6731
- }
6732
6949
  export {
6733
6950
  start,
6734
6951
  logger,
6735
- joinRollbackRoom,
6736
6952
  PacketType,
6737
6953
  exports_mod as Debug,
6738
6954
  App
6739
6955
  };
6740
6956
 
6741
- //# debugId=1A548992A4233E8464756E2164756E21
6957
+ //# debugId=669ECA7E8ACBF62064756E2164756E21
6742
6958
  //# sourceMappingURL=mod.js.map