blockyard 0.0.1 → 0.0.9

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.
Files changed (107) hide show
  1. package/CHANGELOG.md +679 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +172 -4
  5. package/SECURITY.md +38 -0
  6. package/bin/blockyard.js +40 -0
  7. package/config/pool-map.json +2620 -0
  8. package/docs/API.md +1575 -0
  9. package/docs/ARCHITECTURE.md +1307 -0
  10. package/docs/AUTO-UPDATE.md +269 -0
  11. package/docs/CONFIGURATION.md +840 -0
  12. package/docs/DEFECTS.md +813 -0
  13. package/docs/EFFECTS-AGENTS.md +448 -0
  14. package/docs/GETTING-STARTED.md +202 -0
  15. package/docs/INSTALL.md +490 -0
  16. package/docs/MEASUREMENTS.md +1254 -0
  17. package/docs/PRIVATE-LEADERBOARD.md +230 -0
  18. package/docs/RULES.md +681 -0
  19. package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
  20. package/docs/SECURITY-AUDIT.md +258 -0
  21. package/docs/SECURITY.md +195 -0
  22. package/docs/STATE-2026-09-09.md +200 -0
  23. package/docs/TROUBLESHOOTING.md +298 -0
  24. package/docs/USER-GUIDE.md +1022 -0
  25. package/package.json +53 -5
  26. package/public/404.html +9 -0
  27. package/public/css/app.css +1785 -0
  28. package/public/index.html +893 -0
  29. package/public/js/about.js +112 -0
  30. package/public/js/agents.js +964 -0
  31. package/public/js/app.js +1312 -0
  32. package/public/js/arkanoid.js +806 -0
  33. package/public/js/blockanoid.js +347 -0
  34. package/public/js/blockout.js +347 -0
  35. package/public/js/blockpack.js +428 -0
  36. package/public/js/blockscene3d.js +2678 -0
  37. package/public/js/breakout.js +224 -0
  38. package/public/js/charts.js +635 -0
  39. package/public/js/depthchart.js +311 -0
  40. package/public/js/details3d.js +2957 -0
  41. package/public/js/explorer.js +405 -0
  42. package/public/js/feepalette.js +149 -0
  43. package/public/js/fmt.js +162 -0
  44. package/public/js/goggles.js +886 -0
  45. package/public/js/kiosk.js +41 -0
  46. package/public/js/login.js +83 -0
  47. package/public/js/markets.js +357 -0
  48. package/public/js/mining.js +1138 -0
  49. package/public/js/panels.js +966 -0
  50. package/public/js/pricechart.js +188 -0
  51. package/public/js/settings.js +1014 -0
  52. package/public/js/tetris.js +226 -0
  53. package/public/js/tetrust.js +356 -0
  54. package/public/js/tetsound.js +175 -0
  55. package/public/login.html +33 -0
  56. package/scripts/blockfile-measure.js +156 -0
  57. package/scripts/browser-check.mjs +286 -0
  58. package/scripts/check.js +173 -0
  59. package/scripts/decode-check.js +81 -0
  60. package/scripts/doc-counts.js +109 -0
  61. package/scripts/donate-qr.py +20 -0
  62. package/scripts/fake-node.js +534 -0
  63. package/scripts/index-bench.js +216 -0
  64. package/scripts/index-benchmark.js +117 -0
  65. package/scripts/index-build.js +40 -0
  66. package/scripts/live-render-check.mjs +89 -0
  67. package/scripts/manage-users.js +132 -0
  68. package/scripts/motion-check.mjs +138 -0
  69. package/scripts/pool-map.js +157 -0
  70. package/scripts/setup.js +410 -0
  71. package/scripts/shots.mjs +272 -0
  72. package/scripts/smoke.sh +327 -0
  73. package/scripts/ui.js +174 -0
  74. package/server/auth/sessions.js +221 -0
  75. package/server/auth/users.js +243 -0
  76. package/server/chain/blockfile.js +234 -0
  77. package/server/chain/index/build.js +193 -0
  78. package/server/chain/index/heights.js +36 -0
  79. package/server/chain/index/live.js +276 -0
  80. package/server/chain/index/rows.js +145 -0
  81. package/server/chain/index/store.js +154 -0
  82. package/server/chain/index/worker.js +109 -0
  83. package/server/chain/tx.js +310 -0
  84. package/server/collect/gbt.js +229 -0
  85. package/server/collect/logparse.js +765 -0
  86. package/server/collect/logtail.js +189 -0
  87. package/server/collect/markets.js +333 -0
  88. package/server/collect/mining.js +333 -0
  89. package/server/collect/monitor.js +2516 -0
  90. package/server/collect/nextblock.js +275 -0
  91. package/server/collect/sync.js +386 -0
  92. package/server/config.js +620 -0
  93. package/server/http/api.js +1275 -0
  94. package/server/http/explorer.js +418 -0
  95. package/server/http/server.js +412 -0
  96. package/server/http/sse.js +176 -0
  97. package/server/http/static.js +212 -0
  98. package/server/main.js +628 -0
  99. package/server/netinfo.js +253 -0
  100. package/server/rpc/allowlist.js +130 -0
  101. package/server/rpc/client.js +414 -0
  102. package/server/store/audit.js +148 -0
  103. package/server/store/history.js +220 -0
  104. package/server/store/ledger.js +290 -0
  105. package/server/store/ring.js +173 -0
  106. package/server/util/fmt.js +29 -0
  107. package/systemd/blockyard.service +100 -0
