@bloopjs/web 0.0.55 → 0.0.57

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
@@ -1441,8 +1441,21 @@ async function mount(opts, options) {
1441
1441
  }
1442
1442
  },
1443
1443
  __on_tape_full: function(_ctxPtr) {
1444
- if (sim?.onTapeFull) {
1445
- sim.onTapeFull(sim.saveTape());
1444
+ if (!sim) {
1445
+ throw new Error("Sim not initialized in on_tape_full");
1446
+ }
1447
+ const tapeBytes = sim.saveTape();
1448
+ if (sim.onTapeFull) {
1449
+ sim.onTapeFull(tapeBytes);
1450
+ } else {
1451
+ const size = tapeBytes.length / 1024;
1452
+ const duration = sim.time.time;
1453
+ const kbPerSecond = size / duration;
1454
+ console.warn("Tape full. Recording stopped", {
1455
+ size: `${(tapeBytes.length / 1024).toFixed(0)}kb`,
1456
+ duration: `${sim.time.time.toFixed(2)}s`,
1457
+ kbPerSecond: `${kbPerSecond.toFixed(2)} kb/s`
1458
+ });
1446
1459
  }
1447
1460
  },
1448
1461
  console_log: function(ptr, len) {
@@ -3427,6 +3440,7 @@ __export(exports_mod, {
3427
3440
  resetState: () => resetState,
3428
3441
  removePeer: () => removePeer,
3429
3442
  debugState: () => debugState,
3443
+ cycleLayout: () => cycleLayout,
3430
3444
  clearLogs: () => clearLogs,
3431
3445
  addPeer: () => addPeer,
3432
3446
  addLog: () => addLog,
@@ -4458,7 +4472,7 @@ function useSignalEffect(i4) {
4458
4472
  }
4459
4473
 
4460
4474
  // src/debugui/state.ts
4461
- var isVisible = d2(false);
4475
+ var layoutMode = d2("off");
4462
4476
  var netStatus = d2({
4463
4477
  ourId: null,
4464
4478
  remoteId: null,
@@ -4466,16 +4480,35 @@ var netStatus = d2({
4466
4480
  peers: []
4467
4481
  });
4468
4482
  var logs = d2([]);
4483
+ var fps = d2(0);
4484
+ var frameTime = d2(0);
4485
+ var snapshotSize = d2(0);
4486
+ var frameNumber = d2(0);
4469
4487
  var debugState = {
4470
- isVisible,
4488
+ layoutMode,
4489
+ isVisible: w3(() => layoutMode.value !== "off"),
4471
4490
  netStatus,
4472
4491
  logs,
4473
4492
  peer: w3(() => netStatus.value.peers[0] ?? null),
4474
4493
  advantage: w3(() => {
4475
4494
  const peer = netStatus.value.peers[0];
4476
4495
  return peer ? peer.seq - peer.ack : null;
4477
- })
4496
+ }),
4497
+ fps,
4498
+ frameTime,
4499
+ snapshotSize,
4500
+ frameNumber
4478
4501
  };
4502
+ function cycleLayout() {
4503
+ const current = layoutMode.value;
4504
+ if (current === "off") {
4505
+ layoutMode.value = "letterboxed";
4506
+ } else if (current === "letterboxed") {
4507
+ layoutMode.value = "full";
4508
+ } else {
4509
+ layoutMode.value = "off";
4510
+ }
4511
+ }
4479
4512
  function addLog(log) {
4480
4513
  debugState.logs.value = [...debugState.logs.value, log];
4481
4514
  }
@@ -4525,7 +4558,7 @@ function clearLogs() {
4525
4558
  debugState.logs.value = [];
4526
4559
  }
4527
4560
  function resetState() {
4528
- debugState.isVisible.value = false;
4561
+ debugState.layoutMode.value = "off";
4529
4562
  debugState.logs.value = [];
4530
4563
  debugState.netStatus.value = {
4531
4564
  ourId: null,
@@ -4533,6 +4566,10 @@ function resetState() {
4533
4566
  rtt: null,
4534
4567
  peers: []
4535
4568
  };
4569
+ debugState.fps.value = 0;
4570
+ debugState.frameTime.value = 0;
4571
+ debugState.snapshotSize.value = 0;
4572
+ debugState.frameNumber.value = 0;
4536
4573
  }
4537
4574
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
4538
4575
  var f4 = 0;
@@ -4795,26 +4832,257 @@ function LogEntry({ log }) {
4795
4832
 
4796
4833
  // src/debugui/components/DebugToggle.tsx
4797
4834
  function DebugToggle({ hotkey = "Escape" }) {
4798
- const isVisible2 = debugState.isVisible.value;
4799
- const toggle = () => {
4800
- debugState.isVisible.value = !debugState.isVisible.value;
4801
- };
4835
+ const isVisible = debugState.isVisible.value;
4802
4836
  return /* @__PURE__ */ u4("button", {
4803
4837
  className: "debug-toggle",
4804
- onClick: toggle,
4838
+ onClick: cycleLayout,
4805
4839
  onMouseDown: (e4) => e4.stopPropagation(),
4806
4840
  onMouseUp: (e4) => e4.stopPropagation(),
4807
- title: isVisible2 ? `Hide debug (${hotkey})` : `Show debug (${hotkey})`,
4808
- children: isVisible2 ? "✕" : "⚙"
4841
+ title: isVisible ? `Hide debug (${hotkey})` : `Show debug (${hotkey})`,
4842
+ children: isVisible ? "✕" : "⚙"
4809
4843
  }, undefined, false, undefined, this);
4810
4844
  }
4811
4845
 
4846
+ // src/debugui/components/TopBar.tsx
4847
+ function TopBar({ leftLabel, rightLabel }) {
4848
+ const fps2 = debugState.fps.value;
4849
+ const frameNumber2 = debugState.frameNumber.value;
4850
+ const rtt = debugState.netStatus.value.rtt;
4851
+ const isOnline = debugState.netStatus.value.peers.length > 0;
4852
+ return /* @__PURE__ */ u4("div", {
4853
+ className: "top-bar",
4854
+ children: [
4855
+ /* @__PURE__ */ u4("span", {
4856
+ className: "top-bar-side-label",
4857
+ children: leftLabel
4858
+ }, undefined, false, undefined, this),
4859
+ /* @__PURE__ */ u4("div", {
4860
+ className: "top-bar-center",
4861
+ children: [
4862
+ /* @__PURE__ */ u4("div", {
4863
+ className: "top-bar-item",
4864
+ children: [
4865
+ /* @__PURE__ */ u4("span", {
4866
+ className: "top-bar-label",
4867
+ children: "FPS"
4868
+ }, undefined, false, undefined, this),
4869
+ /* @__PURE__ */ u4("span", {
4870
+ className: "top-bar-value",
4871
+ children: fps2
4872
+ }, undefined, false, undefined, this)
4873
+ ]
4874
+ }, undefined, true, undefined, this),
4875
+ /* @__PURE__ */ u4("div", {
4876
+ className: "top-bar-item",
4877
+ children: [
4878
+ /* @__PURE__ */ u4("span", {
4879
+ className: "top-bar-label",
4880
+ children: "Frame"
4881
+ }, undefined, false, undefined, this),
4882
+ /* @__PURE__ */ u4("span", {
4883
+ className: "top-bar-value",
4884
+ children: frameNumber2
4885
+ }, undefined, false, undefined, this)
4886
+ ]
4887
+ }, undefined, true, undefined, this),
4888
+ isOnline && rtt !== null && /* @__PURE__ */ u4("div", {
4889
+ className: "top-bar-item",
4890
+ children: [
4891
+ /* @__PURE__ */ u4("span", {
4892
+ className: "top-bar-label",
4893
+ children: "Ping"
4894
+ }, undefined, false, undefined, this),
4895
+ /* @__PURE__ */ u4("span", {
4896
+ className: "top-bar-value",
4897
+ children: [
4898
+ rtt,
4899
+ "ms"
4900
+ ]
4901
+ }, undefined, true, undefined, this)
4902
+ ]
4903
+ }, undefined, true, undefined, this)
4904
+ ]
4905
+ }, undefined, true, undefined, this),
4906
+ /* @__PURE__ */ u4("span", {
4907
+ className: "top-bar-side-label",
4908
+ children: rightLabel
4909
+ }, undefined, false, undefined, this)
4910
+ ]
4911
+ }, undefined, true, undefined, this);
4912
+ }
4913
+
4914
+ // src/debugui/components/VerticalBar.tsx
4915
+ function VerticalBar({
4916
+ value,
4917
+ max,
4918
+ side,
4919
+ color = "#4a9eff"
4920
+ }) {
4921
+ const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
4922
+ return /* @__PURE__ */ u4("div", {
4923
+ className: `${side}-bar`,
4924
+ children: /* @__PURE__ */ u4("div", {
4925
+ className: "vertical-bar",
4926
+ children: /* @__PURE__ */ u4("div", {
4927
+ className: "vertical-bar-fill",
4928
+ style: { height: `${percentage}%`, background: color }
4929
+ }, undefined, false, undefined, this)
4930
+ }, undefined, false, undefined, this)
4931
+ }, undefined, false, undefined, this);
4932
+ }
4933
+
4934
+ // src/debugui/components/BottomBar.tsx
4935
+ function BottomBar({
4936
+ tapeUtilization = 0,
4937
+ playheadPosition = 0,
4938
+ isPlaying = true
4939
+ }) {
4940
+ const handleJumpBack = () => {};
4941
+ const handleStepBack = () => {};
4942
+ const handlePlayPause = () => {};
4943
+ const handleStepForward = () => {};
4944
+ const handleJumpForward = () => {};
4945
+ const handleSeek = () => {};
4946
+ return /* @__PURE__ */ u4("div", {
4947
+ className: "bottom-bar",
4948
+ children: [
4949
+ /* @__PURE__ */ u4("div", {
4950
+ className: "playbar-controls",
4951
+ children: [
4952
+ /* @__PURE__ */ u4("button", {
4953
+ className: "playbar-btn",
4954
+ onClick: handleJumpBack,
4955
+ children: [
4956
+ "<<",
4957
+ /* @__PURE__ */ u4("span", {
4958
+ className: "tooltip tooltip-left",
4959
+ children: [
4960
+ "Jump back ",
4961
+ /* @__PURE__ */ u4("kbd", {
4962
+ children: "4"
4963
+ }, undefined, false, undefined, this)
4964
+ ]
4965
+ }, undefined, true, undefined, this)
4966
+ ]
4967
+ }, undefined, true, undefined, this),
4968
+ /* @__PURE__ */ u4("button", {
4969
+ className: "playbar-btn",
4970
+ onClick: handleStepBack,
4971
+ children: [
4972
+ "<",
4973
+ /* @__PURE__ */ u4("span", {
4974
+ className: "tooltip",
4975
+ children: [
4976
+ "Step back ",
4977
+ /* @__PURE__ */ u4("kbd", {
4978
+ children: "5"
4979
+ }, undefined, false, undefined, this)
4980
+ ]
4981
+ }, undefined, true, undefined, this)
4982
+ ]
4983
+ }, undefined, true, undefined, this),
4984
+ /* @__PURE__ */ u4("button", {
4985
+ className: "playbar-btn",
4986
+ onClick: handlePlayPause,
4987
+ children: [
4988
+ isPlaying ? "||" : ">",
4989
+ /* @__PURE__ */ u4("span", {
4990
+ className: "tooltip",
4991
+ children: [
4992
+ isPlaying ? "Pause" : "Play",
4993
+ " ",
4994
+ /* @__PURE__ */ u4("kbd", {
4995
+ children: "6"
4996
+ }, undefined, false, undefined, this)
4997
+ ]
4998
+ }, undefined, true, undefined, this)
4999
+ ]
5000
+ }, undefined, true, undefined, this),
5001
+ /* @__PURE__ */ u4("button", {
5002
+ className: "playbar-btn",
5003
+ onClick: handleStepForward,
5004
+ children: [
5005
+ ">",
5006
+ /* @__PURE__ */ u4("span", {
5007
+ className: "tooltip",
5008
+ children: [
5009
+ "Step forward ",
5010
+ /* @__PURE__ */ u4("kbd", {
5011
+ children: "7"
5012
+ }, undefined, false, undefined, this)
5013
+ ]
5014
+ }, undefined, true, undefined, this)
5015
+ ]
5016
+ }, undefined, true, undefined, this),
5017
+ /* @__PURE__ */ u4("button", {
5018
+ className: "playbar-btn",
5019
+ onClick: handleJumpForward,
5020
+ children: [
5021
+ ">>",
5022
+ /* @__PURE__ */ u4("span", {
5023
+ className: "tooltip",
5024
+ children: [
5025
+ "Jump forward ",
5026
+ /* @__PURE__ */ u4("kbd", {
5027
+ children: "8"
5028
+ }, undefined, false, undefined, this)
5029
+ ]
5030
+ }, undefined, true, undefined, this)
5031
+ ]
5032
+ }, undefined, true, undefined, this)
5033
+ ]
5034
+ }, undefined, true, undefined, this),
5035
+ /* @__PURE__ */ u4("div", {
5036
+ className: "seek-bar",
5037
+ onClick: handleSeek,
5038
+ children: [
5039
+ /* @__PURE__ */ u4("div", {
5040
+ className: "seek-bar-fill",
5041
+ style: { width: `${tapeUtilization * 100}%` }
5042
+ }, undefined, false, undefined, this),
5043
+ tapeUtilization > 0 && /* @__PURE__ */ u4("div", {
5044
+ className: "seek-bar-position",
5045
+ style: { left: `${playheadPosition * tapeUtilization * 100}%` }
5046
+ }, undefined, false, undefined, this)
5047
+ ]
5048
+ }, undefined, true, undefined, this)
5049
+ ]
5050
+ }, undefined, true, undefined, this);
5051
+ }
5052
+
4812
5053
  // src/debugui/components/Root.tsx
4813
5054
  function Root({ canvas, hotkey = "Escape" }) {
4814
- const isVisible2 = debugState.isVisible.value;
5055
+ const layoutMode2 = debugState.layoutMode.value;
5056
+ if (layoutMode2 === "off") {
5057
+ return /* @__PURE__ */ u4(k, {
5058
+ children: [
5059
+ /* @__PURE__ */ u4("main", {
5060
+ className: "fullscreen",
5061
+ children: /* @__PURE__ */ u4(GameCanvas, {
5062
+ canvas
5063
+ }, undefined, false, undefined, this)
5064
+ }, undefined, false, undefined, this),
5065
+ /* @__PURE__ */ u4(DebugToggle, {
5066
+ hotkey
5067
+ }, undefined, false, undefined, this)
5068
+ ]
5069
+ }, undefined, true, undefined, this);
5070
+ }
5071
+ if (layoutMode2 === "letterboxed") {
5072
+ return /* @__PURE__ */ u4(k, {
5073
+ children: [
5074
+ /* @__PURE__ */ u4(LetterboxedLayout, {
5075
+ canvas
5076
+ }, undefined, false, undefined, this),
5077
+ /* @__PURE__ */ u4(DebugToggle, {
5078
+ hotkey
5079
+ }, undefined, false, undefined, this)
5080
+ ]
5081
+ }, undefined, true, undefined, this);
5082
+ }
4815
5083
  return /* @__PURE__ */ u4(k, {
4816
5084
  children: [
4817
- isVisible2 ? /* @__PURE__ */ u4("main", {
5085
+ /* @__PURE__ */ u4("main", {
4818
5086
  className: "layout",
4819
5087
  children: [
4820
5088
  /* @__PURE__ */ u4("section", {
@@ -4832,14 +5100,53 @@ function Root({ canvas, hotkey = "Escape" }) {
4832
5100
  children: /* @__PURE__ */ u4(Logs, {}, undefined, false, undefined, this)
4833
5101
  }, undefined, false, undefined, this)
4834
5102
  ]
4835
- }, undefined, true, undefined, this) : /* @__PURE__ */ u4("main", {
4836
- className: "fullscreen",
5103
+ }, undefined, true, undefined, this),
5104
+ /* @__PURE__ */ u4(DebugToggle, {
5105
+ hotkey
5106
+ }, undefined, false, undefined, this)
5107
+ ]
5108
+ }, undefined, true, undefined, this);
5109
+ }
5110
+ function LetterboxedLayout({ canvas }) {
5111
+ const isOnline = debugState.netStatus.value.peers.length > 0;
5112
+ const advantage = debugState.advantage.value ?? 0;
5113
+ const frameTime2 = debugState.frameTime.value;
5114
+ const snapshotSize2 = debugState.snapshotSize.value;
5115
+ const leftValue = isOnline ? Math.abs(advantage) : frameTime2;
5116
+ const leftMax = isOnline ? 10 : 16.67;
5117
+ const leftLabel = isOnline ? "ADV" : "MS";
5118
+ const leftColor = isOnline ? advantage >= 0 ? "#4a9eff" : "#ff4a4a" : frameTime2 > 16.67 ? "#ff4a4a" : "#4aff4a";
5119
+ const rightValue = isOnline ? 0 : snapshotSize2;
5120
+ const rightMax = isOnline ? 10 : 1e4;
5121
+ const rightLabel = isOnline ? "RB" : "KB";
5122
+ return /* @__PURE__ */ u4("main", {
5123
+ className: "layout-letterboxed",
5124
+ children: [
5125
+ /* @__PURE__ */ u4(TopBar, {
5126
+ leftLabel,
5127
+ rightLabel
5128
+ }, undefined, false, undefined, this),
5129
+ /* @__PURE__ */ u4(VerticalBar, {
5130
+ value: leftValue,
5131
+ max: leftMax,
5132
+ side: "left",
5133
+ color: leftColor
5134
+ }, undefined, false, undefined, this),
5135
+ /* @__PURE__ */ u4("div", {
5136
+ className: "letterboxed-game",
4837
5137
  children: /* @__PURE__ */ u4(GameCanvas, {
4838
5138
  canvas
4839
5139
  }, undefined, false, undefined, this)
4840
5140
  }, undefined, false, undefined, this),
4841
- /* @__PURE__ */ u4(DebugToggle, {
4842
- hotkey
5141
+ /* @__PURE__ */ u4(VerticalBar, {
5142
+ value: rightValue,
5143
+ max: rightMax,
5144
+ side: "right"
5145
+ }, undefined, false, undefined, this),
5146
+ /* @__PURE__ */ u4(BottomBar, {
5147
+ tapeUtilization: 0.67,
5148
+ playheadPosition: 0.8,
5149
+ isPlaying: true
4843
5150
  }, undefined, false, undefined, this)
4844
5151
  ]
4845
5152
  }, undefined, true, undefined, this);
@@ -4868,13 +5175,24 @@ var styles = `
4868
5175
 
4869
5176
  /* Layout */
4870
5177
  .fullscreen {
4871
- width: 100%;
4872
- height: 100%;
5178
+ width: 100vw;
5179
+ height: 100vh;
4873
5180
  margin: 0;
4874
5181
  padding: 0;
4875
5182
  overflow: hidden;
4876
5183
  }
4877
5184
 
5185
+ .fullscreen .canvas-container {
5186
+ width: 100%;
5187
+ height: 100%;
5188
+ }
5189
+
5190
+ .fullscreen canvas {
5191
+ width: 100%;
5192
+ height: 100%;
5193
+ display: block;
5194
+ }
5195
+
4878
5196
  .layout {
4879
5197
  display: grid;
4880
5198
  grid-template-areas:
@@ -4888,6 +5206,225 @@ var styles = `
4888
5206
  padding: 1rem;
4889
5207
  }
4890
5208
 
5209
+ /* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */
5210
+ .layout-letterboxed {
5211
+ display: grid;
5212
+ grid-template-areas:
5213
+ "top-bar top-bar top-bar"
5214
+ "left-bar game right-bar"
5215
+ "bottom-bar bottom-bar bottom-bar";
5216
+ grid-template-columns: 2vw 1fr 2vw;
5217
+ grid-template-rows: 2vh 1fr 2vh;
5218
+ width: 100vw;
5219
+ height: 100vh;
5220
+ background: #1a1a1a;
5221
+ overflow: hidden;
5222
+ overscroll-behavior: none;
5223
+ }
5224
+
5225
+ .top-bar {
5226
+ grid-area: top-bar;
5227
+ display: flex;
5228
+ align-items: center;
5229
+ justify-content: space-between;
5230
+ background: #111;
5231
+ color: #aaa;
5232
+ font-family: monospace;
5233
+ font-size: 12px;
5234
+ padding: 0;
5235
+ }
5236
+
5237
+ .top-bar-side-label {
5238
+ width: 2vw;
5239
+ text-align: center;
5240
+ font-size: 9px;
5241
+ text-transform: uppercase;
5242
+ letter-spacing: 0.5px;
5243
+ color: #666;
5244
+ }
5245
+
5246
+ .top-bar-center {
5247
+ display: flex;
5248
+ align-items: center;
5249
+ justify-content: center;
5250
+ gap: 24px;
5251
+ flex: 1;
5252
+ }
5253
+
5254
+ .top-bar-item {
5255
+ display: flex;
5256
+ align-items: center;
5257
+ gap: 6px;
5258
+ }
5259
+
5260
+ .top-bar-label {
5261
+ opacity: 0.6;
5262
+ }
5263
+
5264
+ .top-bar-value {
5265
+ color: #fff;
5266
+ font-weight: 500;
5267
+ }
5268
+
5269
+ .left-bar {
5270
+ grid-area: left-bar;
5271
+ display: flex;
5272
+ flex-direction: column;
5273
+ align-items: center;
5274
+ justify-content: flex-end;
5275
+ background: #111;
5276
+ padding: 4px 0;
5277
+ }
5278
+
5279
+ .right-bar {
5280
+ grid-area: right-bar;
5281
+ display: flex;
5282
+ flex-direction: column;
5283
+ align-items: center;
5284
+ justify-content: flex-end;
5285
+ background: #111;
5286
+ padding: 4px 0;
5287
+ }
5288
+
5289
+ .vertical-bar {
5290
+ width: 12px;
5291
+ flex: 1;
5292
+ background: #333;
5293
+ border-radius: 2px;
5294
+ position: relative;
5295
+ overflow: hidden;
5296
+ }
5297
+
5298
+ .vertical-bar-fill {
5299
+ position: absolute;
5300
+ bottom: 0;
5301
+ left: 0;
5302
+ right: 0;
5303
+ background: #4a9eff;
5304
+ border-radius: 2px;
5305
+ transition: height 0.1s ease-out;
5306
+ }
5307
+
5308
+
5309
+ .bottom-bar {
5310
+ grid-area: bottom-bar;
5311
+ display: flex;
5312
+ align-items: center;
5313
+ background: #111;
5314
+ padding: 0 8px;
5315
+ gap: 8px;
5316
+ }
5317
+
5318
+ .playbar-controls {
5319
+ display: flex;
5320
+ align-items: center;
5321
+ gap: 2px;
5322
+ flex-shrink: 0;
5323
+ }
5324
+
5325
+ .playbar-btn {
5326
+ width: 1.5vh;
5327
+ height: 1.5vh;
5328
+ min-width: 18px;
5329
+ min-height: 18px;
5330
+ border: none;
5331
+ background: transparent;
5332
+ color: #888;
5333
+ font-size: 10px;
5334
+ cursor: pointer;
5335
+ border-radius: 2px;
5336
+ display: flex;
5337
+ align-items: center;
5338
+ justify-content: center;
5339
+ transition: background 0.15s, color 0.15s;
5340
+ position: relative;
5341
+ }
5342
+
5343
+ .playbar-btn:hover {
5344
+ background: #333;
5345
+ color: #fff;
5346
+ }
5347
+
5348
+ .playbar-btn:hover .tooltip {
5349
+ opacity: 1;
5350
+ visibility: visible;
5351
+ }
5352
+
5353
+ .tooltip {
5354
+ position: absolute;
5355
+ bottom: calc(100% + 4px);
5356
+ left: 50%;
5357
+ transform: translateX(-50%);
5358
+ background: #222;
5359
+ color: #ccc;
5360
+ padding: 4px 8px;
5361
+ border-radius: 4px;
5362
+ font-size: 10px;
5363
+ white-space: nowrap;
5364
+ opacity: 0;
5365
+ visibility: hidden;
5366
+ transition: opacity 0.15s;
5367
+ pointer-events: none;
5368
+ z-index: 10;
5369
+ }
5370
+
5371
+ .tooltip-left {
5372
+ left: 0;
5373
+ transform: none;
5374
+ }
5375
+
5376
+ .tooltip kbd {
5377
+ background: #444;
5378
+ padding: 1px 4px;
5379
+ border-radius: 2px;
5380
+ margin-left: 4px;
5381
+ font-family: monospace;
5382
+ }
5383
+
5384
+ .seek-bar {
5385
+ flex: 1;
5386
+ height: 16px;
5387
+ background: #222;
5388
+ border-radius: 4px;
5389
+ position: relative;
5390
+ cursor: pointer;
5391
+ overflow: hidden;
5392
+ }
5393
+
5394
+ .seek-bar-fill {
5395
+ position: absolute;
5396
+ top: 0;
5397
+ left: 0;
5398
+ bottom: 0;
5399
+ background: linear-gradient(to right, #4a2070, #7b3fa0);
5400
+ border-radius: 4px;
5401
+ transition: width 0.1s ease-out;
5402
+ }
5403
+
5404
+ .seek-bar-position {
5405
+ position: absolute;
5406
+ top: 0;
5407
+ bottom: 0;
5408
+ width: 2px;
5409
+ background: #fff;
5410
+ }
5411
+
5412
+ .letterboxed-game {
5413
+ grid-area: game;
5414
+ overflow: hidden;
5415
+ }
5416
+
5417
+ .letterboxed-game .canvas-container {
5418
+ width: 100%;
5419
+ height: 100%;
5420
+ }
5421
+
5422
+ .letterboxed-game canvas {
5423
+ width: 100%;
5424
+ height: 100%;
5425
+ display: block;
5426
+ }
5427
+
4891
5428
  .game {
4892
5429
  grid-area: game;
4893
5430
  border-radius: 8px;
@@ -5074,7 +5611,7 @@ class DebugUi {
5074
5611
  const container = options.container ?? document.body;
5075
5612
  const initiallyVisible = options.initiallyVisible ?? new URLSearchParams(window.location.search).has("debug");
5076
5613
  this.#host = document.createElement("bloop-debug-ui");
5077
- this.#host.style.cssText = "display:block;width:100%;height:100%;position:absolute;top:0;left:0;";
5614
+ this.#host.style.cssText = "display:block;width:100%;height:100%;position:absolute;top:0;left:0;overflow:hidden;overscroll-behavior:none;";
5078
5615
  this.#shadow = this.#host.attachShadow({ mode: "open" });
5079
5616
  const styleEl = document.createElement("style");
5080
5617
  styleEl.textContent = styles;
@@ -5083,12 +5620,12 @@ class DebugUi {
5083
5620
  this.#mountPoint.id = "debug-root";
5084
5621
  this.#mountPoint.style.cssText = "width:100%;height:100%;";
5085
5622
  this.#shadow.appendChild(this.#mountPoint);
5086
- debugState.isVisible.value = initiallyVisible;
5623
+ debugState.layoutMode.value = initiallyVisible ? "letterboxed" : "off";
5087
5624
  this.#canvas = document.createElement("canvas");
5088
5625
  this.#render();
5089
5626
  container.appendChild(this.#host);
5090
5627
  this.#cleanup = this.#setupHotkey();
5091
- debugState.isVisible.subscribe(() => {
5628
+ debugState.layoutMode.subscribe(() => {
5092
5629
  this.#render();
5093
5630
  });
5094
5631
  }
@@ -5098,7 +5635,7 @@ class DebugUi {
5098
5635
  #setupHotkey() {
5099
5636
  const handler = (e4) => {
5100
5637
  if (e4.key === this.#hotkey) {
5101
- debugState.isVisible.value = !debugState.isVisible.value;
5638
+ cycleLayout();
5102
5639
  }
5103
5640
  };
5104
5641
  window.addEventListener("keydown", handler);
@@ -5114,7 +5651,7 @@ class DebugUi {
5114
5651
  return debugState.isVisible.value;
5115
5652
  }
5116
5653
  set isVisible(value) {
5117
- debugState.isVisible.value = value;
5654
+ debugState.layoutMode.value = value ? "letterboxed" : "off";
5118
5655
  }
5119
5656
  unmount() {
5120
5657
  this.#cleanup?.();
@@ -5246,8 +5783,29 @@ class App {
5246
5783
  }
5247
5784
  };
5248
5785
  window.addEventListener("keydown", playbarHotkeys);
5786
+ let fpsFrames = 0;
5787
+ let fpsLastTime = performance.now();
5249
5788
  const frame = () => {
5250
- this.sim.step(performance.now() - this.#now);
5789
+ const stepStart = performance.now();
5790
+ const ticks = this.sim.step(stepStart - this.#now);
5791
+ if (ticks > 0) {
5792
+ const stepEnd = performance.now();
5793
+ debugState.frameTime.value = stepEnd - stepStart;
5794
+ debugState.frameNumber.value = this.sim.time.frame;
5795
+ if (debugState.layoutMode.value === "letterboxed") {
5796
+ const bag = this.game.bag;
5797
+ if (bag) {
5798
+ debugState.snapshotSize.value = JSON.stringify(bag).length;
5799
+ }
5800
+ }
5801
+ fpsFrames++;
5802
+ const elapsed = stepEnd - fpsLastTime;
5803
+ if (elapsed >= 1000) {
5804
+ debugState.fps.value = Math.round(fpsFrames * 1000 / elapsed);
5805
+ fpsFrames = 0;
5806
+ fpsLastTime = stepEnd;
5807
+ }
5808
+ }
5251
5809
  if (!this.sim.isPaused) {
5252
5810
  try {
5253
5811
  this.afterFrame.notify(this.sim.time.frame);
@@ -5453,5 +6011,5 @@ export {
5453
6011
  App
5454
6012
  };
5455
6013
 
5456
- //# debugId=3A298B3E29A818AD64756E2164756E21
6014
+ //# debugId=97C22D9653CE8DB864756E2164756E21
5457
6015
  //# sourceMappingURL=mod.js.map