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
@@ -0,0 +1,456 @@
1
+ # Scorched Yard — a plan for a Scorched Earth Diversion
2
+
3
+ *Scoped 2026-09-16, before any code. Operator: "Scope out a plan for re-creating the classic PC
4
+ DOS game Scorched Earth using our engine. I suggest using blocks to generate the deformable
5
+ landscape unless you have a better idea. Make it at least 3 player. 1 Human Player and 2 AI
6
+ Players. Try to reproduce it as faithfully as possible, but make it look fabulous as an
7
+ additional new diversion."*
8
+
9
+ The working name is **Scorched Yard** (Tetrust, Blockout, Blockanoid, Scorched Yard). The name,
10
+ like every "decision" flagged below, is the operator's to change.
11
+
12
+ ## 1. What Scorched Earth is, and what "faithful" means here
13
+
14
+ Scorched Earth (Wendell Hicken, 1991) is a turn-based artillery game: two to ten tanks sit on a
15
+ randomly generated side-view landscape; each turn a player sets a **barrel angle** (0–180°) and a
16
+ **power** (0–1000), picks a **weapon**, and fires. Shells fly under **gravity** and **wind**,
17
+ carve **craters** out of the ground where they land, and the dirt above a crater **falls** to fill
18
+ it. Tanks take damage from blasts, fall when the ground goes from under them (and take damage
19
+ for the fall unless they have a parachute), and die in a blast of their own. Between rounds
20
+ every player spends the **cash** they earned on weapons and defensive items in a **shop**. The
21
+ game is a fixed number of rounds; the winner is the highest score.
22
+
23
+ What makes it *Scorched Earth* rather than any artillery game, and what this plan reproduces:
24
+
25
+ - the deformable dirt that falls and settles, so every crater reshapes the fight;
26
+ - wind that changes between turns (or every shot, as a setting), and the four **wall** modes:
27
+ concrete (shells explode on the edge), rubber (they bounce), wraparound, and none;
28
+ - the weapon roster with its personalities — Baby Missile to Death's Head, Funky Bomb, MIRV,
29
+ Leapfrog, Rollers that run downhill, Napalm that flows and burns, Diggers and Sandhogs that
30
+ tunnel, Riot charges that clear dirt without hurting, Dirt Clods and a Ton of Dirt that add
31
+ it, Tracers that show the wind, the Laser;
32
+ - the items — Shields (plain, deflector, force), Parachutes, Batteries, Mag Deflector, Auto
33
+ Defense, Fuel (tanks can drive), Contact Triggers, Heat Guidance;
34
+ - the AI personalities by name — Moron, Shooter, Poolshark, Tosser, Chooser, Spoiler, Cyborg,
35
+ Unknown — with their distinct habits;
36
+ - cash for kills, interest between rounds, the shop with weapons sold in packs;
37
+ - and the small things people remember: tanks that talk ("Nuke 'em"), the death explosion,
38
+ the trace of the last shot, the sky that changes each round.
39
+
40
+ What it will not reproduce: the VGA look. The original is 640×350 pixels of flat colour; this
41
+ one is built from the engine's cubes on the oblique camera, with the effects library for the
42
+ blasts and the star field or a day sky behind it. Faithful in the mechanics, fabulous in the
43
+ look.
44
+
45
+ ## 2. The landscape: blocks, as suggested — and how
46
+
47
+ The operator's suggestion is the right one, with one refinement. The playfield is a grid of
48
+ `W × H` cells (proposed **80 × 40**, an aspect of 2:1 like the original's 640×350 minus its
49
+ status bar); the ground is a **bitmap of dirt cells** over that grid, not merely a height per
50
+ column, so that tunnels, overhangs and caves exist (Diggers and Sandhogs need them, and so does
51
+ the moment when a Nuke leaves an arch of dirt that then collapses). Each column also keeps its
52
+ top for the fast questions (where does a shell land, where does a tank sit).
53
+
54
+ *(M1 found otherwise: under the oblique camera a tile's height climbs 0.3 of a row per unit, so a
55
+ tall run does not stack against the run above it -- the strata drew as floating ribbons. The land
56
+ is a cube per cell after all, on its own canvas redrawn only when the dirt changes, with the
57
+ actors on a transparent canvas over it; the tile budget below is therefore per change, not per
58
+ frame, and the measured build for 1,600 cubes is well under a frame.)*
59
+
60
+ The plan as first written: the engine draws it as **one tile per vertical run of dirt**, not one cube per cell:
61
+ `{ txid: 't<x>:<base>', x, y: base, s: 1, tall: runLength, color }`. A column with no holes is
62
+ one tile; a column with a tunnel is two or three. That keeps the tile count at a few hundred
63
+ (the block-space board draws a thousand at full resolution without trouble) while every cell
64
+ stays individually removable. Colour comes from **strata**: a grass or snow cap, soil, clay,
65
+ rock, and a magma glow at the very bottom, with a per-column hash nudging each shade so the
66
+ face is not flat colour. A run that crosses a stratum boundary is split so each piece keeps
67
+ its stratum's colour. `cellTops` in the engine already reads a heightmap off a tile set; the
68
+ game keeps its own to avoid the round trip.
69
+
70
+ **Generation** follows the original's landscape styles as a setting: rolling hills (a sum of
71
+ low-frequency sines with seeded phases), mountains (higher amplitude, a ridge), a valley, a
72
+ plateau, flat, and random. Tanks are placed on the surface at spaced columns, the surface under
73
+ each tank flattened for two cells, as the original does. Everything is drawn from a seeded RNG
74
+ (`agents.rng`) so a round is reproducible in tests.
75
+
76
+ **Craters.** A blast of radius `r` at `(cx, cy)` clears every dirt cell inside the circle. Dirt
77
+ weapons add cells the same way. Diggers clear along the shell's path. Napalm converts cells at
78
+ the surface it flows over.
79
+
80
+ **Settling.** After any change, every column is scanned: a run of dirt above a hole falls until
81
+ it rests on the run below it. The rules commit the new bitmap at once; the screen animates the
82
+ fall with the engine's own `fallMs`/`bounceDrop` timing by handing the falling runs to the
83
+ board with a `floor` offset that decays to zero (the same mechanism as Tetrust's cleared-line
84
+ drift, in reverse). Tanks standing on a falling run fall with it and take the original's fall
85
+ damage unless a parachute is fitted (which then is consumed).
86
+
87
+ ## 3. Physics, shells, and walls
88
+
89
+ All motion is in the plane of the playfield, in grid units, integrated with substeps the way
90
+ Blockout does (`MAX_STEP` per substep, so a full-power shot never tunnels through a two-cell
91
+ ridge; a test holds this at coarse frame times). The shell is a tile: `{ txid: 'shell', x, y,
92
+ s: 0.4, sphere: true }` with a lit trail drawn over it (`chargeTrail`/`bloom`). Its state is
93
+ `{ x, y, vx, vy }`; each substep applies gravity `g` (a setting, the original's 1.0 = Earth) and
94
+ wind `w` (a per-turn value; "wind changes every shot" is a setting, as is "no wind"); a
95
+ viscosity setting damps velocity as the original's "air viscosity" does.
96
+
97
+ Collision each substep: with dirt (the cell at `floor(x), floor(y)` is dirt), with a tank's
98
+ hitbox (2 wide × 1.4 tall), with the floor (`y ≤ 0`), and with the walls by mode:
99
+
100
+ - **concrete**: the shell explodes at the wall;
101
+ - **rubber**: `vx = -vx × 0.8` and it carries on;
102
+ - **wraparound**: `x` wraps to the other side;
103
+ - **none**: the shell is lost off the side (the original's "no walls").
104
+
105
+ The ceiling is open: a shell can leave the top of the screen and come back down; while it is
106
+ above the field the side panel shows a marker at its `x` and its height.
107
+
108
+ Every weapon is a shell with a **behaviour**: what it does on impact, at its apex, on every
109
+ bounce, or every substep. That table is the whole roster (§4); the physics is shared.
110
+
111
+ ## 4. The roster
112
+
113
+ *Checked against the manual on 2026-09-16* (SCORCH.DOC, the copy at abandonwaredos.com). The
114
+ manual's tables are the ones below; `public/js/scorchedshop.js` carries them as data, with each
115
+ blast radius (pixels on the original's 640-wide screen) divided by 6.67 for a 96-cell field.
116
+ Damage is ours: the manual does not number it. The manual's own defaults: **$0 to start, 5%
117
+ interest, walls NONE, computers buy ON**; ours starts with $10,000 so the first shop has a use
118
+ (a setting, down to 0).
119
+
120
+ **Weapons** (cost · bundle · radius px · arms level): Baby Missile $400·10·10·0 (unlimited: you
121
+ always have 99) · Missile $1,875·5·20·0 · Baby Nuke $10,000·3·40·0 · Nuke $12,000·1·75·1 · Leap
122
+ Frog $10,000·2·20/25/30·3 · Funky Bomb $7,000·2·80·4 · MIRV $10,000·3·20·2 · Death's Head
123
+ $20,000·1·35 ×9 warheads·4 · Napalm $10,000·10·2 · Hot Napalm $20,000·2·4 · Tracer $10·20·0 ·
124
+ Smoke Tracer $500·10·1 · Baby Roller $5,000·10·10·2 · Roller $6,000·5·20·2 · Heavy Roller
125
+ $6,750·2·45·3 · Riot Charge $2,000·10·36 (a wedge from the turret)·2 · Riot Blast $5,000·5·60
126
+ (wider wedge)·3 · Riot Bomb $5,000·5·30·3 · Heavy Riot Bomb $4,750·2·45·3 · Baby Digger
127
+ $3,000·10·0 · Digger $2,500·5·0 · Heavy Digger $6,750·2·1 · Baby Sandhog $10,000·10·0 · Sandhog
128
+ $16,750·5·0 · Heavy Sandhog $25,000·2·1 · Dirt Clod $5,000·10·20·0 · Dirt Ball $5,000·5·35·0 ·
129
+ Ton of Dirt $6,750·2·70·1 · Liquid Dirt $5,000·10·2 · Dirt Charge $5,000·5 (a wedge of dirt)·1 ·
130
+ Earth Disrupter $5,000·10·0 · Plasma Blast $9,000·5·10–75·3 · Laser $5,000·5·2.
131
+
132
+ **Accessories**: Heat Guidance $10,000·6 · Ballistic Guidance $10,000·2 · Horz Guidance
133
+ $15,000·5 · Vert Guidance $20,000·5 · Lazy Boy $20,000·2 · Parachute $10,000·8 · Battery
134
+ $5,000·10 · Mag Deflector $10,000·2 · Shield $20,000·3 · Force Shield $25,000·3 · Heavy Shield
135
+ $30,000·2 · Super Mag $40,000·2 · Auto Defense $1,500·1 · Fuel Tank $10,000·10 · Contact
136
+ Trigger $1,000·25. (The guidance systems and Lazy Boy are v2.) Shield strengths are not
137
+ numbered in the manual; ours are 60 / 100 / 150.
138
+
139
+ **The computer players, in the manual's words** (for M3): Moron — "pick an angle and power, and
140
+ shoot"; Shooter — "significantly deadlier … only if they have a straight line of fire";
141
+ Poolshark — "act like Shooters unless … rebounding walls. Then they try to rebound shots";
142
+ Tosser — "start out like Morons, but they'll refine their aim … until they hit"; Chooser —
143
+ "have all the above methods available … decide which one will be most effective"; Spoiler —
144
+ "taking into account the wind factor and gravity, they will get a perfect shot almost every
145
+ time"; Cyborg — "use methods similar to the Spoilers, but are much nastier … attack tanks who
146
+ are weakened, winning, or have attacked them"; Unknown — "one of the above will be chosen
147
+ randomly … you will not be notified". Walls in the original: CONCRETE, PADDED, RUBBER, SPRING,
148
+ WRAP, RANDOM, ERRATIC, NONE.
149
+
150
+ The table as first planned, superseded by the above:
151
+
152
+ ### Weapons (v1 — the first playable set)
153
+
154
+ | weapon | behaviour | crater r | blast damage |
155
+ |---|---|---|---|
156
+ | Baby Missile | explodes on impact | 1.5 | small |
157
+ | Missile | explodes on impact | 2.5 | moderate |
158
+ | Baby Nuke | explodes on impact | 4 | heavy |
159
+ | Nuke | explodes on impact | 6.5 | very heavy |
160
+ | Death's Head | explodes on impact | 9 | most of the field |
161
+ | Funky Bomb | bursts into 6 bomblets that scatter and explode | 2 each | moderate each |
162
+ | MIRV | splits at apex into 5 shells fanning out | 2.5 each | moderate each |
163
+ | Leapfrog | explodes, then bounces on twice more, exploding each time | 2.5 ×3 | moderate ×3 |
164
+ | Tracer | no crater, no damage; leaves its arc drawn until the next shot | 0 | 0 |
165
+ | Smoke Tracer | as Tracer, with a smoke trail that drifts on the wind | 0 | 0 |
166
+ | Roller | on landing rolls downhill until it meets a tank or the bottom of a dip, then explodes | 2.5 | moderate |
167
+ | Heavy Roller | as Roller, bigger | 4 | heavy |
168
+ | Riot Charge / Riot Blast / Riot Bomb | clears dirt in a small / medium / large radius, no damage to tanks | 3 / 5 / 7 | 0 |
169
+ | Dirt Clod / Dirt Ball / Ton of Dirt | adds a ball of dirt on impact | +2 / +4 / +7 | 0 |
170
+ | Napalm / Hot Napalm | on impact becomes liquid that flows downhill and burns what it touches over several turns' worth of frames | surface | burn per contact |
171
+ | Sandhog / Heavy Sandhog | on impact tunnels on along its heading through dirt, exploding at the end | 1 along the bore, 3 at the end | moderate at the end |
172
+ | Baby Digger / Digger / Heavy Digger | on impact digs straight down, exploding at the end | 1 / 1.5 / 2 along the bore | small at the end |
173
+ | Laser | instant straight line from the barrel; burns dirt and tanks along it | 0.6 along the line | moderate, by exposure |
174
+
175
+ ### Items (v1)
176
+
177
+ | item | what it does |
178
+ |---|---|
179
+ | Shield | absorbs damage until spent (a coloured halo on the tank; the halo dims as it goes) |
180
+ | Deflector Shield | as Shield, and shells that hit it bounce off |
181
+ | Force Shield | a stronger Shield |
182
+ | Parachute | opens when the tank falls, cancelling fall damage; one per fall |
183
+ | Battery | restores health, used from the item slot on your turn |
184
+ | Mag Deflector | shells passing near the tank are pushed away (a field, not a wall) |
185
+ | Auto Defense | on the turn a shield would help, it is raised automatically |
186
+ | Fuel Tank | lets the tank drive left or right along the surface, one cell per unit of fuel |
187
+ | Contact Trigger | the shell explodes on the first thing it touches, including a shield or a tank's hull, rather than only on dirt |
188
+ | Heat Guidance | the shell bends toward the nearest tank in its last part of flight |
189
+
190
+ ### v2 (after the first playable build)
191
+
192
+ Plasma Blast, Earth Disrupter, Liquid Dirt, Lazy Boy (a guided shell the player steers), the
193
+ "suicide" options, and the talk table's full set of taunts.
194
+
195
+ ## 5. Turns, rounds, and the economy
196
+
197
+ - A **game** is `rounds` rounds (setting; default 5). Each round: generate a landscape (new
198
+ seed), place every living-and-dead tank afresh with full health (100), wind for the round.
199
+ - A **turn**: the current tank's player sets angle, power and weapon (or drives, or uses an
200
+ item), then fires. The shell plays out, craters carve, dirt settles, tanks fall, damage is
201
+ applied, deaths explode. Then the next living tank in order. Turn order rotates each round.
202
+ - **Death**: health ≤ 0. The tank explodes with the force of a random weapon from its own
203
+ inventory (the original's habit; a setting to turn it off), which can chain. The killer is
204
+ credited: cash per point of damage dealt, and a kill bonus; a tank that kills itself pays.
205
+ - **Round end**: one or zero tanks alive. Survivors score; interest is paid on cash (setting,
206
+ default 5%); the **shop** opens for every human player (AI players buy by their personality's
207
+ taste: Chooser buys wisely, Moron buys Baby Missiles). The shop lists every weapon and item
208
+ with its price and pack size and the count owned, and shows cash.
209
+ - **Game end**: the highest score; the scoreboard shows kills, damage dealt, cash left, and the
210
+ weapon that did the most damage. High scores in `localStorage` as the other games keep them.
211
+
212
+ ## 6. The three players, and the AI
213
+
214
+ At least three players: the human plus two AI, default. The setting allows two to six, any
215
+ mix of human and AI, each with a name and a colour (the original's tank colours), and the
216
+ personality per AI player. All-AI is allowed: it is the demo the original shipped with, and it
217
+ runs on the Kiosk as an attract mode if the operator wants it there.
218
+
219
+ **Personalities**, each a strategy in `scorchedai.js`:
220
+
221
+ - **Moron**: random angle and power, cheapest weapon.
222
+ - **Shooter**: aims at the nearest tank with a rough ballistic guess, then **corrects from the
223
+ last shot's miss** (bisects power, then angle); picks a bigger weapon when close.
224
+ - **Tosser**: high lobs (angles 60–85°) with the Shooter's correction; likes Funky Bombs and
225
+ MIRVs, which suit lobs.
226
+ - **Poolshark**: only exists under rubber walls; aims bank shots off a wall, correcting the
227
+ same way.
228
+ - **Chooser**: the Shooter's aim with a weapon chosen by the distance and the target's health
229
+ (a Nuke on a close, healthy target; a Baby Missile to finish one off); spends its cash well.
230
+ - **Spoiler**: the Chooser's aim aimed at the current **leader**, not the nearest.
231
+ - **Cyborg**: solves the shot outright — angle and power from the target's position, gravity
232
+ and wind, by a short numeric search that integrates the same physics the shell uses — with a
233
+ small error that shrinks each round; the hardest opponent.
234
+ - **Unknown**: one of the above, drawn at the start of each round.
235
+
236
+ A test holds each one to its habit: the Cyborg hits a stationary tank on flat ground within
237
+ two shots; the Shooter's misses shrink monotonically; the Moron's do not.
238
+
239
+ The AI shares nothing with the screen: it takes the rules' state and returns
240
+ `{ angle, power, weapon }` (or `{ drive }` / `{ item }`), so it is testable headless and could
241
+ one day be an effect (the missile agent already flies arcs across the block-space board).
242
+
243
+ ## 7. The look: fabulous, on this engine
244
+
245
+ - **The field** on the oblique camera (`oblique: { ox: 0.10, oy: 0.30 }`, `dome: 0`, `light:
246
+ 'overhead'`), the strata colours above, a faint neon grid off by default (the court games'
247
+ quiet grid as an option), the space floor.
248
+ - **The sky** as its own canvas behind a transparent board, exactly as Tetrust does: the star
249
+ field with the galaxy by default; **day skies** as an option — dawn, noon, dusk, storm —
250
+ built from `softStops` fills (a sun disc with a halo, a band of colour at the horizon), and a
251
+ seeded pick per round so the sky changes as the original's did. Weather that the original
252
+ only hinted at can be real here: rain streaks under the storm sky, snow on the caps.
253
+ - **Tanks**: a two-cube hull in the player's colour with a sheen, a turret cube, and a barrel
254
+ as a `poly`+`rot` tile that turns with the angle; a floating name and health bar over it;
255
+ the shield as a translucent halo ring (`ring`, `bloom`); the parachute as a `poly` canopy
256
+ while falling.
257
+ - **Shells**: a `sphere` tile with a hot trail; MIRV's split as a spark burst; the Roller as a
258
+ spinning `poly`; napalm as lit, flickering surface cells that dim as they burn out; the
259
+ Laser with `drawSaberLine`'s glow and core.
260
+ - **Blasts**, by size: a white flash and a shockwave ring for small shells; the supernova's
261
+ treatment for nukes — flash, `gasCloud` smoke that rises and drifts on the wind,
262
+ `lensFlare`, a shockwave that lights the cubes it crosses (through the same per-tile
263
+ lighting the effects use, `heads` with a colour and a radius) and shakes the tanks it
264
+ reaches; Death's Head whites the sky out for a moment.
265
+ - **Dirt** falls with the engine's gravity and lands with its bounce; dust puffs where it
266
+ lands. A tank's death is a fireworks shell in its colour, the hull tumbling as a `poly`.
267
+ - **The last shot's trace** stays drawn until the next, as the original's did; Tracers keep
268
+ theirs for the turn.
269
+ - **Sound** through `tetsound.js`: a thump for the shot, a whistle that falls as the shell
270
+ does, a crack or a rumble for the blast by size (the synth gains a noise burst for this — a
271
+ short buffer of noise, still no files), a hiss for napalm, a chime for the turn, a fanfare
272
+ for a kill; a chiptune theme of its own with the music switch.
273
+ - **Talk**: a line over the tank when it fires or is hit, from the original's tables ("Nuke
274
+ 'em", "Just wait 'til my turn", "Oops"), a setting.
275
+
276
+ None of this is on the critical path: M1 plays with plain cubes, a flash and a ring.
277
+
278
+ ## 8. Controls and the side panel
279
+
280
+ Keys, following the original where it had them: **←/→** angle by 1° (Shift: 5°), **↑/↓**
281
+ power by 10 (Shift: 1, Ctrl: 100), **PgUp/PgDn** or **[ ]** weapon, **Tab** item, **Space** or
282
+ **Enter** fire, **A/D** drive (with fuel), **B** battery, **S** shield, **Esc** pause, **F**
283
+ fast shells (skips the flight's slow part), **N** next round from the scoreboard. Mouse: drag
284
+ from the tank to aim (direction is the angle, length is the power), click fire. Touch the same.
285
+
286
+ The side panel (the `.tethud` card): whose turn (name, colour), angle and power readouts, the
287
+ weapon with its count, cash, the wind gauge (an arrow with the value), every tank's health
288
+ bar, the round counter; Keys; Switches (stars, galaxy, music, sfx, fast shells, talk); High
289
+ scores. Between rounds the panel becomes the shop.
290
+
291
+ ## 9. Files, settings, wiring, tests
292
+
293
+ **Files** (the rules/screen split every game here keeps):
294
+
295
+ - `public/js/scorched.js` — rules, zero DOM: landscape generation, the dirt bitmap and its
296
+ tops, craters and settling, tanks, the shell integrator with walls and wind, damage, turns,
297
+ rounds, cash; `newGame(opts, seed)`, `fire(g, angle, power, weapon)`, `step(g, dtMs)`,
298
+ `tiles(g)`; every random choice from a seeded stream.
299
+ - `public/js/scorchedshop.js` — the weapon and item table (behaviour, price, pack, damage,
300
+ radius) as data; the shop's arithmetic.
301
+ - `public/js/scorchedai.js` — the personalities.
302
+ - `public/js/scorchedyard.js` — the screen: `FIELD` and `SKY` options, the frame loop with the
303
+ page/hidden pause, input, the panel and the shop DOM, the effects drawn over the board, the
304
+ sounds, `localStorage` scores, `renderScorchedYard(s, state, h)`.
305
+ - `public/js/tetsound.js` — the new sound names and a noise burst.
306
+
307
+ **Settings** (`settings.js`): a `scorched` group in the Diversions row with `stars`, `galaxy`,
308
+ `galaxyAt`, `sky` (space / dawn / noon / dusk / storm / random), `grid`, `gridColour`,
309
+ `gridBrightness`, `neon` (the tanks' finish), `sfx`, `music`, `talk`, `fastShells`, and the
310
+ game's rules that the original exposed: `players` (2–6), `ai` (the personalities per slot),
311
+ `rounds`, `walls`, `gravity`, `wind` (none / steady / changes each shot), `viscosity`,
312
+ `landscape`, `startCash`, `interest`, `deathBlast`. `scorchedOptions(s)` merges the sky's
313
+ make-up as `tetrustOptions` does.
314
+
315
+ **Wiring**: the menu button and the `<section class="page" data-page="scorched">` in
316
+ `index.html` (the `.tetrust` layout with a `.seyard` well at `aspect-ratio: 2 / 1`), the import,
317
+ `case` and `DIVERSION_PAGES` entry in `app.js`, the CSS rules beside the other wells.
318
+
319
+ **Tests**, mirroring `tetris.test.js` and `breakout.test.js` (headless, seeded):
320
+
321
+ - landscape: each style generates within bounds, tanks sit on the surface, the same seed gives
322
+ the same field;
323
+ - craters and settling: a blast removes exactly the cells inside the circle; dirt above a hole
324
+ falls until it rests; nothing floats after settling; dirt weapons add; diggers bore;
325
+ - shells: a shot at 45°/500 lands where the closed form says (no wind); wind shifts it; a
326
+ full-power shot at a coarse frame time does not tunnel a two-cell ridge; each wall mode does
327
+ what it says; the ceiling is open;
328
+ - weapons: MIRV splits into five at the apex; Leapfrog explodes three times; the Roller rolls
329
+ downhill and stops in the dip; napalm flows to the low side; the Laser is a line;
330
+ - damage and death: blast damage by distance, fall damage, the parachute, shields absorb and
331
+ the deflector bounces, death explodes with an inventory weapon and credits the killer;
332
+ - rounds and cash: kill cash, interest, the shop refuses what you cannot afford, packs add
333
+ their count, scores over a game;
334
+ - AI: the personality tests in §6;
335
+ - `tiles(g)` shape and stable ids; the field refuses hover; the sky canvas is separate;
336
+ - the canvas rules (`viewer-canvas-rules.test.js`) extended to the new screen module if it
337
+ draws; `web-contract`, `nav-menu` and `settings` tests pass by construction;
338
+ - `npm run counts:fix` after.
339
+
340
+ **Docs**: a USER-GUIDE section (playing, the roster, the AI, the switches), CHANGELOG, AGENTS.
341
+
342
+ ## 10. Milestones
343
+
344
+ Each milestone is playable at its end and lands as its own commit set, tests included.
345
+
346
+ | # | milestone | what is playable |
347
+ |---|---|---|
348
+ | M0 | this plan, signed off: name, defaults, v1 roster, sky default | — |
349
+ | M1 | **the core**: landscape, tanks, Baby Missile → Nuke, craters, settling, falls, damage, death, turns, one human vs two Morons, keys and mouse, plain flash and ring, the panel | a full round |
350
+ | M2 | **the roster**: every v1 weapon and item, the walls, wind modes, fuel, the checked price table, the shop between rounds, cash and interest, a game of N rounds, the scoreboard, high scores | the game — *landed 2026-09-16; the price table is as remembered, not yet checked against SCORCH.DOC* |
351
+ | M3 | **the AI**: Shooter, Tosser, Poolshark, Chooser, Spoiler, Cyborg, Unknown, AI shopping | a real opponent — *landed 2026-09-16* |
352
+ | M4 | **fabulous**: strata, day skies and weather, the tank models, the blast treatments, dirt dust, death fireworks, traces, sound and music, talk | the look — *landed 2026-09-16 (the Living sky serves as the day sky, with an hour per round)* |
353
+ | M5 | **finish**: the settings tab, Kiosk attract mode (if wanted), docs, the full test set, `counts:fix`, screenshots for the guide and the announcement | ship — *landed 2026-09-16; the attract mode is the game's own **watch** switch rather than a Kiosk panel: the Kiosk is the two boards and the chain, and a game among them would have pushed one of those off the wall* |
354
+
355
+ Rough size, from the three games that exist: M1 is the largest single step (about the size of
356
+ Blockanoid's first cut); M2 and M4 are each about that again; M3 and M5 are smaller.
357
+
358
+ ## 11. Decisions for the operator
359
+
360
+ 1. **The name.** Scorched Yard, or another.
361
+ 2. **Field size**: 80 × 40 cells (proposed), or finer (96 × 48) for smoother hills at some
362
+ frame cost on a small machine.
363
+ 3. **Default sky**: the star field with the galaxy (matches the other games), or a day sky.
364
+ 4. **Death blast** from the inventory: on by default (faithful) or off (kinder).
365
+ 5. **All-AI on the Kiosk**: worth having as an attract mode, or not.
366
+ 6. **v1 roster** as listed in §4, or a shorter first cut (missiles, nukes, MIRV, Funky Bomb,
367
+ Roller, Napalm, Dirt Clod, Riot Bomb, Tracer, Shield, Parachute) to reach M2 sooner.
368
+
369
+ ## 12. A better control system (scoped 2026-09-16; rescoped the same day; landed the same day)
370
+
371
+ *Landed: C1 (the ramp, the nudges, the wheel, typed numbers, R), then C2 (click a tank to mark it, the
372
+ miss on the panel, C to correct), C3 (the weapon grid on W, 1–9, Q, the item pills as buttons), C4
373
+ (the aim guide, short by default, beside cheat mode) and C5 (the fire button names the weapon, the
374
+ last of a kind asks once, the keys as a table). One change from the scope: the drag on the field
375
+ was kept, since it was in use, with a press on your own tank turning the barrel alone.*
376
+
377
+ The first scope proposed a catapult: press on your tank, pull back, release. The operator's answer
378
+ was that it sucks as a control scheme, and on inspection it does -- the drag that shipped in M1 is
379
+ already that gesture, and it is the weakest thing on the screen:
380
+
381
+ * **It is coarse.** The field is about 1,300 screen pixels for 1,000 units of power, and the pull
382
+ only uses the part of it between the tank and the pointer. Four or five units of power per pixel,
383
+ against the keyboard's one.
384
+ * **It forgets.** Every grab starts from wherever the pointer went down, so the aim you spent the
385
+ last turn finding is gone the moment you touch the field.
386
+ * **It cannot bracket.** Artillery is not aiming, it is CORRECTING: fire, watch, move the power by
387
+ half the miss, fire again. That is a sequence of small deltas on a remembered number, which is
388
+ the one thing a drag gesture is bad at.
389
+
390
+ So: the keyboard and the readouts are the control system, the pointer picks TARGETS rather than
391
+ setting numbers, and everything below is about making small corrections fast and exact.
392
+
393
+ ### C1 — the numbers, fast and exact. The one worth doing first
394
+
395
+ * **Hold to repeat, with a ramp.** A held arrow steps 1, then 2, then 5 after half a second, so
396
+ crossing 180 degrees takes a second and a half and the last degree still takes one press.
397
+ * **Modifiers that are written down**: Shift fine (1 power, 0.5 degree), Ctrl coarse (100 power,
398
+ 5 degrees). They exist today and nothing says so outside a paragraph of bullets.
399
+ * **`,` and `.` nudge the power by one**, the classic bracket, without a modifier.
400
+ * **The wheel over the field**: power, and with Shift the angle. The pointer is already there.
401
+ * **The readouts become inputs.** Click the angle or the power on the HUD and type the number; the
402
+ angle and power a player is reading off a previous shot go in directly.
403
+ * **`R` repeats the last shot exactly** -- same angle, same power, same weapon. The most missed
404
+ convenience in the original.
405
+
406
+ ### C2 — correcting, which is what the game is actually made of
407
+
408
+ Click a tank (or press `1`..`4`) to mark it as your TARGET. The HUD then shows, under the power,
409
+ **where your last shot landed relative to it**: "12 short" or "5 over". One key, `C`, applies the
410
+ classic halving correction to the power in that direction -- the Tosser's own arithmetic, offered
411
+ to the human. Nothing is solved for you: the correction is from YOUR last shot, so wind changes
412
+ and the hills are still yours to read. Off is a setting for anyone who wants the 1991 experience.
413
+
414
+ ### C3 — the weapon grid, and the items as buttons
415
+
416
+ `[` and `]` stay. Beside them:
417
+
418
+ * **a grid** dropping out of the weapon line: what you own, counts and blast radii, biggest first,
419
+ click to pick, `W` to open, Escape to close;
420
+ * **1--9 for the nine you own**, in shop order, which is how a Scorched Earth player thinks ("the
421
+ nukes are on 4") -- when a target is marked they still pick the target, so weapons take a
422
+ modifier or their own row: settled at build time, whichever reads better with C2;
423
+ * **`Q` for the last weapon fired**, so a ranging Baby Missile then the real thing is two keys;
424
+ * **the item pills become buttons**. They already name themselves and their key; they should take
425
+ a click too.
426
+
427
+ ### C4 — the ranging arc, optional
428
+
429
+ A ghost path from the muzzle under the current angle, power, wind and gravity, drawn as dim beads
430
+ on the flat plane the wind uses. A setting, because it changes the game: **off** (the original),
431
+ **short** -- the first fifth of the flight, enough to read the lean without giving the landing
432
+ away -- or **full**. Default short. One truncated `simulateShot` per change of aim.
433
+
434
+ ### C5 — what the panel says
435
+
436
+ The **fire button carries the weapon and its count** ("fire · Nuke ×1"), so an expensive shot is
437
+ visible before it goes, and it asks once before the last of a one-of-a-kind (a setting, on by
438
+ default: the most common regret in the original). The keys legend becomes a two-column table with
439
+ the modifiers written out.
440
+
441
+ ### The pointer, after all this
442
+
443
+ The drag that sets angle and power goes. In its place: **a click on the field marks a target**
444
+ (C2), **a drag with the right button pans nothing and cancels** (there is nothing to pan), and a
445
+ **press on your own tank still turns the barrel** for a coarse first approximation, angle only,
446
+ power untouched -- which is the one part of the gesture that was worth keeping, because the angle
447
+ is the cheap half of the aim and the power is the half you bracket. Touch gets the same, with the
448
+ HUD's fire button and the pills, which are already thumb-sized.
449
+
450
+ ### Order, size and risk
451
+
452
+ C1 and C2 change how the game feels and neither touches the rules: both are `scorchedyard.js`, its
453
+ key and pointer handlers, and `aim()` doing the committing as it does now. C3 is a menu and some
454
+ buttons. C4 needs a setting and a truncated `simulateShot`. C5 is copy and one confirm. Nothing
455
+ here needs a change in `scorched.js`, so the tests that hold the physics keep holding them; the new
456
+ tests are the key map, the ramp's arithmetic, and the correction C2 applies for a given miss.
@@ -0,0 +1,142 @@
1
+ # Plan: one sky per board — Galaxy or Earth
2
+
3
+ *Landed 2026-09-16, as scoped, in one change: the schema and its idempotent migration, the Sky tab's
4
+ map and two headings, one Sky choice per board tab, the games' one sky button, the rename, the docs.
5
+ The operator's own installation came forward as every board Galaxy and Scorched Yard none (its
6
+ stars had been off), and was set to Earth through the new map, one click.*
7
+
8
+ Operator, 2026-09-16: "The skybox settings are not clear in the preferences, for which sky settings
9
+ apply to which panel. eg: I want the sky for Scorched Yard to be the Earth Sky. The Galaxy view
10
+ should be the default background for all other 3D backgrounds."
11
+
12
+ ## 1. Why it is confusing today
13
+
14
+ The settings describe the sky twice, from two directions, and neither says which board it is
15
+ talking about.
16
+
17
+ * **One global choice.** Display settings → Sky → *Sky* picks Space or Living sky **for every
18
+ board at once**. Wanting Earth over the artillery and the galaxy behind the candles is not a
19
+ thing the sheet can say.
20
+ * **Six scattered "Star field" switches.** Block space, Markets, Tetrust, Blockout, Blockanoid and
21
+ Scorched Yard each carry a `stars` toggle in their own tab. It reads as "stars on or off", but
22
+ under the Living sky it means "draw the day or draw nothing" — which is the black-sky report
23
+ from earlier today.
24
+ * **Four private galaxy switches.** The games each carry a `galaxy` and a `galaxyAt` of their own,
25
+ beside the Sky tab's galaxy and galaxyAt, so the same spiral is switched in five places.
26
+ * **Nothing joins them up.** No page says "Block space: Space · Scorched Yard: Earth". The player
27
+ has to hold the mapping in their head, and the mapping is one global switch plus six local ones.
28
+
29
+ ## 2. The shape it should have
30
+
31
+ **Two skies, named as things; each board picks one: Galaxy or Earth.**
32
+
33
+ (Operator, later the same day: "Space is too confusing with 'Block Space' — let's rename it
34
+ 'Galaxy'." So the star-field sky is the **Galaxy** sky throughout, and the word "space" is not a
35
+ sky anywhere in the sheet. The one thing it collides with is the Galaxy sky's own *Spiral galaxy*
36
+ toggle, which lays the stars on arms or scatters them: that row becomes **Spiral arms**, and its
37
+ placement row **Arms centre**.)
38
+
39
+ * The **Sky** tab becomes a catalogue with a summary at the top:
40
+
41
+ ```
42
+ WHICH SKY, WHERE
43
+ Block space ·········· Galaxy (also the Kiosk's left panel)
44
+ Markets & Price ······ Galaxy (also the Kiosk's right panel)
45
+ Tetrust ·············· Galaxy
46
+ Blockout ············· Galaxy
47
+ Blockanoid ··········· Galaxy
48
+ Scorched Yard ········ Earth
49
+ ```
50
+
51
+ Every row is a select: **Galaxy · Earth · None**. Changing it here is the same as changing it in
52
+ the board's own tab. This table is the whole answer to "which settings apply to which panel".
53
+
54
+ * Below it, **two titled sections**, each describing one sky and nothing else:
55
+ * **Galaxy** — the star field, its density and brightness, the spiral arms and where their
56
+ centre sits, nebulae, dust lanes, clusters, distant galaxies, colours, glints.
57
+ * **Earth** — the clock (real, a day every 24 minutes, a fixed hour), the weather, cloud cover,
58
+ latitude, sun rays, rainbow, shooting stars.
59
+
60
+ Neither section is ever dimmed: a sky's controls are always live, because some board may be
61
+ using it. The dimming added this morning goes away, since it was compensating for the global
62
+ switch.
63
+
64
+ * Each **board tab** carries exactly one sky control: **Sky: Galaxy / Earth / None**, first in its
65
+ group, with a hint that points at the Sky tab for the make-up. Its `stars`, `galaxy` and
66
+ `galaxyAt` rows are gone. The arms' placement stays a property of the Galaxy sky, set once.
67
+
68
+ * The games' **panel switches** follow: the `stars` and `galaxy` buttons on Tetrust, Blockout,
69
+ Blockanoid and Scorched Yard become one **sky** button that cycles Galaxy → Earth → None and shows
70
+ which it is on.
71
+
72
+ * **"Living sky" is renamed "Earth sky"** everywhere, which is what the operator calls it and is
73
+ shorter.
74
+
75
+ ## 3. Defaults
76
+
77
+ Galaxy for every board — it is BlockYard's signature — except **Scorched Yard, which ships with
78
+ Earth**: an artillery duel wants a day and a horizon, and it already turns each round to its
79
+ own hour. Nothing else changes look by default.
80
+
81
+ ## 4. What existing settings become
82
+
83
+ The stored shape today is one `sky.type` plus six `<board>.stars`. The new shape is one
84
+ `<board>.sky` per board, with values `galaxy | earth | none`. The mapping that preserves exactly
85
+ what every installation draws right now:
86
+
87
+ | stored today | becomes |
88
+ |---|---|
89
+ | `<board>.stars: false` | `<board>.sky: 'none'` |
90
+ | `<board>.stars: true` and `sky.type: 'space'` | `<board>.sky: 'galaxy'` |
91
+ | `<board>.stars: true` and `sky.type: 'living'` | `<board>.sky: 'earth'` |
92
+ | `<board>.galaxy`, `<board>.galaxyAt` (the games') | dropped; `sky.galaxy` / `sky.galaxyAt` stand |
93
+ | `sky.type` | dropped |
94
+
95
+ Because settings stored on the server carry no schema version, a migration there runs on **every
96
+ boot** — so this one is written to be idempotent: it acts only when the old key is present and the
97
+ new one is absent, and then removes the old key. A store already in the new shape is untouched.
98
+ (The same fact is why the wind mode was not migrated this morning; here the transform is a rename,
99
+ which is safe to repeat.)
100
+
101
+ The operator's own installation comes forward as: every board Galaxy except Scorched Yard, which
102
+ has `stars: false` under `type: 'living'` today — that would become `none`, which is not what was
103
+ asked for. The migration special-cases nothing; the sheet's new table makes it a one-click change,
104
+ and the plan's landing note will say so.
105
+
106
+ ## 5. Where the code changes
107
+
108
+ * `public/js/settings.js` — DEFAULTS: `sky.type` out, `<board>.sky` in; the games' galaxy keys out.
109
+ PANEL_GROUPS: the Sky group rebuilt as summary + two sections (the summary is a new row kind,
110
+ `skymap`, that app.js renders as the table); each board group gets its `sky` choice row.
111
+ `skyFor(n, board)` replaces `skyExtras` + the per-board `stars` plumbing: it answers `{ stars,
112
+ skyType, ...earth, ...deepSky }` for a board, and the seven option builders call it. Migration
113
+ `MIGRATIONS[5]`, idempotent as above; `SCHEMA_VERSION` 6.
114
+ * `public/js/app.js` — renders the `skymap` row as the table of selects, wired to the same
115
+ `data-cfgset` path as any choice, so a change in either place is one change.
116
+ * `public/js/details3d.js` — reads `skyType === 'earth'` (accepting `'living'` for one release);
117
+ the star field is drawn for `'galaxy'` (and `'space'` for one release).
118
+ * The boards — `mining.js`, `markets.js`, `kiosk.js`, `tetrust.js`, `blockout.js`, `blockanoid.js`,
119
+ `scorchedyard.js`: take their sky from `skyFor`; the games' panel switch becomes the cycle
120
+ button; `index.html` loses the four `stars` / `galaxy` buttons and gains four `sky` buttons.
121
+ * Docs — USER-GUIDE's Sky section rewritten around the table; CONFIGURATION's settings keys;
122
+ CHANGELOG.
123
+ * Tests — `settings.test.js` (shape, defaults, the migration on all four stored combinations, and
124
+ that it is idempotent when run twice), `finish.test.js` (each option builder's `stars`/`skyType`
125
+ for each value), the games' page-wiring tests (the switch ids), `viewer-canvas-rules` unchanged.
126
+ A test also holds the sheet to never labelling a sky "Space": the word belongs to Block space.
127
+
128
+ ## 6. Size and order
129
+
130
+ About a day's change across settings, the seven boards and their tests; no renderer work beyond
131
+ the one rename. Landing order: schema and migration first (with the tests that prove nothing
132
+ changes on screen), then the Sky tab, then the boards one at a time, then the panel switches and
133
+ the rename, then the docs. Each step leaves the suite green and the boards drawing what they drew.
134
+
135
+ ## 7. What is deliberately not in this
136
+
137
+ * No per-board Earth parameters (a different hour for the candles than for the artillery). One
138
+ Earth, one Galaxy; a board chooses between them. Scorched Yard's per-round hour is the game's own
139
+ business and stays.
140
+ * No third sky. The catalogue shape makes adding one a section and a value, later.
141
+ * The internal option name `space` on the Block space board (its deck texture and floor style) is
142
+ not renamed: it is a board style, not a sky, and nothing in the sheet shows the word.