@@ -0,0 +1,224 @@
1
+ // BLOCKOUT, the rules (operator, 2026-09-12: "take the classic Atari Breakout game, and make a
2
+ // clone of it, in another tab, using our engine, with mouse control for moving the pad left and
3
+ // right. Call it 'Blockout'").
4
+ //
5
+ // Like tetris.js, this file is the RULES and nothing else: no DOM, no canvas, no clock, and no
6
+ // Math.random -- a launch angle is an argument. A game is a plain object and every move is a
7
+ // function on it, so the whole of it runs under node:test and a rally can be asserted step by
8
+ // step. What is drawn, and when, is blockout.js's business.
9
+ //
10
+ // Coordinates follow the engine's board: x runs left to right, y runs UP from row 0 on the floor.
11
+ // The paddle sits near the floor and the bricks are stacked at the top, so the ball travels toward
12
+ // +y to break something -- the opposite of most Breakout code, and on purpose: the engine draws
13
+ // row 0 at the bottom of the screen, so the rules speak the renderer's language.
14
+
15
+ export const COLS = 16;
16
+ export const ROWS = 24;
17
+
18
+ export const PADDLE_W = 4; // grid units; drawn as four 1x1 stones
19
+ export const PADDLE_Y = 1; // the row the paddle stands on
20
+ // TWO DIFFERENT MEASUREMENTS, and confusing them put the ball inside the bat (operator,
21
+ // 2026-09-12: "the ball is inset on the paddle. It needs to rest cleanly on the top of the
22
+ // paddle"). PADDLE_D is the bat's DEPTH across the board -- it is drawn as 1x1 stones, so it
23
+ // covers one whole grid row, from PADDLE_Y to PADDLE_Y + 1. PADDLE_H is how tall those stones
24
+ // STAND, which is a height off the board and has nothing to do with where the ball rests. The
25
+ // rules used the height as the depth, so the ball sat 0.4 of a unit inside the bat and the
26
+ // collision box was little over half the bat's real footprint.
27
+ export const PADDLE_D = 1;
28
+ export const PADDLE_H = 0.55;
29
+ export const BALL_S = 0.84; // the ball is a small cube, like everything else here
30
+ export const BALL_R = BALL_S / 2;
31
+ export const LIVES = 3;
32
+
33
+ // Six rows, cheap at the bottom and dear at the top -- the classic pay-out, and the reason to dig
34
+ // a channel up one side and let the ball loose in the roof. Index 0 is the LOWEST row.
35
+ export const ROW_SPEC = Object.freeze([
36
+ Object.freeze({ color: '#f5d142', points: 1 }),
37
+ Object.freeze({ color: '#f5d142', points: 1 }),
38
+ Object.freeze({ color: '#2ecc8f', points: 3 }),
39
+ Object.freeze({ color: '#2ecc8f', points: 3 }),
40
+ Object.freeze({ color: '#f7931a', points: 5 }),
41
+ Object.freeze({ color: '#ef5a5a', points: 7 }),
42
+ ]);
43
+ export const BRICK_ROWS = ROW_SPEC.length;
44
+ export const BRICK_TOP = ROWS - 3; // the top row sits three under the ceiling
45
+
46
+ export const PADDLE_COLOR = '#3ec9ff';
47
+ export const BALL_COLOR = '#f2f7ff';
48
+
49
+ const BASE_SPEED = 11; // grid units a second at level 1
50
+ const LEVEL_SPEEDUP = 1.09;
51
+ const MAX_BOUNCE = 1.05; // radians off vertical at the very edge of the paddle
52
+ const MAX_STEP = 0.3; // the furthest the ball may move in one substep (see step)
53
+
54
+ /** How fast the ball travels at this level, in grid units a second. */
55
+ export function speed(level) { return BASE_SPEED * Math.pow(LEVEL_SPEEDUP, Math.max(0, level - 1)); }
56
+
57
+ function layout(g) {
58
+ g.bricks = [];
59
+ for (let r = 0; r < BRICK_ROWS; r++) {
60
+ const spec = ROW_SPEC[r];
61
+ const y = BRICK_TOP - (BRICK_ROWS - 1) + r; // r = 0 is the lowest row
62
+ for (let x = 0; x < COLS; x++) g.bricks.push({ id: g.nextId++, x, y, color: spec.color, points: spec.points });
63
+ }
64
+ }
65
+
66
+ /** Park the ball on the paddle, waiting for a launch. */
67
+ export function resetBall(g) {
68
+ // exactly one radius clear of the bat's far edge: resting ON it, touching, not sunk into it
69
+ g.ball = { x: g.paddle.x + g.paddle.w / 2, y: PADDLE_Y + PADDLE_D + BALL_R, vx: 0, vy: 0, r: BALL_R, stuck: true };
70
+ return g.ball;
71
+ }
72
+
73
+ export function newGame(level = 1) {
74
+ const g = {
75
+ cols: COLS, rows: ROWS,
76
+ level, score: 0, lives: LIVES, over: false, cleared: false,
77
+ paddle: { x: (COLS - PADDLE_W) / 2, w: PADDLE_W },
78
+ ball: null, bricks: [], nextId: 1,
79
+ };
80
+ layout(g);
81
+ resetBall(g);
82
+ return g;
83
+ }
84
+
85
+ /** The next level: the same wall again, a faster ball, the score and lives carried over. */
86
+ export function advance(g) {
87
+ g.level++;
88
+ g.cleared = false;
89
+ layout(g);
90
+ resetBall(g);
91
+ return g;
92
+ }
93
+
94
+ /** Put the paddle's CENTRE here, clamped to the board. A stuck ball rides along with it. */
95
+ export function movePaddle(g, centreX) {
96
+ if (g.over) return g.paddle.x;
97
+ const half = g.paddle.w / 2;
98
+ g.paddle.x = Math.max(0, Math.min(COLS - g.paddle.w, centreX - half));
99
+ if (g.ball?.stuck) g.ball.x = g.paddle.x + half;
100
+ return g.paddle.x;
101
+ }
102
+
103
+ /** Shove the paddle by dx units (the keyboard). */
104
+ export function nudge(g, dx) { return movePaddle(g, g.paddle.x + g.paddle.w / 2 + dx); }
105
+
106
+ /**
107
+ * Send the ball on its way. `angle` is radians off straight up, positive to the right -- an
108
+ * argument rather than a random, so a test can play a whole rally and know where the ball went.
109
+ */
110
+ export function launch(g, angle = 0.3) {
111
+ if (g.over || !g.ball?.stuck) return false;
112
+ const s = speed(g.level);
113
+ const a = Math.max(-MAX_BOUNCE, Math.min(MAX_BOUNCE, angle));
114
+ g.ball.vx = Math.sin(a) * s;
115
+ g.ball.vy = Math.cos(a) * s;
116
+ g.ball.stuck = false;
117
+ return true;
118
+ }
119
+
120
+ // The ball leaves the paddle at an angle set by WHERE it lands: dead centre goes straight up, the
121
+ // edges fire off at MAX_BOUNCE. That single rule is what makes Breakout a game of aim rather than
122
+ // a game of waiting, so it is worth stating plainly.
123
+ function offPaddle(g) {
124
+ const b = g.ball, p = g.paddle;
125
+ const off = Math.max(-1, Math.min(1, (b.x - (p.x + p.w / 2)) / (p.w / 2)));
126
+ const s = Math.hypot(b.vx, b.vy) || speed(g.level);
127
+ const a = off * MAX_BOUNCE;
128
+ b.vx = Math.sin(a) * s;
129
+ b.vy = Math.abs(Math.cos(a) * s); // always upward: a graze must never drag it down
130
+ b.y = PADDLE_Y + PADDLE_D + b.r; // put it back on top of the bat, not inside it
131
+ }
132
+
133
+ function hitBrick(g, hits) {
134
+ const b = g.ball;
135
+ for (let i = 0; i < g.bricks.length; i++) {
136
+ const k = g.bricks[i];
137
+ const ox = Math.min(b.x + b.r, k.x + 1) - Math.max(b.x - b.r, k.x);
138
+ const oy = Math.min(b.y + b.r, k.y + 1) - Math.max(b.y - b.r, k.y);
139
+ if (ox <= 0 || oy <= 0) continue;
140
+ // out on the axis it is least buried in, so a corner clip does not send it back the way it came
141
+ if (ox < oy) { b.vx = b.x < k.x + 0.5 ? -Math.abs(b.vx) : Math.abs(b.vx); b.x += b.vx > 0 ? ox : -ox; }
142
+ else { b.vy = b.y < k.y + 0.5 ? -Math.abs(b.vy) : Math.abs(b.vy); b.y += b.vy > 0 ? oy : -oy; }
143
+ g.bricks.splice(i, 1);
144
+ g.score += k.points;
145
+ hits.push({ kind: 'brick', x: k.x, y: k.y, color: k.color, points: k.points });
146
+ return true; // one brick a substep: two reflections in one step is a jitter
147
+ }
148
+ return false;
149
+ }
150
+
151
+ function substep(g, h, hits) {
152
+ const b = g.ball;
153
+ b.x += b.vx * h;
154
+ b.y += b.vy * h;
155
+
156
+ if (b.x - b.r < 0) { b.x = b.r; b.vx = Math.abs(b.vx); hits.push({ kind: 'wall' }); }
157
+ else if (b.x + b.r > COLS) { b.x = COLS - b.r; b.vx = -Math.abs(b.vx); hits.push({ kind: 'wall' }); }
158
+ if (b.y + b.r > ROWS) { b.y = ROWS - b.r; b.vy = -Math.abs(b.vy); hits.push({ kind: 'wall' }); }
159
+
160
+ const p = g.paddle;
161
+ if (b.vy < 0 && b.y - b.r <= PADDLE_Y + PADDLE_D && b.y + b.r >= PADDLE_Y
162
+ && b.x + b.r >= p.x && b.x - b.r <= p.x + p.w) {
163
+ offPaddle(g);
164
+ hits.push({ kind: 'paddle' });
165
+ }
166
+
167
+ hitBrick(g, hits);
168
+
169
+ if (b.y + b.r < 0) { // through the floor: a life
170
+ g.lives -= 1;
171
+ hits.push({ kind: 'life' });
172
+ if (g.lives <= 0) { g.over = true; g.ball.stuck = true; g.ball.vx = 0; g.ball.vy = 0; }
173
+ else resetBall(g);
174
+ return true;
175
+ }
176
+ return false;
177
+ }
178
+
179
+ /**
180
+ * Advance the rally by dtMs. Returns what happened, for the screen to make noises about.
181
+ *
182
+ * SUBSTEPPED, because a ball moving eleven units a second crosses a one-unit brick in 90 ms and
183
+ * would tunnel clean through the wall on a slow frame. The move is cut into hops of at most
184
+ * MAX_STEP units and each is resolved on its own, so the collisions do not depend on the frame
185
+ * rate -- which is also what lets a test run a rally at one big dt and get the same answer.
186
+ */
187
+ export function step(g, dtMs) {
188
+ const hits = [];
189
+ if (g.over || !g.ball || g.ball.stuck) return { hits, lost: false, cleared: g.cleared };
190
+ // was the wall still standing when this step began? "Cleared" is the TRANSITION -- a court that
191
+ // was already empty was never cleared, it was just empty, and treating the two alike parks the
192
+ // ball for ever (and would loop the level-done screen on a wall that somehow started bare)
193
+ const standing = g.bricks.length > 0;
194
+ const dt = Math.min(60, Math.max(0, dtMs)) / 1000;
195
+ const sp = Math.hypot(g.ball.vx, g.ball.vy) || 1;
196
+ const n = Math.max(1, Math.ceil((sp * dt) / MAX_STEP));
197
+ const h = dt / n;
198
+ let lost = false;
199
+ for (let i = 0; i < n && !lost && !g.over; i++) lost = substep(g, h, hits);
200
+ if (standing && !g.bricks.length) { g.cleared = true; g.ball.stuck = true; }
201
+ return { hits, lost, cleared: g.cleared };
202
+ }
203
+
204
+ /**
205
+ * The board as tiles for the 3D engine: every brick a stone in its row's colour, the paddle as
206
+ * four low stones, and the ball a small cube. Bricks are exactly one grid cell, so a brick IS a
207
+ * tile -- which is the whole reason this game fits the engine without a translation layer.
208
+ */
209
+ export function tiles(g) {
210
+ const out = [];
211
+ for (const k of g.bricks) out.push({ txid: `b${k.id}`, x: k.x, y: k.y, s: 1, tall: 1, color: k.color });
212
+ const p = g.paddle;
213
+ for (let i = 0; i < Math.round(p.w); i++) {
214
+ out.push({ txid: `pad${i}`, x: p.x + i, y: PADDLE_Y, s: 1, tall: PADDLE_H, color: PADDLE_COLOR, paddle: true });
215
+ }
216
+ const b = g.ball;
217
+ // `sphere`: the engine draws this one round instead of as a cube (operator: "Can we have a ball
218
+ // for blockout instead of a block for the bouncing dot?")
219
+ if (b) out.push({ txid: 'ball', x: b.x - BALL_R, y: b.y - BALL_R, s: BALL_S, tall: BALL_S, color: BALL_COLOR, ball: true, sphere: true });
220
+ return out;
221
+ }
222
+
223
+ /** How much of the wall is left, 0..1 — the HUD's progress figure. */
224
+ export function remaining(g) { return g.bricks.length / (BRICK_ROWS * COLS); }