@bloopjs/web 0.0.56 → 0.0.58

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
@@ -3435,11 +3435,13 @@ function joinRoom(brokerUrl, _roomId, cbs) {
3435
3435
  var exports_mod = {};
3436
3436
  __export(exports_mod, {
3437
3437
  updatePeer: () => updatePeer,
3438
+ triggerHmrFlash: () => triggerHmrFlash,
3438
3439
  setRemoteId: () => setRemoteId,
3439
3440
  setLocalId: () => setLocalId,
3440
3441
  resetState: () => resetState,
3441
3442
  removePeer: () => removePeer,
3442
3443
  debugState: () => debugState,
3444
+ cycleLayout: () => cycleLayout,
3443
3445
  clearLogs: () => clearLogs,
3444
3446
  addPeer: () => addPeer,
3445
3447
  addLog: () => addLog,
@@ -4471,7 +4473,7 @@ function useSignalEffect(i4) {
4471
4473
  }
4472
4474
 
4473
4475
  // src/debugui/state.ts
4474
- var isVisible = d2(false);
4476
+ var layoutMode = d2("off");
4475
4477
  var netStatus = d2({
4476
4478
  ourId: null,
4477
4479
  remoteId: null,
@@ -4479,16 +4481,63 @@ var netStatus = d2({
4479
4481
  peers: []
4480
4482
  });
4481
4483
  var logs = d2([]);
4484
+ var fps = d2(0);
4485
+ var frameTime = d2(0);
4486
+ var snapshotSize = d2(0);
4487
+ var frameNumber = d2(0);
4488
+ var hmrFlash = d2(false);
4482
4489
  var debugState = {
4483
- isVisible,
4490
+ layoutMode,
4491
+ isVisible: w3(() => layoutMode.value !== "off"),
4484
4492
  netStatus,
4485
4493
  logs,
4486
4494
  peer: w3(() => netStatus.value.peers[0] ?? null),
4487
4495
  advantage: w3(() => {
4488
4496
  const peer = netStatus.value.peers[0];
4489
4497
  return peer ? peer.seq - peer.ack : null;
4490
- })
4498
+ }),
4499
+ fps,
4500
+ frameTime,
4501
+ snapshotSize,
4502
+ frameNumber,
4503
+ hmrFlash
4491
4504
  };
4505
+ function cycleLayout() {
4506
+ const current = layoutMode.value;
4507
+ if (current === "off") {
4508
+ layoutMode.value = "letterboxed";
4509
+ } else if (current === "letterboxed") {
4510
+ layoutMode.value = "full";
4511
+ } else {
4512
+ layoutMode.value = "off";
4513
+ }
4514
+ }
4515
+ var hmrFlashQueued = false;
4516
+ function triggerHmrFlash() {
4517
+ if (!debugState.isVisible.value)
4518
+ return;
4519
+ if (!document.hasFocus()) {
4520
+ if (!hmrFlashQueued) {
4521
+ hmrFlashQueued = true;
4522
+ window.addEventListener("focus", onWindowFocus);
4523
+ }
4524
+ return;
4525
+ }
4526
+ doFlash();
4527
+ }
4528
+ function onWindowFocus() {
4529
+ if (hmrFlashQueued) {
4530
+ hmrFlashQueued = false;
4531
+ window.removeEventListener("focus", onWindowFocus);
4532
+ doFlash();
4533
+ }
4534
+ }
4535
+ function doFlash() {
4536
+ debugState.hmrFlash.value = true;
4537
+ setTimeout(() => {
4538
+ debugState.hmrFlash.value = false;
4539
+ }, 300);
4540
+ }
4492
4541
  function addLog(log) {
4493
4542
  debugState.logs.value = [...debugState.logs.value, log];
4494
4543
  }
@@ -4538,7 +4587,7 @@ function clearLogs() {
4538
4587
  debugState.logs.value = [];
4539
4588
  }
4540
4589
  function resetState() {
4541
- debugState.isVisible.value = false;
4590
+ debugState.layoutMode.value = "off";
4542
4591
  debugState.logs.value = [];
4543
4592
  debugState.netStatus.value = {
4544
4593
  ourId: null,
@@ -4546,6 +4595,11 @@ function resetState() {
4546
4595
  rtt: null,
4547
4596
  peers: []
4548
4597
  };
4598
+ debugState.fps.value = 0;
4599
+ debugState.frameTime.value = 0;
4600
+ debugState.snapshotSize.value = 0;
4601
+ debugState.frameNumber.value = 0;
4602
+ debugState.hmrFlash.value = false;
4549
4603
  }
4550
4604
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
4551
4605
  var f4 = 0;
@@ -4808,26 +4862,257 @@ function LogEntry({ log }) {
4808
4862
 
4809
4863
  // src/debugui/components/DebugToggle.tsx
4810
4864
  function DebugToggle({ hotkey = "Escape" }) {
4811
- const isVisible2 = debugState.isVisible.value;
4812
- const toggle = () => {
4813
- debugState.isVisible.value = !debugState.isVisible.value;
4814
- };
4865
+ const isVisible = debugState.isVisible.value;
4815
4866
  return /* @__PURE__ */ u4("button", {
4816
4867
  className: "debug-toggle",
4817
- onClick: toggle,
4868
+ onClick: cycleLayout,
4818
4869
  onMouseDown: (e4) => e4.stopPropagation(),
4819
4870
  onMouseUp: (e4) => e4.stopPropagation(),
4820
- title: isVisible2 ? `Hide debug (${hotkey})` : `Show debug (${hotkey})`,
4821
- children: isVisible2 ? "✕" : "⚙"
4871
+ title: isVisible ? `Hide debug (${hotkey})` : `Show debug (${hotkey})`,
4872
+ children: isVisible ? "✕" : "⚙"
4822
4873
  }, undefined, false, undefined, this);
4823
4874
  }
4824
4875
 
4876
+ // src/debugui/components/TopBar.tsx
4877
+ function TopBar({ leftLabel, rightLabel }) {
4878
+ const fps2 = debugState.fps.value;
4879
+ const frameNumber2 = debugState.frameNumber.value;
4880
+ const rtt = debugState.netStatus.value.rtt;
4881
+ const isOnline = debugState.netStatus.value.peers.length > 0;
4882
+ return /* @__PURE__ */ u4("div", {
4883
+ className: "top-bar",
4884
+ children: [
4885
+ /* @__PURE__ */ u4("span", {
4886
+ className: "top-bar-side-label",
4887
+ children: leftLabel
4888
+ }, undefined, false, undefined, this),
4889
+ /* @__PURE__ */ u4("div", {
4890
+ className: "top-bar-center",
4891
+ children: [
4892
+ /* @__PURE__ */ u4("div", {
4893
+ className: "top-bar-item",
4894
+ children: [
4895
+ /* @__PURE__ */ u4("span", {
4896
+ className: "top-bar-label",
4897
+ children: "FPS"
4898
+ }, undefined, false, undefined, this),
4899
+ /* @__PURE__ */ u4("span", {
4900
+ className: "top-bar-value",
4901
+ children: fps2
4902
+ }, undefined, false, undefined, this)
4903
+ ]
4904
+ }, undefined, true, undefined, this),
4905
+ /* @__PURE__ */ u4("div", {
4906
+ className: "top-bar-item",
4907
+ children: [
4908
+ /* @__PURE__ */ u4("span", {
4909
+ className: "top-bar-label",
4910
+ children: "Frame"
4911
+ }, undefined, false, undefined, this),
4912
+ /* @__PURE__ */ u4("span", {
4913
+ className: "top-bar-value",
4914
+ children: frameNumber2
4915
+ }, undefined, false, undefined, this)
4916
+ ]
4917
+ }, undefined, true, undefined, this),
4918
+ isOnline && rtt !== null && /* @__PURE__ */ u4("div", {
4919
+ className: "top-bar-item",
4920
+ children: [
4921
+ /* @__PURE__ */ u4("span", {
4922
+ className: "top-bar-label",
4923
+ children: "Ping"
4924
+ }, undefined, false, undefined, this),
4925
+ /* @__PURE__ */ u4("span", {
4926
+ className: "top-bar-value",
4927
+ children: [
4928
+ rtt,
4929
+ "ms"
4930
+ ]
4931
+ }, undefined, true, undefined, this)
4932
+ ]
4933
+ }, undefined, true, undefined, this)
4934
+ ]
4935
+ }, undefined, true, undefined, this),
4936
+ /* @__PURE__ */ u4("span", {
4937
+ className: "top-bar-side-label",
4938
+ children: rightLabel
4939
+ }, undefined, false, undefined, this)
4940
+ ]
4941
+ }, undefined, true, undefined, this);
4942
+ }
4943
+
4944
+ // src/debugui/components/VerticalBar.tsx
4945
+ function VerticalBar({
4946
+ value,
4947
+ max,
4948
+ side,
4949
+ color = "#4a9eff"
4950
+ }) {
4951
+ const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
4952
+ return /* @__PURE__ */ u4("div", {
4953
+ className: `${side}-bar`,
4954
+ children: /* @__PURE__ */ u4("div", {
4955
+ className: "vertical-bar",
4956
+ children: /* @__PURE__ */ u4("div", {
4957
+ className: "vertical-bar-fill",
4958
+ style: { height: `${percentage}%`, background: color }
4959
+ }, undefined, false, undefined, this)
4960
+ }, undefined, false, undefined, this)
4961
+ }, undefined, false, undefined, this);
4962
+ }
4963
+
4964
+ // src/debugui/components/BottomBar.tsx
4965
+ function BottomBar({
4966
+ tapeUtilization = 0,
4967
+ playheadPosition = 0,
4968
+ isPlaying = true
4969
+ }) {
4970
+ const handleJumpBack = () => {};
4971
+ const handleStepBack = () => {};
4972
+ const handlePlayPause = () => {};
4973
+ const handleStepForward = () => {};
4974
+ const handleJumpForward = () => {};
4975
+ const handleSeek = () => {};
4976
+ return /* @__PURE__ */ u4("div", {
4977
+ className: "bottom-bar",
4978
+ children: [
4979
+ /* @__PURE__ */ u4("div", {
4980
+ className: "playbar-controls",
4981
+ children: [
4982
+ /* @__PURE__ */ u4("button", {
4983
+ className: "playbar-btn",
4984
+ onClick: handleJumpBack,
4985
+ children: [
4986
+ "<<",
4987
+ /* @__PURE__ */ u4("span", {
4988
+ className: "tooltip tooltip-left",
4989
+ children: [
4990
+ "Jump back ",
4991
+ /* @__PURE__ */ u4("kbd", {
4992
+ children: "4"
4993
+ }, undefined, false, undefined, this)
4994
+ ]
4995
+ }, undefined, true, undefined, this)
4996
+ ]
4997
+ }, undefined, true, undefined, this),
4998
+ /* @__PURE__ */ u4("button", {
4999
+ className: "playbar-btn",
5000
+ onClick: handleStepBack,
5001
+ children: [
5002
+ "<",
5003
+ /* @__PURE__ */ u4("span", {
5004
+ className: "tooltip",
5005
+ children: [
5006
+ "Step back ",
5007
+ /* @__PURE__ */ u4("kbd", {
5008
+ children: "5"
5009
+ }, undefined, false, undefined, this)
5010
+ ]
5011
+ }, undefined, true, undefined, this)
5012
+ ]
5013
+ }, undefined, true, undefined, this),
5014
+ /* @__PURE__ */ u4("button", {
5015
+ className: "playbar-btn",
5016
+ onClick: handlePlayPause,
5017
+ children: [
5018
+ isPlaying ? "||" : ">",
5019
+ /* @__PURE__ */ u4("span", {
5020
+ className: "tooltip",
5021
+ children: [
5022
+ isPlaying ? "Pause" : "Play",
5023
+ " ",
5024
+ /* @__PURE__ */ u4("kbd", {
5025
+ children: "6"
5026
+ }, undefined, false, undefined, this)
5027
+ ]
5028
+ }, undefined, true, undefined, this)
5029
+ ]
5030
+ }, undefined, true, undefined, this),
5031
+ /* @__PURE__ */ u4("button", {
5032
+ className: "playbar-btn",
5033
+ onClick: handleStepForward,
5034
+ children: [
5035
+ ">",
5036
+ /* @__PURE__ */ u4("span", {
5037
+ className: "tooltip",
5038
+ children: [
5039
+ "Step forward ",
5040
+ /* @__PURE__ */ u4("kbd", {
5041
+ children: "7"
5042
+ }, undefined, false, undefined, this)
5043
+ ]
5044
+ }, undefined, true, undefined, this)
5045
+ ]
5046
+ }, undefined, true, undefined, this),
5047
+ /* @__PURE__ */ u4("button", {
5048
+ className: "playbar-btn",
5049
+ onClick: handleJumpForward,
5050
+ children: [
5051
+ ">>",
5052
+ /* @__PURE__ */ u4("span", {
5053
+ className: "tooltip",
5054
+ children: [
5055
+ "Jump forward ",
5056
+ /* @__PURE__ */ u4("kbd", {
5057
+ children: "8"
5058
+ }, undefined, false, undefined, this)
5059
+ ]
5060
+ }, undefined, true, undefined, this)
5061
+ ]
5062
+ }, undefined, true, undefined, this)
5063
+ ]
5064
+ }, undefined, true, undefined, this),
5065
+ /* @__PURE__ */ u4("div", {
5066
+ className: "seek-bar",
5067
+ onClick: handleSeek,
5068
+ children: [
5069
+ /* @__PURE__ */ u4("div", {
5070
+ className: "seek-bar-fill",
5071
+ style: { width: `${tapeUtilization * 100}%` }
5072
+ }, undefined, false, undefined, this),
5073
+ tapeUtilization > 0 && /* @__PURE__ */ u4("div", {
5074
+ className: "seek-bar-position",
5075
+ style: { left: `${playheadPosition * tapeUtilization * 100}%` }
5076
+ }, undefined, false, undefined, this)
5077
+ ]
5078
+ }, undefined, true, undefined, this)
5079
+ ]
5080
+ }, undefined, true, undefined, this);
5081
+ }
5082
+
4825
5083
  // src/debugui/components/Root.tsx
4826
5084
  function Root({ canvas, hotkey = "Escape" }) {
4827
- const isVisible2 = debugState.isVisible.value;
5085
+ const layoutMode2 = debugState.layoutMode.value;
5086
+ if (layoutMode2 === "off") {
5087
+ return /* @__PURE__ */ u4(k, {
5088
+ children: [
5089
+ /* @__PURE__ */ u4("main", {
5090
+ className: "fullscreen",
5091
+ children: /* @__PURE__ */ u4(GameCanvas, {
5092
+ canvas
5093
+ }, undefined, false, undefined, this)
5094
+ }, undefined, false, undefined, this),
5095
+ /* @__PURE__ */ u4(DebugToggle, {
5096
+ hotkey
5097
+ }, undefined, false, undefined, this)
5098
+ ]
5099
+ }, undefined, true, undefined, this);
5100
+ }
5101
+ if (layoutMode2 === "letterboxed") {
5102
+ return /* @__PURE__ */ u4(k, {
5103
+ children: [
5104
+ /* @__PURE__ */ u4(LetterboxedLayout, {
5105
+ canvas
5106
+ }, undefined, false, undefined, this),
5107
+ /* @__PURE__ */ u4(DebugToggle, {
5108
+ hotkey
5109
+ }, undefined, false, undefined, this)
5110
+ ]
5111
+ }, undefined, true, undefined, this);
5112
+ }
4828
5113
  return /* @__PURE__ */ u4(k, {
4829
5114
  children: [
4830
- isVisible2 ? /* @__PURE__ */ u4("main", {
5115
+ /* @__PURE__ */ u4("main", {
4831
5116
  className: "layout",
4832
5117
  children: [
4833
5118
  /* @__PURE__ */ u4("section", {
@@ -4845,14 +5130,55 @@ function Root({ canvas, hotkey = "Escape" }) {
4845
5130
  children: /* @__PURE__ */ u4(Logs, {}, undefined, false, undefined, this)
4846
5131
  }, undefined, false, undefined, this)
4847
5132
  ]
4848
- }, undefined, true, undefined, this) : /* @__PURE__ */ u4("main", {
4849
- className: "fullscreen",
5133
+ }, undefined, true, undefined, this),
5134
+ /* @__PURE__ */ u4(DebugToggle, {
5135
+ hotkey
5136
+ }, undefined, false, undefined, this)
5137
+ ]
5138
+ }, undefined, true, undefined, this);
5139
+ }
5140
+ function LetterboxedLayout({ canvas }) {
5141
+ const isOnline = debugState.netStatus.value.peers.length > 0;
5142
+ const advantage = debugState.advantage.value ?? 0;
5143
+ const frameTime2 = debugState.frameTime.value;
5144
+ const snapshotSize2 = debugState.snapshotSize.value;
5145
+ const hmrFlash2 = debugState.hmrFlash.value;
5146
+ const leftValue = isOnline ? Math.abs(advantage) : frameTime2;
5147
+ const leftMax = isOnline ? 10 : 16.67;
5148
+ const leftLabel = isOnline ? "ADV" : "MS";
5149
+ const leftColor = isOnline ? advantage >= 0 ? "#4a9eff" : "#ff4a4a" : frameTime2 > 16.67 ? "#ff4a4a" : "#4aff4a";
5150
+ const rightValue = isOnline ? 0 : snapshotSize2;
5151
+ const rightMax = isOnline ? 10 : 1e4;
5152
+ const rightLabel = isOnline ? "RB" : "KB";
5153
+ const gameClassName = hmrFlash2 ? "letterboxed-game hmr-flash" : "letterboxed-game";
5154
+ return /* @__PURE__ */ u4("main", {
5155
+ className: "layout-letterboxed",
5156
+ children: [
5157
+ /* @__PURE__ */ u4(TopBar, {
5158
+ leftLabel,
5159
+ rightLabel
5160
+ }, undefined, false, undefined, this),
5161
+ /* @__PURE__ */ u4(VerticalBar, {
5162
+ value: leftValue,
5163
+ max: leftMax,
5164
+ side: "left",
5165
+ color: leftColor
5166
+ }, undefined, false, undefined, this),
5167
+ /* @__PURE__ */ u4("div", {
5168
+ className: gameClassName,
4850
5169
  children: /* @__PURE__ */ u4(GameCanvas, {
4851
5170
  canvas
4852
5171
  }, undefined, false, undefined, this)
4853
5172
  }, undefined, false, undefined, this),
4854
- /* @__PURE__ */ u4(DebugToggle, {
4855
- hotkey
5173
+ /* @__PURE__ */ u4(VerticalBar, {
5174
+ value: rightValue,
5175
+ max: rightMax,
5176
+ side: "right"
5177
+ }, undefined, false, undefined, this),
5178
+ /* @__PURE__ */ u4(BottomBar, {
5179
+ tapeUtilization: 0.67,
5180
+ playheadPosition: 0.8,
5181
+ isPlaying: true
4856
5182
  }, undefined, false, undefined, this)
4857
5183
  ]
4858
5184
  }, undefined, true, undefined, this);
@@ -4881,13 +5207,24 @@ var styles = `
4881
5207
 
4882
5208
  /* Layout */
4883
5209
  .fullscreen {
4884
- width: 100%;
4885
- height: 100%;
5210
+ width: 100vw;
5211
+ height: 100vh;
4886
5212
  margin: 0;
4887
5213
  padding: 0;
4888
5214
  overflow: hidden;
4889
5215
  }
4890
5216
 
5217
+ .fullscreen .canvas-container {
5218
+ width: 100%;
5219
+ height: 100%;
5220
+ }
5221
+
5222
+ .fullscreen canvas {
5223
+ width: 100%;
5224
+ height: 100%;
5225
+ display: block;
5226
+ }
5227
+
4891
5228
  .layout {
4892
5229
  display: grid;
4893
5230
  grid-template-areas:
@@ -4901,6 +5238,242 @@ var styles = `
4901
5238
  padding: 1rem;
4902
5239
  }
4903
5240
 
5241
+ /* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */
5242
+ .layout-letterboxed {
5243
+ display: grid;
5244
+ grid-template-areas:
5245
+ "top-bar top-bar top-bar"
5246
+ "left-bar game right-bar"
5247
+ "bottom-bar bottom-bar bottom-bar";
5248
+ grid-template-columns: 2vw 1fr 2vw;
5249
+ grid-template-rows: 2vh 1fr 2vh;
5250
+ width: 100vw;
5251
+ height: 100vh;
5252
+ background: #1a1a1a;
5253
+ overflow: hidden;
5254
+ overscroll-behavior: none;
5255
+ }
5256
+
5257
+ .top-bar {
5258
+ grid-area: top-bar;
5259
+ display: flex;
5260
+ align-items: center;
5261
+ justify-content: space-between;
5262
+ background: #111;
5263
+ color: #aaa;
5264
+ font-family: monospace;
5265
+ font-size: 12px;
5266
+ padding: 0;
5267
+ }
5268
+
5269
+ .top-bar-side-label {
5270
+ width: 2vw;
5271
+ text-align: center;
5272
+ font-size: 9px;
5273
+ text-transform: uppercase;
5274
+ letter-spacing: 0.5px;
5275
+ color: #666;
5276
+ }
5277
+
5278
+ .top-bar-center {
5279
+ display: flex;
5280
+ align-items: center;
5281
+ justify-content: center;
5282
+ gap: 24px;
5283
+ flex: 1;
5284
+ }
5285
+
5286
+ .top-bar-item {
5287
+ display: flex;
5288
+ align-items: center;
5289
+ gap: 6px;
5290
+ }
5291
+
5292
+ .top-bar-label {
5293
+ opacity: 0.6;
5294
+ }
5295
+
5296
+ .top-bar-value {
5297
+ color: #fff;
5298
+ font-weight: 500;
5299
+ }
5300
+
5301
+ .left-bar {
5302
+ grid-area: left-bar;
5303
+ display: flex;
5304
+ flex-direction: column;
5305
+ align-items: center;
5306
+ justify-content: flex-end;
5307
+ background: #111;
5308
+ padding: 4px 0;
5309
+ }
5310
+
5311
+ .right-bar {
5312
+ grid-area: right-bar;
5313
+ display: flex;
5314
+ flex-direction: column;
5315
+ align-items: center;
5316
+ justify-content: flex-end;
5317
+ background: #111;
5318
+ padding: 4px 0;
5319
+ }
5320
+
5321
+ .vertical-bar {
5322
+ width: 12px;
5323
+ flex: 1;
5324
+ background: #333;
5325
+ border-radius: 2px;
5326
+ position: relative;
5327
+ overflow: hidden;
5328
+ }
5329
+
5330
+ .vertical-bar-fill {
5331
+ position: absolute;
5332
+ bottom: 0;
5333
+ left: 0;
5334
+ right: 0;
5335
+ background: #4a9eff;
5336
+ border-radius: 2px;
5337
+ transition: height 0.1s ease-out;
5338
+ }
5339
+
5340
+
5341
+ .bottom-bar {
5342
+ grid-area: bottom-bar;
5343
+ display: flex;
5344
+ align-items: center;
5345
+ background: #111;
5346
+ padding: 0 8px;
5347
+ gap: 8px;
5348
+ }
5349
+
5350
+ .playbar-controls {
5351
+ display: flex;
5352
+ align-items: center;
5353
+ gap: 2px;
5354
+ flex-shrink: 0;
5355
+ }
5356
+
5357
+ .playbar-btn {
5358
+ width: 1.5vh;
5359
+ height: 1.5vh;
5360
+ min-width: 18px;
5361
+ min-height: 18px;
5362
+ border: none;
5363
+ background: transparent;
5364
+ color: #888;
5365
+ font-size: 10px;
5366
+ cursor: pointer;
5367
+ border-radius: 2px;
5368
+ display: flex;
5369
+ align-items: center;
5370
+ justify-content: center;
5371
+ transition: background 0.15s, color 0.15s;
5372
+ position: relative;
5373
+ }
5374
+
5375
+ .playbar-btn:hover {
5376
+ background: #333;
5377
+ color: #fff;
5378
+ }
5379
+
5380
+ .playbar-btn:hover .tooltip {
5381
+ opacity: 1;
5382
+ visibility: visible;
5383
+ }
5384
+
5385
+ .tooltip {
5386
+ position: absolute;
5387
+ bottom: calc(100% + 4px);
5388
+ left: 50%;
5389
+ transform: translateX(-50%);
5390
+ background: #222;
5391
+ color: #ccc;
5392
+ padding: 4px 8px;
5393
+ border-radius: 4px;
5394
+ font-size: 10px;
5395
+ white-space: nowrap;
5396
+ opacity: 0;
5397
+ visibility: hidden;
5398
+ transition: opacity 0.15s;
5399
+ pointer-events: none;
5400
+ z-index: 10;
5401
+ }
5402
+
5403
+ .tooltip-left {
5404
+ left: 0;
5405
+ transform: none;
5406
+ }
5407
+
5408
+ .tooltip kbd {
5409
+ background: #444;
5410
+ padding: 1px 4px;
5411
+ border-radius: 2px;
5412
+ margin-left: 4px;
5413
+ font-family: monospace;
5414
+ }
5415
+
5416
+ .seek-bar {
5417
+ flex: 1;
5418
+ height: 16px;
5419
+ background: #222;
5420
+ border-radius: 4px;
5421
+ position: relative;
5422
+ cursor: pointer;
5423
+ overflow: hidden;
5424
+ }
5425
+
5426
+ .seek-bar-fill {
5427
+ position: absolute;
5428
+ top: 0;
5429
+ left: 0;
5430
+ bottom: 0;
5431
+ background: linear-gradient(to right, #4a2070, #7b3fa0);
5432
+ border-radius: 4px;
5433
+ transition: width 0.1s ease-out;
5434
+ }
5435
+
5436
+ .seek-bar-position {
5437
+ position: absolute;
5438
+ top: 0;
5439
+ bottom: 0;
5440
+ width: 2px;
5441
+ background: #fff;
5442
+ }
5443
+
5444
+ .letterboxed-game {
5445
+ grid-area: game;
5446
+ overflow: hidden;
5447
+ }
5448
+
5449
+ .letterboxed-game .canvas-container {
5450
+ width: 100%;
5451
+ height: 100%;
5452
+ }
5453
+
5454
+ .letterboxed-game canvas {
5455
+ width: 100%;
5456
+ height: 100%;
5457
+ display: block;
5458
+ }
5459
+
5460
+ .letterboxed-game {
5461
+ position: relative;
5462
+ }
5463
+
5464
+ .letterboxed-game.hmr-flash::after {
5465
+ content: "";
5466
+ position: absolute;
5467
+ inset: 0;
5468
+ pointer-events: none;
5469
+ animation: hmr-pulse 0.3s ease-out forwards;
5470
+ }
5471
+
5472
+ @keyframes hmr-pulse {
5473
+ 0% { box-shadow: inset 0 0 0 36px #7b3fa0; }
5474
+ 100% { box-shadow: inset 0 0 0 0 #7b3fa0; }
5475
+ }
5476
+
4904
5477
  .game {
4905
5478
  grid-area: game;
4906
5479
  border-radius: 8px;
@@ -5087,7 +5660,7 @@ class DebugUi {
5087
5660
  const container = options.container ?? document.body;
5088
5661
  const initiallyVisible = options.initiallyVisible ?? new URLSearchParams(window.location.search).has("debug");
5089
5662
  this.#host = document.createElement("bloop-debug-ui");
5090
- this.#host.style.cssText = "display:block;width:100%;height:100%;position:absolute;top:0;left:0;";
5663
+ this.#host.style.cssText = "display:block;width:100%;height:100%;position:absolute;top:0;left:0;overflow:hidden;overscroll-behavior:none;";
5091
5664
  this.#shadow = this.#host.attachShadow({ mode: "open" });
5092
5665
  const styleEl = document.createElement("style");
5093
5666
  styleEl.textContent = styles;
@@ -5096,12 +5669,12 @@ class DebugUi {
5096
5669
  this.#mountPoint.id = "debug-root";
5097
5670
  this.#mountPoint.style.cssText = "width:100%;height:100%;";
5098
5671
  this.#shadow.appendChild(this.#mountPoint);
5099
- debugState.isVisible.value = initiallyVisible;
5672
+ debugState.layoutMode.value = initiallyVisible ? "letterboxed" : "off";
5100
5673
  this.#canvas = document.createElement("canvas");
5101
5674
  this.#render();
5102
5675
  container.appendChild(this.#host);
5103
5676
  this.#cleanup = this.#setupHotkey();
5104
- debugState.isVisible.subscribe(() => {
5677
+ debugState.layoutMode.subscribe(() => {
5105
5678
  this.#render();
5106
5679
  });
5107
5680
  }
@@ -5111,7 +5684,7 @@ class DebugUi {
5111
5684
  #setupHotkey() {
5112
5685
  const handler = (e4) => {
5113
5686
  if (e4.key === this.#hotkey) {
5114
- debugState.isVisible.value = !debugState.isVisible.value;
5687
+ cycleLayout();
5115
5688
  }
5116
5689
  };
5117
5690
  window.addEventListener("keydown", handler);
@@ -5127,7 +5700,7 @@ class DebugUi {
5127
5700
  return debugState.isVisible.value;
5128
5701
  }
5129
5702
  set isVisible(value) {
5130
- debugState.isVisible.value = value;
5703
+ debugState.layoutMode.value = value ? "letterboxed" : "off";
5131
5704
  }
5132
5705
  unmount() {
5133
5706
  this.#cleanup?.();
@@ -5196,6 +5769,7 @@ class App {
5196
5769
  }
5197
5770
  beforeFrame = createListener();
5198
5771
  afterFrame = createListener();
5772
+ onHmr = createListener();
5199
5773
  subscribe() {
5200
5774
  const handleKeydown = (event) => {
5201
5775
  this.sim.emit.keydown(event.code);
@@ -5259,8 +5833,29 @@ class App {
5259
5833
  }
5260
5834
  };
5261
5835
  window.addEventListener("keydown", playbarHotkeys);
5836
+ let fpsFrames = 0;
5837
+ let fpsLastTime = performance.now();
5262
5838
  const frame = () => {
5263
- this.sim.step(performance.now() - this.#now);
5839
+ const stepStart = performance.now();
5840
+ const ticks = this.sim.step(stepStart - this.#now);
5841
+ if (ticks > 0) {
5842
+ const stepEnd = performance.now();
5843
+ debugState.frameTime.value = stepEnd - stepStart;
5844
+ debugState.frameNumber.value = this.sim.time.frame;
5845
+ if (debugState.layoutMode.value === "letterboxed") {
5846
+ const bag = this.game.bag;
5847
+ if (bag) {
5848
+ debugState.snapshotSize.value = JSON.stringify(bag).length;
5849
+ }
5850
+ }
5851
+ fpsFrames++;
5852
+ const elapsed = stepEnd - fpsLastTime;
5853
+ if (elapsed >= 1000) {
5854
+ debugState.fps.value = Math.round(fpsFrames * 1000 / elapsed);
5855
+ fpsFrames = 0;
5856
+ fpsLastTime = stepEnd;
5857
+ }
5858
+ }
5264
5859
  if (!this.sim.isPaused) {
5265
5860
  try {
5266
5861
  this.afterFrame.notify(this.sim.time.frame);
@@ -5293,6 +5888,7 @@ class App {
5293
5888
  this.sim.unmount();
5294
5889
  this.beforeFrame.unsubscribeAll();
5295
5890
  this.afterFrame.unsubscribeAll();
5891
+ this.onHmr.unsubscribeAll();
5296
5892
  this.#debugUi?.unmount();
5297
5893
  }
5298
5894
  async acceptHmr(module, opts) {
@@ -5310,6 +5906,8 @@ class App {
5310
5906
  this.sim.unmount();
5311
5907
  this.sim = sim;
5312
5908
  this.game = game;
5909
+ triggerHmrFlash();
5910
+ this.onHmr.notify({ files: opts?.files ?? [] });
5313
5911
  }
5314
5912
  }
5315
5913
  function createListener() {
@@ -5466,5 +6064,5 @@ export {
5466
6064
  App
5467
6065
  };
5468
6066
 
5469
- //# debugId=720184D07033E06D64756E2164756E21
6067
+ //# debugId=0239438F2F65EFC064756E2164756E21
5470
6068
  //# sourceMappingURL=mod.js.map