blockyard 0.0.1 → 0.1.0

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 (122) hide show
  1. package/CHANGELOG.md +929 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +191 -4
  5. package/SECURITY.md +38 -0
  6. package/bin/blockyard.js +41 -0
  7. package/config/pool-map.json +2620 -0
  8. package/docs/API.md +1577 -0
  9. package/docs/ARCHITECTURE.md +1394 -0
  10. package/docs/AUTO-UPDATE.md +269 -0
  11. package/docs/CONFIGURATION.md +847 -0
  12. package/docs/DEFECTS.md +813 -0
  13. package/docs/EFFECTS-AGENTS.md +448 -0
  14. package/docs/GETTING-STARTED.md +205 -0
  15. package/docs/INSTALL.md +547 -0
  16. package/docs/MEASUREMENTS.md +1401 -0
  17. package/docs/RULES.md +681 -0
  18. package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
  19. package/docs/SECURITY-AUDIT.md +258 -0
  20. package/docs/SECURITY.md +212 -0
  21. package/docs/TROUBLESHOOTING.md +332 -0
  22. package/docs/USER-GUIDE.md +1262 -0
  23. package/package.json +53 -5
  24. package/public/404.html +9 -0
  25. package/public/css/app.css +2009 -0
  26. package/public/donate-qr.png +0 -0
  27. package/public/index.html +1085 -0
  28. package/public/js/about.js +112 -0
  29. package/public/js/agents.js +1141 -0
  30. package/public/js/app.js +1386 -0
  31. package/public/js/arkanoid.js +806 -0
  32. package/public/js/blockanoid.js +347 -0
  33. package/public/js/blockout.js +347 -0
  34. package/public/js/blockpack.js +428 -0
  35. package/public/js/blockscene3d.js +2830 -0
  36. package/public/js/breakout.js +224 -0
  37. package/public/js/charts.js +635 -0
  38. package/public/js/depthchart.js +315 -0
  39. package/public/js/details3d.js +4342 -0
  40. package/public/js/doom.js +31 -0
  41. package/public/js/dosaudio.js +48 -0
  42. package/public/js/dosgame.js +389 -0
  43. package/public/js/dosio.js +186 -0
  44. package/public/js/dospc.js +1353 -0
  45. package/public/js/dosworker.js +196 -0
  46. package/public/js/explorer.js +405 -0
  47. package/public/js/feepalette.js +149 -0
  48. package/public/js/fmt.js +162 -0
  49. package/public/js/goggles.js +886 -0
  50. package/public/js/kiosk.js +41 -0
  51. package/public/js/login.js +88 -0
  52. package/public/js/markets.js +395 -0
  53. package/public/js/mining.js +1416 -0
  54. package/public/js/panels.js +970 -0
  55. package/public/js/pricechart.js +189 -0
  56. package/public/js/quake.js +20 -0
  57. package/public/js/settings.js +1096 -0
  58. package/public/js/soundcard.js +459 -0
  59. package/public/js/tetris.js +226 -0
  60. package/public/js/tetrust.js +356 -0
  61. package/public/js/tetsound.js +175 -0
  62. package/public/js/theme.js +235 -0
  63. package/public/js/wolf3d.js +22 -0
  64. package/public/js/x86.js +1978 -0
  65. package/public/login.html +33 -0
  66. package/scripts/blockfile-measure.js +156 -0
  67. package/scripts/browser-check.mjs +286 -0
  68. package/scripts/check.js +173 -0
  69. package/scripts/decode-check.js +81 -0
  70. package/scripts/doc-counts.js +109 -0
  71. package/scripts/donate-qr.py +23 -0
  72. package/scripts/dos-bench.js +56 -0
  73. package/scripts/fake-node.js +534 -0
  74. package/scripts/index-bench.js +216 -0
  75. package/scripts/index-benchmark.js +117 -0
  76. package/scripts/index-build.js +40 -0
  77. package/scripts/live-render-check.mjs +89 -0
  78. package/scripts/manage-users.js +132 -0
  79. package/scripts/motion-check.mjs +138 -0
  80. package/scripts/pool-map.js +157 -0
  81. package/scripts/setup.js +432 -0
  82. package/scripts/shots.mjs +278 -0
  83. package/scripts/smoke.sh +327 -0
  84. package/scripts/tls.js +31 -0
  85. package/scripts/ui.js +174 -0
  86. package/server/auth/sessions.js +221 -0
  87. package/server/auth/users.js +243 -0
  88. package/server/chain/blockfile.js +234 -0
  89. package/server/chain/index/build.js +210 -0
  90. package/server/chain/index/heights.js +36 -0
  91. package/server/chain/index/live.js +276 -0
  92. package/server/chain/index/rows.js +145 -0
  93. package/server/chain/index/store.js +154 -0
  94. package/server/chain/index/worker.js +109 -0
  95. package/server/chain/tx.js +310 -0
  96. package/server/collect/gbt.js +229 -0
  97. package/server/collect/logparse.js +765 -0
  98. package/server/collect/logtail.js +189 -0
  99. package/server/collect/markets.js +333 -0
  100. package/server/collect/mining.js +333 -0
  101. package/server/collect/monitor.js +2545 -0
  102. package/server/collect/network.js +295 -0
  103. package/server/collect/nextblock.js +275 -0
  104. package/server/collect/sync.js +386 -0
  105. package/server/config.js +644 -0
  106. package/server/http/api.js +1319 -0
  107. package/server/http/explorer.js +418 -0
  108. package/server/http/games.js +77 -0
  109. package/server/http/server.js +420 -0
  110. package/server/http/sse.js +176 -0
  111. package/server/http/static.js +212 -0
  112. package/server/main.js +673 -0
  113. package/server/netinfo.js +253 -0
  114. package/server/rpc/allowlist.js +130 -0
  115. package/server/rpc/client.js +414 -0
  116. package/server/store/audit.js +148 -0
  117. package/server/store/history.js +220 -0
  118. package/server/store/ledger.js +290 -0
  119. package/server/store/ring.js +173 -0
  120. package/server/tls/selfsigned.js +160 -0
  121. package/server/util/fmt.js +29 -0
  122. package/systemd/blockyard.service +102 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,929 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-09-16
