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
package/CHANGELOG.md ADDED
@@ -0,0 +1,679 @@
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
+ Nothing yet.
10
+
11
+ ## [0.0.9] — 2026-09-14
12
+
13
+ The initial release. Everything in it, like everything before it, was written by an AI directed by a human
14
+ operator, and audited by AI (`docs/SECURITY-AUDIT.md`, `docs/SECURITY-AUDIT-2026-09-14.md`). It is
15
+ experimental pre-release software. BlockYard runs **on the machine that runs Bitcoin Core** (25.0 or
16
+ later), because the explorer's address index is built from the node's own block files; a node on
17
+ another machine, and the experimental node that earlier measurements were taken on, are not supported.
18
+ 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.
19
+
20
+ ### The 3D boards' effects, per board
21
+
22
+ - **Each 3D board has its own effects list.** The one Effects tab is now two: **Space effects**
23
+ (the Block space board's 28 switches — every effect but the two drawn on a price line) and
24
+ **Market effects** (the Markets board's 12: ripple, outline sweep, tide, cascade, twinkle,
25
+ scan line, fireworks, solar flare, wave, energy pulse, pipe bulge, ball lightning — the ones
26
+ 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
27
+ all on / all off, which the no-repeat slider had silently taken away. A saved settings store is
28
+ migrated (schema 4): the Markets list starts as a copy of the list there was.
29
+ - **Each effects tab sets its board's cadence.** *Between effects, at least* / *at most*
30
+ (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*
31
+ (1.2 s by default, up to two minutes) on Space effects only — the candle board has no landing
32
+ — drive the scheduler's own timers, per board.
33
+ The no-repeat slider on each tab tops out at that list's length.
34
+ - **Nothing on the Markets board waits its turn.** The pulse, the bulge and ball lightning were
35
+ held to a clock of 2.5-6 minutes between plays; they are ordinary picks now, and the list you
36
+ leave on decides how often any one of them comes round.
37
+ - **Everything on the Markets board moves along the hours** — left or right, never toward the
38
+ viewer — and lights the candles or the line: fronts (outline, scan, tide, wave) run along the
39
+ chart, rings (ripple, shockwave, nova) start on the candle row, the light cycles ride in from
40
+ the two ends with their walls on the candle tops, the lightning ball rides the price line, and
41
+ ball lightning flies through the chart's own height. Candle faces light under an effect now
42
+ (the camera is low, and a glow painted on a candle's top alone was invisible).
43
+ - **Ball lightning strikes candles.** `cellTops` began each tile at its own fractional x and y,
44
+ which on the candle board (tiles between grid lines) stored nothing, so every cell top was
45
+ zero and no arc found a target. Half the arcs now chain on from the struck block to
46
+ another in electric green, the instant they land, and half of those on to a third block.
47
+ - The lightning ball is one pale gradient with a white-hot heart, and a pale burn behind it,
48
+ instead of five stacked discs of deepening blue that read as a dark blot.
49
+
50
+ ### The explorer's address history, from an index of our own
51
+
52
+ - **Address pages show full history and balances on Bitcoin Core.** This was the explorer's one
53
+ real gap, and it was not a bug: Core has **no address index at any setting**. `getaddressbalance`
54
+ and `getaddresstxids` are insight-style extensions carried by forks, and stock Core answers
55
+ `Method not found` (measured 2026-09-13 against two Core nodes). mempool.space
56
+ shows the same address's history only because `electrs` builds that index from the block files
57
+ itself. So this monitor now does the same, in a form it can afford: `server/chain/`.
58
+ - **Reading the node's own files, not asking the node.** `blk*.dat` on a current Core is
59
+ XOR-obfuscated at rest (`-blocksxor`, default since v28, the key in `blocks/xor.dat`) -- the
60
+ first spike concluded "not a Core datadir" and was wrong. De-obfuscated, `server/chain/blockfile.js`
61
+ frames the records; `server/chain/tx.js` decodes transactions and blocks, checked
62
+ **field-for-field** against `getblock <hash> 3` (91,813 spent coins in 16 sampled file pairs, 0
63
+ mismatches); and the undo (`rev*.dat`) records supply each spent coin's script and amount,
64
+ which is how the spending side is known without replaying the UTXO set. Pairing blocks with
65
+ undo records by trying every candidate was quadratic on the tiny early blocks (file 0: 35 minutes
66
+ and not finished); Core appends undo in connection order, so blocks are put in chain order by
67
+ their previous-block links and walked in step -- file 0 pairs in 5.9 s.
68
+ - **A lean row.** One 21-byte row per (script, transaction that touched it): 8 bytes of
69
+ sha256(script), the height, the position in the block, and the **net satoshis** the transaction
70
+ moved for that script -- so a balance is a sum, with no node call. A script paid and spent in the
71
+ same transaction is one row (18% fewer). The full decoder builds Core's verbose shape and was 5.9
72
+ of the 9.5 single-core hours measured for the chain; the index needs only each output's value and
73
+ script bytes, so `server/chain/index/rows.js` walks the raw transaction itself and is checked
74
+ row-for-row against rows from the full decoder.
75
+ - **Flat sorted files, no database.** Measured against `node:sqlite` on three real file pairs
76
+ (`docs/MEASUREMENTS.md` §29): 21 bytes per row against 24-27, a 5.5 M rows/s sort against a
77
+ 1.4 M rows/s key-ordered load, and no B-tree collapse once the keys outgrow memory. 256 sorted
78
+ segments by key prefix with a sparse index (one key per 4,096 rows) held in memory; a lookup
79
+ binary-searches the sparse keys and reads the one 86 KB block that can hold its key.
80
+ - **Built and measured** (§30): `node scripts/index-build.js --out <dir> --workers 16` read all
81
+ 5,756 file pairs in **29 min 45 s** (7.8 CPU-hours across 16 workers) and wrote
82
+ **5,890,519,289 rows, 123.7 GB** -- every height present exactly once, two stale blocks skipped,
83
+ and within 1.5% of the projection. **40 of 40 balances equal `scantxoutset`** at the same
84
+ height, to the satoshi; every (script, transaction) pair of four whole blocks from 2009 to the
85
+ tip found at its height and position with its amount. Lookups: **0.25 ms** median first touch,
86
+ 0.03 ms warm; a 2.3 M-transaction address's whole history summed for its balance in 83 ms.
87
+ For scale: `scantxoutset`, the only thing Core offers, took 26.5 s for one scan of 40 addresses
88
+ holding the node's RPC thread, and answers only the current balance, never a history.
89
+ - **It follows the chain.** `server/chain/index/live.js`, started by the server for each configured
90
+ index directory, polls every 30 s, fetches each new block with `getblock <hash> 3` over RPC (so
91
+ the files are read once, at the build), and **logs the rows before serving them** in a
92
+ CRC-framed `live.log` that is replayed on restart and drops a record torn by a crash. A
93
+ reorganisation rolls the tail back to the fork; blocks 100 deep are **folded** into immutable
94
+ sorted layers and layers past 32 are merged; a reorganisation below what is folded stops the
95
+ follower and the page says to rebuild. Checked live: 12 blocks caught up in 6.5 s, 40 of 40
96
+ balances then equal to `scantxoutset`, and on release day the index reached a new block 16 s
97
+ after the node did. The rows it derives through RPC agree row-for-row with the rows built from
98
+ the files (`test/chain-index-live.test.js`).
99
+ - **The address page reads it.** `addressIndex` in a node's config names the directory (one index
100
+ serves every node on the same chain); the page shows the transaction count, balance, total
101
+ received and sent, and the transactions newest first with the **net change each made**, 25 a
102
+ page, deep pages costing no more than the first. The reply carries `index.tip`, `index.behind`,
103
+ `index.following` and `index.stale`, and the page says when the index is behind the node or has
104
+ stopped following. Checked live: every row of four pages, including page 4 of a 2.3
105
+ M-transaction address, matched the node's decoded transaction for txid, height and amount.
106
+ - **Without an index, the page is honest rather than empty.** A node's refusal used to become `[]`,
107
+ then `txCount: 0`, then "no transactions in this node's address index" -- a fabricated zero
108
+ indistinguishable from an unused address. The reply now carries `indexed: false` with a **null**
109
+ count, the page says the index is absent, and the dead RPCs are not re-sent on every view: a
110
+ "method not found" is remembered per node for ten minutes, then asked again, because the daemon
111
+ behind a node id can change.
112
+ - **A rebuild in place is safe.** `buildIndex` empties its output directory first, so a follower's
113
+ `live.log` and layers -- holding the reorganised-away blocks -- cannot survive into a new index and
114
+ a fresh follower catches up on the chain as it is (`test/chain-index-live.test.js`). Stop the
115
+ server, run the same command, start it; the advice to build into a fresh directory is withdrawn.
116
+ - **A missing address index is built by BlockYard itself, in the background** (operator: "Is it
117
+ possible to run step 6 in the background, and have a status notification in blockyard when the
118
+ index process is finished?"). On start, an `addressIndex` directory with no index in it is built
119
+ on worker threads while every page keeps serving; the progress is a quality flag on the Overview
120
+ and a line on the address page (phase, files done, rows so far, time left); an event marks the
121
+ start, the finish and a failure, and the browser toasts it; when it finishes the follower starts
122
+ and address pages work with no restart. `addressIndexBuild: "manual"` keeps it from happening.
123
+ The installer's step 6 offers background (the default), here, or later.
124
+ - **The build shares the machine with the node, and behaves like it.** Every build -- the server's
125
+ background one, the installer's build-here and `scripts/index-build.js` -- goes through one pacer
126
+ (`rpcPacer`) that reads the monitor's own RPC telemetry: before each file it holds, ten seconds at
127
+ a time, while the node's RPC is failing, its breaker is open or its answers average more than
128
+ `rpc.slowLatencyMs` (5 s), and eases to one file at a time with a pause between above 40% of that.
129
+ It first held at one second, which ran a healthy build on a node whose heavy reads take a second
130
+ at a sixth of its speed; the threshold is the monitor's own notion of slow now. The height batches
131
+ are paced the same way, 1,000 at a time at the lowest priority. The server's build opens its own
132
+ RPC connection, as `scripts/index-build.js` always did, so it and the monitor stop starving each
133
+ other: on the first Mac its `getblockhash` batches queued behind multi-second mempool and block
134
+ reads on the monitor's one lane, and both sides starved ("heights 1,000 of 967,015" for a quarter
135
+ of an hour). Workers: `addressIndexWorkers` in the node's config, written by the installer; unset,
136
+ the server takes half of what a dedicated build would and at most four. The `rpc-slow` and
137
+ `rpc-timeouts` quality flags name the build when one is running and no longer assert a cause from
138
+ another node's era. **An interrupted build starts over**: there is no resume after Ctrl-C, and the
139
+ installer says so when it happens.
140
+ - **An address's unspent outputs are listed** (operator, the same day: "Why don't we do this"): the
141
+ index names every transaction that touched the address, each one's outputs paying it are asked of
142
+ `gettxout` (the UTXO set, less what the mempool already spends), and the page lists them with the
143
+ index's own height and their count on the card. The walk is the whole history, so it is made for
144
+ an address with at most 100 transactions and declined in words for a longer one.
145
+ - **Not yet:** an address's transactions still in the mempool.
146
+ - **An unconfirmed transaction shows its inputs and fee** (operator: "Unknown script?!", of a
147
+ mempool transaction whose 858 inputs all read *unknown script*). Core carries no `prevout` on a
148
+ mempool transaction's inputs, so `fillPrevouts` fetches the parents in one batch and fills each
149
+ input's script and amount; checked on that transaction, 858 of 858 inputs, and a fee of 77,958
150
+ sat equal to `getmempoolentry`.
151
+
152
+ ### The installer: `npm run setup`, `npm run check`, and the `blockyard` command
153
+
154
+ - **An installer: `npm run setup`** (operator: "build a test into the installer so we can verify it
155
+ properly connects to an RPC server and finds the bitcoin logs ... something that writes out a
156
+ config/local.json at the end ... that we can up and run immediately to start building the
157
+ transaction set"). It asks for the RPC URL and data directory, finds the cookie (or asks for
158
+ `rpcUser`/`rpcPassword`), and proves the answers before writing anything: the RPC server answers
159
+ and on which chain, the node's version is 25.0 or later, `txindex` is on and synced,
160
+ `getblock <tip> 3` carries prevouts, the node is not pruned, `blocks/` holds matched block and
161
+ undo files and the first one opens to the genesis block through the XOR key, `debug.log` is where
162
+ it should be, and a configured index directory is readable, writable and not far behind. Then it
163
+ writes `config/local.json` (mode 0600, a backup of any previous one) and offers to build the
164
+ address index on the spot. `npm run check` runs the same checks against every configured node
165
+ and exits non-zero on a FAIL; `--yes` with flags is the scripted form. `docs/GETTING-STARTED.md`
166
+ walks a macOS or Linux command prompt through it. Six numbered steps, every answer validated and
167
+ explained before it is accepted (a URL that is not one, a port off the range, a directory that
168
+ is not there, a hostname where the config wants an address), a warning when something already
169
+ listens on the port, a progress bar with the rows so far and the time left for each phase of the
170
+ build (the same bar in `scripts/index-build.js`), Ctrl-C that leaves everything as it was, and a
171
+ last question that starts the monitor in the same terminal.
172
+ It reads the node's own `bitcoin.conf` first (operator: "can't you look through the user's
173
+ .conf and find the rpc values?"): chain, `rpcport`, `rpcconnect`, `rpcuser`/`rpcpassword`,
174
+ `rpcauth` users, a cookie file named elsewhere, `server=`, `txindex=`, `prune=`, sections and
175
+ `includeconf=`, so the RPC URL and the credentials arrive as defaults rather than questions.
176
+ - **Every check is timed**, because how fast the node answers is half of what an install needs to
177
+ know, and the verbose mempool read -- the monitor's heaviest regular call, every 20 s -- is one of
178
+ the checks, with a warning when it is slow enough to lag the board. An unbuilt index is reported
179
+ as information, not a fault.
180
+ - **Defaults**: the index in the checkout's `data/index`, like everything else the install writes;
181
+ at most **four** build workers (the shared-machine number; one on spinning disks, each needing
182
+ ~2.5 GB of memory); the build in the background once BlockYard starts.
183
+ - **The banner** (operator: "I want the installer to have amazing ANSI Art here for the BY logo",
184
+ then "It needs to fit in 80 character space. Standard CRT"): the BY monogram -- the same tile the
185
+ favicon is -- in 26x12 solid cells, one cell per pixel painted as a background colour so it fills
186
+ the cell in every font, the About text beside it, and the whole run inside 80 columns (piped at
187
+ 80: widest line 79). Every line wraps to the terminal with its indent kept, a box cuts a line past
188
+ its room with an ellipsis, widths are measured on what is seen rather than on colour codes, and
189
+ colour steps aside without a TTY or under `NO_COLOR` (`FORCE_COLOR` turns it on).
190
+ - **An npm package and a `blockyard` command** (operator: "push to npm"). `package.json` loses
191
+ `private` and gains `bin` and `files` (bin, server, public, scripts, the pool map, systemd, the
192
+ docs, the licence and notice; no tests, no images: 924 kB packed). `blockyard setup | start |
193
+ check | index-build | users` keeps the config and data under `~/.blockyard` (`BLOCKYARD_HOME`),
194
+ since a global install's own directory is nowhere to keep a config or 124 GB of index; a checkout
195
+ run with `npm run …` is unchanged.
196
+
197
+ ### Fixes from the first fresh install (a Mac, Core 29.1)
198
+
199
+ - **A node without `coinstatsindex` was sent a full UTXO-set walk every minute.** Found on the
200
+ first Mac install: `getindexinfo` said there was no coinstats index, and the rule read that as
201
+ "not assumed unindexed" and asked `gettxoutsetinfo` anyway, on the slow tier, every 60 s -- a
202
+ walk of 165 M outputs that Core kept computing after the 90 s timeout, holding its chain lock,
203
+ so every other call answered in 18 s, the mempool read was dropped and the block-space board
204
+ stayed empty. Everything was blamed on the index build, which had nothing to do with it. The
205
+ UTXO figures are now asked only of a node that has said it keeps the index; the indexes are
206
+ asked first, alone, before anything expensive.
207
+ - **A fresh install attributed no blocks** and showed raw coinbase tags: the curated pool map
208
+ lived only in `data/`, written by a script nobody had run. The map (mempool.space's
209
+ mining-pools list, MIT, 151 pools) ships in `config/` and is used until `scripts/pool-map.js`
210
+ writes a newer one into `data/`.
211
+ - **Nothing holds hundreds of files open any more.** The index store kept one descriptor per
212
+ segment and layer -- 256 and more -- for the life of the process, and the build kept all 256
213
+ bucket files open through the scan; a stock macOS allows a process 256 (`ulimit -n`) before it
214
+ has opened a socket. A lookup opens the one file it reads and closes it (measured on the full
215
+ index afterwards: 0.02 ms median warm, 21 descriptors held by the whole process), and the build
216
+ keeps at most 64 bucket files open, least recently written closed first. Found while preparing
217
+ the first macOS install.
218
+ - **The installer wrote 16 workers when Enter was pressed.** The suggestion was the machine's core
219
+ count; it is the shared-machine number now, at most four. And it asked for what the node's own
220
+ `bitcoin.conf` already answered -- read first now, above.
221
+ - **The banner's half-block art seamed on the Mac's Terminal** (operator: "What is this garbage?!"):
222
+ a font decides where a half-block glyph sits in its cell. One character cell per pixel, painted as
223
+ a background colour, which every font fills.
224
+ - **Lines wider than 80 columns.** The summary box's one long line broke its frame on an 80-column
225
+ terminal; every line the installer says now wraps to the terminal width.
226
+ - **Block cards clipped and wrapped numbers on a wider font**: a value could wrap mid-number ("604"
227
+ drawn as "60" over "4"), and the stats columns clipped. Values keep their line, the columns size
228
+ to their content, and the card is ten pixels wider.
229
+ - **Windows.** The run-as-main check compared a file URL (`/C:/x`) with a realpath (`C:\x`) as
230
+ strings, so no script's `main()` ran; they are compared as paths (`fileURLToPath`). LF on every
231
+ checkout (`.gitattributes`): several tests match line-anchored patterns in source files, and a
232
+ CRLF checkout turned every one into a miss. The temp directory is the platform's, not `/tmp`;
233
+ defaults and paths are spelled per platform; a file-mode assertion knows Windows has none; the
234
+ scanner is keyed with `/`. **CI runs on Ubuntu, macOS and Windows**, Node 22 and 24, and the suite
235
+ passed on all six at `2010b24`.
236
+
237
+ ### Sync: a long gap is not a stall
238
+
239
+ - **Stalled only when the peers know a higher tip.** Two independent nodes at the same height, no
240
+ block for 42 minutes, and the header said STALLED in red. The network finds no block for 40
241
+ minutes about once in fifty. The state is stalled only when a connected peer reports a tip above
242
+ this node's (`getpeerinfo` `synced_headers`, `startingheight` as the fallback); when the peers
243
+ agree on the tip the node is synced and the caveat names the gap as the network's; with no peer
244
+ height to check, the word waits for two hours. The caveats say which of the three it is.
245
+ - The sync detail's three log-derived rows exist only where the figure does; Bitcoin Core prints
246
+ none of them.
247
+
248
+ ### Block space and Markets
249
+
250
+ - **Agent effects.** The board's idle repertoire is **30 effects**, each with a switch: to the
251
+ fields (ripples, plasma, code rain, fireworks and the rest) the operator asked for things that
252
+ *happen* -- "think more TRON light cycles" -- and fifty video-game-inspired effects were designed
253
+ (`docs/EFFECTS-AGENTS.md`), built, watched, and cut to the ones that earned their place. The
254
+ agents that stayed: **light cycles**, the **lightning ball**, a **centipede** that weaves down the
255
+ board and splits, a **UFO** whose tractor beam lifts the tallest transaction and drops it back
256
+ under gravity, **Missile Command** arcs against rising interceptors, **Boulder Dash** where the
257
+ board gives way from a point, and **ball lightning** drifting across the whole view, its arcs
258
+ electrifying the blocks they strike (it replaced a portal pair). Every agent is checked on a
259
+ flat uniform board too, so none can be blinded by a skyline it happens not to read.
260
+ - **No repeats within N** (default 12): an effect is never played again until that many others
261
+ have played; where fewer are switched on, the one that has waited longest plays next.
262
+ - **The Markets price line is one continuous pipe** rather than forty segments, with a rarer
263
+ **energy pulse** and a new **pipe bulge**, each of them rare -- 2.5-6 minutes between plays: a ball forced through the tube,
264
+ swelling the wall with an arced, stretched skin, the bright core magnified through it as through
265
+ a fish-eye lens; it enters at the line's first point at exactly the tube's size, is as large as
266
+ fits for as long as possible, leaves at the last, and runs quicker downhill than up.
267
+ - **A chrome finish** (Display settings → Metallic finish): every face mirrors a horizon that
268
+ slides as the blocks move; satin is the softer highlight that was there before.
269
+ - **Depth**: a touch of perspective, off by default, so a cube's top grows a little wider than its
270
+ base and a flying block swells as it rises. Capped at 0.001 after larger values put flyers
271
+ through resting cubes.
272
+ - **Departures and arrivals**: how blocks leave and rejoin the board on a refresh, chosen by
273
+ measurement (flights clear their neighbours before fanning, leave the frame rather than popping
274
+ at its edge, and take a lane per leg); a recoloured cube blends to its new colour instead of
275
+ popping; and where a flyer is clearly above a resting cube it paints over it.
276
+ - **The block-flow cards are linked as a chain that reads as one** (operator: "looks bad on a black
277
+ background. Re-do it to be much more stylized and visible"): two pale outlined pills that vanished
278
+ on the dark panel are two interlocked links now, accent-coloured tubes with a highlight and a
279
+ glow, the left link's top strand painted again over the right so the pair weaves.
280
+
281
+ ### Security
282
+
283
+ - **The second AI security audit's findings, the same day** (`docs/SECURITY-AUDIT-2026-09-14.md`,
284
+ which re-verified the 09-13 fixes live and covered the address index and explorer). Its one
285
+ medium: `/api/x/address` sized an allocation by the request's page number, 525 MB for
286
+ `page=999999` on a two-row address -- a deep page now counts the history first and keeps at most
287
+ what exists. The rest: rows above the node's tip (a reorganised tail the follower has not yet
288
+ rolled back) are left out of the count, balance and page and reported as `index.postTip` with a
289
+ caveat on the page; a pool key is escaped like everything around it; a transaction summary with
290
+ more than 2,000 inputs and outputs is never cached; a test fixture no longer lives at a fixed
291
+ name in `/tmp`; `audit.jsonl` and `history.json` are created owner-only.
292
+ - **The first AI security audit** (`docs/SECURITY-AUDIT.md`, 2026-09-13) found one HIGH and four
293
+ more, all fixed the same day; they are the next four items.
294
+ - **The node-connection probe leaked the node's RPC credential** (HIGH, found by audit and
295
+ reproduced with a working exploit). `POST /api/config/node/test` built its throwaway client from
296
+ the live node's config, so `resolveCookie` read the real `.cookie` and sent it as an
297
+ `Authorization` header **to whatever URL the request named** -- on a request needing no session
298
+ and no CSRF token, reachable by a plain cross-site form. Credentials now go only to the endpoint
299
+ the monitor is already configured for; anywhere else is probed unauthenticated and says so. Open
300
+ mode additionally refuses any state-changing request whose `Origin` is not this server or whose
301
+ `Sec-Fetch-Site` says cross-site.
302
+ - **Session TTLs were inverted**: an 8-hour absolute lifetime with a 72-hour idle ceiling meant the
303
+ idle check could never fire. Now 72 h absolute, 8 h idle, with a test on the invariant.
304
+ - **`randomPassword()` drew with modulo bias**, over-representing the first 58 characters of its
305
+ 66-character alphabet. It uses `crypto.randomInt` now.
306
+ - **The audit trail redacts by key shape**, not by two hardcoded field names, so an action echoing a
307
+ key-shaped argument cannot write a secret into the one file designed to be kept.
308
+ - A re-audit is planned after the first install (`AGENTS.md`).
309
+
310
+ ### Scope, documentation and the name
311
+
312
+ - **A node on another machine, over RPC alone: tried, and dropped.** On 2026-09-13 the monitor was
313
+ pointed at a node appliance on the LAN with `rpcUser` / `rpcPassword` and no `datadir` -- a
314
+ configuration the validator had refused at boot until it was fixed -- and the monitor half
315
+ worked: chain, mempool, peers, blocks, the block being built. The explorer did not, and no
316
+ setting would make it: Core has no address index, `scantxoutset` holds the node's single RPC
317
+ thread for tens of seconds per query and knows no history, and a node answering over the
318
+ network in seconds left address pages waiting minutes. **Real-time explorer data over RPC was
319
+ a failed idea.** The address data is rebuilt from the block files and stored locally instead,
320
+ the way mempool.space's `electrs` does it (above), which is why BlockYard runs on the node's
321
+ machine. The install guide's appliance section is gone; `rpcUser` / `rpcPassword` remain for a
322
+ node that uses `rpcauth` instead of the cookie file, and the `bitcoin.conf` lines that measured
323
+ as worth having (`txindex`, `coinstatsindex`, `dbcache`) are kept, each annotated with what it
324
+ does for this monitor.
325
+ - **This is a Bitcoin Core-centric release.** The README, install, configuration, API and
326
+ architecture documents describe Core; the shipped defaults are Core's own (`id: main`,
327
+ `127.0.0.1:8332`, `~/.bitcoin`, `bitcoind.service`). Measurement records taken against a
328
+ non-Core build are anonymised rather than relabelled -- they describe what was measured, and
329
+ claiming otherwise would invent measurements that never happened.
330
+ - **Log parsing is documented as unavailable for Bitcoin Core.** The parsers target an experimental
331
+ node's log grammar; fed Core's `debug.log` they extract no figures and timestamp entries at read
332
+ time. The log source is off by default and should stay off against Core --
333
+ `test/log-core-unsupported.test.js` pins that so it cannot be assumed away.
334
+ - **The old name is gone from the tree** (operator: "WHY THE FUCK DO I STILL SEE THE OLD NAME
335
+ REFERENCES IN OUR TREE DOCS"): a `git grep` for the old prefix finds nothing but bytes in a JPEG.
336
+ The allowlist's vendor-prefixed read verbs -- an earlier node's, and that node is not supported --
337
+ are gone with their test, and a vendor-shaped method is denied by default; two scripts that
338
+ defaulted a CA path to a directory named after the old project take it from the environment or
339
+ nothing; `NOTICE` and `LICENSE` name BlockYard and its copyright holder.
340
+ - **Every document read against the code**: sessions are 72 h absolute / 8 h idle (two documents had
341
+ it inverted); display settings live on the server; the effects list is the thirty that exist; four
342
+ API routes that were undocumented are documented from their handlers; ARCHITECTURE gains the
343
+ address-index subsystem; three stale code comments and the mempool feed's stated cadence
344
+ (60 -> 20 s) corrected; screenshots re-shot against the local Core node. `docs/GETTING-STARTED.md`
345
+ walks a macOS or Linux command prompt through the install (brew's plain node formula, where
346
+ `bitcoin-cli` lives inside the macOS app bundle, one worker on spinning disks, the build's memory
347
+ per worker). `docs/DEFECTS.md` states the scope and closes the entries whose only subject was the
348
+ experimental node or a remote one. Eight one-off pixel probes whose questions are answered in
349
+ MEASUREMENTS and DEFECTS are removed; the checks still used stay.
350
+ - **Donations are in two places only**: the foot of the README, in small type, and the About page,
351
+ where the address is a pill that copies on a click (verified in a headless browser by reading the
352
+ clipboard back) beside a QR of it (`scripts/donate-qr.py` generates the inline SVG and checks it
353
+ decodes; the rendered page's screenshot decodes to the address too). They are out of the installer.
354
+ - **0.0.9 is the initial release number**, the operator's. The two earlier CHANGELOG sections that
355
+ carried version numbers were never tagged or released and are kept as dated milestones;
356
+ `server/main.js` reads the version from `package.json`, and `test/version.test.js` ties the
357
+ CHANGELOG's newest release and the README to it.
358
+
359
+ ### Since the 2026-09-11 milestone: added
360
+
361
+ - **A node connection form**, on **Node & RPC** (operator: "Still left to do is a config connection
362
+ in the web settings. We have no way for users to configure a connection to their rpc backend").
363
+ Enter an RPC URL, a data directory, a chain and a label; **test connection** probes it with a
364
+ throwaway client that never touches the live node's request lane, and **save** is disabled until
365
+ a test has actually answered — and goes back to disabled the moment a field changes. No password
366
+ field: authentication is the node's own `.cookie`, found from the data directory.
367
+ - The save is honest about two things it would otherwise hide. It **keeps the fields the form does
368
+ not show** — `logFile`, `systemdUnit`, the colour — because the config merge replaces arrays
369
+ whole, so a naive write would quietly unconfigure the log tail. And where the environment sets
370
+ `BLOCKYARD_NODE_URL` (a systemd drop-in, say), it says the environment beats the file rather than
371
+ reporting a success the next restart contradicts.
372
+ - **The mempool page carries two panels it was already collecting data for.** `renderMempool` has
373
+ been writing ingest/reject figures and orphan-pool figures into elements that did not exist —
374
+ collected from the node's log, sent to the browser and dropped. They have cards now, and on a
375
+ monitor running without a log tail they say so rather than showing a column of dashes. The page
376
+ also lost four dead grid columns, and Pool usage gained the pool's total vsize, average vsize and
377
+ total fees, all of which were computed on every sample and never drawn.
378
+ - **The grid is yours, per board** (operator: "we need to break out the green grid settings per
379
+ game. We should also add a grid color picker, and a transparency slider ... I really want to turn
380
+ down the intensity on blockanoid", and "add a color selector and brightness setting for the grid
381
+ lighting for blockspace"). Block space, Tetrust, Blockout and Blockanoid each get a **grid
382
+ colour** and a **grid intensity**, and the three games get a **grid** switch as well; Block space
383
+ already had one. Every board is independent, so a court can be turned right down while the board
384
+ stays bright. Until now the colour was hardcoded green in four separate files and could not be
385
+ changed at all.
386
+ - One colour drives the whole grid rather than a single value. The board does not draw its grid in
387
+ one colour: it lays an opaque core under a translucent halo and glow with a brighter line along
388
+ the edge, and that relationship is deliberate — a see-through core reads dimmer wherever the
389
+ floor beneath it is shadowed, and composite modes are off the table. The picker recolours the
390
+ family and keeps each layer's relative weight; intensity multiplies them together.
391
+
392
+ - **Tetrust**: a playable Tetris on the 3D engine (`public/js/tetris.js` for the rules,
393
+ `public/js/tetrust.js` for the screen). The well is the block-space board with its oblique
394
+ camera and curved surface; a neon-blue wireframe marks where the piece will land; cleared
395
+ lines fly up off the top of the canvas. Seven-bag piece order, wall kicks, the classic score
396
+ table times the level, ten lines a level, and a top-ten high score table kept per browser.
397
+ Pauses when the tab or the page loses focus. Arrows or WASD.
398
+ - **Music and sound effects for Tetrust**, synthesised in the browser with the Web Audio API —
399
+ no audio files and no dependencies. Korobeiniki on a lookahead scheduler running on the audio
400
+ clock, and nine shaped tones for move, rotate, soft drop, hard drop, lock, line clear, tetris,
401
+ level up and game over. A switch for each.
402
+ - **Blockout**: Breakout on the 3D engine, in its own tab beside Tetrust (`public/js/breakout.js`
403
+ for the rules, `public/js/blockout.js` for the screen). The bat follows the mouse — or the arrow
404
+ keys — the wall is six rows of block-space stones one grid cell each, and where the ball lands on
405
+ the bat decides where it goes. Three balls, a faster wall each level, and a per-browser high score
406
+ table. The rules file has no DOM, no clock and no randomness (a launch angle is an argument), so
407
+ the whole of it runs under the test suite, and the ball is sub-stepped so it cannot tunnel through
408
+ a brick on a slow frame.
409
+ - **Every timed power expires after 30 seconds** — laser, wide, catch and slow, each counted down on
410
+ the heads-up display. Three balls and the extra life are one-shot and have nothing to run out. One
411
+ timer table and one expiry loop rather than four hand-written countdowns, for the same reason
412
+ `loseLife()` exists: separate copies of a rule drift apart. Slow puts the pace back when it lapses
413
+ (or "slow" would be permanent by omission) and wide restores the bat about its own centre,
414
+ re-gripping a held ball into the narrower span.
415
+ - **A caught ball is locked to the bat.** It recorded no grip, so a stuck ball held its absolute
416
+ position while the bat slid underneath and moved only when an edge caught up with it — which read
417
+ as the ball drifting around. It remembers where along the bat it landed and is placed from that.
418
+ - **The ball bounces off a minion** instead of passing through. The collision forced the ball
419
+ downward whatever direction it had arrived from, so dropping onto a minion pushed it further down.
420
+ It reflects on the axis of least penetration now, exactly as a brick does, which gives the side
421
+ bounces as well.
422
+ - **Catch expires after 30 seconds.** Held indefinitely it stopped being a power-up and became a
423
+ different game: park the ball, aim every shot, and the rally ceases to exist. The countdown runs
424
+ off `step`'s own elapsed milliseconds, like the laser cooldown and the minion timer, so the rules
425
+ still carry no clock and a run stays reproducible. A ball still held when it lapses is released at
426
+ the angle its position on the bat implies, rather than stranded there with nothing to explain it.
427
+ - **Minions, pill capsules and a bat that morphs.** The engine gained a rotated-polygon tile kind
428
+ (`poly` + `rot`, with optional `eyes`), built the way the sphere is — nested filled polygons,
429
+ since the op format has no arcs and gradients are forbidden — and claiming its own face name so
430
+ the sphere's "nothing but ball ops" guarantee is untouched. On it: four **minion** types with
431
+ their own silhouettes, spin rates and drift behaviours (a swinging cone, a tumbling cube, a
432
+ wobbling orb, a zig-zagging molecule), each with eyes; **capsules** are now elongated pills that
433
+ tumble as they fall, the angle taken from their own height rather than a clock so a frame stays
434
+ reproducible; and **Vaus grows cannons** and a raised housing while the laser is up, so the bat
435
+ shows what it can do rather than only changing colour. The capsules keep their per-capsule band
436
+ mark, so the seven remain separable without colour even though they now share one silhouette.
437
+ - **An About page**, reached by clicking the BlockYard monogram in the header rather than by a nav
438
+ tab of its own — the bar is already full enough to wrap below 2000 px. It shows the version and
439
+ live build, the host's operating system, architecture, processors, memory and runtime, and the
440
+ Bitcoin node's own version and protocol, over the spiral galaxy the other boards draw. New
441
+ `GET /api/about` supplies the host facts and deliberately reports no hostname, username, network
442
+ address or environment: the monitor is open-access by default, so the OS and processor describe
443
+ the machine's shape and never its owner.
444
+ - **A stylised BY monogram and a real gear.** The brand mark was the letter `B` in a tile; it is now
445
+ a drawn monogram whose tile, gradient and courses of blocks live in the SVG, so the favicon is the
446
+ same drawing. The settings button was a circle with eight radiating rays — the standard sun glyph,
447
+ which is why it read as a light/dark toggle — and is now a cog with teeth and a punched bore.
448
+ - **The Kiosk's price panel becomes Price & order book depth.** The 24 h high, low, volume,
449
+ spread across books and the per-exchange table are gone; in their place is the depth chart,
450
+ compact and toolbar-less, fixed at ±2.5% around the mid. A wall display is read from across a
451
+ room, where a four-column table is unreadable and the shape of the book says more than a spread
452
+ figure — all of it is still on the Markets tab. The chart shares the Markets tab's single poll,
453
+ so having both open does not double the traffic to five exchanges, and the depth endpoint marks
454
+ the collector as watched, so an unattended kiosk keeps its books fresh by itself.
455
+ - **Blockanoid**: Arkanoid on the 3D engine, the third Diversion (`public/js/arkanoid.js` for the
456
+ rules, `public/js/blockanoid.js` for the screen). Six hand-built walls that cycle; **silver**
457
+ bricks that take two hits and one more every four levels, standing lower once damaged; **gold**
458
+ that never breaks and never blocks a level, since a wall is cleared when its *breakable* bricks
459
+ are gone. Six **capsules** fall out of broken bricks — laser, enlarge, catch, slow, disrupt
460
+ (three balls) and player (a life) — one on the court at a time, as the
461
+ arcade did it. Vaus turns red while the laser is up, so the bat says what it can do. Minions
462
+ drift down the court and pay when destroyed. Which brick carries a capsule is a **hash of the
463
+ brick and the level, never `Math.random`**, so a wall always drops the same letters and the
464
+ whole thing is assertable under `node:test`. Capsules and minions each have a switch, and
465
+ because they change the rules rather than the look, flipping one reaches the game in play.
466
+ - **Seventeen new idle effects**, bringing the total then to **26** (30 at release; above), each with its own switch:
467
+ shockwave, nova, fireworks, solar flare, wave, quake, code rain, sparkle, checkerboard, radar,
468
+ vortex, laser, power-up, combo chain, aurora, plasma and glitch. All are pure functions of the
469
+ tile and the effect's clock, so each replays identically and is covered by tests rather than
470
+ by watching.
471
+ - **Block finishes**: **neon blocks** (a dim solid body in the block's fee-rate colour under lit
472
+ tubes on every visible edge) and a **metallic sheen** (a specular highlight on the lit edge of
473
+ each top face, a dark roll-off on the far one). Both work at every level of detail. The neon
474
+ tubes can take the block's own colour or one colour of your choosing, at a brightness you set.
475
+ - **A movable lamp**: `Light` chooses straight above (now the default for Block space), upper
476
+ left, upper right, or from the viewer.
477
+ - **A thickness slider for Tetrust's landing marker**, 0.3 to 2.5 times the shipped weight, so the
478
+ outline can be thinned out of the way of the stack behind it.
479
+ - Broken bricks in Blockout **fly up off the court** instead of vanishing, the same launch Tetrust's
480
+ cleared lines take.
481
+ - **`txindex=1` is documented as required** for the explorer's transaction pages — a transaction page
482
+ asks for `getrawtransaction <txid> 2` with no block hash, which a node without the index can only
483
+ answer for its mempool. Block pages pass the hash and are unaffected. Install, README and
484
+ troubleshooting all say so now.
485
+ - **The Tetrust landing marker's colour is a setting.** The wireframe showing where the falling
486
+ piece will land is drawn instead of a block rather than over one, so the neon finish never
487
+ applied to it and it stayed the shipped blue whatever else was changed; it has its own colour
488
+ now, and `tiles()` takes it as an argument so the rules file still knows nothing of the store.
489
+ - **A tabbed Display settings panel**, with all-on / all-off on the Effects tab, whose
490
+ twenty-six switches -- thirty now -- are a lot of clicking otherwise.
491
+ - **Markets remembers its toolbar**: the exchange and the range are settings now, so the page
492
+ opens where you left it.
493
+
494
+ ### Since the 2026-09-11 milestone: changed
495
+
496
+ - **The block being built is assembled here now, and costs your node nothing.** It used to be a
497
+ `getblocktemplate` call worth 1.3-1.5 s of the node's single RPC thread and 1.79 MB per reply,
498
+ fetched on demand so a page nobody had open did not pay it every minute. Bitcoin Core publishes
499
+ everything the selection needs in the `getrawmempool(true)` reply this monitor **already reads
500
+ every 20 s** for the mempool view: `depends`, the ancestor sizes and fees, and
501
+ `fees.chunk`/`chunkweight` -- Core's own cluster-mempool linearization, which is the order its
502
+ miner sorts by. `server/collect/gbt.js` selects greedily over that, taking each transaction with
503
+ its unselected ancestors, and returns the result in the shape a `getblocktemplate` reply has, so
504
+ the summary, the histogram, the package analysis and the block economy read it unchanged.
505
+
506
+ Measured against the node's own template on a back-to-back pair at height 966821, so the two
507
+ describe the same pool: **6,546 transactions / 3,995,859 weight / 643,076 sat** against the
508
+ node's **6,535 / 3,991,951 / 642,860** -- 0.03% apart on fees, with the set difference confined
509
+ to the 0.30 sat/vB margin where ties are arbitrary. Assembly takes ~50-70 ms of this process's
510
+ CPU. It is a reconstruction of what a miner would choose, not the node's answer: sigop limits
511
+ and policy the mempool does not publish are not modelled, and the card says so.
512
+
513
+ The old measurement in `docs/MEASUREMENTS.md` -- that `getrawmempool` verbose carries no
514
+ `depends` -- was true of the experimental node it was taken on, and is kept there with the
515
+ correction appended rather than rewritten.
516
+ - **The default web port is 21000** (was 8088).
517
+ - **Display settings are stored on the server** in `config/blockyard.json`, so a phone and a
518
+ desktop pointed at the same monitor agree. The browser keeps a cache so boards still draw when
519
+ the server cannot be reached.
520
+ - **Block space ships with simple cubes and shadows off.** Shadows are the costliest single thing
521
+ the board draws, and the board is the first thing most people open; both remain one click away in
522
+ Display settings.
523
+ - The two games sit at the end of the nav under a **Diversions** pop-down, rather than among the
524
+ working tabs.
525
+ - The Markets energy pulse now runs along the neon price line itself, leaving an electric-blue
526
+ tail that fades back to yellow behind a bright head, with a nebula of blue smoke emitted along
527
+ the whole charged span and a shimmer over it. The lightning ball trails the same charge across
528
+ the block-space board; the light cycles do not.
529
+ - The pulse's nebula is emitted over the whole charged span rather than per segment — emitting per
530
+ segment gave neighbouring puffs the same age, so they shared a radius and lined up into the
531
+ concentric rings they were meant to replace. (Its motes and crackle branches were removed at the
532
+ same time and restored afterwards; they are present.)
533
+ - The Simple viewer packs the block exactly: the block's own area is solved so the tiles fill
534
+ the grid flush, and the remainder is tiled to the edge instead of leaving a partial top row.
535
+ - Pool attribution moved out of the block card's body into a readable pill beneath it.
536
+
537
+ ### Since the 2026-09-11 milestone: fixed
538
+
539
+ - **Coinbase attribution stopped permanently after one failed block.** `pumpMining` cleared the
540
+ whole queue on a single failure and nothing ever re-queued it, so one slow moment discarded the
541
+ entire 36-block boot window and the Mining page sat empty. The failed height is put back, the
542
+ rest of the queue survives, and a backoff decides when to retry.
543
+ - **The block template no longer monopolises the RPC lane.** `getblocktemplate` went through as an
544
+ ordinary call with a 12-second freshness budget; on a node where it takes seconds, everything
545
+ queued behind it was stale-dropped and `/api/nextblock` took 75 s. It is now heavy, keyed and
546
+ given a realistic budget -- measured 52.8 s to 4.2 s on the same node.
547
+ - **The display-settings sliders no longer jitter while dragging** (operator: "the grid intensity
548
+ slider jitters when I move it"). Every `input` event ran a full synchronous re-render; a drag
549
+ across the grid intensity control queued forty of them, each repainting a board. The value and
550
+ the readout still update on every event — only the repaint is coalesced, to one per animation
551
+ frame. All twelve range controls were affected; the new one merely made it visible.
552
+ - **The Diversions menu renders correctly in Safari** (operator: "rendering on safari is still
553
+ broken. It's only showing half the drop-down contents"). The panel was inside `header.top`, which
554
+ is `overflow: hidden` and 46px tall, and WebKit clipped the fixed panel to it. It is a top-level
555
+ element now, like the settings dialog, which is the fixed overlay that always rendered correctly.
556
+ Its position is measured and set rather than pulled back by a transform.
557
+ - **Display-settings sliders jumped as their value changed**: the readout's width changed with its
558
+ digits and pushed the slider about. The value is printed to the step's decimals in a fixed-width
559
+ box.
560
+ - **The header's uptime blanked every second** and **the node you pick stays picked**.
561
+ - **The travelling cube's perspective froze in flight** (a regression of our own, recorded).
562
+ - **The pulse-gap simulation was flaky**: seeded now, its bound the real worst case.
563
+ - The block-being-built card named a call it does not make; lightning stopped whiskering; the
564
+ paint-order comments described a camera the viewer no longer has.
565
+ - **The star field never animated on a board that asked for no tile choreography.** `still` is
566
+ about the tiles; it was also returning before the animation loop started, so Tetrust's galaxy
567
+ repainted only when the page happened to redraw — measured at zero repaints in three seconds.
568
+ The loop now parks only when there is genuinely nothing moving. Measured after: 87 repaints in
569
+ three seconds, idle and in play.
570
+ - **Blocks swapped in front of each other during refreshes.** Where cubes overlap, the paint
571
+ order is solved as a graph; a cube flying past could pull a settled pair into a tangle and the
572
+ tangle was ordered by depth alone, discarding the pair's own decision. A tangle now keeps the
573
+ relative order it had in the previous frame. Replayed over a 634-frame transition: 19 flickers
574
+ to none.
575
+ - **Neon and the metallic sheen did nothing when switched on** — `render3d` never passed either
576
+ option through to the scene builder.
577
+ - The galaxy is much cheaper to draw: its gas is painted once into an offscreen bitmap and drawn
578
+ turned, and the stars are batched by colour and brightness instead of setting a fill style per
579
+ star.
580
+ - Blocks with no pool attribution showed no statistics at all.
581
+ - Taller cubes no longer clip the neighbour they lean over on a settled board.
582
+ - **The Explorer's Latest blocks cubes were drawn with faces that did not meet.** The top face and
583
+ the right face were each inset five pixels on two sides, so neither reached the top-right corner:
584
+ every block carried a dark triangular wedge there, a sliver of bare card at the top left and a
585
+ gap at the bottom right. The faces are now flush with the card and with each other.
586
+ - **The network hash rate read "0.0 EH/s".** Two bugs, one hiding the other. The estimator divided
587
+ difficulty by the average block gap and left out the 2^32 hashes a difficulty-1 target expects,
588
+ so it was out by a factor of 4.29 billion; and the formatter's unit prefixes were each one step
589
+ too low, so a four-digit EH/s figure would have printed as a single-digit one. Checked against
590
+ the node's own `getnetworkhashps`, which the monitor had never used: the corrected estimate is
591
+ 1111.8 EH/s against the node's 1097.9, agreeing to 1.26%. The test covering it asserted that the
592
+ wrong magnitude was "of the right order", which is why it survived; it now checks the figure
593
+ could be true rather than restating the implementation.
594
+
595
+ ## Milestone 2026-09-11 (labelled 0.9.0 internally; never tagged or released)
596
+
597
+ Licensed Apache-2.0 from here on. The block-space packer and feerate palette
598
+ are an original implementation (`public/js/blockpack.js`, `public/js/feepalette.js`).
599
+
600
+ ### Monitor
601
+
602
+ - Live dashboard for one or more Bitcoin nodes over JSON-RPC, with an optional
603
+ log source: Overview, Chain & Sync, Mempool, Peers, Network, Mining, Events, Node & RPC and
604
+ Admin tabs, updated once a second over Server-Sent Events.
605
+ - Sync viewer whose bar is blocks held over announced headers, with the node's own progress
606
+ shown separately and an ETA computed only from a measured rate window.
607
+ - Block flow: projected blocks beyond the one being assembled (fee range, median, fees,
608
+ transaction count, time estimate), the block being built with its age ring, and recent
609
+ blocks linked as a chain.
610
+ - Peer table from `getpeerinfo`: direction, transport, services, height at connect, bytes and
611
+ rates per connection.
612
+ - A provenance table naming the source of every figure, and explicit "not reported" markers
613
+ instead of zeros.
614
+
615
+ ### Block space viewer
616
+
617
+ - A 3D board of the next block's worth of the mempool: square tiles sized by vbytes and
618
+ coloured by feerate, on a curved, neon-gridded board.
619
+ - 128 feerate colours from under 0.1 to 2,000 sat/vB, sky blue through green, yellow, orange
620
+ and red to purple, with neighbouring bands stepped in tone so they read apart.
621
+ - Choreographed refreshes: blocks lift, travel in collision-free lanes and land under gravity
622
+ with bounces; cube-on-cube shadows; a refresh countdown and a "refresh now" button.
623
+ - Idle effects at rest: ripples, scans, tides, cascades, twinkles, TRON light cycles and a
624
+ lightning ball that runs along the grid lighting the cubes it passes.
625
+ - Viewer modes: **Simple** (the richest few hundred transactions as cubes, the rest as
626
+ equal pieces coloured by their feerate) and
627
+ **Detailed** (every transaction in the next block, one square each on a 96-unit
628
+ grid, drawn as low slabs, each square area-true so a full block fills the board),
629
+ remembered per browser.
630
+
631
+ ### Explorer
632
+
633
+ - Search by block height, block hash, transaction id or address; every page is a shareable
634
+ link (`#explorer/…`).
635
+ - Transaction pages: status, fee and fee rate with dollar values, feature badges (SegWit,
636
+ Taproot, RBF, consolidation, OP_RETURN, coinbase), a flow diagram from inputs to outputs,
637
+ inputs and outputs with links to the spent and spending transactions, copy buttons.
638
+ - Block pages with statistics and paged transactions; address pages with balance, totals and
639
+ history (with the node's address index); the latest blocks as fee-coloured cubes.
640
+ - Links into the explorer from the rest of the app.
641
+
642
+ ### Markets
643
+
644
+ - Prices from Coinbase, Kraken, Bitstamp, Bitfinex and OKX, fetched by the server only while
645
+ someone has the Markets or Kiosk tab open.
646
+ - A 3D candle chart on a low side-on camera with a neon close line, volume band, labelled
647
+ price levels, a star field and light from the front right.
648
+ - A flat candlestick chart with a crosshair readout and other exchanges overlaid; an exchange
649
+ table; 24 h / 48 h / 7 d ranges.
650
+ - An order-book depth chart: cumulative bids and asks per exchange and in total, the total as
651
+ it stood 1–60 minutes ago, and change bars on a symmetric-log axis.
652
+
653
+ ### Kiosk
654
+
655
+ - The 3D markets board, a price panel and the block-space board side by side, with a
656
+ full-screen button.
657
+
658
+ ### Display settings
659
+
660
+ - A gear in the header opens a settings panel: shadows, idle effects, stone edges, the neon grid,
661
+ a star field, level of detail (full / simple cubes / flat tiles), refresh animation
662
+ (full / quick / none) and board curve for the block-space board; star field, density, brightness
663
+ and grid glow for the markets board. Kept in the browser, applied without a reload, and each one
664
+ changes what is drawn rather than only what is stored.
665
+
666
+ ### Security
667
+
668
+ - Open, read-only access by default; optional accounts with scrypt hashing, hashed sessions,
669
+ CSRF protection, lockouts and a rotated audit trail.
670
+ - Default-deny RPC allowlist; node writes off unless explicitly enabled per action.
671
+ - Built-in HTTPS, multi-address binding, a CIDR gate, and a strict Content Security Policy
672
+ with no inline styles and no third-party assets.
673
+
674
+ ## Milestone 2026-09-08 (labelled 0.1.0 internally; never released)
675
+
676
+ Internal first version: multi-user monitor with charts, sync viewer, mempool view, peers and
677
+ event feed.
678
+
679
+ [0.0.9]: https://github.com/BobClawblaw/blockyard/releases/tag/v0.0.9