blockyard 0.1.0 → 0.1.1

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 (71) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +13 -11
  3. package/SECURITY.md +2 -2
  4. package/docs/API.md +1 -1
  5. package/docs/ARCHITECTURE.md +36 -5
  6. package/docs/CONFIGURATION.md +6 -4
  7. package/docs/DEFECTS.md +4 -1
  8. package/docs/GETTING-STARTED.md +14 -7
  9. package/docs/INSTALL.md +7 -4
  10. package/docs/PLAN-SCORCHED-YARD.md +456 -0
  11. package/docs/PLAN-SKIES.md +142 -0
  12. package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
  13. package/docs/SECURITY.md +26 -7
  14. package/docs/TROUBLESHOOTING.md +10 -5
  15. package/docs/USER-GUIDE.md +239 -9
  16. package/package.json +4 -2
  17. package/public/css/app.css +87 -0
  18. package/public/index.html +58 -6
  19. package/public/js/app.js +60 -19
  20. package/public/js/blockanoid.js +15 -7
  21. package/public/js/blockout.js +15 -7
  22. package/public/js/blockscene3d.js +51 -11
  23. package/public/js/depthchart.js +1 -1
  24. package/public/js/details3d.js +25 -2
  25. package/public/js/explorer.js +7 -1
  26. package/public/js/livingsky.js +494 -0
  27. package/public/js/login.js +3 -2
  28. package/public/js/mining.js +4 -4
  29. package/public/js/panels.js +27 -18
  30. package/public/js/safenext.js +14 -0
  31. package/public/js/scorched.js +1051 -0
  32. package/public/js/scorchedai.js +227 -0
  33. package/public/js/scorchedair.js +286 -0
  34. package/public/js/scorchedfx.js +376 -0
  35. package/public/js/scorchedshop.js +105 -0
  36. package/public/js/scorchedwind.js +69 -0
  37. package/public/js/scorchedyard.js +1338 -0
  38. package/public/js/settings.js +266 -80
  39. package/public/js/tetrust.js +15 -6
  40. package/public/js/tetsound.js +35 -5
  41. package/scripts/check.js +46 -0
  42. package/scripts/index-build.js +9 -2
  43. package/scripts/pool-map.js +152 -36
  44. package/scripts/setup.js +108 -10
  45. package/scripts/shots.mjs +21 -0
  46. package/scripts/smoke.sh +6 -5
  47. package/scripts/ui.js +4 -2
  48. package/server/auth/sessions.js +33 -13
  49. package/server/chain/blockfile.js +64 -5
  50. package/server/chain/index/build.js +432 -56
  51. package/server/chain/index/heights.js +29 -3
  52. package/server/chain/index/live.js +13 -7
  53. package/server/chain/index/rows.js +6 -1
  54. package/server/chain/index/store.js +28 -5
  55. package/server/chain/index/worker.js +23 -11
  56. package/server/collect/logparse.js +65 -18
  57. package/server/collect/markets.js +76 -7
  58. package/server/collect/mining.js +32 -0
  59. package/server/collect/monitor.js +24 -11
  60. package/server/collect/network.js +19 -9
  61. package/server/config.js +7 -0
  62. package/server/http/api.js +70 -13
  63. package/server/http/server.js +22 -5
  64. package/server/http/sse.js +53 -7
  65. package/server/main.js +13 -3
  66. package/server/rpc/allowlist.js +26 -0
  67. package/server/rpc/client.js +30 -2
  68. package/server/store/audit.js +6 -1
  69. package/server/store/history.js +19 -3
  70. package/server/store/ledger.js +15 -4
  71. package/systemd/blockyard.service +34 -3
package/docs/SECURITY.md CHANGED
@@ -23,8 +23,9 @@ someone using the monitor to make the node do something (prevented: writes are o
23
23
  console is read-only behind a default-deny allowlist), someone reading your node's state who
24
24
  should not (controlled by where it listens, a CIDR gate, and optional accounts), and the