10
+
11
+ The first minor release, two days after the initial 0.0.9, and everything since it in one place:
12
+ the 0.0.10 that was prepared on 2026-09-15 was never published, and its changes ship here. Four
13
+ things shaped it. The **first outside review** of 0.0.9 on bitcointalk, which called out a monitor
14
+ that bound every interface with no sign-in: 0.1.0 ships hardened — this machine only, sign-in on,
15
+ HTTPS with a certificate it makes itself, and no outbound connection at all until the exchange
16
+ feed is switched on. A day of **use on a laptop screen**, which found every tab a scroll: every
17
+ page now packs to one screen at 1093×945, and the Mining tab follows mempool.space's dashboard
18
+ with its **View more** panels. **Appearance**: the layout's colours are a setting — light or
19
+ dark, five themes, or nine colours of your own. And the **DOS Diversions**: Wolfenstein 3D, DOOM
20
+ and Quake, unmodified shareware on a PC emulated in the browser, written here with no
21
+ dependencies, because "can it run DOOM?" deserved a straight answer. 945 tests, from 862.
22
+
23
+ Upgrading from 0.0.9: [docs/INSTALL.md §11](docs/INSTALL.md#11-updating) — the first start after
24
+ the update serves HTTPS, creates the `admin` account and prints its password once.
25
+
26
+ ### Security
27
+
28
+ - **Hardened defaults** (after the first outside review of 0.0.9): the monitor binds `127.0.0.1`
29
+ and requires sign-in out of the box — the first start creates the `admin` account and prints its
30
+ password once (or takes `BLOCKYARD_ADMIN_PASSWORD`). Reach it from elsewhere over an SSH tunnel,
31
+ or bind a LAN address with `BLOCKYARD_BIND` / `server.hosts`; open read-only access is a choice
32
+ (`BLOCKYARD_AUTH=0`), announced at boot. The boot banner says how to reach a loopback-only
33
+ monitor. Existing installs keep whatever `config/local.json` says.
34
+ - **HTTPS by default.** Every listener serves HTTPS; with no certificate named, the first start
35
+ makes the monitor its own self-signed one under `<data>/tls/` — written without dependencies
36
+ (`server/tls/selfsigned.js`: a v3 X.509 certificate assembled in DER and signed with ECDSA P-256
37
+ over SHA-256), naming the bound hosts, this machine's addresses and hostname — and remakes it
38
+ near expiry or when a bound address is missing from it. `blockyard tls` remakes it by hand
39
+ (`--san` to add names, `--print` to export it). Your own certificate still replaces it;
40
+ `BLOCKYARD_TLS=0` serves plain HTTP behind a proxy. The installer's summary and port probe know
41
+ both schemes.
42
+ - **The installer asks twice before a non-loopback bind.** A LAN or wildcard address is written
43
+ only when the operator types it and confirms it; the default it offers is `127.0.0.1`.
44
+ - **Market polling is off by default** — a new checkbox, **Display settings → Markets & Price →
45
+ Enable market polling**, shared by every screen. A fresh install makes no outbound connection
46
+ but to the node: the Markets and Kiosk tabs say that polling is off and where the switch is,
47
+ Overview's price line stays hidden and the explorer shows no dollar figures. Ticking it takes
48
+ effect at once; unticking parks the feed at once. `BLOCKYARD_MARKETS=0` still removes the feed
49
+ so that the checkbox cannot turn it on. Existing installs tick the box once.
50
+
51
+ ### Appearance
52
+
53
+ - **A first tab in Display settings for the colours of the layout.** A theme mode — Light, Dark,
54
+ or System, which follows the operating system and changes with it — and theme cards with swatch
55
+ strips of the face the mode picks: **BlockYard** (the shipped look, the default; its dark face
56
+ is value for value what the monitor drew before), **Mono** (clean grayscale), **Nous** (GitHub's
57
+ chrome with a Nous blue accent), **GitHub** (Light Default and Dark Default) and **Catppuccin**
58
+ (Latte and Mocha). **Custom** is nine colour pickers — page, panels, text, muted text, accent,
59
+ lines, good, warning, bad — with the rest of a face derived from them (a raised panel, the
60
+ softer rules, the fainter text, the darker accent of a pressed button), and whether Custom is
61
+ light or dark read off its page colour; **customise <theme>** copies the theme on screen into
62
+ the pickers so a custom scheme starts from a look. Saved on the server with the other settings,
63
+ so every screen of the monitor draws the same; the sign-in page draws in the theme the browser
64
+ last saw.
65
+ - The theme is applied through the CSSOM (the page's custom properties rewritten on `<html>`),
66
+ which the content-security policy permits where a style attribute is refused. The charts' axis
67
+ text, grid lines and tooltips, the order-book depth chart's two sides and the market charts'
68
+ grounds follow the theme — they were literals of the dark look, and a light theme would have
69
+ drawn grey-on-grey. The 3D boards stay space, and the Explorer's block and transaction pages
70
+ keep their own dark cards.
71
+
72
+ ### Mining
73
+
74
+ - **The Mining tab has the network row**: reward stats over the last 144 blocks, the difficulty
75
+ period (blocks remaining, the estimated and the previous change, the next halving), a week of
76
+ pools with luck and counts, a year of hashrate with the difficulty's steps, and the last twelve
77
+ adjustments as a table — every figure from this node alone (`server/collect/network.js`:
78
+ `getblockstats` for the reward window, block headers for the periods and a daily sample, the
79
+ week's coinbases read in the background at the lowest RPC priority).
80
+ - **mempool.space's layout**: two columns in its order (reward stats, difficulty adjustment;
81
+ pools, hashrate; recent blocks, adjustments) under the block flow, our own panels beneath, and
82
+ **View more »** on four cards opening that card full screen — the whole week of pools as a
83
+ table, the year's chart large, the whole attributed window of blocks, a year of adjustments.
84
+ - **The pool chart is a labelled pie**: every pool named beside its slice and joined to it by a
85
+ leader in the slice's colour, the small ones stacked, slices under half a percent gathered as
86
+ "Other" — and the "Other" threshold rises until every label fits the card, so no name runs off
87
+ the bottom at any window height. The pie and the year chart size themselves to the window.
88
+ - **Dollar figures** on the reward stats while market polling is on, from a new `/api/price` that
89
+ answers the cached spot price and never starts the exchange polling itself.
90
+ - The Mempool space viewer left this page — it is on Overview, Block space and Mempool — and with
91
+ it the full-pool poll the page used to ask for. The reward window is exactly 144 blocks.
92
+ - At a narrow window the block flow spans the width and is thinner, and the packages table scrolls
93
+ inside its card instead of bleeding into the neighbour; the three stacks become two below 1400px.
94
+ - **A streak of stale drops of the full-pool poll is one story**: one warning event when it
95
+ starts, a counter on Node & RPC while it lasts, one event when the poll answers again with the
96
+ count and the span. On a day the node answered slowly for thirteen hours, each drop had been its
97
+ own event, 188 of the feed's 200 rows.
98
+
99
+ ### Every tab on one screen
100
+
101
+ Measured at 1093×945 and 1600×1000 with a dead-height probe, page by page:
102
+
103
+ - **Globally**: tighter card padding and heading spacing, and a short card beside a tall one keeps
104
+ its own height instead of being stretched to match (Chain & Sync's drill-down card had a
105
+ thousand pixels of nothing under its input; Node & RPC's poll cadence and connection cards
106
+ hundreds).
107
+ - **Overview**: the block flow card a quarter shorter, and the Block space board capped to the
108
+ viewport (it set the whole right column's height), so the events are on the first screen.
109
+ - **Block space**: the card tighter and the board sized so the whole page, note included, fits the
110
+ viewport without a scroll.
111
+ - **Markets**: the board takes what the window leaves, and the exchange table and the order-book
112
+ depth sit side by side beneath it (under 1400px the table drops the pair, 24 h range and volume
113
+ columns; all still in the summary line and on the Kiosk) instead of the depth chart starting a
114
+ thousand pixels down.
115
+ - **Kiosk**: tighter heads and gaps, the price panel's padding trimmed, the Block flow strip a
116
+ quarter shorter, and the full-screen button in that strip's head instead of floating over its
117
+ cards.
118
+ - **Mempool**: keeps its two columns down to 900px (it stacked from 1100), and the room over goes
119
+ to its time-series charts, a fifth taller.
120
+ - **Explorer**: a smaller title, the block strip's padding and scrollbar band trimmed, tighter
121
+ table rows and the table reaching the window's bottom edge — three more blocks on the first
122
+ screen.
123
+ - **Peers**: a long onion or i2p address no longer makes the whole page scroll sideways (it clips
124
+ with an ellipsis and carries the full text as its title; a wide table scrolls inside its card),
125
+ rows and service badges tighter, the top strip's cards at their own heights.
126
+ - **Network**: Throughput beside a stack of Upload and Traffic accounting, the throughput chart
127
+ taller, and the sources table taking the rest of the window.
128
+ - **Events**: the feed reaches the window's bottom edge (it stopped a hundred pixels short).
129
+ - **Chain & Sync**: one-line notes under the block-size and tip charts (the longer sentences are
130
+ their tooltips), the transaction-rate figures as one wrapping line, values that keep to their
131
+ line, and a shorter uptime label — the first three rows sit level and the drill-down and indexes
132
+ reach the first screen.
133
+ - **Node & RPC**: two stacks (RPC etiquette with its figures in two columns, Data quality and Node
134
+ connection; Poll cadence, self-telemetry beside the log tail, Panel sources and the RPC
135
+ console) instead of grid rows that left holes beside the short cards.
136
+ - **Admin**: two stacks (Users over the audit log; Node actions over Change own password, whose
137
+ two fields share a row). **Sign-in** and **404**: the same tighter card.
138
+ - **Display settings**: the tabs in three labelled rows — Boards (Appearance, Block space, Sky,
139
+ Markets & Price), Effects, and the Diversions last; rows, note and groups tighter, the sheet a little wider so hints wrap
140
+ less, and on the two effects tabs the switches run two across with each hint as the row's
141
+ tooltip — the Space effects tab is under half the scrolling it was. The price line on Overview
142
+ switch sits directly under the polling switch it depends on.
143
+ - **About**: the sky runs to the bottom edge with the credit line at its foot, and the GitHub
144
+ link and the tip pill share a row.
145
+ - **The Diversions pages take the window**: the wells of Tetrust, Blockout and Blockanoid grow to
146
+ the window's height (they stopped at 600px), and the DOS screen is always 4:3 and as large as
147
+ the window allows — at a 1093px window the 320×200 picture had been drawn squashed into a
148
+ 761×720 box.
149
+
150
+ ### DOS Diversions
151
+
152
+ - **DOOM.** The shareware `DOOM.EXE` v1.9 and `DOOM1.WAD` from `games/doom_dos/`, unmodified,
153
+ running on a PC emulated in the browser with no dependencies: an i386 interpreter
154
+ (`public/js/x86.js`), the DOS/4GW machine around it — DOS, DPMI, BIOS, PIC, timer, keyboard and
155
+ a planar VGA (`dospc.js`) — and a Sound Blaster Pro 2 with an OPL3 for the effects and the music
156
+ (`soundcard.js`), in a worker. Demos, menus, savegames and ENDOOM all work; 35 frames a second.
157
+ Mouse capture, WASD keys by default (the switch rebinds the running game at once), smoothing,
158
+ full screen; pauses when you look away; savegames and DOOM's config stay in the browser.
159
+ - **Wolfenstein 3D.** The shareware `WOLF3D.EXE` v1.4 and its `.WL1` files from
160
+ `games/wolf3d_dos/`, unmodified, on the same emulated PC — which now also runs a 16-bit real-mode
161
+ DOS program: real mode in the CPU (segments times sixteen, a wrapping SP, 16-bit string
162
+ instructions and interrupt frames), a plain MZ loader with its relocations, DOS's memory
163
+ allocator, the interrupt vector table, and VGA write mode 1. Menus, demos, AdLib music,
164
+ digitised sound effects, savegames and high scores kept in the browser, at its full 70 frames a
165
+ second. It sits before DOOM and Quake in the Diversions menu.
166
+ - **Quake.** The shareware `QUAKE.EXE` v1.06 and `PAK0.PAK` from `games/quake_dos/`, unmodified,
167
+ on the same emulated PC — which now also plays the go32 stub and CWSDPMI for a DJGPP program:
168
+ segment bases and 16-bit code in the CPU, a COFF loader, a directory tree, DOS's system file
169
+ table, the BIOS tick count. Demos, menus, savegames, sound effects through the Sound Blaster,
170
+ and the order screen on quit. W A S D and mouse look on the first start, Quake's own bindings
171
+ after. Quake starts with a smaller view (`viewsize 80`); **−** and **=** change it and Quake
172
+ keeps the choice.
173
+ - **The emulated PC is fast.** Each instruction is decoded once and cached rather than decoded
174
+ every time it runs (writes into cached code clear what they overlap, because both games patch
175
+ their own drawing code), the arithmetic and the x87 forms Quake runs most have handlers of their
176
+ own, and memory addresses are formed inline: Quake from 77 to about 141 million instructions a
177
+ second, its `timedemo demo1` from 29.7 fps to over 50; in the browser Quake plays at 40–50
178
+ frames a second.
179
+ - **A way out of full screen**: an **exit full screen** button in the top corner while full
180
+ screen, and holding **Esc** for a second (a tap of Esc stays the game's menu, which is what the
181
+ keyboard lock is for); leaving unlocks the keyboard and releases the mouse.
182
+ - The game files are served at `/games/<game>/<path>` (`server/http/games.js`); the shareware
183
+ packages live under `games/` (DOOM's moved from `doom_dos/` at the repository root).
184
+
185
+ ### Markets and the effects
186
+
187
+ - **Black hole**, a Markets effect (modelled on NASA's visualisation, svs.gsfc.nasa.gov/14576): a
188
+ point of darkness opens and grows to a horizon over 26 s, the price line bends round it as
189
+ through a lens (the Einstein-radius push), the candles it reaches stretch and lean toward it and
190
+ shrink away into it, and an accretion disk seen nearly edge-on — a thin band white-yellow at its
191
+ inner edge through orange to a dark red rim, five hundred fibres spiralling inward sheared by
192
+ the Keplerian flow, its far side lensed into an arch over the shadow and a smaller one beneath,
193
+ a photon ring hugging the horizon, starlight bent into arcs. **It crosses the board**: it opens
194
+ a fifth of the way in on one side and runs a straight line through the chart to a fifth from the
195
+ other, from one height to another, so the line is seen bending round it as it passes; the
196
+ candles' orbits ride along at a quarter of the first cut's speed. It finishes its crossing before
197
+ it lets go, and lets go evenly, every candle gliding home and growing back smoothly. It hovered
198
+ over Block space for a day and its cubes never came home cleanly enough, so it is a Markets
199
+ effect only.
200
+ - **Supernova** (the solar flare, renamed and remade on NASA Goddard's animation,
201
+ svs.gsfc.nasa.gov/20413): the star is a glint — halo, hot core, diffraction spikes — growing and
202
+ blue-shifting as it goes; the opening explosion is a **white-hot flash**, held and washing far
203
+ out, that throws **a hundred and twenty-eight plumes of white gas** in every direction, each
204
+ flying and growing on its own out to nine star radii and drifting on until the effect is nearly
205
+ done (drawn on a half-resolution layer to keep the frame cheap); the debris is a volume of gas in
206
+ three layers — a blue cloud out in front, a white one behind it into which **violet grows patch
207
+ by patch from a tenth of the run**, and a deep violet heart — with a soft shock band throwing
208
+ and shaking the candles it crosses; and as the cloud dims a **pulsar** beats seven times a
209
+ second at the centre, two thin beams sweeping round, inside a nebula of the same gas that holds
210
+ its violet weight until four fifths of the run. 16 s (24 on Markets).
211
+ - **Fireworks are a display**, on both boards: every shell drawn — the rocket's arc, a white flash
212
+ and a shockwave ring at the burst, seven shell kinds by turn (peony, chrysanthemum, willow, ring,
213
+ crossette, strobe and more), sixty to ninety sparks each a curved trail, a lens flare at every
214
+ burst, glitter rain, and a nebula of smoke that drifts up and lingers; up to ten shells on
215
+ Markets over 14 s (five on Block space), each lighting the candles under it in its own colour.
216
+ **Off by default** on both boards, kept for special occasions.
217
+ - **X-ray**, on both boards: a front sweeps the board and everything behind it goes x-ray — bodies
218
+ dimmed to glass, every edge lit, a raster of thin lines across each face — then develops back.
219
+ - **Breathe** and **Light saber**, on the price line: three slow swells from the plain wire to
220
+ white heat and back; and the line igniting from its left end, blue, green, red or purple by the
221
+ run, humming with a breathing glow, spitting sparks, retracting to the hilt.
222
+ - **The energy pulse is heat, not blue, and its head is a crackling ball of plasma**; **the scan
223
+ line is a light curtain** standing on the board floor to the chart's top; **ball lightning is a
224
+ light source**, its arcs discharges built by midpoint displacement rather than zigzags, a quarter
225
+ of its size and a third of its speed on Markets; the scanner's cone is dimmer on both boards.
226
+ - **The Markets board keeps its price fit across refreshes**: the last fit is kept while the data
227
+ still sits inside it and fills at least two-thirds of it, so a refresh no longer re-scales the
228
+ whole chart unanimated. Whatever a head pulls is painted last, nearest the head on top; a held
229
+ pool refresh waits a moment and a half after an idle effect ends, with the board whole and still.
230
+ - **Layered soft fills add up correctly** (`softStops`): each nested disc carries only its
231
+ increment, so a stop's cumulative opacity is exactly the stop's; measured on the Kiosk, the
232
+ supernova's peak opacity fell from about 0.9 to about 0.45 and the chart shows through it.
233
+ - **The no-repeat window holds from the first pick** on both boards: every effect plays once
234
+ before any repeats (a negative slice index had let one come round at the tenth pick). The
235
+ searchlight and the tractor beam, which fly the same saucer, share the window.
236
+ - **A Kiosk test suite** (`test/kiosk.test.js`): every Markets and Block space effect plays through
237
+ the frame loop on a Kiosk-sized panel; the supernova, the black hole and the fireworks are
238
+ bounded by the board's width so no panel is covered.
239
+
240
+ ### Fixed
241
+
242
+ - **The donation QR shows on Safari.** It was an inline SVG of 800 module rects, and Safari on
243
+ the first Mac install painted its white ground and none of the rects. It is a PNG now, painted
244
+ the same everywhere.
245
+ - **A dead index worker fails the build instead of hanging it.** A worker killed outright (out of
246
+ memory, with four beside the node) left its job unanswered and the build sat at "scan 5,720 of
247
+ 5,721, about 1 s left" for an hour and a half on the first Mac install. The run now rejects the
248
+ moment a worker exits or throws, naming the job and the fix (fewer `addressIndexWorkers`); the
249
+ progress flag says *no progress for N min* when nothing has moved.
250
+ - The 0.0.9 release notes' test count corrected (862, not 864).
251
+
252
+ ### Documentation
253
+
254
+ - INSTALL, GETTING-STARTED, SECURITY, CONFIGURATION, API, TROUBLESHOOTING and the README describe
255
+ the hardened defaults, HTTPS and the polling switch; INSTALL §11 has the notes for updating a
256
+ 0.0.9 install; the USER-GUIDE has the Appearance section, the Mining row and the packed layouts;
257
+ TROUBLESHOOTING explains a streak of stale pool-poll drops. Screenshots re-shot at 0.1.0, with a
258
+ Mining shot added. The announcement for the bitcointalk thread is in `docs/announcement/0.1.0/`.
259
+
260
+ ## [0.0.9] — 2026-09-14
261
+
262
+ The initial release. Everything in it, like everything before it, was written by an AI directed by a human
263
+ operator, and audited by AI (`docs/SECURITY-AUDIT.md`, `docs/SECURITY-AUDIT-2026-09-14.md`). It is
264
+ experimental pre-release software. BlockYard runs **on the machine that runs Bitcoin Core** (25.0 or
265
+ later), because the explorer's address index is built from the node's own block files; a node on
266
+ another machine, and the experimental node that earlier measurements were taken on, are not supported.
267
+ 864 unit tests, no dependencies. Tagged `v0.0.9`. **The address index takes a few hours to build** on first start (about two on four workers on NVMe); every other page works meanwhile.
268
+
269
+ ### The 3D boards' effects, per board
270
+
271
+ - **Each 3D board has its own effects list.** The one Effects tab is now two: **Space effects**
272
+ (the Block space board's 28 switches — every effect but the two drawn on a price line) and
273
+ **Market effects** (the Markets board's 12: ripple, outline sweep, tide, cascade, twinkle,
274
+ scan line, fireworks, solar flare, wave, energy pulse, pipe bulge, ball lightning — the ones
275
+ that translate to a candle chart; ball lightning is half its Block space size there). Each has its own no-repeat window and its own
276
+ all on / all off, which the no-repeat slider had silently taken away. A saved settings store is
277
+ migrated (schema 4): the Markets list starts as a copy of the list there was.
278
+ - **Each effects tab sets its board's cadence.** *Between effects, at least* / *at most*
279
+ (seconds, 5–9 by default, up to ten minutes on Space effects and five on Market effects) on both tabs, and *First effect after landing*
280
+ (1.2 s by default, up to two minutes) on Space effects only — the candle board has no landing
281
+ — drive the scheduler's own timers, per board.
282
+ The no-repeat slider on each tab tops out at that list's length.
283
+ - **Nothing on the Markets board waits its turn.** The pulse, the bulge and ball lightning were
284
+ held to a clock of 2.5-6 minutes between plays; they are ordinary picks now, and the list you
285
+ leave on decides how often any one of them comes round.
286
+ - **Everything on the Markets board moves along the hours** — left or right, never toward the
287
+ viewer — and lights the candles or the line: fronts (outline, scan, tide, wave) run along the
288
+ chart, rings (ripple, shockwave, nova) start on the candle row, the light cycles ride in from
289
+ the two ends with their walls on the candle tops, the lightning ball rides the price line, and
290
+ ball lightning flies through the chart's own height. Candle faces light under an effect now
291
+ (the camera is low, and a glow painted on a candle's top alone was invisible).
292
+ - **Ball lightning strikes candles.** `cellTops` began each tile at its own fractional x and y,
293
+ which on the candle board (tiles between grid lines) stored nothing, so every cell top was
294
+ zero and no arc found a target. Half the arcs now chain on from the struck block to
295
+ another in electric green, the instant they land, and half of those on to a third block.
296
+ - The lightning ball is one pale gradient with a white-hot heart, and a pale burn behind it,
297
+ instead of five stacked discs of deepening blue that read as a dark blot.
298
+
299
+ ### The explorer's address history, from an index of our own
300
+
301
+ - **Address pages show full history and balances on Bitcoin Core.** This was the explorer's one
302
+ real gap, and it was not a bug: Core has **no address index at any setting**. `getaddressbalance`
303
+ and `getaddresstxids` are insight-style extensions carried by forks, and stock Core answers
304
+ `Method not found` (measured 2026-09-13 against two Core nodes). mempool.space
305
+ shows the same address's history only because `electrs` builds that index from the block files
306
+ itself. So this monitor now does the same, in a form it can afford: `server/chain/`.
307
+ - **Reading the node's own files, not asking the node.** `blk*.dat` on a current Core is
308
+ XOR-obfuscated at rest (`-blocksxor`, default since v28, the key in `blocks/xor.dat`) -- the
309
+ first spike concluded "not a Core datadir" and was wrong. De-obfuscated, `server/chain/blockfile.js`
310
+ frames the records; `server/chain/tx.js` decodes transactions and blocks, checked
311
+ **field-for-field** against `getblock <hash> 3` (91,813 spent coins in 16 sampled file pairs, 0
312
+ mismatches); and the undo (`rev*.dat`) records supply each spent coin's script and amount,
313
+ which is how the spending side is known without replaying the UTXO set. Pairing blocks with
314
+ undo records by trying every candidate was quadratic on the tiny early blocks (file 0: 35 minutes
315
+ and not finished); Core appends undo in connection order, so blocks are put in chain order by
316
+ their previous-block links and walked in step -- file 0 pairs in 5.9 s.
317
+ - **A lean row.** One 21-byte row per (script, transaction that touched it): 8 bytes of
318
+ sha256(script), the height, the position in the block, and the **net satoshis** the transaction
319
+ moved for that script -- so a balance is a sum, with no node call. A script paid and spent in the
320
+ same transaction is one row (18% fewer). The full decoder builds Core's verbose shape and was 5.9
321
+ of the 9.5 single-core hours measured for the chain; the index needs only each output's value and
322
+ script bytes, so `server/chain/index/rows.js` walks the raw transaction itself and is checked
323
+ row-for-row against rows from the full decoder.
324
+ - **Flat sorted files, no database.** Measured against `node:sqlite` on three real file pairs
325
+ (`docs/MEASUREMENTS.md` §29): 21 bytes per row against 24-27, a 5.5 M rows/s sort against a
326
+ 1.4 M rows/s key-ordered load, and no B-tree collapse once the keys outgrow memory. 256 sorted
327
+ segments by key prefix with a sparse index (one key per 4,096 rows) held in memory; a lookup
328
+ binary-searches the sparse keys and reads the one 86 KB block that can hold its key.
329
+ - **Built and measured** (§30): `node scripts/index-build.js --out <dir> --workers 16` read all
330
+ 5,756 file pairs in **29 min 45 s** (7.8 CPU-hours across 16 workers) and wrote
331
+ **5,890,519,289 rows, 123.7 GB** -- every height present exactly once, two stale blocks skipped,
332
+ and within 1.5% of the projection. **40 of 40 balances equal `scantxoutset`** at the same
333
+ height, to the satoshi; every (script, transaction) pair of four whole blocks from 2009 to the
334
+ tip found at its height and position with its amount. Lookups: **0.25 ms** median first touch,
335
+ 0.03 ms warm; a 2.3 M-transaction address's whole history summed for its balance in 83 ms.
336
+ For scale: `scantxoutset`, the only thing Core offers, took 26.5 s for one scan of 40 addresses
337
+ holding the node's RPC thread, and answers only the current balance, never a history.
338
+ - **It follows the chain.** `server/chain/index/live.js`, started by the server for each configured
339
+ index directory, polls every 30 s, fetches each new block with `getblock <hash> 3` over RPC (so
340
+ the files are read once, at the build), and **logs the rows before serving them** in a
341
+ CRC-framed `live.log` that is replayed on restart and drops a record torn by a crash. A
342
+ reorganisation rolls the tail back to the fork; blocks 100 deep are **folded** into immutable
343
+ sorted layers and layers past 32 are merged; a reorganisation below what is folded stops the
344
+ follower and the page says to rebuild. Checked live: 12 blocks caught up in 6.5 s, 40 of 40
345
+ balances then equal to `scantxoutset`, and on release day the index reached a new block 16 s
346
+ after the node did. The rows it derives through RPC agree row-for-row with the rows built from
347
+ the files (`test/chain-index-live.test.js`).
348
+ - **The address page reads it.** `addressIndex` in a node's config names the directory (one index
349
+ serves every node on the same chain); the page shows the transaction count, balance, total
350
+ received and sent, and the transactions newest first with the **net change each made**, 25 a
351
+ page, deep pages costing no more than the first. The reply carries `index.tip`, `index.behind`,
352
+ `index.following` and `index.stale`, and the page says when the index is behind the node or has
353
+ stopped following. Checked live: every row of four pages, including page 4 of a 2.3
354
+ M-transaction address, matched the node's decoded transaction for txid, height and amount.
355
+ - **Without an index, the page is honest rather than empty.** A node's refusal used to become `[]`,
356
+ then `txCount: 0`, then "no transactions in this node's address index" -- a fabricated zero
357
+ indistinguishable from an unused address. The reply now carries `indexed: false` with a **null**
358
+ count, the page says the index is absent, and the dead RPCs are not re-sent on every view: a
359
+ "method not found" is remembered per node for ten minutes, then asked again, because the daemon
360
+ behind a node id can change.
361
+ - **A rebuild in place is safe.** `buildIndex` empties its output directory first, so a follower's
362
+ `live.log` and layers -- holding the reorganised-away blocks -- cannot survive into a new index and
363
+ a fresh follower catches up on the chain as it is (`test/chain-index-live.test.js`). Stop the
364
+ server, run the same command, start it; the advice to build into a fresh directory is withdrawn.
365
+ - **A missing address index is built by BlockYard itself, in the background** (operator: "Is it
366
+ possible to run step 6 in the background, and have a status notification in blockyard when the
367
+ index process is finished?"). On start, an `addressIndex` directory with no index in it is built
368
+ on worker threads while every page keeps serving; the progress is a quality flag on the Overview
369
+ and a line on the address page (phase, files done, rows so far, time left); an event marks the
370
+ start, the finish and a failure, and the browser toasts it; when it finishes the follower starts
371
+ and address pages work with no restart. `addressIndexBuild: "manual"` keeps it from happening.
372
+ The installer's step 6 offers background (the default), here, or later.
373
+ - **The build shares the machine with the node, and behaves like it.** Every build -- the server's
374
+ background one, the installer's build-here and `scripts/index-build.js` -- goes through one pacer
375
+ (`rpcPacer`) that reads the monitor's own RPC telemetry: before each file it holds, ten seconds at
376
+ a time, while the node's RPC is failing, its breaker is open or its answers average more than
377
+ `rpc.slowLatencyMs` (5 s), and eases to one file at a time with a pause between above 40% of that.
378
+ It first held at one second, which ran a healthy build on a node whose heavy reads take a second
379
+ at a sixth of its speed; the threshold is the monitor's own notion of slow now. The height batches
380
+ are paced the same way, 1,000 at a time at the lowest priority. The server's build opens its own
381
+ RPC connection, as `scripts/index-build.js` always did, so it and the monitor stop starving each
382
+ other: on the first Mac its `getblockhash` batches queued behind multi-second mempool and block
383
+ reads on the monitor's one lane, and both sides starved ("heights 1,000 of 967,015" for a quarter
384
+ of an hour). Workers: `addressIndexWorkers` in the node's config, written by the installer; unset,
385
+ the server takes half of what a dedicated build would and at most four. The `rpc-slow` and
386
+ `rpc-timeouts` quality flags name the build when one is running and no longer assert a cause from
387
+ another node's era. **An interrupted build starts over**: there is no resume after Ctrl-C, and the
388
+ installer says so when it happens.
389
+ - **An address's unspent outputs are listed** (operator, the same day: "Why don't we do this"): the
390
+ index names every transaction that touched the address, each one's outputs paying it are asked of
391
+ `gettxout` (the UTXO set, less what the mempool already spends), and the page lists them with the
392
+ index's own height and their count on the card. The walk is the whole history, so it is made for
393
+ an address with at most 100 transactions and declined in words for a longer one.
394
+ - **Not yet:** an address's transactions still in the mempool.
395
+ - **An unconfirmed transaction shows its inputs and fee** (operator: "Unknown script?!", of a
396
+ mempool transaction whose 858 inputs all read *unknown script*). Core carries no `prevout` on a
397
+ mempool transaction's inputs, so `fillPrevouts` fetches the parents in one batch and fills each
398
+ input's script and amount; checked on that transaction, 858 of 858 inputs, and a fee of 77,958
399
+ sat equal to `getmempoolentry`.
400
+
401
+ ### The installer: `npm run setup`, `npm run check`, and the `blockyard` command
402
+
403
+ - **An installer: `npm run setup`** (operator: "build a test into the installer so we can verify it
404
+ properly connects to an RPC server and finds the bitcoin logs ... something that writes out a
405
+ config/local.json at the end ... that we can up and run immediately to start building the
406
+ transaction set"). It asks for the RPC URL and data directory, finds the cookie (or asks for
407
+ `rpcUser`/`rpcPassword`), and proves the answers before writing anything: the RPC server answers
408
+ and on which chain, the node's version is 25.0 or later, `txindex` is on and synced,
409
+ `getblock <tip> 3` carries prevouts, the node is not pruned, `blocks/` holds matched block and
410
+ undo files and the first one opens to the genesis block through the XOR key, `debug.log` is where
411
+ it should be, and a configured index directory is readable, writable and not far behind. Then it
412
+ writes `config/local.json` (mode 0600, a backup of any previous one) and offers to build the
413
+ address index on the spot. `npm run check` runs the same checks against every configured node
414
+ and exits non-zero on a FAIL; `--yes` with flags is the scripted form. `docs/GETTING-STARTED.md`
415
+ walks a macOS or Linux command prompt through it. Six numbered steps, every answer validated and
416
+ explained before it is accepted (a URL that is not one, a port off the range, a directory that
417
+ is not there, a hostname where the config wants an address), a warning when something already
418
+ listens on the port, a progress bar with the rows so far and the time left for each phase of the
419
+ build (the same bar in `scripts/index-build.js`), Ctrl-C that leaves everything as it was, and a
420
+ last question that starts the monitor in the same terminal.
421
+ It reads the node's own `bitcoin.conf` first (operator: "can't you look through the user's
422
+ .conf and find the rpc values?"): chain, `rpcport`, `rpcconnect`, `rpcuser`/`rpcpassword`,
423
+ `rpcauth` users, a cookie file named elsewhere, `server=`, `txindex=`, `prune=`, sections and
424
+ `includeconf=`, so the RPC URL and the credentials arrive as defaults rather than questions.
425
+ - **Every check is timed**, because how fast the node answers is half of what an install needs to
426
+ know, and the verbose mempool read -- the monitor's heaviest regular call, every 20 s -- is one of
427
+ the checks, with a warning when it is slow enough to lag the board. An unbuilt index is reported
428
+ as information, not a fault.
429
+ - **Defaults**: the index in the checkout's `data/index`, like everything else the install writes;
430
+ at most **four** build workers (the shared-machine number; one on spinning disks, each needing
431
+ ~2.5 GB of memory); the build in the background once BlockYard starts.
432
+ - **The banner** (operator: "I want the installer to have amazing ANSI Art here for the BY logo",
433
+ then "It needs to fit in 80 character space. Standard CRT"): the BY monogram -- the same tile the
434
+ favicon is -- in 26x12 solid cells, one cell per pixel painted as a background colour so it fills
435
+ the cell in every font, the About text beside it, and the whole run inside 80 columns (piped at
436
+ 80: widest line 79). Every line wraps to the terminal with its indent kept, a box cuts a line past
437
+ its room with an ellipsis, widths are measured on what is seen rather than on colour codes, and
438
+ colour steps aside without a TTY or under `NO_COLOR` (`FORCE_COLOR` turns it on).
439
+ - **An npm package and a `blockyard` command** (operator: "push to npm"). `package.json` loses
440
+ `private` and gains `bin` and `files` (bin, server, public, scripts, the pool map, systemd, the
441
+ docs, the licence and notice; no tests, no images: 924 kB packed). `blockyard setup | start |
442
+ check | index-build | users` keeps the config and data under `~/.blockyard` (`BLOCKYARD_HOME`),
443
+ since a global install's own directory is nowhere to keep a config or 124 GB of index; a checkout
444
+ run with `npm run …` is unchanged.
445
+
446
+ ### Fixes from the first fresh install (a Mac, Core 29.1)
447
+
448
+ - **A node without `coinstatsindex` was sent a full UTXO-set walk every minute.** Found on the
449
+ first Mac install: `getindexinfo` said there was no coinstats index, and the rule read that as
450
+ "not assumed unindexed" and asked `gettxoutsetinfo` anyway, on the slow tier, every 60 s -- a
451
+ walk of 165 M outputs that Core kept computing after the 90 s timeout, holding its chain lock,
452
+ so every other call answered in 18 s, the mempool read was dropped and the block-space board
453
+ stayed empty. Everything was blamed on the index build, which had nothing to do with it. The
454
+ UTXO figures are now asked only of a node that has said it keeps the index; the indexes are
455
+ asked first, alone, before anything expensive.
456
+ - **A fresh install attributed no blocks** and showed raw coinbase tags: the curated pool map
457
+ lived only in `data/`, written by a script nobody had run. The map (mempool.space's
458
+ mining-pools list, MIT, 151 pools) ships in `config/` and is used until `scripts/pool-map.js`
459
+ writes a newer one into `data/`.
460
+ - **Nothing holds hundreds of files open any more.** The index store kept one descriptor per
461
+ segment and layer -- 256 and more -- for the life of the process, and the build kept all 256
462
+ bucket files open through the scan; a stock macOS allows a process 256 (`ulimit -n`) before it
463
+ has opened a socket. A lookup opens the one file it reads and closes it (measured on the full
464
+ index afterwards: 0.02 ms median warm, 21 descriptors held by the whole process), and the build
465
+ keeps at most 64 bucket files open, least recently written closed first. Found while preparing
466
+ the first macOS install.
467
+ - **The installer wrote 16 workers when Enter was pressed.** The suggestion was the machine's core
468
+ count; it is the shared-machine number now, at most four. And it asked for what the node's own
469
+ `bitcoin.conf` already answered -- read first now, above.
470
+ - **The banner's half-block art seamed on the Mac's Terminal** (operator: "What is this garbage?!"):
471
+ a font decides where a half-block glyph sits in its cell. One character cell per pixel, painted as
472
+ a background colour, which every font fills.
473
+ - **Lines wider than 80 columns.** The summary box's one long line broke its frame on an 80-column
474
+ terminal; every line the installer says now wraps to the terminal width.
475
+ - **Block cards clipped and wrapped numbers on a wider font**: a value could wrap mid-number ("604"
476
+ drawn as "60" over "4"), and the stats columns clipped. Values keep their line, the columns size
477
+ to their content, and the card is ten pixels wider.
478
+ - **Windows.** The run-as-main check compared a file URL (`/C:/x`) with a realpath (`C:\x`) as
479
+ strings, so no script's `main()` ran; they are compared as paths (`fileURLToPath`). LF on every
480
+ checkout (`.gitattributes`): several tests match line-anchored patterns in source files, and a
481
+ CRLF checkout turned every one into a miss. The temp directory is the platform's, not `/tmp`;
482
+ defaults and paths are spelled per platform; a file-mode assertion knows Windows has none; the
483
+ scanner is keyed with `/`. **CI runs on Ubuntu, macOS and Windows**, Node 22 and 24, and the suite
484
+ passed on all six at `2010b24`.
485
+
486
+ ### Sync: a long gap is not a stall
487
+
488
+ - **Stalled only when the peers know a higher tip.** Two independent nodes at the same height, no
489
+ block for 42 minutes, and the header said STALLED in red. The network finds no block for 40
490
+ minutes about once in fifty. The state is stalled only when a connected peer reports a tip above
491
+ this node's (`getpeerinfo` `synced_headers`, `startingheight` as the fallback); when the peers
492
+ agree on the tip the node is synced and the caveat names the gap as the network's; with no peer
493
+ height to check, the word waits for two hours. The caveats say which of the three it is.
494
+ - The sync detail's three log-derived rows exist only where the figure does; Bitcoin Core prints
495
+ none of them.
496
+
497
+ ### Block space and Markets
498
+
499
+ - **Agent effects.** The board's idle repertoire is **30 effects**, each with a switch: to the
500
+ fields (ripples, plasma, code rain, fireworks and the rest) the operator asked for things that
501
+ *happen* -- "think more TRON light cycles" -- and fifty video-game-inspired effects were designed
502
+ (`docs/EFFECTS-AGENTS.md`), built, watched, and cut to the ones that earned their place. The
503
+ agents that stayed: **light cycles**, the **lightning ball**, a **centipede** that weaves down the
504
+ board and splits, a **UFO** whose tractor beam lifts the tallest transaction and drops it back
505
+ under gravity, **Missile Command** arcs against rising interceptors, **Boulder Dash** where the
506
+ board gives way from a point, and **ball lightning** drifting across the whole view, its arcs
507
+ electrifying the blocks they strike (it replaced a portal pair). Every agent is checked on a
508
+ flat uniform board too, so none can be blinded by a skyline it happens not to read.
509
+ - **No repeats within N** (default 12): an effect is never played again until that many others
510
+ have played; where fewer are switched on, the one that has waited longest plays next.
511
+ - **The Markets price line is one continuous pipe** rather than forty segments, with a rarer
512
+ **energy pulse** and a new **pipe bulge**, each of them rare -- 2.5-6 minutes between plays: a ball forced through the tube,
513
+ swelling the wall with an arced, stretched skin, the bright core magnified through it as through
514
+ a fish-eye lens; it enters at the line's first point at exactly the tube's size, is as large as
515
+ fits for as long as possible, leaves at the last, and runs quicker downhill than up.
516
+ - **A chrome finish** (Display settings → Metallic finish): every face mirrors a horizon that
517
+ slides as the blocks move; satin is the softer highlight that was there before.
518
+ - **Depth**: a touch of perspective, off by default, so a cube's top grows a little wider than its
519
+ base and a flying block swells as it rises. Capped at 0.001 after larger values put flyers
520
+ through resting cubes.
521
+ - **Departures and arrivals**: how blocks leave and rejoin the board on a refresh, chosen by
522
+ measurement (flights clear their neighbours before fanning, leave the frame rather than popping
523
+ at its edge, and take a lane per leg); a recoloured cube blends to its new colour instead of
524
+ popping; and where a flyer is clearly above a resting cube it paints over it.
525
+ - **The block-flow cards are linked as a chain that reads as one** (operator: "looks bad on a black
526
+ background. Re-do it to be much more stylized and visible"): two pale outlined pills that vanished
527
+ on the dark panel are two interlocked links now, accent-coloured tubes with a highlight and a
528
+ glow, the left link's top strand painted again over the right so the pair weaves.
529
+
530
+ ### Security
531
+
532
+ - **The second AI security audit's findings, the same day** (`docs/SECURITY-AUDIT-2026-09-14.md`,
533
+ which re-verified the 09-13 fixes live and covered the address index and explorer). Its one
534
+ medium: `/api/x/address` sized an allocation by the request's page number, 525 MB for
535
+ `page=999999` on a two-row address -- a deep page now counts the history first and keeps at most
536
+ what exists. The rest: rows above the node's tip (a reorganised tail the follower has not yet
537
+ rolled back) are left out of the count, balance and page and reported as `index.postTip` with a
538
+ caveat on the page; a pool key is escaped like everything around it; a transaction summary with
539
+ more than 2,000 inputs and outputs is never cached; a test fixture no longer lives at a fixed
540
+ name in `/tmp`; `audit.jsonl` and `history.json` are created owner-only.
541
+ - **The first AI security audit** (`docs/SECURITY-AUDIT.md`, 2026-09-13) found one HIGH and four
542
+ more, all fixed the same day; they are the next four items.
543
+ - **The node-connection probe leaked the node's RPC credential** (HIGH, found by audit and
544
+ reproduced with a working exploit). `POST /api/config/node/test` built its throwaway client from
545
+ the live node's config, so `resolveCookie` read the real `.cookie` and sent it as an
546
+ `Authorization` header **to whatever URL the request named** -- on a request needing no session
547
+ and no CSRF token, reachable by a plain cross-site form. Credentials now go only to the endpoint
548
+ the monitor is already configured for; anywhere else is probed unauthenticated and says so. Open
549
+ mode additionally refuses any state-changing request whose `Origin` is not this server or whose
550
+ `Sec-Fetch-Site` says cross-site.
551
+ - **Session TTLs were inverted**: an 8-hour absolute lifetime with a 72-hour idle ceiling meant the
552
+ idle check could never fire. Now 72 h absolute, 8 h idle, with a test on the invariant.
553
+ - **`randomPassword()` drew with modulo bias**, over-representing the first 58 characters of its
554
+ 66-character alphabet. It uses `crypto.randomInt` now.
555
+ - **The audit trail redacts by key shape**, not by two hardcoded field names, so an action echoing a
556
+ key-shaped argument cannot write a secret into the one file designed to be kept.
557
+ - A re-audit is planned after the first install (`AGENTS.md`).
558
+
559
+ ### Scope, documentation and the name
560
+
561
+ - **A node on another machine, over RPC alone: tried, and dropped.** On 2026-09-13 the monitor was
562
+ pointed at a node appliance on the LAN with `rpcUser` / `rpcPassword` and no `datadir` -- a
563
+ configuration the validator had refused at boot until it was fixed -- and the monitor half
564
+ worked: chain, mempool, peers, blocks, the block being built. The explorer did not, and no
565
+ setting would make it: Core has no address index, `scantxoutset` holds the node's single RPC
566
+ thread for tens of seconds per query and knows no history, and a node answering over the
567
+ network in seconds left address pages waiting minutes. **Real-time explorer data over RPC was
568
+ a failed idea.** The address data is rebuilt from the block files and stored locally instead,
569
+ the way mempool.space's `electrs` does it (above), which is why BlockYard runs on the node's
570
+ machine. The install guide's appliance section is gone; `rpcUser` / `rpcPassword` remain for a
571
+ node that uses `rpcauth` instead of the cookie file, and the `bitcoin.conf` lines that measured
572
+ as worth having (`txindex`, `coinstatsindex`, `dbcache`) are kept, each annotated with what it
573
+ does for this monitor.
574
+ - **This is a Bitcoin Core-centric release.** The README, install, configuration, API and
575
+ architecture documents describe Core; the shipped defaults are Core's own (`id: main`,
576
+ `127.0.0.1:8332`, `~/.bitcoin`, `bitcoind.service`). Measurement records taken against a
577
+ non-Core build are anonymised rather than relabelled -- they describe what was measured, and
578
+ claiming otherwise would invent measurements that never happened.
579
+ - **Log parsing is documented as unavailable for Bitcoin Core.** The parsers target an experimental
580
+ node's log grammar; fed Core's `debug.log` they extract no figures and timestamp entries at read
581
+ time. The log source is off by default and should stay off against Core --
582
+ `test/log-core-unsupported.test.js` pins that so it cannot be assumed away.
583
+ - **The old name is gone from the tree** (operator: "WHY THE FUCK DO I STILL SEE THE OLD NAME
584
+ REFERENCES IN OUR TREE DOCS"): a `git grep` for the old prefix finds nothing but bytes in a JPEG.
585
+ The allowlist's vendor-prefixed read verbs -- an earlier node's, and that node is not supported --
586
+ are gone with their test, and a vendor-shaped method is denied by default; two scripts that
587
+ defaulted a CA path to a directory named after the old project take it from the environment or
588
+ nothing; `NOTICE` and `LICENSE` name BlockYard and its copyright holder.
589
+ - **Every document read against the code**: sessions are 72 h absolute / 8 h idle (two documents had
590
+ it inverted); display settings live on the server; the effects list is the thirty that exist; four
591
+ API routes that were undocumented are documented from their handlers; ARCHITECTURE gains the
592
+ address-index subsystem; three stale code comments and the mempool feed's stated cadence
593
+ (60 -> 20 s) corrected; screenshots re-shot against the local Core node. `docs/GETTING-STARTED.md`
594
+ walks a macOS or Linux command prompt through the install (brew's plain node formula, where
595
+ `bitcoin-cli` lives inside the macOS app bundle, one worker on spinning disks, the build's memory
596
+ per worker). `docs/DEFECTS.md` states the scope and closes the entries whose only subject was the
597
+ experimental node or a remote one. Eight one-off pixel probes whose questions are answered in
598
+ MEASUREMENTS and DEFECTS are removed; the checks still used stay.
599
+ - **Donations are in two places only**: the foot of the README, in small type, and the About page,
600
+ where the address is a pill that copies on a click (verified in a headless browser by reading the
601
+ clipboard back) beside a QR of it (`scripts/donate-qr.py` generates the inline SVG and checks it
602
+ decodes; the rendered page's screenshot decodes to the address too). They are out of the installer.
603
+ - **0.0.9 is the initial release number**, the operator's. The two earlier CHANGELOG sections that
604
+ carried version numbers were never tagged or released and are kept as dated milestones;
605
+ `server/main.js` reads the version from `package.json`, and `test/version.test.js` ties the
606
+ CHANGELOG's newest release and the README to it.
607
+
608
+ ### Since the 2026-09-11 milestone: added
609
+
610
+ - **A node connection form**, on **Node & RPC** (operator: "Still left to do is a config connection
611
+ in the web settings. We have no way for users to configure a connection to their rpc backend").
612
+ Enter an RPC URL, a data directory, a chain and a label; **test connection** probes it with a
613
+ throwaway client that never touches the live node's request lane, and **save** is disabled until
614
+ a test has actually answered — and goes back to disabled the moment a field changes. No password
615
+ field: authentication is the node's own `.cookie`, found from the data directory.
616
+ - The save is honest about two things it would otherwise hide. It **keeps the fields the form does
617
+ not show** — `logFile`, `systemdUnit`, the colour — because the config merge replaces arrays
618
+ whole, so a naive write would quietly unconfigure the log tail. And where the environment sets
619
+ `BLOCKYARD_NODE_URL` (a systemd drop-in, say), it says the environment beats the file rather than
620
+ reporting a success the next restart contradicts.
621
+ - **The mempool page carries two panels it was already collecting data for.** `renderMempool` has
622
+ been writing ingest/reject figures and orphan-pool figures into elements that did not exist —
623
+ collected from the node's log, sent to the browser and dropped. They have cards now, and on a
624
+ monitor running without a log tail they say so rather than showing a column of dashes. The page
625
+ also lost four dead grid columns, and Pool usage gained the pool's total vsize, average vsize and
626
+ total fees, all of which were computed on every sample and never drawn.
627
+ - **The grid is yours, per board** (operator: "we need to break out the green grid settings per
628
+ game. We should also add a grid color picker, and a transparency slider ... I really want to turn
629
+ down the intensity on blockanoid", and "add a color selector and brightness setting for the grid
630
+ lighting for blockspace"). Block space, Tetrust, Blockout and Blockanoid each get a **grid
631
+ colour** and a **grid intensity**, and the three games get a **grid** switch as well; Block space
632
+ already had one. Every board is independent, so a court can be turned right down while the board
633
+ stays bright. Until now the colour was hardcoded green in four separate files and could not be
634
+ changed at all.
635
+ - One colour drives the whole grid rather than a single value. The board does not draw its grid in
636
+ one colour: it lays an opaque core under a translucent halo and glow with a brighter line along
637
+ the edge, and that relationship is deliberate — a see-through core reads dimmer wherever the
638
+ floor beneath it is shadowed, and composite modes are off the table. The picker recolours the
639
+ family and keeps each layer's relative weight; intensity multiplies them together.
640
+
641
+ - **Tetrust**: a playable Tetris on the 3D engine (`public/js/tetris.js` for the rules,
642
+ `public/js/tetrust.js` for the screen). The well is the block-space board with its oblique
643
+ camera and curved surface; a neon-blue wireframe marks where the piece will land; cleared
644
+ lines fly up off the top of the canvas. Seven-bag piece order, wall kicks, the classic score
645
+ table times the level, ten lines a level, and a top-ten high score table kept per browser.
646
+ Pauses when the tab or the page loses focus. Arrows or WASD.
647
+ - **Music and sound effects for Tetrust**, synthesised in the browser with the Web Audio API —
648
+ no audio files and no dependencies. Korobeiniki on a lookahead scheduler running on the audio
649
+ clock, and nine shaped tones for move, rotate, soft drop, hard drop, lock, line clear, tetris,
650
+ level up and game over. A switch for each.
651
+ - **Blockout**: Breakout on the 3D engine, in its own tab beside Tetrust (`public/js/breakout.js`
652
+ for the rules, `public/js/blockout.js` for the screen). The bat follows the mouse — or the arrow
653
+ keys — the wall is six rows of block-space stones one grid cell each, and where the ball lands on
654
+ the bat decides where it goes. Three balls, a faster wall each level, and a per-browser high score
655
+ table. The rules file has no DOM, no clock and no randomness (a launch angle is an argument), so
656
+ the whole of it runs under the test suite, and the ball is sub-stepped so it cannot tunnel through
657
+ a brick on a slow frame.
658
+ - **Every timed power expires after 30 seconds** — laser, wide, catch and slow, each counted down on
659
+ the heads-up display. Three balls and the extra life are one-shot and have nothing to run out. One
660
+ timer table and one expiry loop rather than four hand-written countdowns, for the same reason
661
+ `loseLife()` exists: separate copies of a rule drift apart. Slow puts the pace back when it lapses
662
+ (or "slow" would be permanent by omission) and wide restores the bat about its own centre,
663
+ re-gripping a held ball into the narrower span.
664
+ - **A caught ball is locked to the bat.** It recorded no grip, so a stuck ball held its absolute
665
+ position while the bat slid underneath and moved only when an edge caught up with it — which read
666
+ as the ball drifting around. It remembers where along the bat it landed and is placed from that.
667
+ - **The ball bounces off a minion** instead of passing through. The collision forced the ball
668
+ downward whatever direction it had arrived from, so dropping onto a minion pushed it further down.
669
+ It reflects on the axis of least penetration now, exactly as a brick does, which gives the side
670
+ bounces as well.
671
+ - **Catch expires after 30 seconds.** Held indefinitely it stopped being a power-up and became a
672
+ different game: park the ball, aim every shot, and the rally ceases to exist. The countdown runs
673
+ off `step`'s own elapsed milliseconds, like the laser cooldown and the minion timer, so the rules
674
+ still carry no clock and a run stays reproducible. A ball still held when it lapses is released at
675
+ the angle its position on the bat implies, rather than stranded there with nothing to explain it.
676
+ - **Minions, pill capsules and a bat that morphs.** The engine gained a rotated-polygon tile kind
677
+ (`poly` + `rot`, with optional `eyes`), built the way the sphere is — nested filled polygons,
678
+ since the op format has no arcs and gradients are forbidden — and claiming its own face name so
679
+ the sphere's "nothing but ball ops" guarantee is untouched. On it: four **minion** types with
680
+ their own silhouettes, spin rates and drift behaviours (a swinging cone, a tumbling cube, a
681
+ wobbling orb, a zig-zagging molecule), each with eyes; **capsules** are now elongated pills that
682
+ tumble as they fall, the angle taken from their own height rather than a clock so a frame stays
683
+ reproducible; and **Vaus grows cannons** and a raised housing while the laser is up, so the bat
684
+ shows what it can do rather than only changing colour. The capsules keep their per-capsule band
685
+ mark, so the seven remain separable without colour even though they now share one silhouette.
686
+ - **An About page**, reached by clicking the BlockYard monogram in the header rather than by a nav
687
+ tab of its own — the bar is already full enough to wrap below 2000 px. It shows the version and
688
+ live build, the host's operating system, architecture, processors, memory and runtime, and the
689
+ Bitcoin node's own version and protocol, over the spiral galaxy the other boards draw. New
690
+ `GET /api/about` supplies the host facts and deliberately reports no hostname, username, network
691
+ address or environment: the monitor is open-access by default, so the OS and processor describe
692
+ the machine's shape and never its owner.
693
+ - **A stylised BY monogram and a real gear.** The brand mark was the letter `B` in a tile; it is now
694
+ a drawn monogram whose tile, gradient and courses of blocks live in the SVG, so the favicon is the
695
+ same drawing. The settings button was a circle with eight radiating rays — the standard sun glyph,
696
+ which is why it read as a light/dark toggle — and is now a cog with teeth and a punched bore.
697
+ - **The Kiosk's price panel becomes Price & order book depth.** The 24 h high, low, volume,
698
+ spread across books and the per-exchange table are gone; in their place is the depth chart,
699
+ compact and toolbar-less, fixed at ±2.5% around the mid. A wall display is read from across a
700
+ room, where a four-column table is unreadable and the shape of the book says more than a spread
701
+ figure — all of it is still on the Markets tab. The chart shares the Markets tab's single poll,
702
+ so having both open does not double the traffic to five exchanges, and the depth endpoint marks
703
+ the collector as watched, so an unattended kiosk keeps its books fresh by itself.
704
+ - **Blockanoid**: Arkanoid on the 3D engine, the third Diversion (`public/js/arkanoid.js` for the
705
+ rules, `public/js/blockanoid.js` for the screen). Six hand-built walls that cycle; **silver**
706
+ bricks that take two hits and one more every four levels, standing lower once damaged; **gold**
707
+ that never breaks and never blocks a level, since a wall is cleared when its *breakable* bricks
708
+ are gone. Six **capsules** fall out of broken bricks — laser, enlarge, catch, slow, disrupt
709
+ (three balls) and player (a life) — one on the court at a time, as the
710
+ arcade did it. Vaus turns red while the laser is up, so the bat says what it can do. Minions
711
+ drift down the court and pay when destroyed. Which brick carries a capsule is a **hash of the
712
+ brick and the level, never `Math.random`**, so a wall always drops the same letters and the
713
+ whole thing is assertable under `node:test`. Capsules and minions each have a switch, and
714
+ because they change the rules rather than the look, flipping one reaches the game in play.
715
+ - **Seventeen new idle effects**, bringing the total then to **26** (30 at release; above), each with its own switch:
716
+ shockwave, nova, fireworks, solar flare, wave, quake, code rain, sparkle, checkerboard, radar,
717
+ vortex, laser, power-up, combo chain, aurora, plasma and glitch. All are pure functions of the
718
+ tile and the effect's clock, so each replays identically and is covered by tests rather than
719
+ by watching.
720
+ - **Block finishes**: **neon blocks** (a dim solid body in the block's fee-rate colour under lit
721
+ tubes on every visible edge) and a **metallic sheen** (a specular highlight on the lit edge of
722
+ each top face, a dark roll-off on the far one). Both work at every level of detail. The neon
723
+ tubes can take the block's own colour or one colour of your choosing, at a brightness you set.
724
+ - **A movable lamp**: `Light` chooses straight above (now the default for Block space), upper
725
+ left, upper right, or from the viewer.
726
+ - **A thickness slider for Tetrust's landing marker**, 0.3 to 2.5 times the shipped weight, so the
727
+ outline can be thinned out of the way of the stack behind it.
728
+ - Broken bricks in Blockout **fly up off the court** instead of vanishing, the same launch Tetrust's
729
+ cleared lines take.
730
+ - **`txindex=1` is documented as required** for the explorer's transaction pages — a transaction page
731
+ asks for `getrawtransaction <txid> 2` with no block hash, which a node without the index can only
732
+ answer for its mempool. Block pages pass the hash and are unaffected. Install, README and
733
+ troubleshooting all say so now.
734
+ - **The Tetrust landing marker's colour is a setting.** The wireframe showing where the falling
735
+ piece will land is drawn instead of a block rather than over one, so the neon finish never
736
+ applied to it and it stayed the shipped blue whatever else was changed; it has its own colour
737
+ now, and `tiles()` takes it as an argument so the rules file still knows nothing of the store.
738
+ - **A tabbed Display settings panel**, with all-on / all-off on the Effects tab, whose
739
+ twenty-six switches -- thirty now -- are a lot of clicking otherwise.
740
+ - **Markets remembers its toolbar**: the exchange and the range are settings now, so the page
741
+ opens where you left it.
742
+
743
+ ### Since the 2026-09-11 milestone: changed
744
+
745
+ - **The block being built is assembled here now, and costs your node nothing.** It used to be a
746
+ `getblocktemplate` call worth 1.3-1.5 s of the node's single RPC thread and 1.79 MB per reply,
747
+ fetched on demand so a page nobody had open did not pay it every minute. Bitcoin Core publishes
748
+ everything the selection needs in the `getrawmempool(true)` reply this monitor **already reads
749
+ every 20 s** for the mempool view: `depends`, the ancestor sizes and fees, and
750
+ `fees.chunk`/`chunkweight` -- Core's own cluster-mempool linearization, which is the order its
751
+ miner sorts by. `server/collect/gbt.js` selects greedily over that, taking each transaction with
752
+ its unselected ancestors, and returns the result in the shape a `getblocktemplate` reply has, so
753
+ the summary, the histogram, the package analysis and the block economy read it unchanged.
754
+
755
+ Measured against the node's own template on a back-to-back pair at height 966821, so the two
756
+ describe the same pool: **6,546 transactions / 3,995,859 weight / 643,076 sat** against the
757
+ node's **6,535 / 3,991,951 / 642,860** -- 0.03% apart on fees, with the set difference confined
758
+ to the 0.30 sat/vB margin where ties are arbitrary. Assembly takes ~50-70 ms of this process's
759
+ CPU. It is a reconstruction of what a miner would choose, not the node's answer: sigop limits
760
+ and policy the mempool does not publish are not modelled, and the card says so.
761
+
762
+ The old measurement in `docs/MEASUREMENTS.md` -- that `getrawmempool` verbose carries no
763
+ `depends` -- was true of the experimental node it was taken on, and is kept there with the
764
+ correction appended rather than rewritten.
765
+ - **The default web port is 21000** (was 8088).
766
+ - **Display settings are stored on the server** in `config/blockyard.json`, so a phone and a
767
+ desktop pointed at the same monitor agree. The browser keeps a cache so boards still draw when
768
+ the server cannot be reached.
769
+ - **Block space ships with simple cubes and shadows off.** Shadows are the costliest single thing
770
+ the board draws, and the board is the first thing most people open; both remain one click away in
771
+ Display settings.
772
+ - The two games sit at the end of the nav under a **Diversions** pop-down, rather than among the
773
+ working tabs.
774
+ - The Markets energy pulse now runs along the neon price line itself, leaving an electric-blue
775
+ tail that fades back to yellow behind a bright head, with a nebula of blue smoke emitted along
776
+ the whole charged span and a shimmer over it. The lightning ball trails the same charge across
777
+ the block-space board; the light cycles do not.
778
+ - The pulse's nebula is emitted over the whole charged span rather than per segment — emitting per
779
+ segment gave neighbouring puffs the same age, so they shared a radius and lined up into the
780
+ concentric rings they were meant to replace. (Its motes and crackle branches were removed at the
781
+ same time and restored afterwards; they are present.)
782
+ - The Simple viewer packs the block exactly: the block's own area is solved so the tiles fill
783
+ the grid flush, and the remainder is tiled to the edge instead of leaving a partial top row.
784
+ - Pool attribution moved out of the block card's body into a readable pill beneath it.
785
+
786
+ ### Since the 2026-09-11 milestone: fixed
787
+
788
+ - **Coinbase attribution stopped permanently after one failed block.** `pumpMining` cleared the
789
+ whole queue on a single failure and nothing ever re-queued it, so one slow moment discarded the
790
+ entire 36-block boot window and the Mining page sat empty. The failed height is put back, the
791
+ rest of the queue survives, and a backoff decides when to retry.
792
+ - **The block template no longer monopolises the RPC lane.** `getblocktemplate` went through as an
793
+ ordinary call with a 12-second freshness budget; on a node where it takes seconds, everything
794
+ queued behind it was stale-dropped and `/api/nextblock` took 75 s. It is now heavy, keyed and
795
+ given a realistic budget -- measured 52.8 s to 4.2 s on the same node.
796
+ - **The display-settings sliders no longer jitter while dragging** (operator: "the grid intensity
797
+ slider jitters when I move it"). Every `input` event ran a full synchronous re-render; a drag
798
+ across the grid intensity control queued forty of them, each repainting a board. The value and
799
+ the readout still update on every event — only the repaint is coalesced, to one per animation
800
+ frame. All twelve range controls were affected; the new one merely made it visible.
801
+ - **The Diversions menu renders correctly in Safari** (operator: "rendering on safari is still
802
+ broken. It's only showing half the drop-down contents"). The panel was inside `header.top`, which
803
+ is `overflow: hidden` and 46px tall, and WebKit clipped the fixed panel to it. It is a top-level
804
+ element now, like the settings dialog, which is the fixed overlay that always rendered correctly.
805
+ Its position is measured and set rather than pulled back by a transform.
806
+ - **Display-settings sliders jumped as their value changed**: the readout's width changed with its
807
+ digits and pushed the slider about. The value is printed to the step's decimals in a fixed-width
808
+ box.
809
+ - **The header's uptime blanked every second** and **the node you pick stays picked**.
810
+ - **The travelling cube's perspective froze in flight** (a regression of our own, recorded).
811
+ - **The pulse-gap simulation was flaky**: seeded now, its bound the real worst case.
812
+ - The block-being-built card named a call it does not make; lightning stopped whiskering; the
813
+ paint-order comments described a camera the viewer no longer has.
814
+ - **The star field never animated on a board that asked for no tile choreography.** `still` is
815
+ about the tiles; it was also returning before the animation loop started, so Tetrust's galaxy
816
+ repainted only when the page happened to redraw — measured at zero repaints in three seconds.
817
+ The loop now parks only when there is genuinely nothing moving. Measured after: 87 repaints in
818
+ three seconds, idle and in play.
819
+ - **Blocks swapped in front of each other during refreshes.** Where cubes overlap, the paint
820
+ order is solved as a graph; a cube flying past could pull a settled pair into a tangle and the
821
+ tangle was ordered by depth alone, discarding the pair's own decision. A tangle now keeps the
822
+ relative order it had in the previous frame. Replayed over a 634-frame transition: 19 flickers
823
+ to none.
824
+ - **Neon and the metallic sheen did nothing when switched on** — `render3d` never passed either
825
+ option through to the scene builder.
826
+ - The galaxy is much cheaper to draw: its gas is painted once into an offscreen bitmap and drawn
827
+ turned, and the stars are batched by colour and brightness instead of setting a fill style per
828
+ star.
829
+ - Blocks with no pool attribution showed no statistics at all.
830
+ - Taller cubes no longer clip the neighbour they lean over on a settled board.
831
+ - **The Explorer's Latest blocks cubes were drawn with faces that did not meet.** The top face and
832
+ the right face were each inset five pixels on two sides, so neither reached the top-right corner:
833
+ every block carried a dark triangular wedge there, a sliver of bare card at the top left and a
834
+ gap at the bottom right. The faces are now flush with the card and with each other.
835
+ - **The network hash rate read "0.0 EH/s".** Two bugs, one hiding the other. The estimator divided
836
+ difficulty by the average block gap and left out the 2^32 hashes a difficulty-1 target expects,
837
+ so it was out by a factor of 4.29 billion; and the formatter's unit prefixes were each one step
838
+ too low, so a four-digit EH/s figure would have printed as a single-digit one. Checked against
839
+ the node's own `getnetworkhashps`, which the monitor had never used: the corrected estimate is
840
+ 1111.8 EH/s against the node's 1097.9, agreeing to 1.26%. The test covering it asserted that the
841
+ wrong magnitude was "of the right order", which is why it survived; it now checks the figure
842
+ could be true rather than restating the implementation.
843
+
844
+ ## Milestone 2026-09-11 (labelled 0.9.0 internally; never tagged or released)
845
+
846
+ Licensed Apache-2.0 from here on. The block-space packer and feerate palette
847
+ are an original implementation (`public/js/blockpack.js`, `public/js/feepalette.js`).
848
+
849
+ ### Monitor
850
+
851
+ - Live dashboard for one or more Bitcoin nodes over JSON-RPC, with an optional
852
+ log source: Overview, Chain & Sync, Mempool, Peers, Network, Mining, Events, Node & RPC and
853
+ Admin tabs, updated once a second over Server-Sent Events.
854
+ - Sync viewer whose bar is blocks held over announced headers, with the node's own progress
855
+ shown separately and an ETA computed only from a measured rate window.
856
+ - Block flow: projected blocks beyond the one being assembled (fee range, median, fees,
857
+ transaction count, time estimate), the block being built with its age ring, and recent
858
+ blocks linked as a chain.
859
+ - Peer table from `getpeerinfo`: direction, transport, services, height at connect, bytes and
860
+ rates per connection.
861
+ - A provenance table naming the source of every figure, and explicit "not reported" markers
862
+ instead of zeros.
863
+
864
+ ### Block space viewer
865
+
866
+ - A 3D board of the next block's worth of the mempool: square tiles sized by vbytes and
867
+ coloured by feerate, on a curved, neon-gridded board.
868
+ - 128 feerate colours from under 0.1 to 2,000 sat/vB, sky blue through green, yellow, orange
869
+ and red to purple, with neighbouring bands stepped in tone so they read apart.
870
+ - Choreographed refreshes: blocks lift, travel in collision-free lanes and land under gravity
871
+ with bounces; cube-on-cube shadows; a refresh countdown and a "refresh now" button.
872
+ - Idle effects at rest: ripples, scans, tides, cascades, twinkles, TRON light cycles and a
873
+ lightning ball that runs along the grid lighting the cubes it passes.
874
+ - Viewer modes: **Simple** (the richest few hundred transactions as cubes, the rest as
875
+ equal pieces coloured by their feerate) and
876
+ **Detailed** (every transaction in the next block, one square each on a 96-unit
877
+ grid, drawn as low slabs, each square area-true so a full block fills the board),
878
+ remembered per browser.
879
+
880
+ ### Explorer
881
+
882
+ - Search by block height, block hash, transaction id or address; every page is a shareable
883
+ link (`#explorer/…`).
884
+ - Transaction pages: status, fee and fee rate with dollar values, feature badges (SegWit,
885
+ Taproot, RBF, consolidation, OP_RETURN, coinbase), a flow diagram from inputs to outputs,
886
+ inputs and outputs with links to the spent and spending transactions, copy buttons.
887
+ - Block pages with statistics and paged transactions; address pages with balance, totals and
888
+ history (with the node's address index); the latest blocks as fee-coloured cubes.
889
+ - Links into the explorer from the rest of the app.
890
+
891
+ ### Markets
892
+
893
+ - Prices from Coinbase, Kraken, Bitstamp, Bitfinex and OKX, fetched by the server only while
894
+ someone has the Markets or Kiosk tab open.
895
+ - A 3D candle chart on a low side-on camera with a neon close line, volume band, labelled
896
+ price levels, a star field and light from the front right.
897
+ - A flat candlestick chart with a crosshair readout and other exchanges overlaid; an exchange
898
+ table; 24 h / 48 h / 7 d ranges.
899
+ - An order-book depth chart: cumulative bids and asks per exchange and in total, the total as
900
+ it stood 1–60 minutes ago, and change bars on a symmetric-log axis.
901
+
902
+ ### Kiosk
903
+
904
+ - The 3D markets board, a price panel and the block-space board side by side, with a
905
+ full-screen button.
906
+
907
+ ### Display settings
908
+
909
+ - A gear in the header opens a settings panel: shadows, idle effects, stone edges, the neon grid,
910
+ a star field, level of detail (full / simple cubes / flat tiles), refresh animation
911
+ (full / quick / none) and board curve for the block-space board; star field, density, brightness
912
+ and grid glow for the markets board. Kept in the browser, applied without a reload, and each one
913
+ changes what is drawn rather than only what is stored.
914
+
915
+ ### Security
916
+
917
+ - Open, read-only access by default; optional accounts with scrypt hashing, hashed sessions,
918
+ CSRF protection, lockouts and a rotated audit trail.
919
+ - Default-deny RPC allowlist; node writes off unless explicitly enabled per action.
920
+ - Built-in HTTPS, multi-address binding, a CIDR gate, and a strict Content Security Policy
921
+ with no inline styles and no third-party assets.
922
+
923
+ ## Milestone 2026-09-08 (labelled 0.1.0 internally; never released)
924
+
925
+ Internal first version: multi-user monitor with charts, sync viewer, mempool view, peers and
926
+ event feed.
927
+
928
+ [0.0.10]: https://github.com/BobClawblaw/blockyard/releases/tag/v0.0.10
929
+ [0.0.9]: https://github.com/BobClawblaw/blockyard/releases/tag/v0.0.9