@bloopjs/web 0.0.89 → 0.0.91

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,9 +77,11 @@ __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,
85
87
  PLAYER_INPUTS_MOUSE_CTX_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
@@ -141,6 +143,7 @@ var EventType;
141
143
  EventType2[EventType2["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
142
144
  EventType2[EventType2["NetPacketReceived"] = 13] = "NetPacketReceived";
143
145
  EventType2[EventType2["NetSessionInit"] = 14] = "NetSessionInit";
146
+ EventType2[EventType2["Resize"] = 15] = "Resize";
144
147
  })(EventType ||= {});
145
148
  var MouseButton;
146
149
  ((MouseButton2) => {
@@ -412,6 +415,11 @@ var NET_CTX_PEERS_OFFSET = 32;
412
415
  var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET = 128;
413
416
  var NET_CTX_TOTAL_ROLLBACKS_OFFSET = 132;
414
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;
415
423
  var MOUSE_CTX_X_OFFSET = 0;
416
424
  var MOUSE_CTX_Y_OFFSET = 4;
417
425
  var MOUSE_CTX_WHEEL_X_OFFSET = 8;
@@ -1328,6 +1336,43 @@ class NetContext {
1328
1336
  }
1329
1337
  }
1330
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);
1373
+ }
1374
+ }
1375
+
1331
1376
  class TimeContext {
1332
1377
  dataView;
1333
1378
  constructor(dataView) {
@@ -1387,12 +1432,13 @@ function readTapeHeader(tape) {
1387
1432
  eventCount: view.getUint16(14, true)
1388
1433
  };
1389
1434
  }
1390
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.89/wasm/bloop.wasm");
1435
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.91/wasm/bloop.wasm");
1391
1436
  var MAX_ROLLBACK_FRAMES = 500;
1392
1437
  var TIME_CTX_OFFSET = 0;
1393
1438
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
1394
1439
  var EVENTS_OFFSET = INPUT_CTX_OFFSET + 4;
1395
1440
  var NET_CTX_OFFSET = EVENTS_OFFSET + 4;
1441
+ var SCREEN_CTX_OFFSET = NET_CTX_OFFSET + 4;
1396
1442
  var SNAPSHOT_HEADER_LEN = 16;
1397
1443
  var SNAPSHOT_HEADER_USER_LEN_OFFSET = 4;
1398
1444
  var SNAPSHOT_HEADER_ENGINE_LEN_OFFSET = 8;
@@ -1414,6 +1460,7 @@ class Sim {
1414
1460
  #serialize;
1415
1461
  #isPaused = false;
1416
1462
  #net;
1463
+ #screen;
1417
1464
  onTapeFull;
1418
1465
  constructor(wasm, memory, opts) {
1419
1466
  this.wasm = wasm;
@@ -1422,6 +1469,7 @@ class Sim {
1422
1469
  this.id = `${Math.floor(Math.random() * 1e6)}`;
1423
1470
  this.#serialize = opts?.serialize;
1424
1471
  this.#net = new NetContext;
1472
+ this.#screen = new ScreenContext;
1425
1473
  }
1426
1474
  step(ms) {
1427
1475
  if (this.#isPaused) {
@@ -1534,6 +1582,12 @@ class Sim {
1534
1582
  }
1535
1583
  return this.#net;
1536
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
+ }
1537
1591
  get buffer() {
1538
1592
  return this.#memory.buffer;
1539
1593
  }
@@ -1565,6 +1619,9 @@ class Sim {
1565
1619
  mousewheel: (x, y, peerId = 0) => {
1566
1620
  this.wasm.emit_mousewheel(x, y, peerId);
1567
1621
  },
1622
+ resize: (width, height, physicalWidth, physicalHeight, pixelRatio) => {
1623
+ this.wasm.emit_resize(width, height, physicalWidth, physicalHeight, pixelRatio);
1624
+ },
1568
1625
  network: (type, data) => {
1569
1626
  switch (type) {
1570
1627
  case "join:ok": {
@@ -1730,6 +1787,39 @@ function calculateTapeConfig(tape) {
1730
1787
  return { maxEvents, maxPacketBytes };
1731
1788
  }
1732
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
+
1733
1823
  class Bloop {
1734
1824
  #systems = [];
1735
1825
  #context;
@@ -1742,15 +1832,16 @@ class Bloop {
1742
1832
  throw new Error("Bloop constructor is private. Use Bloop.create() to create a new game instance.");
1743
1833
  }
1744
1834
  const inputs = new InputContext;
1835
+ const net = new NetContext;
1836
+ const screen = new ScreenContext;
1745
1837
  this.#context = {
1746
1838
  bag: opts.bag ?? {},
1747
1839
  time: new TimeContext,
1748
1840
  inputs,
1749
- get players() {
1750
- return inputs.players;
1751
- },
1841
+ players: new Players(inputs, net),
1752
1842
  rawPointer: -1,
1753
- net: new NetContext
1843
+ net,
1844
+ screen
1754
1845
  };
1755
1846
  }
1756
1847
  get bag() {
@@ -1798,6 +1889,7 @@ class Bloop {
1798
1889
  const timeCtxPtr = dv.getUint32(TIME_CTX_OFFSET, true);
1799
1890
  const inputCtxPtr = dv.getUint32(INPUT_CTX_OFFSET, true);
1800
1891
  const netCtxPtr = dv.getUint32(NET_CTX_OFFSET, true);
1892
+ const screenCtxPtr = dv.getUint32(SCREEN_CTX_OFFSET, true);
1801
1893
  this.#context.rawPointer = ptr;
1802
1894
  if (!this.#context.inputs.hasDataView() || this.#context.inputs.dataView.buffer !== this.#engineBuffer || this.#context.inputs.dataView.byteOffset !== inputCtxPtr) {
1803
1895
  this.#context.inputs.dataView = new DataView(this.#engineBuffer, inputCtxPtr);
@@ -1808,6 +1900,9 @@ class Bloop {
1808
1900
  if (this.#context.net.dataView?.buffer !== this.#engineBuffer || this.#context.net.dataView?.byteOffset !== netCtxPtr) {
1809
1901
  this.#context.net.dataView = new DataView(this.#engineBuffer, netCtxPtr);
1810
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
+ }
1811
1906
  },
1812
1907
  systemsCallback: (system_handle, ptr) => {
1813
1908
  this.hooks.setContext(ptr);
@@ -1919,6 +2014,15 @@ class Bloop {
1919
2014
  };
1920
2015
  system.netcode?.(this.#context);
1921
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;
1922
2026
  default:
1923
2027
  break;
1924
2028
  }
@@ -1970,6 +2074,7 @@ var EventType2;
1970
2074
  EventType22[EventType22["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
1971
2075
  EventType22[EventType22["NetPacketReceived"] = 13] = "NetPacketReceived";
1972
2076
  EventType22[EventType22["NetSessionInit"] = 14] = "NetSessionInit";
2077
+ EventType22[EventType22["Resize"] = 15] = "Resize";
1973
2078
  })(EventType2 ||= {});
1974
2079
  var MouseButton2;
1975
2080
  ((MouseButton22) => {
@@ -3168,15 +3273,17 @@ function readTapeHeader2(tape) {
3168
3273
  eventCount: view.getUint16(14, true)
3169
3274
  };
3170
3275
  }
3171
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.89/wasm/bloop.wasm");
3276
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.91/wasm/bloop.wasm");
3172
3277
  var TIME_CTX_OFFSET2 = 0;
3173
3278
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3174
3279
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
3175
3280
  var NET_CTX_OFFSET2 = EVENTS_OFFSET2 + 4;
3281
+ var SCREEN_CTX_OFFSET2 = NET_CTX_OFFSET2 + 4;
3176
3282
 
3177
3283
  // src/debugui/mod.ts
3178
3284
  var exports_mod = {};
3179
3285
  __export(exports_mod, {
3286
+ wireTapeLoadHandlers: () => wireTapeLoadHandlers,
3180
3287
  wireTapeDragDrop: () => wireTapeDragDrop,
3181
3288
  wirePlaybarHandlers: () => wirePlaybarHandlers,
3182
3289
  updatePeer: () => updatePeer,
@@ -3188,6 +3295,7 @@ __export(exports_mod, {
3188
3295
  debugState: () => debugState,
3189
3296
  cycleLayout: () => cycleLayout,
3190
3297
  clearLogs: () => clearLogs,
3298
+ checkForSavedTape: () => checkForSavedTape,
3191
3299
  addPeer: () => addPeer,
3192
3300
  addLog: () => addLog,
3193
3301
  DebugUi: () => DebugUi
@@ -3531,6 +3639,45 @@ function p2(n2, t3) {
3531
3639
  var u3 = r2.__H || (r2.__H = { __: [], __h: [] });
3532
3640
  return n2 >= u3.__.length && u3.__.push({}), u3.__[n2];
3533
3641
  }
3642
+ function d2(n2) {
3643
+ return o2 = 1, h2(D2, n2);
3644
+ }
3645
+ function h2(n2, u3, i3) {
3646
+ var o3 = p2(t2++, 2);
3647
+ if (o3.t = n2, !o3.__c && (o3.__ = [i3 ? i3(u3) : D2(undefined, u3), function(n3) {
3648
+ var t3 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t3, n3);
3649
+ t3 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));
3650
+ }], o3.__c = r2, !r2.__f)) {
3651
+ var f3 = function(n3, t3, r3) {
3652
+ if (!o3.__c.__H)
3653
+ return true;
3654
+ var u4 = o3.__c.__H.__.filter(function(n4) {
3655
+ return !!n4.__c;
3656
+ });
3657
+ if (u4.every(function(n4) {
3658
+ return !n4.__N;
3659
+ }))
3660
+ return !c3 || c3.call(this, n3, t3, r3);
3661
+ var i4 = o3.__c.props !== n3;
3662
+ return u4.forEach(function(n4) {
3663
+ if (n4.__N) {
3664
+ var t4 = n4.__[0];
3665
+ n4.__ = n4.__N, n4.__N = undefined, t4 !== n4.__[0] && (i4 = true);
3666
+ }
3667
+ }), c3 && c3.call(this, n3, t3, r3) || i4;
3668
+ };
3669
+ r2.__f = true;
3670
+ var { shouldComponentUpdate: c3, componentWillUpdate: e3 } = r2;
3671
+ r2.componentWillUpdate = function(n3, t3, r3) {
3672
+ if (this.__e) {
3673
+ var u4 = c3;
3674
+ c3 = undefined, f3(n3, t3, r3), c3 = u4;
3675
+ }
3676
+ e3 && e3.call(this, n3, t3, r3);
3677
+ }, r2.shouldComponentUpdate = f3;
3678
+ }
3679
+ return o3.__N || o3.__;
3680
+ }
3534
3681
  function y2(n2, u3) {
3535
3682
  var i3 = p2(t2++, 3);
3536
3683
  !c2.__s && C2(i3.__H, u3) && (i3.__ = n2, i3.u = u3, r2.__H.__h.push(i3));
@@ -3617,15 +3764,18 @@ function C2(n2, t3) {
3617
3764
  return t4 !== n2[r3];
3618
3765
  });
3619
3766
  }
3767
+ function D2(n2, t3) {
3768
+ return typeof t3 == "function" ? t3(n2) : t3;
3769
+ }
3620
3770
 
3621
3771
  // ../../node_modules/@preact/signals-core/dist/signals-core.module.js
3622
3772
  var i3 = Symbol.for("preact-signals");
3623
3773
  function t3() {
3624
3774
  if (!(s3 > 1)) {
3625
3775
  var i4, t4 = false;
3626
- while (h2 !== undefined) {
3627
- var r3 = h2;
3628
- h2 = undefined;
3776
+ while (h3 !== undefined) {
3777
+ var r3 = h3;
3778
+ h3 = undefined;
3629
3779
  f3++;
3630
3780
  while (r3 !== undefined) {
3631
3781
  var o3 = r3.o;
@@ -3660,7 +3810,7 @@ function n2(i4) {
3660
3810
  o3 = t4;
3661
3811
  }
3662
3812
  }
3663
- var h2 = undefined;
3813
+ var h3 = undefined;
3664
3814
  var s3 = 0;
3665
3815
  var f3 = 0;
3666
3816
  var v3 = 0;
@@ -3791,7 +3941,7 @@ Object.defineProperty(u3.prototype, "value", { get: function() {
3791
3941
  }
3792
3942
  }
3793
3943
  } });
3794
- function d2(i4, t4) {
3944
+ function d3(i4, t4) {
3795
3945
  return new u3(i4, t4);
3796
3946
  }
3797
3947
  function c3(i4) {
@@ -3991,8 +4141,8 @@ p3.prototype.S = function() {
3991
4141
  p3.prototype.N = function() {
3992
4142
  if (!(2 & this.f)) {
3993
4143
  this.f |= 2;
3994
- this.o = h2;
3995
- h2 = this;
4144
+ this.o = h3;
4145
+ h3 = this;
3996
4146
  }
3997
4147
  };
3998
4148
  p3.prototype.d = function() {
@@ -4022,12 +4172,12 @@ var s4;
4022
4172
  function l4(i4, n3) {
4023
4173
  l[i4] = n3.bind(null, l[i4] || function() {});
4024
4174
  }
4025
- function d3(i4) {
4175
+ function d4(i4) {
4026
4176
  if (s4)
4027
4177
  s4();
4028
4178
  s4 = i4 && i4.S();
4029
4179
  }
4030
- function h3(i4) {
4180
+ function h4(i4) {
4031
4181
  var r4 = this, f4 = i4.data, o4 = useSignal(f4);
4032
4182
  o4.value = f4;
4033
4183
  var e4 = T2(function() {
@@ -4053,8 +4203,8 @@ function h3(i4) {
4053
4203
  }, []);
4054
4204
  return e4.value;
4055
4205
  }
4056
- h3.displayName = "_st";
4057
- Object.defineProperties(u3.prototype, { constructor: { configurable: true, value: undefined }, type: { configurable: true, value: h3 }, props: { configurable: true, get: function() {
4206
+ h4.displayName = "_st";
4207
+ Object.defineProperties(u3.prototype, { constructor: { configurable: true, value: undefined }, type: { configurable: true, value: h4 }, props: { configurable: true, get: function() {
4058
4208
  return { data: this };
4059
4209
  } }, __b: { configurable: true, value: 1 } });
4060
4210
  l4("__b", function(i4, r4) {
@@ -4074,7 +4224,7 @@ l4("__b", function(i4, r4) {
4074
4224
  i4(r4);
4075
4225
  });
4076
4226
  l4("__r", function(i4, r4) {
4077
- d3();
4227
+ d4();
4078
4228
  var n3, t4 = r4.__c;
4079
4229
  if (t4) {
4080
4230
  t4.__$f &= -2;
@@ -4092,16 +4242,16 @@ l4("__r", function(i4, r4) {
4092
4242
  }();
4093
4243
  }
4094
4244
  v4 = t4;
4095
- d3(n3);
4245
+ d4(n3);
4096
4246
  i4(r4);
4097
4247
  });
4098
4248
  l4("__e", function(i4, r4, n3, t4) {
4099
- d3();
4249
+ d4();
4100
4250
  v4 = undefined;
4101
4251
  i4(r4, n3, t4);
4102
4252
  });
4103
4253
  l4("diffed", function(i4, r4) {
4104
- d3();
4254
+ d4();
4105
4255
  v4 = undefined;
4106
4256
  var n3;
4107
4257
  if (typeof r4.type == "string" && (n3 = r4.__e)) {
@@ -4131,7 +4281,7 @@ l4("diffed", function(i4, r4) {
4131
4281
  i4(r4);
4132
4282
  });
4133
4283
  function p4(i4, r4, n3, t4) {
4134
- var f4 = r4 in i4 && i4.ownerSVGElement === undefined, o4 = d2(n3);
4284
+ var f4 = r4 in i4 && i4.ownerSVGElement === undefined, o4 = d3(n3);
4135
4285
  return { o: function(i5, r5) {
4136
4286
  o4.value = i5;
4137
4287
  t4 = r5;
@@ -4204,7 +4354,7 @@ x.prototype.shouldComponentUpdate = function(i4, r4) {
4204
4354
  };
4205
4355
  function useSignal(i4) {
4206
4356
  return T2(function() {
4207
- return d2(i4);
4357
+ return d3(i4);
4208
4358
  }, []);
4209
4359
  }
4210
4360
  function useSignalEffect(i4) {
@@ -4218,30 +4368,34 @@ function useSignalEffect(i4) {
4218
4368
  }
4219
4369
 
4220
4370
  // src/debugui/state.ts
4221
- var layoutMode = d2("off");
4222
- var netStatus = d2({
4371
+ var layoutMode = d3("off");
4372
+ var netStatus = d3({
4223
4373
  ourId: null,
4224
4374
  remoteId: null,
4225
4375
  rtt: null,
4226
4376
  peers: []
4227
4377
  });
4228
- var logs = d2([]);
4229
- var fps = d2(0);
4230
- var frameTime = d2(0);
4231
- var snapshotSize = d2(0);
4232
- var frameNumber = d2(0);
4233
- var hmrFlash = d2(false);
4234
- var isPlaying = d2(true);
4235
- var tapeUtilization = d2(0);
4236
- var playheadPosition = d2(0);
4237
- var tapeStartFrame = d2(0);
4238
- var tapeFrameCount = d2(0);
4239
- var onJumpBack = d2(null);
4240
- var onStepBack = d2(null);
4241
- var onPlayPause = d2(null);
4242
- var onStepForward = d2(null);
4243
- var onJumpForward = d2(null);
4244
- var onSeek = d2(null);
4378
+ var logs = d3([]);
4379
+ var fps = d3(0);
4380
+ var frameTime = d3(0);
4381
+ var snapshotSize = d3(0);
4382
+ var frameNumber = d3(0);
4383
+ var hmrFlash = d3(false);
4384
+ var isPlaying = d3(true);
4385
+ var tapeUtilization = d3(0);
4386
+ var playheadPosition = d3(0);
4387
+ var tapeStartFrame = d3(0);
4388
+ var tapeFrameCount = d3(0);
4389
+ var onJumpBack = d3(null);
4390
+ var onStepBack = d3(null);
4391
+ var onPlayPause = d3(null);
4392
+ var onStepForward = d3(null);
4393
+ var onJumpForward = d3(null);
4394
+ var onSeek = d3(null);
4395
+ var onLoadTape = d3(null);
4396
+ var onReplayLastTape = d3(null);
4397
+ var lastTapeName = d3(null);
4398
+ var isLoadDialogOpen = d3(false);
4245
4399
  var debugState = {
4246
4400
  layoutMode,
4247
4401
  isVisible: w3(() => layoutMode.value !== "off"),
@@ -4267,7 +4421,11 @@ var debugState = {
4267
4421
  onPlayPause,
4268
4422
  onStepForward,
4269
4423
  onJumpForward,
4270
- onSeek
4424
+ onSeek,
4425
+ onLoadTape,
4426
+ onReplayLastTape,
4427
+ lastTapeName,
4428
+ isLoadDialogOpen
4271
4429
  };
4272
4430
  function cycleLayout() {
4273
4431
  const current = layoutMode.value;
@@ -4423,6 +4581,61 @@ function wireTapeDragDrop(canvas, app) {
4423
4581
  app.loadTape(bytes);
4424
4582
  });
4425
4583
  }
4584
+ var TAPE_DB_NAME = "bloop-debug";
4585
+ var TAPE_STORE_NAME = "tapes";
4586
+ var TAPE_KEY = "last";
4587
+ function openTapeDB() {
4588
+ return new Promise((resolve, reject) => {
4589
+ const request = indexedDB.open(TAPE_DB_NAME, 1);
4590
+ request.onerror = () => reject(request.error);
4591
+ request.onsuccess = () => resolve(request.result);
4592
+ request.onupgradeneeded = () => {
4593
+ request.result.createObjectStore(TAPE_STORE_NAME);
4594
+ };
4595
+ });
4596
+ }
4597
+ async function saveTapeToStorage(bytes, fileName) {
4598
+ const db = await openTapeDB();
4599
+ return new Promise((resolve, reject) => {
4600
+ const tx = db.transaction(TAPE_STORE_NAME, "readwrite");
4601
+ tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
4602
+ tx.oncomplete = () => resolve();
4603
+ tx.onerror = () => reject(tx.error);
4604
+ });
4605
+ }
4606
+ async function loadTapeFromStorage() {
4607
+ try {
4608
+ const db = await openTapeDB();
4609
+ return new Promise((resolve, reject) => {
4610
+ const tx = db.transaction(TAPE_STORE_NAME, "readonly");
4611
+ const request = tx.objectStore(TAPE_STORE_NAME).get(TAPE_KEY);
4612
+ request.onsuccess = () => resolve(request.result ?? null);
4613
+ request.onerror = () => reject(request.error);
4614
+ });
4615
+ } catch {
4616
+ return null;
4617
+ }
4618
+ }
4619
+ async function checkForSavedTape() {
4620
+ const saved = await loadTapeFromStorage();
4621
+ debugState.lastTapeName.value = saved?.fileName ?? null;
4622
+ }
4623
+ function wireTapeLoadHandlers(app) {
4624
+ debugState.onLoadTape.value = async (bytes, fileName) => {
4625
+ app.loadTape(bytes);
4626
+ await saveTapeToStorage(bytes, fileName);
4627
+ debugState.lastTapeName.value = fileName;
4628
+ debugState.isLoadDialogOpen.value = false;
4629
+ };
4630
+ debugState.onReplayLastTape.value = async () => {
4631
+ const saved = await loadTapeFromStorage();
4632
+ if (saved) {
4633
+ app.loadTape(saved.bytes);
4634
+ debugState.isLoadDialogOpen.value = false;
4635
+ }
4636
+ };
4637
+ checkForSavedTape();
4638
+ }
4426
4639
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
4427
4640
  var f4 = 0;
4428
4641
  function u4(e4, t4, n3, o4, i4, u5) {
@@ -4783,6 +4996,108 @@ function VerticalBar({
4783
4996
  }, undefined, false, undefined, this);
4784
4997
  }
4785
4998
 
4999
+ // src/debugui/components/LoadTapeDialog.tsx
5000
+ function LoadTapeDialog() {
5001
+ const dialogRef = A2(null);
5002
+ const fileInputRef = A2(null);
5003
+ const [isDragOver, setIsDragOver] = d2(false);
5004
+ const isOpen = debugState.isLoadDialogOpen.value;
5005
+ const lastTapeName2 = debugState.lastTapeName.value;
5006
+ y2(() => {
5007
+ const dialog = dialogRef.current;
5008
+ if (!dialog)
5009
+ return;
5010
+ if (isOpen && !dialog.open) {
5011
+ dialog.showModal();
5012
+ } else if (!isOpen && dialog.open) {
5013
+ dialog.close();
5014
+ }
5015
+ }, [isOpen]);
5016
+ const handleClose = q2(() => {
5017
+ debugState.isLoadDialogOpen.value = false;
5018
+ }, []);
5019
+ const handleFileSelect = q2(async (file) => {
5020
+ if (!file.name.endsWith(".bloop"))
5021
+ return;
5022
+ const bytes = new Uint8Array(await file.arrayBuffer());
5023
+ debugState.onLoadTape.value?.(bytes, file.name);
5024
+ }, []);
5025
+ const handleDropZoneClick = q2(() => {
5026
+ fileInputRef.current?.click();
5027
+ }, []);
5028
+ const handleFileInputChange = q2((e4) => {
5029
+ const input = e4.currentTarget;
5030
+ const file = input.files?.[0];
5031
+ if (file) {
5032
+ handleFileSelect(file);
5033
+ input.value = "";
5034
+ }
5035
+ }, [handleFileSelect]);
5036
+ const handleDragOver = q2((e4) => {
5037
+ e4.preventDefault();
5038
+ if (e4.dataTransfer)
5039
+ e4.dataTransfer.dropEffect = "copy";
5040
+ setIsDragOver(true);
5041
+ }, []);
5042
+ const handleDragLeave = q2(() => {
5043
+ setIsDragOver(false);
5044
+ }, []);
5045
+ const handleDrop = q2((e4) => {
5046
+ e4.preventDefault();
5047
+ setIsDragOver(false);
5048
+ const file = e4.dataTransfer?.files[0];
5049
+ if (file) {
5050
+ handleFileSelect(file);
5051
+ }
5052
+ }, [handleFileSelect]);
5053
+ const handleReplayLast = q2(() => {
5054
+ debugState.onReplayLastTape.value?.();
5055
+ }, []);
5056
+ return /* @__PURE__ */ u4("dialog", {
5057
+ ref: dialogRef,
5058
+ className: "load-tape-dialog",
5059
+ onClose: handleClose,
5060
+ children: /* @__PURE__ */ u4("div", {
5061
+ className: "load-tape-dialog-content",
5062
+ children: [
5063
+ /* @__PURE__ */ u4("h3", {
5064
+ children: "Load Tape"
5065
+ }, undefined, false, undefined, this),
5066
+ /* @__PURE__ */ u4("div", {
5067
+ className: `drop-zone ${isDragOver ? "drag-over" : ""}`,
5068
+ onClick: handleDropZoneClick,
5069
+ onDragOver: handleDragOver,
5070
+ onDragLeave: handleDragLeave,
5071
+ onDrop: handleDrop,
5072
+ children: /* @__PURE__ */ u4("span", {
5073
+ className: "drop-zone-text",
5074
+ children: [
5075
+ "Drop .bloop file here",
5076
+ /* @__PURE__ */ u4("br", {}, undefined, false, undefined, this),
5077
+ "or click to browse"
5078
+ ]
5079
+ }, undefined, true, undefined, this)
5080
+ }, undefined, false, undefined, this),
5081
+ /* @__PURE__ */ u4("input", {
5082
+ ref: fileInputRef,
5083
+ type: "file",
5084
+ accept: ".bloop",
5085
+ className: "hidden-file-input",
5086
+ onChange: handleFileInputChange
5087
+ }, undefined, false, undefined, this),
5088
+ lastTapeName2 && /* @__PURE__ */ u4("button", {
5089
+ className: "replay-last-btn",
5090
+ onClick: handleReplayLast,
5091
+ children: [
5092
+ "Replay last: ",
5093
+ lastTapeName2
5094
+ ]
5095
+ }, undefined, true, undefined, this)
5096
+ ]
5097
+ }, undefined, true, undefined, this)
5098
+ }, undefined, false, undefined, this);
5099
+ }
5100
+
4786
5101
  // src/debugui/components/BottomBar.tsx
4787
5102
  function useRepeatOnHold(action) {
4788
5103
  const rafId = A2(null);
@@ -4869,6 +5184,9 @@ function BottomBar() {
4869
5184
  debugState.onSeek.value?.(ratio);
4870
5185
  }, []);
4871
5186
  const seekDrag = useSeekDrag(handleSeek);
5187
+ const handleLoadTapeClick = q2(() => {
5188
+ debugState.isLoadDialogOpen.value = true;
5189
+ }, []);
4872
5190
  return /* @__PURE__ */ u4("div", {
4873
5191
  className: "bottom-bar",
4874
5192
  children: [
@@ -4955,6 +5273,17 @@ function BottomBar() {
4955
5273
  ]
4956
5274
  }, undefined, true, undefined, this)
4957
5275
  ]
5276
+ }, undefined, true, undefined, this),
5277
+ /* @__PURE__ */ u4("button", {
5278
+ className: "playbar-btn load-tape-btn",
5279
+ onClick: handleLoadTapeClick,
5280
+ children: [
5281
+ "Load",
5282
+ /* @__PURE__ */ u4("span", {
5283
+ className: "tooltip",
5284
+ children: "Load tape"
5285
+ }, undefined, false, undefined, this)
5286
+ ]
4958
5287
  }, undefined, true, undefined, this)
4959
5288
  ]
4960
5289
  }, undefined, true, undefined, this),
@@ -4971,7 +5300,8 @@ function BottomBar() {
4971
5300
  style: { left: `${playheadPosition2 * tapeUtilization2 * 100}%` }
4972
5301
  }, undefined, false, undefined, this)
4973
5302
  ]
4974
- }, undefined, true, undefined, this)
5303
+ }, undefined, true, undefined, this),
5304
+ /* @__PURE__ */ u4(LoadTapeDialog, {}, undefined, false, undefined, this)
4975
5305
  ]
4976
5306
  }, undefined, true, undefined, this);
4977
5307
  }
@@ -5538,6 +5868,89 @@ var styles = `
5538
5868
  border-radius: 4px;
5539
5869
  border: 1px inset lavender;
5540
5870
  }
5871
+
5872
+ /* Load Tape Dialog */
5873
+ .load-tape-dialog {
5874
+ background: #1a1a1a;
5875
+ border: 1px solid #333;
5876
+ border-radius: 8px;
5877
+ padding: 0;
5878
+ color: #ccc;
5879
+ font-family: monospace;
5880
+ max-width: 320px;
5881
+ width: 90vw;
5882
+ }
5883
+
5884
+ .load-tape-dialog::backdrop {
5885
+ background: rgba(0, 0, 0, 0.7);
5886
+ }
5887
+
5888
+ .load-tape-dialog-content {
5889
+ padding: 16px;
5890
+ }
5891
+
5892
+ .load-tape-dialog h3 {
5893
+ margin: 0 0 16px 0;
5894
+ font-size: 14px;
5895
+ font-weight: 600;
5896
+ color: #fff;
5897
+ }
5898
+
5899
+ .drop-zone {
5900
+ border: 2px dashed #444;
5901
+ border-radius: 8px;
5902
+ padding: 32px 16px;
5903
+ text-align: center;
5904
+ cursor: pointer;
5905
+ transition: border-color 0.15s, background 0.15s;
5906
+ }
5907
+
5908
+ .drop-zone:hover {
5909
+ border-color: #666;
5910
+ background: #222;
5911
+ }
5912
+
5913
+ .drop-zone.drag-over {
5914
+ border-color: #7b3fa0;
5915
+ background: rgba(123, 63, 160, 0.1);
5916
+ }
5917
+
5918
+ .drop-zone-text {
5919
+ color: #888;
5920
+ font-size: 12px;
5921
+ line-height: 1.5;
5922
+ }
5923
+
5924
+ .hidden-file-input {
5925
+ display: none;
5926
+ }
5927
+
5928
+ .replay-last-btn {
5929
+ width: 100%;
5930
+ margin-top: 12px;
5931
+ padding: 8px 12px;
5932
+ background: #333;
5933
+ border: none;
5934
+ border-radius: 4px;
5935
+ color: #ccc;
5936
+ font-family: monospace;
5937
+ font-size: 12px;
5938
+ cursor: pointer;
5939
+ transition: background 0.15s;
5940
+ text-align: left;
5941
+ overflow: hidden;
5942
+ text-overflow: ellipsis;
5943
+ white-space: nowrap;
5944
+ }
5945
+
5946
+ .replay-last-btn:hover {
5947
+ background: #444;
5948
+ color: #fff;
5949
+ }
5950
+
5951
+ .load-tape-btn {
5952
+ margin-left: 4px;
5953
+ }
5541
5954
  `;
5542
5955
 
5543
5956
  // src/debugui/DebugUi.ts
@@ -6565,6 +6978,7 @@ class App {
6565
6978
  this.#debugUi = new DebugUi(opts);
6566
6979
  wirePlaybarHandlers(this);
6567
6980
  wireTapeDragDrop(this.#debugUi.canvas, this);
6981
+ wireTapeLoadHandlers(this);
6568
6982
  return this.#debugUi;
6569
6983
  }
6570
6984
  get debugUi() {
@@ -6592,6 +7006,41 @@ class App {
6592
7006
  onHmr = createListener();
6593
7007
  subscribe() {
6594
7008
  const shouldEmitInputs = () => !this.sim.isReplaying;
7009
+ let resizeObserver = null;
7010
+ let cleanupPixelRatio = null;
7011
+ const emitResize = () => {
7012
+ const canvas2 = this.canvas;
7013
+ const pixelRatio = window.devicePixelRatio || 1;
7014
+ let width;
7015
+ let height;
7016
+ if (canvas2) {
7017
+ const rect = canvas2.getBoundingClientRect();
7018
+ width = Math.round(rect.width);
7019
+ height = Math.round(rect.height);
7020
+ } else {
7021
+ width = window.innerWidth;
7022
+ height = window.innerHeight;
7023
+ }
7024
+ this.sim.emit.resize(width, height, Math.round(width * pixelRatio), Math.round(height * pixelRatio), pixelRatio);
7025
+ };
7026
+ const canvas = this.canvas;
7027
+ if (canvas) {
7028
+ resizeObserver = new ResizeObserver(() => emitResize());
7029
+ resizeObserver.observe(canvas);
7030
+ } else {
7031
+ window.addEventListener("resize", emitResize);
7032
+ }
7033
+ const updatePixelRatioListener = () => {
7034
+ const mediaQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
7035
+ const handler = () => {
7036
+ emitResize();
7037
+ updatePixelRatioListener();
7038
+ };
7039
+ mediaQuery.addEventListener("change", handler, { once: true });
7040
+ return () => mediaQuery.removeEventListener("change", handler);
7041
+ };
7042
+ cleanupPixelRatio = updatePixelRatioListener();
7043
+ emitResize();
6595
7044
  const handleKeydown = (event) => {
6596
7045
  if (shouldEmitInputs())
6597
7046
  this.sim.emit.keydown(event.code);
@@ -6745,6 +7194,9 @@ class App {
6745
7194
  window.removeEventListener("touchstart", handleTouchstart);
6746
7195
  window.removeEventListener("touchend", handleTouchend);
6747
7196
  window.removeEventListener("touchmove", handleTouchmove);
7197
+ window.removeEventListener("resize", emitResize);
7198
+ resizeObserver?.disconnect();
7199
+ cleanupPixelRatio?.();
6748
7200
  if (this.#rafHandle != null) {
6749
7201
  cancelAnimationFrame(this.#rafHandle);
6750
7202
  }
@@ -6810,5 +7262,5 @@ export {
6810
7262
  App
6811
7263
  };
6812
7264
 
6813
- //# debugId=164BA35FD7424E1864756E2164756E21
7265
+ //# debugId=3429E07BC69AE66A64756E2164756E21
6814
7266
  //# sourceMappingURL=mod.js.map