25
25
  monitor leaking information about you to third parties (limited to the on-demand market data
26
- connections listed below, which you can turn off). The monitor does not hold keys and has no
27
- wallet access.
26
+ connections listed below, which you can turn off). The monitor does not hold keys, and every
27
+ wallet RPC is refused by name (since 2026-09-16; before that, wallet reads such as
28
+ `listdescriptors` passed the allowlist's `list` prefix, see the audit of that date).
28
29
 
29
30
  ## Access: sign-in by default, open on request
30
31
 
@@ -46,6 +47,7 @@ Open access is still available as a posture you choose: with `auth.enabled: fals
46
47
  | peer, mempool, block and transaction detail, the explorer | the audit trail |
47
48
  | the read-only RPC console (behind the allowlist) | password changes and sessions |
48
49
  | the live Server-Sent Events stream | **every node write**, even if actions are enabled |
50
+ | Display settings (they are normalised before use) | the node connection form, except from this machine's loopback address (`auth.openNodeConfigFromNetwork` widens it) |
49
51
 
50
52
  The `viewer` ceiling cannot be raised by configuration or by any credential while accounts
51
53
  are off. The start-up log states which addresses are readable and how to close them, so
@@ -78,19 +80,30 @@ The last enabled admin cannot be demoted, disabled or deleted.
78
80
  routes had no cross-site protection at all. An audit proved it with a working exploit against
79
81
  the node-connection test. Open mode now refuses any state-changing request whose `Origin` is
80
82
  not this server, or whose `Sec-Fetch-Site` says cross-site. A client that sends neither header
81
- (curl, a script) is unaffected: it can already reach the port, and this guards against what a
82
- *browser* can be made to do on someone's behalf.
83
+ (curl, a script) is not stopped by that check, which only guards against what a *browser* can be
84
+ made to do. So the one form where a script could do real harm, the node connection (a saved
85
+ address decides where the node's cookie goes after a restart, and its test makes the server
86
+ connect somewhere), answers only a loopback caller while accounts are off (audit 2026-09-16).
87
+ A save that moves the node to a different host drops the old endpoint's `rpcUser`,
88
+ `rpcPassword` and `cookieFile`, and a test of a foreign endpoint reports the kind of failure,
89
+ never what the endpoint answered.
83
90
  - **Brute force**: sign-in is locked after 8 failures in 5 minutes per username and per
84
91
  address, for 10 minutes, with the same error and the same hashing time for unknown users
85
92
  and wrong passwords. A separate throttle limits sign-in attempts per address, because each
86
93
  attempt costs a deliberately expensive hash.
87
94
  - **Rate limits** apply per client address.
95
+ - **Slow and stalled clients** cannot hold memory or connections: a request, body included, must
96
+ arrive within 30 seconds; an event stream whose reader stops reading is sent nothing more until
97
+ it drains, and is dropped when 4 MB is buffered or it stays blocked for a minute; one address
98
+ (or account) holds at most 16 streams. Free-form audit fields are clamped to 1,024 characters,
99
+ so a caller cannot rotate real events out of the trail.
88
100
 
89
101
  ## Talking to the node
90
102
 
91
103
  - **Read-only allowlist, default deny.** The RPC console and every internal call go through
92
- an allowlist: read-shaped methods are allowed; wallet, key-material, spending, peer-control,
93
- chain-mutating and very heavy methods are refused by name — including `getnewaddress` and
104
+ an allowlist: read-shaped methods are allowed; every wallet method (reads included, since some
105
+ return private keys), spending, peer-control, chain-mutating and very heavy methods are refused
106
+ by name — including `getnewaddress` and
94
107
  `getrawchangeaddress`, which start with "get" but create keys. Unknown methods are refused.
95
108
  - **One request at a time.** The node's RPC server is single-threaded, so the monitor runs a
96
109
  single serialized request lane with a minimum spacing, batching, priorities and a stale-drop
@@ -202,7 +215,13 @@ and errors; they never contain passwords, session tokens or RPC credentials.
202
215
  ## Hardening checklist
203
216
 
204
217
  - Run as a dedicated, unprivileged account that can read only the node's cookie and its
205
- `blocks/` directory (and log), and write only its own `data/` and the index directory.
218
+ `blocks/` directory (and log), and write only its own `data/` and the index directory. The
219
+ shipped systemd unit enforces the write half (`ProtectSystem=strict` with `ReadWritePaths=` for
220
+ `data/`, `config/` and the index) and drops capabilities, system calls and address families the
221
+ monitor does not use; add your index directory to it.
222
+ - Point `addressIndex` (or `--out`) at a new or empty directory. The build refuses a symlink, the
223
+ filesystem root, a home or working directory, the node's blocks directory, and any directory
224
+ holding files an index does not write; it only ever removes its own files.
206
225
  - Bind the narrowest set of addresses that serves your users; add a firewall rule if needed.
207
226
  - Turn accounts on if anyone who can reach the port should not see your node.
208
227
  - Use HTTPS, a reverse proxy, or an SSH tunnel on untrusted networks.
@@ -168,17 +168,22 @@ so far and an ETA; the address page repeats it. Things it says, and what they me
168
168
  2026-09-15 that fails the build at once with `an index worker exited with code N while on
169
169
  {"type":"scan","file":...}` and the flag turns to *build failed*; before that fix the build
170
170
  hung there for good. Either way: restart BlockYard with fewer workers (`addressIndexWorkers` on
171
- the node entry in `config/local.json`). The build starts over; it is not resumable. The flag
171
+ the node entry in `config/local.json`). The build resumes with the files it had finished. The flag
172
172
  also says **no progress for N min** whenever nothing has moved for two minutes, so a stall is
173
173
  visible as one rather than as a stale ETA.
174
174
  - **Hours, not minutes** — expect **a few hours**: 29 min 45 s is 16 workers on NVMe, and four
175
175
  workers (the installer's default) are roughly four times slower; **spinning disks** are slower still whatever the
176
176
  number, and there one worker is the fast setting, because parallel readers only seek against
177
177
  each other and against the node. Set `addressIndexWorkers` on the node entry in
178
- `config/local.json` (the installer writes the number you gave it) and restart: there is no
179
- resume, so the build starts over from the first file.
180
- - **It started over** — stopping BlockYard stops the build, and the next start begins it again
181
- from scratch. Leave it running until the notification says it is done.
178
+ `config/local.json` (the installer writes the number you gave it) and restart: the build resumes with the files already scanned, and only the rest are read with the
179
+ new number of workers.
180
+ - **It started over** — a stopped build normally resumes: the log says `address index build:
181
+ resuming the interrupted build in <dir> at block N` with how much was already done. When it
182
+ says `discarding the interrupted build … and starting over` instead, the reason follows: the
183
+ node's chain reorganised below the block the build was working to, the node is behind it (a
184
+ reindex, or another node), the index format changed with an update, or the journal or a bucket
185
+ file was damaged. Unfinished work that cannot be proven whole is never used, so the build begins
186
+ again from the first file.
182
187
  - **the address index build failed** — the reason is in the events feed and the log; fix it and
183
188
  restart (the server builds again), or run `node scripts/index-build.js --out <dir>` by hand.
184
189
  A pruned node, unreadable block files and a full disk are the usual causes.
@@ -20,6 +20,7 @@ are node actions, which are off unless an operator explicitly enables them (see
20
20
  - [Tetrust](#tetrust)
21
21
  - [Blockout](#blockout)
22
22
  - [Blockanoid](#blockanoid)
23
+ - [Scorched Yard](#scorched-yard)
23
24
  - [Wolfenstein 3D](#wolfenstein-3d)
24
25
  - [DOOM](#doom)
25
26
  - [Quake](#quake)
@@ -408,8 +409,8 @@ page reads **not indexed** and says so in a note with the phase, the progress, t
408
409
  and the time left; the Overview's "what this panel cannot tell you" box shows the same line, and
409
410
  adds `paused while the node's RPC is slow` whenever the build is holding back so the node keeps
410
411
  answering. A notification appears in every open tab when the build starts, when it finishes and
411
- if it fails; on finish the page fills in with no restart. The build does not resume after the
412
- server is stopped: the next start begins it again.
412
+ if it fails; on finish the page fills in with no restart. A build stopped with the server
413
+ resumes on the next start from the files it had finished, and its progress picks up from there.
413
414
 
414
415
  **Without an index configured**, the address and its type are still confirmed
415
416
  (`validateaddress` needs none), and balance, totals and history read **not indexed**. It does
@@ -717,6 +718,195 @@ rather than the look, flipping them reaches the game you are playing, not just t
717
718
 
718
719
  ---
719
720
 
721
+ ## Scorched Yard
722
+
723
+ Scorched Earth, the 1991 DOS artillery game, on the block engine: a landscape of dirt cubes that
724
+ craters and falls, tanks that aim by angle and power, wind, and a round that ends when one tank is
725
+ left. Under **Diversions**, after Blockanoid. You play against two computer opponents out of the
726
+ box (**Display settings → Scorched Yard → Computer players** fields up to seven, for the original's
727
+ eight seats; every tank has its own colour).
728
+
729
+ The rules are the original's (docs/PLAN-SCORCHED-YARD.md is the whole plan): the manual's roster
730
+ of thirty-three weapons and eleven accessories at the manual's prices, packs and blast radii,
731
+ craters, falling dirt, fall damage, death and its blast, the walls, wind, cash for damage and
732
+ kills, a shop between rounds, and a game of rounds. The manual's eight computer
733
+ personalities take the other seats. The fabulous part is on its way.
734
+
735
+ ### Playing
736
+
737
+ | Keys | |
738
+ |---|---|
739
+ | **←** **→** or **A** **D** | the barrel's angle, a degree at a time (Shift: five) |
740
+ | **↑** **↓** or **W** **S** | power, ten at a time (Shift: one, Ctrl: a hundred) |
741
+ | **[** **]** or **PgUp** **PgDn** | the weapon, through what you own |
742
+ | **space** or **Enter** | fire |
743
+ | the mouse | drag on the field to aim: the direction from your tank is the angle, the distance the power |
744
+ | **A** **D** | drive a cell left or right, a unit of fuel each |
745
+ | **B** | a battery: 30 health back |
746
+ | **S** | raise a shield (the best you own) |
747
+ | **T** **H** | arm a contact trigger or heat guidance for the next shot |
748
+ | **P** or **Esc** | pause |
749
+ | **N** | the next round, from the shop |
750
+
751
+ ### The roster
752
+
753
+ You start with the bottomless **Baby Missile** and your cash; everything else is bought. What a
754
+ shell does when it lands:
755
+
756
+ | weapon | what it does |
757
+ |---|---|
758
+ | Baby Missile, Missile, Baby Nuke, Nuke | a blast: a crater and damage that falls off with distance, from a cell and a half across to eleven |
759
+ | Leap Frog | three warheads, one after another, each blast bigger than the last |
760
+ | Funky Bomb | a blast, then six bomblets thrown out that each blast where they land |
761
+ | MIRV, Death's Head | five (or nine, large) warheads splitting at the top of the arc |
762
+ | Tracer, Smoke Tracer | no crater and no damage: it shows you the wind |
763
+ | Baby Roller, Roller, Heavy Roller | lands and rolls downhill until it meets a tank or the bottom of a dip, then blasts |
764
+ | Riot Charge, Riot Blast | a wedge of dirt cut from the turret along the barrel's line, at once; nobody hurt |
765
+ | Riot Bomb, Heavy Riot Bomb | a shell that clears a sphere of dirt and hurts no one |
766
+ | Dirt Clod, Dirt Ball, Ton of Dirt | a shell that bursts into a ball of dirt |
767
+ | Liquid Dirt | oozes downhill and sets where it pools, filling the holes |
768
+ | Dirt Charge | a wedge of dirt thrown from the turret, at once |
769
+ | Earth Disrupter | every hanging piece of dirt on the field settles, at once |
770
+ | Napalm, Hot Napalm | liquid fire that runs downhill along the surface and burns what it reaches |
771
+ | Baby Digger, Digger, Heavy Digger | digs straight down, then blasts |
772
+ | Baby Sandhog, Sandhog, Heavy Sandhog | tunnels on along its heading through the dirt, then blasts |
773
+ | Plasma Blast | energy thrown from your own tank, its reach set by the power; you are spared, no crater |
774
+ | Laser | a straight line from the barrel, at once: dirt along it goes, a tank on it burns |
775
+
776
+ The accessories: **Shield**, **Force Shield** and **Heavy Shield** absorb 60, 100 and 150 damage;
777
+ a **Mag Deflector** pushes passing shells away and a **Super Mag** harder and from further; a
778
+ **Parachute** opens when you fall; a **Battery** restores health; **Auto Defense** raises a shield
779
+ for you as your turn begins; a **Fuel Tank** lets you drive; a **Contact Trigger** sets the shell
780
+ off within reach of a tank before it buries; **Heat Guidance** bends the shell toward the nearest
781
+ tank as it falls. The items line under the fire button shows what you carry and what is armed.
782
+ Prices, packs and radii are the manual's (SCORCH.DOC); the original started every player with $0
783
+ and 5% interest, which the settings allow.
784
+
785
+ ### The computer players
786
+
787
+ ![Scorched Yard](images/scorched.jpg)
788
+
789
+ Out of the box you face a **mix**, climbing from the easy ones: a Shooter and a Tosser, then a
790
+ Chooser, a Spoiler, a Cyborg and a Poolshark as you add seats. **Display settings → Scorched Yard
791
+ → Their kind** fields one kind in every seat instead. They are the manual's eight, and they
792
+ behave as it says:
793
+
794
+ | kind | how it plays |
795
+ |---|---|
796
+ | Moron | picks an angle and a power, and shoots; buys at random |
797
+ | Shooter | takes low, straight shots when it has a line of fire, and shoots like a Moron when it does not |
798
+ | Poolshark | a Shooter, except that under rubber or spring walls it looks for the bank shot too |
799
+ | Tosser | lobs high, then corrects its power from each miss, by halves, until it hits |
800
+ | Chooser | tries the straight shot, the lob and the bank, and takes whichever lands closest |
801
+ | Spoiler | works the shot out from the wind and gravity and is nearly perfect, with a wobble now and then |
802
+ | Cyborg | a Spoiler with a grudge: it fires at whoever hit it last, else at the weakest, else at the leader |
803
+ | Unknown | one of the others, drawn afresh each round and never announced |
804
+
805
+ All but the Moron use a battery when hurt and raise a shield before firing, and each shops to its
806
+ own taste between rounds: Shooters buy missiles, Tossers MIRVs and Funky Bombs, Cyborgs the
807
+ Death's Head and the heaviest shields.
808
+
809
+ ### Cash and the shop
810
+
811
+ Every point of damage you land earns $10, a kill $2,000, and the last tank standing $1,000; interest
812
+ (a setting, 5% out of the box) is paid on what you keep between rounds. When a round ends the
813
+ **shop** opens: every weapon and item with its price and pack size, what you own, and a buy button
814
+ while you can afford it. The computer players shop at the same moment, in their own way. What you
815
+ buy stays with you for the rest of the game.
816
+
817
+ ### The controls
818
+
819
+ **← →** turn the barrel, **↑ ↓** change the power, and a **held key accelerates**: one a step, then
820
+ two, then five, so crossing the whole arc takes about a second and a half and the last degree still
821
+ takes one press. **Shift** is fine (1° and 1), **Ctrl** coarse (100 of power). **, and .** nudge the
822
+ power by one. The **wheel** over the field changes the power, and with **Shift** the angle. **Click
823
+ the angle or the power** on the panel and type the number. **Drag** on the field to aim; **press on
824
+ your own tank** and drag to turn the barrel without touching the power. **R** fires the last shot
825
+ again, exactly.
826
+
827
+ **Correcting.** Click an enemy tank to mark it (a red ring goes under it; click it again to clear).
828
+ After your next shot the panel says how it did against that tank — *10 over*, *6 short*, *close*,
829
+ *hit* — and **C** sets your power to what that miss says it should have been. It works from your
830
+ own last shot, so the wind and the hills are still yours to read; change the angle and it is only
831
+ an estimate. Display settings → Scorched Yard → *Correction helper* turns it off.
832
+
833
+ **Weapons.** **W** opens a grid of what you own, biggest blast first, with counts and the key for
834
+ each; click one or press its number. **1–9** pick the weapons you own in the shop's order, **Q**
835
+ picks the last one you fired, **[ ]** still cycle. The **fire button names what it will send**
836
+ (*fire · Missile × 5*), and the last of a weapon sold one at a time — a Nuke, a Death's Head —
837
+ **asks once**: the button turns red and says so, and a second press within three seconds sends it
838
+ (*Confirm the last of a weapon* in Display settings).
839
+
840
+ **The aim guide** draws a ghost of the shell's path while you aim, under this round's wind and
841
+ gravity: by default just the first fifth of the flight, enough to read the lean of the shot without
842
+ giving the landing away. *Aim guide* in Display settings chooses short, the whole flight, or off.
843
+ **Cheat mode** draws the whole flight with a ring where it lands.
844
+
845
+ **Items** are buttons: click a pill to use a battery, raise a shield or arm a trigger, or press
846
+ **B**, **S**, **T**, **H**; **A D** drive if you have fuel. **Space** or the fire button sends the
847
+ shot; **P** pauses; **N** starts the next round.
848
+
849
+ **Restart** on the panel (or **F2**) starts a fresh war from round one, whatever the board is doing:
850
+ a war is five rounds, and a player knocked out early should not have to watch the computer finish.
851
+
852
+ The wind is **simulated air**, behind the land. A small fluid simulation is pushed by the round's
853
+ wind and treats the land as its solid floor, so the air rises over the hills, speeds across the
854
+ crests and rolls into eddies behind them, and it follows the craters as they are blown. You see it
855
+ as thin flow lines, the wind-map kind: each one the recent path of something weightless carried by
856
+ that air, fading in and out and thinning away at the edges of the field. The sky behind them sways
857
+ very slightly, as if seen through moving air. When the wind changes, the air turns round by its own
858
+ momentum rather than jumping. A row of **chevrons** at the top of the field points the way it
859
+ blows, as many as it is strong, brightest just after a change. A computer player decides its shot
860
+ as its turn begins, and you can watch its barrel swing onto it while it thinks.
861
+
862
+ **Cheat mode** (a switch on the panel, also under Display settings → Scorched Yard) draws the
863
+ firing solution while you aim: the shell's own path under this round's wind and the game's
864
+ gravity, clipped where it would meet the dirt, with a ring where it lands. It moves as you move
865
+ the barrel or the power, so you can watch the arc bend under the wind. It is the rules' own
866
+ arithmetic, so what it draws is what the shot does.
867
+
868
+ Over the tank whose turn it is stands the **aim gauge**: a protractor with a tick every fifteen
869
+ degrees, a needle along the current angle whose length is the power, and both numbers at its tip.
870
+ It is dimmed on a computer player's turn, so you can read what it is about to do before it fires.
871
+
872
+ The HUD shows whose turn it is, the round, the **wind** (an arrow and its strength: it changes
873
+ every turn, and it bends every shot; you can see it too — streaks of moving air run level across
874
+ the sky behind the land, short and slow in a breeze, long and quick in a gale, in three bands of
875
+ depth so the air has thickness; the pennant on every turret streams downwind; and the Earth sky's
876
+ clouds go with it), your angle, power and weapon with its count, and every
877
+ tank's health. **Fire** on the HUD does what space does. A shell that lands carves a circle out of
878
+ the dirt; the blast itself is drawn the way the fireworks are — a white core, a shockwave, sparks
879
+ that curve and trail, and smoke that rises, spreads, drifts downwind and thins; the dirt above the
880
+ crater falls until it rests, and a tank left in the air falls with
881
+ it and is hurt by the fall. A blast hurts every tank in reach, most at its centre. A tank at zero
882
+ health explodes, and its blast can take a neighbour with it.
883
+
884
+ A round ends when one tank is left (or none); the survivor scores 200, a kill 100, and a tank
885
+ that kills itself loses 50. **Rounds** (a setting, five out of the box) make a game; the highest
886
+ score at the end wins, and your score goes in the high-score table, this browser's.
887
+
888
+ ### The switches on the panel
889
+
890
+ **Sky** goes round the Galaxy, the Earth and none: the same choice as the **Sky** tab's map. The
891
+ game ships under the Earth, and each round takes its own hour of it, dawn to night, unless **A sky
892
+ per round** is off. **Music** is a march in D minor on oscillators. **Sound** is the shot, the blast, a hit, a
893
+ fall, a death. **Talk** is what the tanks say when they fire, are hit, or die. **Fast** flies
894
+ shells at three times the pace. **Watch** is the attract mode: the computer takes your chair as
895
+ well, so every seat is a computer player and the war runs on by itself — a round rolls into the
896
+ next without waiting at the shop, and when one of them wins the war a fresh one begins. Nothing
897
+ is scored in the high-score table for a war nobody played. Turn it off and the next game seats
898
+ you again; it is also in **Display settings → Scorched Yard → Attract mode**, and it is what to
899
+ leave running on a screen in the corner.
900
+ The rules the original exposed are in **Display settings → Scorched Yard**: the number of
901
+ computer players, the rounds, the **walls** (none loses a shell off the edge, the original's
902
+ default; concrete explodes it there; padded stops it and drops it; rubber bounces it; spring
903
+ bounces it back harder; wraparound brings it in the other side), the **wind** (once a round, which is how it ships: the round's
904
+ direction is drawn when the round starts and held to the end of it, while the strength is drawn
905
+ again for every turn, so the air blows one way and how hard is still something to read before each
906
+ shot; every turn, which is the original's and can turn right around between two shots; every shot;
907
+ or none), **gravity**, the **landscape** the rounds are drawn from (hills, mountains, a valley,
908
+ flat), the **starting cash** and the **interest** rate. They take effect at the next new game.
909
+
720
910
  ## Wolfenstein 3D
721
911
 
722
912
  The shareware episode of Wolfenstein 3D, **Escape from Wolfenstein**, under **Diversions**, first of
@@ -1159,23 +1349,63 @@ these are the settings that buy it back, roughly most expensive first:
1159
1349
  | **Metallic finish** | *Chrome* mirrors a horizon in every face, and the reflection slides as the blocks move; *satin* is the softer highlight along the lit edge. Needs Metallic sheen on. |
1160
1350
  | **Departures and arrivals** | How blocks leave and rejoin the board on a refresh. |
1161
1351
  | **Depth** | How much height foreshortens, 0 to 0.001. 0 is the flat parallel camera the board shipped with: a cube is the same size however high it flies. Raise it and a cube's top grows a little wider than its base and a flying block swells slightly as it rises. |
1162
- | **Star field** | On by default. The stars twinkle, so the board keeps repainting while they are on; switch it off to save that. What the stars *look* like is the **Sky** tab. |
1352
+ | **Sky** | Which sky stands behind the board: the **Galaxy**, the **Earth**, or none. What each sky is made of is the **Sky** tab; this is only the choice, and the Kiosk's left panel follows it. The Galaxy twinkles, so the board keeps repainting while it is up; none saves that. |
1163
1353
  | **Board curve** | How far the board bows toward you. 0 is flat. |
1164
1354
  | **Light** | Where the lamp hangs: *straight above* (the default) lights the whole board evenly, which keeps the front rows as bright as the middle; a corner shades the far slope of the curve and the sides turned away from it. |
1165
1355
 
1166
1356
  ### Sky
1167
1357
 
1168
- One sky, shared by every board that shows stars so the density you choose applies to Block
1169
- space, Markets and Tetrust alike. Whether a given board shows it stays that board's own switch.
1358
+ There are two skies, and every board that has a sky behind it chooses one. The tab opens with the
1359
+ map, which is the whole answer to "which settings apply to which panel":
1360
+
1361
+ | board | draws |
1362
+ |---|---|
1363
+ | **Block space** (and the Kiosk's left panel) | Galaxy |
1364
+ | **Markets & Price** (and the Kiosk's right panel) | Galaxy |
1365
+ | **Tetrust**, **Blockout**, **Blockanoid** | Galaxy |
1366
+ | **Scorched Yard** | Earth |
1367
+
1368
+ Those are the shipped choices; each row is a select with **Galaxy**, **Earth** and **None**, and
1369
+ changing it here is the same setting as the **Sky** row at the top of that board's own tab. The
1370
+ games also carry a **sky** button on their panels that goes round the three. Below the map, the
1371
+ two skies themselves, each with only its own controls; neither is ever dimmed, because any board
1372
+ may be using it.
1373
+
1374
+ **The Galaxy** is the star field, BlockYard's own sky: one set of stars shared by every board that
1375
+ draws it, so the density you choose applies to Block space, the candles and the games alike.
1170
1376
 
1171
1377
  | setting | what it does |
1172
1378
  |---|---|
1379
+ | **Spiral arms** | Lays the stars on slowly turning spiral arms instead of scattering them evenly. One turn takes about a quarter of an hour. |
1380
+ | **Arms centre** | Behind the board, or any of the four corners. A corner crowds the bright nucleus there and sweeps the arms across the panel. |
1173
1381
  | **Star density** / **Star brightness** | How many stars (up to 8x the shipped number) and how strongly they burn. |
1174
- | **Spiral galaxy** | Lays the same stars on slowly turning spiral arms instead of scattering them evenly. One turn takes about a quarter of an hour. |
1175
- | **Galaxy centre** | Behind the board, or any of the four corners. A corner crowds the bright nucleus there and sweeps the arms across the panel. |
1176
- | **Nebulae**, **Dust lanes**, **Star clusters**, **Distant galaxies** | The layers of the sky, each its own switch: gas clouds along the arms, dark ribbons on their inner edges, tight knots out in the halo, and small faint galaxies in the deep field behind everything. |
1177
1382
  | **Star colours** | Warm old stars in the nucleus, blue-white young ones in the arms. Off is one colour of starlight. |
1178
1383
  | **Star glints** | The halo and cross glint on the brightest stars. |
1384
+ | **Nebulae**, **Dust lanes**, **Star clusters**, **Distant galaxies** | The layers of the sky, each its own switch: gas clouds along the arms, dark ribbons on their inner edges, tight knots out in the halo, and small faint galaxies in the deep field behind everything. |
1385
+
1386
+ **The Earth** is a real day drawn from this machine's clock: the sun climbs and sets, the dome
1387
+ goes through night, dawn, day and dusk by the sun's height, clouds drift lit from the sun's side,
1388
+ the moon rises at tonight's phase, and the stars come out as the sun goes down (the Galaxy's
1389
+ density and brightness apply to them; its arms and layers do not, since a spiral over a real night
1390
+ is wrong).
1391
+
1392
+ - **Clock** — real time, a whole day every 24 minutes (to watch it turn), or a fixed hour to
1393
+ admire; **Fixed hour** is that hour.
1394
+ - **Weather** — clear, scattered cloud, overcast, or a storm with rain and lightning; **Cloud
1395
+ cover** overrides the weather's amount, and -1 leaves it to the weather.
1396
+ - **Latitude** — with one set, sunrise, sunset and the sun's height follow the real formula for
1397
+ the date (short winter days, the midnight sun); left unset, the day runs six to six.
1398
+ - **Sun rays**, **Rainbow** (opposite a low sun, in scattered weather) and **Shooting stars** (now
1399
+ and then, at night) are the touches, each a switch.
1400
+ - **Moon** — up every night, highest at midnight and never thinner than a fat crescent (the
1401
+ shipped choice), or on its real track at its real phase, which some nights means no moon at all.
1402
+
1403
+ The sun and the moon are drawn in front of the clouds, so neither is lost behind one, and a board
1404
+ whose land fills the lower part of the panel sets its horizon where the land is, so they go down
1405
+ behind the hills rather than under them.
1406
+
1407
+ A board under the Earth plays no idle effects: a supernova over a blue afternoon is wrong, on the
1408
+ block board and on the candles alike. Their switches are kept, and the Galaxy gets them back.
1179
1409
 
1180
1410
  ### Space effects and Market effects
1181
1411
 
@@ -1232,7 +1462,7 @@ What the sky is *made of* — density, brightness, nebulae, dust and the rest
1232
1462
 
1233
1463
  ### Blockanoid
1234
1464
 
1235
- The Arkanoid court. **Star field**, **spiral galaxy**, **neon bricks** and **sound effects** are the
1465
+ The Arkanoid court. **Sky** (the Galaxy, the Earth, or none), **neon bricks** and **sound effects** are the
1236
1466
  same switches that sit on the game's own panel. Two more are here only, and they change the *rules*:
1237
1467
 
1238
1468
  | setting | what it does |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockyard",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Live web monitor, 3D block-space viewer, block explorer and markets view for a Bitcoin Core node",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "type": "module",
23
23
  "engines": {
24
- "node": ">=22.0.0"
24
+ "node": ">=22.2.0"
25
25
  },
26
26
  "main": "server/main.js",
27
27
  "bin": {
@@ -35,6 +35,8 @@
35
35
  "config/pool-map.json",
36
36
  "systemd/",
37
37
  "docs/*.md",
38
+ "!docs/STATE-*.md",
39
+ "!docs/PRIVATE-*.md",
38
40
  "CHANGELOG.md",
39
41
  "SECURITY.md",
40
42
  "NOTICE"
@@ -1818,6 +1818,7 @@ body { font-variant-numeric: tabular-nums; }
1818
1818
  .cfggroup { border: 1px solid var(--line-soft); border-radius: var(--radius); padding: 8px 10px 10px; margin-bottom: 10px; background: var(--bg-2); }
1819
1819
  .cfggroup > h3 { margin: 0 0 4px; font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--accent); }
1820
1820
  .cfggroup > p { margin: 0 0 8px; color: var(--fg-faint); font-size: 11px; line-height: 1.4; }
1821
+ .cfggroup > p.cfgheld { color: var(--warn); }
1821
1822
  /* packed (2026-09-15): rows 5px not 7, hints tighter, and on the effects tabs the switches two across
1822
1823
  with the hint as the row's tooltip */
1823
1824
  .cfgrow { display: grid; grid-template-columns: 1fr auto; gap: 3px 10px; align-items: center; padding: 5px 0; border-top: 1px solid var(--line-soft); }
@@ -1938,6 +1939,92 @@ body { font-variant-numeric: tabular-nums; }
1938
1939
  /* Blockanoid's court is 13 x 26: narrower than Blockout's and taller, which is what gives Vaus a
1939
1940
  court you can dig a channel up the side of. Same furniture, its own shape. */
1940
1941
  .tetcard .treemapwrap.tetwell.banwell { aspect-ratio: 14 / 27; }
1942
+ /* SCORCHED YARD's field is 96 x 48, wide and low like the original's 640 x 350 screen: it takes
1943
+ the card's width and its height follows (docs/PLAN-SCORCHED-YARD.md). The page gives the field the
1944
+ first column, as the DOS games do, so the HUD sits beside a field that fills the window's width. */
1945
+ #scorched { grid-template-columns: minmax(0, 1fr) 270px; justify-content: stretch; }
1946
+ #scorched > .tetcard { min-width: 0; }
1947
+ .tetcard .treemapwrap.tetwell.seyard { width: min(100%, calc((100vh - 170px) * 2)); height: auto; aspect-ratio: 2 / 1; }
1948
+ /* two canvases stacked: the land under, the actors over, the sky behind both (scorchedyard.js);
1949
+ scaled a little from the bottom centre so the board's own margins leave the frame -- the land
1950
+ meets the bottom edge and runs off both sides, and no sky shows under it (operator, 2026-09-16:
1951
+ "Play field needs to go to the edges") */
1952
+ .seyard { overflow: hidden; }
1953
+ .seyard canvas { position: absolute; inset: 0; transform: scale(1.085); transform-origin: 50% 100%; }
1954
+ /* THE WIND'S OWN PLANE: flat, behind the land, and NOT scaled with the board -- it is not in the
1955
+ scene, it is the air behind it (operator: "drawn behind everything else on a flat plane") */
1956
+ .seyard canvas.sywind { width: 100%; height: 100%; transform: none; }
1957
+ /* the two numbers a player actually tunes: click one and type it */
1958
+ #syStats b.syval { cursor: text; border-bottom: 1px dotted var(--line); }
1959
+ #syStats b.syval:hover { color: var(--accent); }
1960
+ .syrestart { width: 100%; margin-top: 6px; }
1961
+ /* the controls (docs/PLAN-SCORCHED-YARD.md §12): the confirm state, item buttons, the weapon grid, the keys table */
1962
+ .syfire.syconfirm { background: var(--bad); border-color: var(--bad); }
1963
+ button.sypillbtn { cursor: pointer; font: inherit; }
1964
+ button.sypillbtn:hover { border-color: var(--accent); }
1965
+ .syweapons { position: absolute; right: 12px; top: 12px; z-index: 4; width: min(360px, 60%); max-height: 70%; overflow: auto; background: rgba(8, 10, 14, .9); border: 1px solid var(--line); border-radius: 6px; padding: 8px; }
1966
+ .syweapons.hidden { display: none; }
1967
+ .syweaponshead { display: flex; align-items: baseline; gap: 6px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-dim); margin-bottom: 6px; }
1968
+ .syweaponshead small { text-transform: none; letter-spacing: 0; }
1969
+ .syweaponlist { display: grid; gap: 3px; }
1970
+ .syweapon { display: grid; grid-template-columns: 1fr auto auto 1.6em; gap: 8px; align-items: baseline; text-align: left; padding: 4px 6px; background: transparent; border: 1px solid transparent; border-radius: 4px; color: var(--text); font: inherit; font-size: 12px; cursor: pointer; }
1971
+ .syweapon:hover { border-color: var(--line); }
1972
+ .syweapon.on { border-color: var(--accent); }
1973
+ .syweapon i { color: var(--fg-dim); font-style: normal; font-size: 11px; }
1974
+ .syweapon kbd { color: var(--accent); font-family: var(--mono); text-align: center; }
1975
+ table.sykeys { border-collapse: collapse; width: 100%; }
1976
+ table.sykeys th { text-align: left; vertical-align: top; padding: 2px 8px 2px 0; font-weight: 500; color: var(--fg-dim); white-space: nowrap; }
1977
+ table.sykeys td { padding: 2px 0; }
1978
+ /* the Sky tab's map of boards to skies, and the two headings under it (docs/PLAN-SKIES.md) */
1979
+ .skymap { border-collapse: collapse; }
1980
+ .skymap th { text-align: left; font-weight: 500; padding: 3px 14px 3px 0; white-space: nowrap; }
1981
+ .skymap th small { display: block; color: var(--fg-faint); font-size: 11px; font-weight: 400; }
1982
+ .skymap td { padding: 3px 0; }
1983
+ .cfghead { margin: 18px 0 6px; padding-top: 10px; border-top: 1px solid var(--line); }
1984
+ .cfghead b { display: block; font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: var(--accent); }
1985
+ .cfghead i { display: block; color: var(--fg-dim); font-style: normal; font-size: 12px; margin-top: 2px; }
1986
+ .syedit { width: 5.2em; font: inherit; color: var(--text); background: var(--bg); border: 1px solid var(--accent); border-radius: 3px; padding: 0 2px; text-align: right; }
1987
+ .syfire { width: 100%; margin: 2px 0 10px; }
1988
+ .syfire:disabled { opacity: .45; cursor: default; }
1989
+ .sytanks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; font-size: 12px; }
1990
+ .sytank { display: grid; grid-template-columns: 10px auto 1fr auto; gap: 3px 7px; align-items: center; padding: 3px 6px; border-radius: var(--radius-sm); border: 1px solid transparent; }
1991
+ .sytank.now { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
1992
+ .sytank.dead { opacity: .45; }
1993
+ .sytank b { font-weight: 600; }
1994
+ .sytank meter { width: 100%; height: 9px; }
1995
+ .sytank span { font-family: var(--mono); font-variant-numeric: tabular-nums; min-width: 3ch; text-align: right; }
1996
+ .sytank small { grid-column: 2 / -1; color: var(--fg-faint); font-size: 10.5px; }
1997
+ .sydot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; background: var(--fg-faint); }
1998
+ /* what a tank says: a bubble over the field at the tank's place (its position is two custom
1999
+ properties set through the CSSOM, never a style attribute -- the CSP) */
2000
+ .sytalk { position: absolute; z-index: 3; left: var(--x, 50%); top: var(--y, 50%); transform: translate(-50%, -100%);
2001
+ padding: 4px 9px; border-radius: 10px; background: rgba(14, 16, 22, .88); border: 1px solid var(--line); color: var(--fg);
2002
+ font-size: 12px; white-space: nowrap; pointer-events: none; }
2003
+ .sytalk.hidden { display: none; }
2004
+ .sydot-0 { background: #f7931a; } .sydot-1 { background: #4d8dff; } .sydot-2 { background: #2ecc8f; }
2005
+ .sydot-3 { background: #ef5a5a; } .sydot-4 { background: #c78bff; } .sydot-5 { background: #f0c419; }
2006
+ /* the items line: pills, lit when armed */
2007
+ .syitems { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; font-size: 11px; min-height: 18px; }
2008
+ .sypill { padding: 2px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--fg-dim); }
2009
+ .sypill.on { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
2010
+ /* THE SHOP (M2): in the overlay between rounds, two columns of rows -- name, note, price and pack,
2011
+ what you own, a buy button while you can afford it. Scrolls inside the card on a short window. */
2012
+ .syshop { width: min(100%, 980px); max-height: min(66vh, 620px); overflow: auto; text-align: left; background: rgba(8, 10, 14, .92);
2013
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 12px 10px; font-size: 11.5px; }
2014
+ .syshop.hidden { display: none; }
2015
+ .syshophead { display: flex; align-items: center; gap: 8px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-dim); margin-bottom: 4px; }
2016
+ .syshophead b { color: var(--ok); font-family: var(--mono); letter-spacing: 0; text-transform: none; font-size: 13px; }
2017
+ .sycols { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }
2018
+ .sycols h4 { margin: 6px 0 2px; font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--accent); }
2019
+ .syrow { display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; gap: 1px 8px; align-items: center; padding: 3px 0; border-top: 1px solid var(--line-soft); }
2020
+ .syrow b { font-weight: 600; }
2021
+ .syrow .syhint { grid-column: 1 / -1; grid-row: 2; color: var(--fg-faint); font-size: 10px; }
2022
+ .syrow .syprice { font-family: var(--mono); color: var(--fg-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
2023
+ .syrow .syown { color: var(--ok); font-size: 10.5px; min-width: 5ch; text-align: right; }
2024
+ .syrow.poor .syprice { color: var(--fg-faint); }
2025
+ .sybuy { font-size: 10.5px; padding: 2px 8px; }
2026
+ .sybuy:disabled { opacity: .35; cursor: default; }
2027
+ @media (max-width: 900px) { .sycols { grid-template-columns: 1fr; } }
1941
2028
  .tetwell canvas { width: 100%; height: 100%; display: block; background: transparent; border: 0; } /* the sky shows through */
1942
2029
  /* THE PANEL IS THE SKY (operator: "Have this entire panel filled black and rendering the spiral
1943
2030
  galaxy for this display. Have the text floating over the spiral galaxy, and then the playboard
package/public/index.html CHANGED
@@ -99,6 +99,7 @@
99
99
  <button data-page="tetrust" role="menuitem" title="Tetrust: trust, but verify">Tetrust</button>
100
100
  <button data-page="blockout" role="menuitem" title="Blockout: break the wall">Blockout</button>
101
101
  <button data-page="blockanoid" role="menuitem" title="Blockanoid: Vaus, capsules and the minions">Blockanoid</button>
102
+ <button data-page="scorched" role="menuitem" title="Scorched Yard: artillery on a deformable block landscape">Scorched Yard</button>
102
103
  <button data-page="wolf3d" role="menuitem" title="Wolfenstein 3D: the shareware WOLF3D.EXE on an emulated PC">Wolfenstein 3D</button>
103
104
  <button data-page="doom" role="menuitem" title="DOOM: the shareware DOOM.EXE on an emulated 486">DOOM</button>
104
105
  <button data-page="quake" role="menuitem" title="Quake: the shareware QUAKE.EXE on an emulated PC">Quake</button>
@@ -515,8 +516,7 @@
515
516
  <div class="tetkeys">move the mouse over the court, or ← → / A D · space or click serves · P pause · Enter play</div>
516
517
  <h3>Switches</h3>
517
518
  <div class="tetswitches">
518
- <button type="button" class="tetsw" id="boStars" aria-pressed="false" title="the star field across the panel">★ stars</button>
519
- <button type="button" class="tetsw" id="boGalaxy" aria-pressed="false" title="the spiral galaxy in that sky">🌀 galaxy</button>
519
+ <button type="button" class="tetsw" id="boSkySw" aria-pressed="false" title="the sky behind the court: Galaxy, Earth, or none">☀ sky</button>
520
520
  <button type="button" class="tetsw" id="boNeon" aria-pressed="false" title="the wall under lit neon tubes">◉ neon</button>
521
521
  <button type="button" class="tetsw" id="boSfx" aria-pressed="false" title="bat, bricks, walls and a lost ball">♫ sound</button>
522
522
  </div>
@@ -560,8 +560,7 @@
560
560
  <div class="tetkeys">move the mouse over the court, or ← → / A D · space or click serves · the laser fires itself while armed · P pause · Enter play</div>
561
561
  <h3>Switches</h3>
562
562
  <div class="tetswitches">
563
- <button type="button" class="tetsw" id="baStars" aria-pressed="false" title="the star field across the panel">★ stars</button>
564
- <button type="button" class="tetsw" id="baGalaxy" aria-pressed="false" title="the spiral galaxy in that sky">🌀 galaxy</button>
563
+ <button type="button" class="tetsw" id="baSkySw" aria-pressed="false" title="the sky behind the court: Galaxy, Earth, or none">☀ sky</button>
565
564
  <button type="button" class="tetsw" id="baNeon" aria-pressed="false" title="the wall under lit neon tubes">◉ neon</button>
566
565
  <button type="button" class="tetsw" id="baSfx" aria-pressed="false" title="Vaus, bricks, capsules, the laser and a lost ball">♫ sound</button>
567
566
  </div>
@@ -594,8 +593,7 @@
594
593
  <div class="tetkeys">← → or A D move · ↑ W or X rotate · Z or Q rotate back · ↓ or S soft drop · space hard drop · P pause · Enter play</div>
595
594
  <h3>Switches</h3>
596
595
  <div class="tetswitches">
597
- <button type="button" class="tetsw" id="tetStars" aria-pressed="false" title="the star field across the panel">★ stars</button>
598
- <button type="button" class="tetsw" id="tetGalaxy" aria-pressed="false" title="the spiral galaxy in that sky">🌀 galaxy</button>
596
+ <button type="button" class="tetsw" id="tetSkySw" aria-pressed="false" title="the sky behind the well: Galaxy, Earth, or none">☀ sky</button>
599
597
  <button type="button" class="tetsw" id="tetMusic" aria-pressed="false" title="Korobeiniki, on oscillators">♪ music</button>
600
598
  <button type="button" class="tetsw" id="tetSfx" aria-pressed="false" title="move, rotate, drop, clear, game over">♫ sound</button>
601
599
  </div>
@@ -605,6 +603,60 @@
605
603
  </div>
606
604
  </section>
607
605
 
606
+ <section class="page" data-page="scorched">
607
+ <div class="tetrust" id="scorched">
608
+ <div class="card tetcard">
609
+ <canvas class="tetsky" id="sySky"></canvas>
610
+ <h3>Scorched Yard <span class="sp"></span><span class="src">artillery on the block space — the land is dirt, and it falls</span></h3>
611
+ <div class="treemapwrap tetwell seyard idle" id="syFieldWrap">
612
+ <canvas class="sywind" id="syWind"></canvas>
613
+ <canvas class="treemap" id="syLand"></canvas>
614
+ <canvas class="treemap" id="syField"></canvas>
615
+ <div class="syweapons hidden" id="syWeapons"></div>
616
+ <div class="sytalk hidden" id="syTalk"></div>
617
+ </div>
618
+ <div class="tetover" id="syOver">
619
+ <div class="tetmsg" id="syMsg">Scorched Yard</div>
620
+ <div class="tetsub" id="sySub"></div>
621
+ <button type="button" class="btn primary" id="syResume">play</button>
622
+ <div class="syshop hidden" id="syShop"></div>
623
+ </div>
624
+ </div>
625
+ <div class="card tethud">
626
+ <h3>This turn</h3>
627
+ <div class="tetstats" id="syStats"></div>
628
+ <button type="button" class="btn primary syfire" id="syFire" disabled>fire</button>
629
+ <button type="button" class="btn syrestart" id="syRestart" title="start a new war from round one (F2)">restart</button>
630
+ <h3>Items</h3>
631
+ <div class="syitems" id="syItems"></div>
632
+ <h3>Tanks</h3>
633
+ <div class="sytanks" id="syTanks"></div>
634
+ <h3>Keys</h3>
635
+ <table class="tetkeys sykeys">
636
+ <tr><th>aim</th><td>← → angle · ↑ ↓ power · held keys speed up · shift fine · ctrl coarse · , . power ±1</td></tr>
637
+ <tr><th>mouse</th><td>drag the field to aim · press your tank to turn the barrel only · wheel: power, shift+wheel: angle · click a readout to type it</td></tr>
638
+ <tr><th>target</th><td>click an enemy tank to mark it · C corrects power from your last miss</td></tr>
639
+ <tr><th>weapons</th><td>W the grid · 1–9 pick · Q your last · [ ] cycle</td></tr>
640
+ <tr><th>fire</th><td>space or the button · R repeats the last shot</td></tr>
641
+ <tr><th>items</th><td>click a pill · B battery · S shield · T trigger · H heat guidance · A D drive</td></tr>
642
+ <tr><th>game</th><td>P pause · N next round · F2 restart</td></tr>
643
+ </table>
644
+ <h3>Switches</h3>
645
+ <div class="tetswitches">
646
+ <button type="button" class="tetsw" id="sySkySw" aria-pressed="false" title="the sky behind the field: Galaxy, Earth, or none">☀ sky</button>
647
+ <button type="button" class="tetsw" id="syMusic" aria-pressed="false" title="a march in D minor, on oscillators">♪ music</button>
648
+ <button type="button" class="tetsw" id="sySfx" aria-pressed="false" title="the shot, the blast, a hit, a death">♫ sound</button>
649
+ <button type="button" class="tetsw" id="syTalkSw" aria-pressed="false" title="what the tanks say">💬 talk</button>
650
+ <button type="button" class="tetsw" id="syFast" aria-pressed="false" title="shells at three times the pace">⏩ fast</button>
651
+ <button type="button" class="tetsw" id="syCheat" aria-pressed="false" title="cheat mode: the firing solution drawn while you aim">◎ cheat</button>
652
+ <button type="button" class="tetsw" id="syDemo" aria-pressed="false" title="attract mode: the computer takes every seat and plays on by itself">▶ watch</button>
653
+ </div>
654
+ <h3>High scores <span class="sp"></span><span class="src">this browser</span></h3>
655
+ <table class="tetscores" id="syScores"></table>
656
+ </div>
657
+ </div>
658
+ </section>
659
+
608
660
  <!-- WOLFENSTEIN 3D (public/js/wolf3d.js): the shareware WOLF3D.EXE v1.4 from games/wolf3d_dos/,
609
661
  unmodified, on the same emulated PC -- this time in real mode, as a 16-bit DOS program. -->
610
662
  <section class="page" data-page="wolf3d">