blockyard 0.0.9 → 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.
- package/CHANGELOG.md +359 -1
- package/README.md +48 -27
- package/SECURITY.md +2 -2
- package/bin/blockyard.js +2 -1
- package/docs/API.md +17 -15
- package/docs/ARCHITECTURE.md +128 -10
- package/docs/CONFIGURATION.md +39 -30
- package/docs/DEFECTS.md +4 -1
- package/docs/GETTING-STARTED.md +18 -8
- package/docs/INSTALL.md +97 -37
- package/docs/MEASUREMENTS.md +147 -0
- package/docs/PLAN-SCORCHED-YARD.md +456 -0
- package/docs/PLAN-SKIES.md +142 -0
- package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
- package/docs/SECURITY.md +58 -22
- package/docs/TROUBLESHOOTING.md +44 -5
- package/docs/USER-GUIDE.md +505 -35
- package/package.json +4 -2
- package/public/404.html +1 -1
- package/public/css/app.css +393 -82
- package/public/donate-qr.png +0 -0
- package/public/index.html +353 -109
- package/public/js/agents.js +228 -51
- package/public/js/app.js +131 -16
- package/public/js/blockanoid.js +15 -7
- package/public/js/blockout.js +15 -7
- package/public/js/blockscene3d.js +230 -38
- package/public/js/charts.js +21 -21
- package/public/js/depthchart.js +31 -27
- package/public/js/details3d.js +1481 -73
- package/public/js/doom.js +31 -0
- package/public/js/dosaudio.js +48 -0
- package/public/js/dosgame.js +389 -0
- package/public/js/dosio.js +186 -0
- package/public/js/dospc.js +1353 -0
- package/public/js/dosworker.js +196 -0
- package/public/js/explorer.js +7 -1
- package/public/js/livingsky.js +494 -0
- package/public/js/login.js +8 -2
- package/public/js/markets.js +46 -8
- package/public/js/mining.js +314 -36
- package/public/js/panels.js +41 -28
- package/public/js/pricechart.js +14 -13
- package/public/js/quake.js +20 -0
- package/public/js/safenext.js +14 -0
- package/public/js/scorched.js +1051 -0
- package/public/js/scorchedai.js +227 -0
- package/public/js/scorchedair.js +286 -0
- package/public/js/scorchedfx.js +376 -0
- package/public/js/scorchedshop.js +105 -0
- package/public/js/scorchedwind.js +69 -0
- package/public/js/scorchedyard.js +1338 -0
- package/public/js/settings.js +368 -100
- package/public/js/soundcard.js +459 -0
- package/public/js/tetrust.js +15 -6
- package/public/js/tetsound.js +35 -5
- package/public/js/theme.js +235 -0
- package/public/js/wolf3d.js +22 -0
- package/public/js/x86.js +1978 -0
- package/scripts/check.js +46 -0
- package/scripts/donate-qr.py +12 -9
- package/scripts/dos-bench.js +56 -0
- package/scripts/index-build.js +9 -2
- package/scripts/pool-map.js +152 -36
- package/scripts/setup.js +142 -22
- package/scripts/shots.mjs +27 -0
- package/scripts/smoke.sh +7 -6
- package/scripts/tls.js +31 -0
- package/scripts/ui.js +4 -2
- package/server/auth/sessions.js +33 -13
- package/server/chain/blockfile.js +64 -5
- package/server/chain/index/build.js +451 -58
- package/server/chain/index/heights.js +29 -3
- package/server/chain/index/live.js +13 -7
- package/server/chain/index/rows.js +6 -1
- package/server/chain/index/store.js +28 -5
- package/server/chain/index/worker.js +23 -11
- package/server/collect/logparse.js +65 -18
- package/server/collect/markets.js +76 -7
- package/server/collect/mining.js +32 -0
- package/server/collect/monitor.js +54 -12
- package/server/collect/network.js +305 -0
- package/server/config.js +53 -22
- package/server/http/api.js +119 -18
- package/server/http/games.js +77 -0
- package/server/http/server.js +30 -5
- package/server/http/sse.js +53 -7
- package/server/main.js +66 -11
- package/server/rpc/allowlist.js +26 -0
- package/server/rpc/client.js +30 -2
- package/server/store/audit.js +6 -1
- package/server/store/history.js +19 -3
- package/server/store/ledger.js +15 -4
- package/server/tls/selfsigned.js +160 -0
- package/systemd/blockyard.service +41 -8
- package/docs/PRIVATE-LEADERBOARD.md +0 -230
- package/docs/STATE-2026-09-09.md +0 -200
package/public/js/settings.js
CHANGED
|
@@ -19,9 +19,27 @@
|
|
|
19
19
|
// survivable, and `sky` below is the first one to take it.
|
|
20
20
|
|
|
21
21
|
export const SETTINGS_KEY = 'blockyard.settings';
|
|
22
|
-
export const SCHEMA_VERSION =
|
|
22
|
+
export const SCHEMA_VERSION = 6;
|
|
23
23
|
|
|
24
24
|
export const DEFAULTS = Object.freeze({
|
|
25
|
+
// APPEARANCE (operator, 2026-09-16: "add an appearance section in preferences to change the colors
|
|
26
|
+
// of our layout. Our current scheme should be the default appearance ... as well as options for
|
|
27
|
+
// configure custom schemes by allowing users to pick colors and save to settings so it sticks").
|
|
28
|
+
// The faces themselves live in theme.js; this holds the choice and the nine custom colours, whose
|
|
29
|
+
// defaults are the shipped dark look so that Custom starts where the page starts.
|
|
30
|
+
appearance: Object.freeze({
|
|
31
|
+
mode: 'dark', // 'light' | 'dark' | 'system' -- which face of the theme
|
|
32
|
+
theme: 'blockyard', // a preset in theme.js PRESETS, or 'custom'
|
|
33
|
+
customBg: '#0b0d10', // the page
|
|
34
|
+
customPanel: '#12151a', // cards, the header, the settings sheet
|
|
35
|
+
customText: '#dfe5ee',
|
|
36
|
+
customMuted: '#96a0b0',
|
|
37
|
+
customAccent: '#f7931a',
|
|
38
|
+
customLine: '#262c36',
|
|
39
|
+
customOk: '#2ecc8f',
|
|
40
|
+
customWarn: '#f0b429',
|
|
41
|
+
customBad: '#ef5a5a',
|
|
42
|
+
}),
|
|
25
43
|
space: Object.freeze({
|
|
26
44
|
// OFF by default (operator, 2026-09-12: "make simple cubes the default, disable shadows by
|
|
27
45
|
// default"). Shadows are the costliest single thing the board draws -- one per resting stone
|
|
@@ -59,7 +77,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
59
77
|
// ON (operator, 2026-09-13: "the current settings I have saved out should be the shipping
|
|
60
78
|
// defaults"). It was opt-in because a twinkling field means the board never stops repainting.
|
|
61
79
|
// That cost is now paid deliberately rather than avoided: it is the look that was chosen.
|
|
62
|
-
|
|
80
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none' -- which sky this board draws (docs/PLAN-SKIES.md)
|
|
63
81
|
dome: 5, // how far the board bows toward the viewer, 0 = flat
|
|
64
82
|
// HEIGHT THAT FORESHORTENS (operator, 2026-09-13: "I'm not seeing the bottom or the front face
|
|
65
83
|
// changing at all during movement ... fix camera stuff"). The oblique camera is parallel: height
|
|
@@ -81,6 +99,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
81
99
|
// ten stops. A stored value above it clamps on load.
|
|
82
100
|
perspective: 0, // 0 = the parallel camera; 0.001 the most allowed
|
|
83
101
|
light: 'overhead', // where the lamp is (operator, 2026-09-12: "directly above the board centered")
|
|
102
|
+
lightHeight: 'middle', // and how high it hangs when it is not overhead: 'low' | 'middle' | 'high'
|
|
84
103
|
detail: 'simple', // 'full' | 'simple' | 'flat' -- facet and crown thresholds below; simple by default
|
|
85
104
|
motion: 'full', // 'full' | 'quick' | 'still' -- the refresh choreography
|
|
86
105
|
// HOW CUBES LEAVE AND ARRIVE (operator, 2026-09-13: "all the left and right side blocks are
|
|
@@ -100,11 +119,24 @@ export const DEFAULTS = Object.freeze({
|
|
|
100
119
|
// unknown choice to this default, so those boards land on 'normal' rather than on nothing.
|
|
101
120
|
departures: 'normal', // 'normal' | 'arcing'
|
|
102
121
|
}),
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
122
|
+
// TWO SKIES, GALAXY AND EARTH (docs/PLAN-SKIES.md; operator, 2026-09-16: "The skybox settings are
|
|
123
|
+
// not clear in the preferences, for which sky settings apply to which panel"). This group is what
|
|
124
|
+
// each sky is MADE of. WHICH sky a board draws is that board's own `sky` key -- 'galaxy', 'earth'
|
|
125
|
+
// or 'none' -- and the Sky tab opens with a table of every board's choice. There is no global
|
|
126
|
+
// switch any more: the one that was here picked Space or Living for every board at once, beside
|
|
127
|
+
// six scattered "Star field" toggles, and nobody could tell which board drew what.
|
|
107
128
|
sky: Object.freeze({
|
|
129
|
+
// THE EARTH SKY (operator: "something realistic with blue skies, clouds, the sun, change of
|
|
130
|
+
// day, night with the moon coming out"; livingsky.js)
|
|
131
|
+
clock: 'real', // 'real' (this machine's time) | 'cycle' (a day every 24 minutes) | 'fixed' (the hour below)
|
|
132
|
+
hour: 17.5, // the hour to admire when the clock is fixed
|
|
133
|
+
weather: 'scattered', // 'clear' | 'scattered' | 'overcast' | 'storm'
|
|
134
|
+
cover: -1, // cloud cover 0..1; -1 leaves it to the weather
|
|
135
|
+
lat: -100, // latitude for real sunrise and season; -100 means "not set": a six-to-six day
|
|
136
|
+
rays: true, // crepuscular rays when the sun is low
|
|
137
|
+
rainbow: true, // a rainbow opposite a low sun in scattered weather
|
|
138
|
+
shooting: true, // shooting stars at night
|
|
139
|
+
moon: 'night', // 'night' (up every night, highest at midnight) | 'real' (its real track and phase)
|
|
108
140
|
density: 3, // multiplies the star count (0.2 .. 3) -- the shipped look sits at the top of the range
|
|
109
141
|
brightness: 1, // multiplies each star's alpha (0.2 .. 1.5)
|
|
110
142
|
galaxy: true, // the same stars laid on spiral arms, turning once a quarter hour
|
|
@@ -123,7 +155,11 @@ export const DEFAULTS = Object.freeze({
|
|
|
123
155
|
// the grid glow. that's just terrible"). Never-show makes the switch a control nobody may use,
|
|
124
156
|
// and a control that must stay off is worse than no control: the board forces it off now.
|
|
125
157
|
markets: Object.freeze({
|
|
126
|
-
|
|
158
|
+
// THE SWITCH (operator, 2026-09-15: "an app wide 'Enable Market Polling' checkbox"): off out
|
|
159
|
+
// of the box, so a fresh monitor makes no outbound connection but to the node. The server
|
|
160
|
+
// reads it from the shared settings file on every market request (http/api.js).
|
|
161
|
+
polling: false,
|
|
162
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
127
163
|
effects: true, // the idle effects and the flight when the candles refresh
|
|
128
164
|
// THE TOOLBAR REMEMBERS (operator, 2026-09-12: "We need to remember the user settings for the
|
|
129
165
|
// Markets page"). Which exchange and how many hours were a click that survived until the tab
|
|
@@ -166,9 +202,12 @@ export const DEFAULTS = Object.freeze({
|
|
|
166
202
|
// for, and the board picks among whatever is left on (scheduleFx). Turn them all off and the
|
|
167
203
|
// board simply rests, which `idleFx` also does in one click.
|
|
168
204
|
effects: Object.freeze({
|
|
169
|
-
ripple: true, outline: true, tide: true, cascade: true, twinkle: true, scan: true,
|
|
205
|
+
ripple: true, outline: true, tide: true, cascade: true, twinkle: true, scan: true, xray: true,
|
|
170
206
|
lightcycle: true, ball: true,
|
|
171
|
-
|
|
207
|
+
// FIREWORKS ARE KEPT FOR OCCASIONS (operator, 2026-09-15: "make it disabled by default. I want
|
|
208
|
+
// to re-use that one for special occasions in the future, and don't want it in the rotation
|
|
209
|
+
// unless user selects it"): off on both boards until someone ticks it
|
|
210
|
+
shockwave: true, nova: true, firework: false, flare: true, wave: true, quake: true,
|
|
172
211
|
rain: true, sparkle: true, checker: true, radar: true, vortex: true, powerup: true, combo: true, aurora: true, plasma: true,
|
|
173
212
|
// the agents: something happening on the board, rather than a pattern over it
|
|
174
213
|
centipede: true, tractor: true, missile: true, boulderdash: true, stormball: true,
|
|
@@ -192,10 +231,14 @@ export const DEFAULTS = Object.freeze({
|
|
|
192
231
|
// should ship with ... Many of the effects don't translate over to the market chart"). Its own
|
|
193
232
|
// no-repeat window too. All on, twelve.
|
|
194
233
|
marketEffects: Object.freeze({
|
|
195
|
-
ripple: true, outline: true, tide: true, cascade: true, twinkle: true, scan: true,
|
|
234
|
+
ripple: true, outline: true, tide: true, cascade: true, twinkle: true, scan: true, xray: true,
|
|
196
235
|
pulse: true, // the surge that runs the price line
|
|
197
236
|
bulge: true, // a sphere rolls through the pipe and it swells round it
|
|
198
|
-
|
|
237
|
+
breathe: true, // the line breathes between the wire and the pulse's heat
|
|
238
|
+
saber: true, // the line ignites as a light saber
|
|
239
|
+
blackhole: true, // the chart collapses into a black hole
|
|
240
|
+
firework: false, // kept for occasions, off until ticked (see the block board's list)
|
|
241
|
+
flare: true, wave: true, stormball: true,
|
|
199
242
|
noRepeat: 12,
|
|
200
243
|
// no firstAfter here: the candle board does not land (operator, 2026-09-14: "there is no
|
|
201
244
|
// 'landing' for the markets display"); after a refresh its first effect keeps the cadence below
|
|
@@ -206,9 +249,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
206
249
|
// in another tab, using our engine"). The same shape as the Tetrust group: the game says whether
|
|
207
250
|
// there is a sky and a galaxy; what the sky is MADE of comes from the Sky group.
|
|
208
251
|
blockout: Object.freeze({
|
|
209
|
-
|
|
210
|
-
galaxy: true,
|
|
211
|
-
galaxyAt: 'center',
|
|
252
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
212
253
|
neon: false, // the bricks as dim bodies under lit tubes
|
|
213
254
|
neonSource: 'brick', // 'brick' (each row's own colour) | 'colour'
|
|
214
255
|
neonColour: '#3d8bff',
|
|
@@ -223,9 +264,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
223
264
|
// engine, and make it a new Diversion called 'Blockanoid'"). Blockout's shape plus the two
|
|
224
265
|
// switches Arkanoid earns: whether capsules fall at all, and whether the minions turn up.
|
|
225
266
|
blockanoid: Object.freeze({
|
|
226
|
-
|
|
227
|
-
galaxy: true,
|
|
228
|
-
galaxyAt: 'center',
|
|
267
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
229
268
|
neon: false,
|
|
230
269
|
neonSource: 'brick', // 'brick' (the brick's own colour) | 'colour'
|
|
231
270
|
neonColour: '#3d8bff',
|
|
@@ -242,9 +281,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
242
281
|
// those settings in the game and have them persistent in settings"). The sky is the panel's own
|
|
243
282
|
// canvas behind the well, so it costs the game nothing per key press.
|
|
244
283
|
tetrust: Object.freeze({
|
|
245
|
-
|
|
246
|
-
galaxy: true, // the spiral galaxy in it
|
|
247
|
-
galaxyAt: 'center', // where its centre sits: behind the title
|
|
284
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none' -- the panel's own canvas behind the well
|
|
248
285
|
// the landing marker's colour (operator: "I want to be able to set the ghost wireframe
|
|
249
286
|
// color"). Its own setting, not the neon tubes': the wireframe is drawn instead of a block,
|
|
250
287
|
// so the neon finish never touches it.
|
|
@@ -260,6 +297,33 @@ export const DEFAULTS = Object.freeze({
|
|
|
260
297
|
gridColour: '#1844bf', // blue rather than the engine green
|
|
261
298
|
gridBrightness: 1.55, // and above full, which the blue needs to read at all
|
|
262
299
|
}),
|
|
300
|
+
// SCORCHED YARD (operator, 2026-09-16; docs/PLAN-SCORCHED-YARD.md): the sky and the sound as the
|
|
301
|
+
// other games have them, and the rules the original exposed in its own menus
|
|
302
|
+
scorched: Object.freeze({
|
|
303
|
+
sky: 'earth', // an artillery duel wants a day and a horizon (docs/PLAN-SKIES.md §3)
|
|
304
|
+
sfx: true,
|
|
305
|
+
music: true, // the march
|
|
306
|
+
talk: true, // what the tanks say, over the field
|
|
307
|
+
roundSky: true, // under the Living sky, each round draws its own hour
|
|
308
|
+
fast: false, // shells fly at three times the pace
|
|
309
|
+
cheat: false, // cheat mode: the firing solution drawn live, wind and gravity and all
|
|
310
|
+
aimGuide: 'short', // 'off' | 'short' (the first fifth of the flight) | 'full': the ranging arc while you aim
|
|
311
|
+
helper: true, // mark a target, see how your last shot missed it, and C corrects the power
|
|
312
|
+
confirmLast: true, // the last of a one-of-a-kind weapon asks once before it goes
|
|
313
|
+
demo: false, // attract mode: every seat is a computer player, and it plays on by itself
|
|
314
|
+
grid: false, // the quiet grid under the field, off: the land is the picture
|
|
315
|
+
gridColour: '#2a5a8f',
|
|
316
|
+
gridBrightness: 1,
|
|
317
|
+
opponents: 2, // computer players against the one human; up to seven, for the original's eight seats
|
|
318
|
+
opponentKind: 'mix', // 'mix' (Shooter, Tosser, Chooser, Spoiler, Cyborg, Poolshark in turn) or one of the manual's eight
|
|
319
|
+
rounds: 5,
|
|
320
|
+
walls: 'none', // the manual's default: 'none' | 'concrete' | 'padded' | 'rubber' | 'spring' | 'wrap'
|
|
321
|
+
wind: 'round', // 'round' (one wind a round, the shipped mode) | 'turn' | 'shot' | 'none'
|
|
322
|
+
gravity: 1, // 1 = Earth
|
|
323
|
+
land: 'hills', // 'hills' | 'mountains' | 'valley' | 'flat'
|
|
324
|
+
cash: 10000, // to start, dollars
|
|
325
|
+
interest: 5, // per cent, on what is left between rounds
|
|
326
|
+
}),
|
|
263
327
|
});
|
|
264
328
|
|
|
265
329
|
const DETAIL = {
|
|
@@ -287,15 +351,19 @@ const FX_ROW = Object.freeze({
|
|
|
287
351
|
tide: Object.freeze({ label: 'Tide', hint: 'A swell that lifts the blocks it passes under' }),
|
|
288
352
|
cascade: Object.freeze({ label: 'Cascade', hint: 'The blocks light in feerate order, richest first' }),
|
|
289
353
|
twinkle: Object.freeze({ label: 'Twinkle', hint: 'Scattered blocks flash white, each on its own beat' }),
|
|
290
|
-
scan: Object.freeze({ label: 'Scan line', hint: 'A
|
|
354
|
+
scan: Object.freeze({ label: 'Scan line', hint: 'A curtain of light standing on the board, floor to top, sweeping across: a white core, soft cyan faces, raster rippling down it, a bar it hangs from, a glowing foot with a phosphor tail, motes in the beam' }),
|
|
355
|
+
xray: Object.freeze({ label: 'X-ray', hint: 'A front sweeps the board and everything behind it goes x-ray -- bodies to glass, edges and a raster lit -- then develops back to solid' }),
|
|
291
356
|
lightcycle: Object.freeze({ label: 'Light cycles', hint: 'Two riders from opposite edges, leaving light walls, until one crashes' }),
|
|
292
357
|
ball: Object.freeze({ label: 'Lightning ball', hint: 'A plasma ball tracing the grid, throwing bolts and a dust trail' }),
|
|
293
358
|
pulse: Object.freeze({ label: 'Energy pulse', hint: 'The surge that runs the price line on Markets, blue behind the head' }),
|
|
294
359
|
bulge: Object.freeze({ label: 'Pipe bulge', hint: 'On Markets: a glowing sphere rolls through the price line left to right, and the pipe swells around it as it passes' }),
|
|
360
|
+
breathe: Object.freeze({ label: 'Breathe', hint: 'The price line breathes: three slow swells from the plain wire to the pulse\u2019s white heat and back' }),
|
|
361
|
+
saber: Object.freeze({ label: 'Light saber', hint: 'The price line ignites from its left end as a light saber -- blue, green, red or purple -- hums, spits sparks, and retracts' }),
|
|
362
|
+
blackhole: Object.freeze({ label: 'Black hole', hint: 'A point of darkness opens on the price line and grows, then drifts slowly across the chart from one side to the other: the line bends round it, the candles nearest are swallowed into orbit, and an accretion disk of the chart\u2019s own colours spirals in round the shadow -- brighter on the side coming toward you, its far side arched over the top -- until it lets everything go' }),
|
|
295
363
|
shockwave: Object.freeze({ label: 'Shockwave', hint: 'A hard ring that throws the blocks it passes into the air' }),
|
|
296
364
|
nova: Object.freeze({ label: 'Nova', hint: 'An implosion to the middle, then a brighter blast back out' }),
|
|
297
|
-
firework: Object.freeze({ label: 'Fireworks', hint: '
|
|
298
|
-
flare: Object.freeze({ label: '
|
|
365
|
+
firework: Object.freeze({ label: 'Fireworks', hint: 'A display of up to ten shells, each at its own moment and place, with its smoke. Off by default -- kept for special occasions; tick it to put it in the rotation' }),
|
|
366
|
+
flare: Object.freeze({ label: 'Supernova', hint: 'One block goes supernova: a star swells and blows out in a white-out, a debris cloud of gas expands and cools white to blue to violet, a shockwave throws what it crosses, and a pulsar is left beating at the centre' }),
|
|
299
367
|
wave: Object.freeze({ label: 'Wave', hint: 'Several crests rolling across the board, the blocks riding them' }),
|
|
300
368
|
quake: Object.freeze({ label: 'Quake', hint: 'The board shakes, hardest at the start, and settles' }),
|
|
301
369
|
rain: Object.freeze({ label: 'Code rain', hint: 'A drop falls down every column with a white head and a green tail' }),
|
|
@@ -315,12 +383,13 @@ const FX_ROW = Object.freeze({
|
|
|
315
383
|
});
|
|
316
384
|
// where an effect reads differently on the price board, the price board's hint
|
|
317
385
|
const MARKET_HINT = Object.freeze({
|
|
386
|
+
blackhole: 'A point of darkness opens on the price line and grows, then drifts slowly along the hours from one side to the other: the line bends round it, the candles nearest are swallowed into orbit, and an accretion disk of the chart\u2019s own colours spirals in round the shadow -- brighter on the side coming toward you, its far side arched over the top -- until it lets everything go',
|
|
318
387
|
twinkle: 'Scattered candles flash white, each on its own beat',
|
|
319
|
-
pulse: 'The surge that runs the price line
|
|
388
|
+
pulse: 'The surge that runs the price line: the tube swells round the head and goes white-hot behind it',
|
|
320
389
|
bulge: 'A glowing sphere rolls through the price line left to right, and the pipe swells around it as it passes',
|
|
321
390
|
stormball: 'An electric blue sphere in a nebula flies through the chart, striking candles and charging the price line where it passes; a struck candle often throws a green arc on to another, and that one sometimes on to a third',
|
|
322
391
|
cascade: 'The candles light in order, tallest first',
|
|
323
|
-
flare: 'One candle goes supernova and
|
|
392
|
+
flare: 'One candle goes supernova: it swells white-hot, blows out in a flash and a lens flare, a shockwave rings out across the chart, plasma is flung on every side, and a ring nebula expands and cools for the rest of the run',
|
|
324
393
|
});
|
|
325
394
|
// THE WINDOW'S TOP IS THE LIST'S LENGTH (operator, 2026-09-14: "for the markets page, all we have
|
|
326
395
|
// is 12 effects, so max the slider out at max effects"): a window wider than the list is the same
|
|
@@ -349,7 +418,56 @@ export function fxCadence(g) {
|
|
|
349
418
|
return { idleEvery, idleFirst };
|
|
350
419
|
}
|
|
351
420
|
|
|
352
|
-
|
|
421
|
+
// THE TABS, IN ORDER AND IN ROWS (operator, 2026-09-16: "we really need to group these better.
|
|
422
|
+
// Diversions go at the very end"). The groups are written below in the order they were added; the
|
|
423
|
+
// sheet shows them in this order, each row of the strip labelled: the boards, their effects, the
|
|
424
|
+
// games last.
|
|
425
|
+
export const TAB_ROWS = Object.freeze([
|
|
426
|
+
Object.freeze({ label: 'Boards', groups: Object.freeze(['appearance', 'space', 'sky', 'markets']) }),
|
|
427
|
+
Object.freeze({ label: 'Effects', groups: Object.freeze(['effects', 'marketEffects']) }),
|
|
428
|
+
Object.freeze({ label: 'Diversions', groups: Object.freeze(['tetrust', 'blockout', 'blockanoid', 'scorched']) }),
|
|
429
|
+
]);
|
|
430
|
+
/** The three answers to "which sky": the Galaxy, the Earth, or none. */
|
|
431
|
+
export const SKIES = Object.freeze(['galaxy', 'earth', 'none']);
|
|
432
|
+
export const SKY_CHOICES = Object.freeze([['galaxy', 'Galaxy'], ['earth', 'Earth'], ['none', 'None']]);
|
|
433
|
+
export const SKY_LABELS = Object.freeze({ galaxy: 'Galaxy', earth: 'Earth', none: 'no sky' });
|
|
434
|
+
/** Every board that has a sky behind it, in the order the Sky tab's table lists them. */
|
|
435
|
+
export const SKY_BOARDS = Object.freeze([
|
|
436
|
+
Object.freeze({ group: 'space', label: 'Block space', note: 'also the Kiosk\u2019s left panel' }),
|
|
437
|
+
Object.freeze({ group: 'markets', label: 'Markets & Price', note: 'also the Kiosk\u2019s right panel' }),
|
|
438
|
+
Object.freeze({ group: 'tetrust', label: 'Tetrust', note: '' }),
|
|
439
|
+
Object.freeze({ group: 'blockout', label: 'Blockout', note: '' }),
|
|
440
|
+
Object.freeze({ group: 'blockanoid', label: 'Blockanoid', note: '' }),
|
|
441
|
+
Object.freeze({ group: 'scorched', label: 'Scorched Yard', note: '' }),
|
|
442
|
+
]);
|
|
443
|
+
/** The next sky round the ring, for the games' one-button switch. */
|
|
444
|
+
export function nextSky(v) { return SKIES[(SKIES.indexOf(v) + 1) % SKIES.length]; }
|
|
445
|
+
|
|
446
|
+
const PANEL_GROUPS = Object.freeze([
|
|
447
|
+
Object.freeze({
|
|
448
|
+
group: 'appearance',
|
|
449
|
+
title: 'Appearance',
|
|
450
|
+
note: 'The colours of the layout: the page, its cards, text, lines, accent and charts. The 3D boards are space whatever is chosen here, and the Explorer\u2019s block and transaction pages keep their own dark cards.',
|
|
451
|
+
rows: Object.freeze([
|
|
452
|
+
Object.freeze({
|
|
453
|
+
key: 'mode', label: 'Theme mode', kind: 'segment', hint: 'Light or dark, or whichever the operating system asks for (System follows it as it changes)',
|
|
454
|
+
options: Object.freeze([['light', 'Light'], ['dark', 'Dark'], ['system', 'System']]),
|
|
455
|
+
}),
|
|
456
|
+
Object.freeze({
|
|
457
|
+
key: 'theme', label: 'Theme', kind: 'cards', hint: 'Each has a light and a dark face; the mode above picks which. Custom is the nine colours below',
|
|
458
|
+
options: Object.freeze([['blockyard', 'BlockYard'], ['mono', 'Mono'], ['nous', 'Nous'], ['github', 'GitHub'], ['catppuccin', 'Catppuccin'], ['custom', 'Custom']]),
|
|
459
|
+
}),
|
|
460
|
+
Object.freeze({ key: 'customBg', label: 'Page', kind: 'colour', hint: 'The colour behind everything. Whether Custom is a light or a dark theme follows from this one: the derived shades go the other way', custom: true }),
|
|
461
|
+
Object.freeze({ key: 'customPanel', label: 'Panels', kind: 'colour', hint: 'Cards, the header, tables and the settings sheet. Raised panels and hovers are this nudged toward the text colour', custom: true }),
|
|
462
|
+
Object.freeze({ key: 'customText', label: 'Text', kind: 'colour', hint: 'Body text and figures', custom: true }),
|
|
463
|
+
Object.freeze({ key: 'customMuted', label: 'Muted text', kind: 'colour', hint: 'Labels, hints and notes. The fainter text is this halfway to the page colour', custom: true }),
|
|
464
|
+
Object.freeze({ key: 'customAccent', label: 'Accent', kind: 'colour', hint: 'Titles, the active tab, links, buttons and the primary lines of charts', custom: true }),
|
|
465
|
+
Object.freeze({ key: 'customLine', label: 'Lines', kind: 'colour', hint: 'Card borders and table rules. The softer rules are this halfway to the page colour', custom: true }),
|
|
466
|
+
Object.freeze({ key: 'customOk', label: 'Good', kind: 'colour', hint: 'Synced, fresh, a block on time', custom: true }),
|
|
467
|
+
Object.freeze({ key: 'customWarn', label: 'Warning', kind: 'colour', hint: 'Late, stale, a caveat', custom: true }),
|
|
468
|
+
Object.freeze({ key: 'customBad', label: 'Bad', kind: 'colour', hint: 'Overdue, failed, offline', custom: true }),
|
|
469
|
+
]),
|
|
470
|
+
}),
|
|
353
471
|
Object.freeze({
|
|
354
472
|
group: 'space',
|
|
355
473
|
title: 'Block space',
|
|
@@ -373,7 +491,7 @@ export const PANEL = Object.freeze([
|
|
|
373
491
|
key: 'sheenStyle', label: 'Metallic finish', kind: 'choice', hint: 'Chrome mirrors a horizon in every face that slides as the blocks move; satin is a softer highlight along the lit edge. Needs Metallic sheen on',
|
|
374
492
|
options: Object.freeze([['chrome', 'Chrome'], ['satin', 'Satin']]),
|
|
375
493
|
}),
|
|
376
|
-
Object.freeze({ key: '
|
|
494
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the board: the Galaxy, the Earth, or none. What each sky is made of is the Sky tab\u2019s; this is only the choice. The Kiosk\u2019s left panel is this board', options: SKY_CHOICES }),
|
|
377
495
|
Object.freeze({
|
|
378
496
|
key: 'detail', label: 'Level of detail', kind: 'choice', hint: 'Simpler cubes draw fewer polygons at the same size',
|
|
379
497
|
options: Object.freeze([['full', 'Full'], ['simple', 'Simple cubes'], ['flat', 'Flat tiles']]),
|
|
@@ -391,37 +509,78 @@ export const PANEL = Object.freeze([
|
|
|
391
509
|
Object.freeze({ key: 'perspective', label: 'Depth', kind: 'range', min: 0, max: 0.001, step: 0.0001, hint: 'How much height foreshortens. 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' }),
|
|
392
510
|
Object.freeze({
|
|
393
511
|
key: 'light', label: 'Light', kind: 'choice', hint: 'Where the lamp hangs. Straight above lights the whole board evenly; a corner shades the far slope of the curve and the sides turned away',
|
|
394
|
-
options: Object.freeze([['overhead', 'Straight above'], ['
|
|
512
|
+
options: Object.freeze([['overhead', 'Straight above'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right'], ['front', 'From the viewer']]),
|
|
513
|
+
}),
|
|
514
|
+
Object.freeze({
|
|
515
|
+
key: 'lightHeight', label: 'Light height', kind: 'choice', hint: 'How high the lamp hangs over that place: low rakes the sides and shades the far slope hard, high is nearly overhead',
|
|
516
|
+
options: Object.freeze([['low', 'Low'], ['middle', 'Middle'], ['high', 'High']]),
|
|
395
517
|
}),
|
|
396
518
|
]),
|
|
397
519
|
}),
|
|
398
520
|
Object.freeze({
|
|
399
521
|
group: 'sky',
|
|
400
522
|
title: 'Sky',
|
|
401
|
-
note: '
|
|
523
|
+
note: 'Two skies, and which board draws which. The table is the whole map: every board that has a sky behind it, and the sky it is drawing \u2014 change one here or in the board\u2019s own tab, it is the same setting. Below it, what each sky is made of.',
|
|
402
524
|
rows: Object.freeze([
|
|
403
|
-
Object.freeze({ key: '
|
|
525
|
+
Object.freeze({ key: 'map', label: 'Which sky, where', kind: 'skymap', hint: 'Every board with a sky, and the one it draws' }),
|
|
526
|
+
Object.freeze({ key: 'galaxyHead', label: 'The Galaxy', kind: 'heading', hint: 'The star field: stars on slowly turning spiral arms, with the layers of a real galaxy. BlockYard\u2019s own sky' }),
|
|
527
|
+
Object.freeze({ key: 'galaxy', label: 'Spiral arms', kind: 'toggle', hint: 'Lay the stars on slowly turning spiral arms instead of scattering them evenly. One turn takes about fifteen minutes' }),
|
|
404
528
|
Object.freeze({
|
|
405
|
-
key: 'galaxyAt', label: '
|
|
406
|
-
hint: 'Where
|
|
529
|
+
key: 'galaxyAt', label: 'Arms centre', kind: 'choice',
|
|
530
|
+
hint: 'Where the middle of the spiral sits. A corner crowds the bright centre there and sweeps the arms across; behind the board shows the whole spiral',
|
|
407
531
|
options: Object.freeze([['center', 'Behind the board'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
408
532
|
}),
|
|
533
|
+
Object.freeze({ key: 'density', label: 'Star density', kind: 'range', min: 0.2, max: 8, step: 0.1, hint: 'How many stars, against the shipped number. High values are a lot of drawing on a big panel' }),
|
|
534
|
+
Object.freeze({ key: 'brightness', label: 'Star brightness', kind: 'range', min: 0.2, max: 1.5, step: 0.1, hint: 'How brightly they burn' }),
|
|
535
|
+
Object.freeze({ key: 'colours', label: 'Star colours', kind: 'toggle', hint: 'Warm old stars in the middle, blue-white young ones in the arms. Off is one colour of starlight' }),
|
|
536
|
+
Object.freeze({ key: 'glints', label: 'Star glints', kind: 'toggle', hint: 'The halo and cross glint on the brightest stars' }),
|
|
409
537
|
Object.freeze({ key: 'nebulae', label: 'Nebulae', kind: 'toggle', hint: 'Clouds of gas along the spiral arms, in the colours of star-forming lanes' }),
|
|
410
538
|
Object.freeze({ key: 'dust', label: 'Dust lanes', kind: 'toggle', hint: 'Dark ribbons along the inner edge of each arm, the way a real spiral carries them' }),
|
|
411
|
-
Object.freeze({ key: 'clusters', label: 'Star clusters', kind: 'toggle', hint: 'Tight knots of stars out in the halo, turning with the
|
|
539
|
+
Object.freeze({ key: 'clusters', label: 'Star clusters', kind: 'toggle', hint: 'Tight knots of stars out in the halo, turning with the arms' }),
|
|
412
540
|
Object.freeze({ key: 'galaxies', label: 'Distant galaxies', kind: 'toggle', hint: 'Other galaxies, small and faint and far, behind everything else' }),
|
|
413
|
-
Object.freeze({ key: '
|
|
414
|
-
Object.freeze({
|
|
415
|
-
|
|
416
|
-
|
|
541
|
+
Object.freeze({ key: 'earthHead', label: 'The Earth', kind: 'heading', hint: 'A real day from this machine\u2019s clock: the sun, clouds, dusk, the moon at its phase, and the stars at night' }),
|
|
542
|
+
Object.freeze({
|
|
543
|
+
key: 'clock', label: 'Clock', kind: 'choice', hint: 'What time the Earth sky shows',
|
|
544
|
+
options: Object.freeze([['real', 'Real time'], ['cycle', 'A day every 24 minutes'], ['fixed', 'A fixed hour']]),
|
|
545
|
+
}),
|
|
546
|
+
Object.freeze({ key: 'hour', label: 'Fixed hour', kind: 'range', min: 0, max: 24, step: 0.25, hint: 'The hour the Earth sky holds when the clock is fixed; 17.5 is late afternoon' }),
|
|
547
|
+
Object.freeze({
|
|
548
|
+
key: 'weather', label: 'Weather', kind: 'choice', hint: 'How much cloud, and whether it rains',
|
|
549
|
+
options: Object.freeze([['clear', 'Clear'], ['scattered', 'Scattered cloud'], ['overcast', 'Overcast'], ['storm', 'Storm: rain and lightning']]),
|
|
550
|
+
}),
|
|
551
|
+
Object.freeze({ key: 'cover', label: 'Cloud cover', kind: 'range', min: -1, max: 1, step: 0.05, hint: 'Overrides the weather\u2019s cloud amount, 0 to 1; -1 leaves it to the weather' }),
|
|
552
|
+
Object.freeze({ key: 'lat', label: 'Latitude', kind: 'range', min: -100, max: 90, step: 1, hint: 'Your latitude, for real sunrise and sunset and the season; -100 leaves a six-to-six day' }),
|
|
553
|
+
Object.freeze({ key: 'rays', label: 'Sun rays', kind: 'toggle', hint: 'Crepuscular rays when the sun is low' }),
|
|
554
|
+
Object.freeze({ key: 'rainbow', label: 'Rainbow', kind: 'toggle', hint: 'A rainbow opposite a low sun in scattered weather' }),
|
|
555
|
+
Object.freeze({ key: 'shooting', label: 'Shooting stars', kind: 'toggle', hint: 'Now and then, at night' }),
|
|
556
|
+
Object.freeze({
|
|
557
|
+
key: 'moon', label: 'Moon', kind: 'choice', hint: 'Up every night, highest at midnight, never thinner than a fat crescent \u2014 or on its real track at its real phase, which some nights means no moon at all',
|
|
558
|
+
options: Object.freeze([['night', 'Up every night'], ['real', 'Its real track and phase']]),
|
|
559
|
+
}),
|
|
417
560
|
]),
|
|
418
561
|
}),
|
|
419
562
|
Object.freeze({
|
|
420
563
|
group: 'markets',
|
|
421
564
|
title: 'Markets & Price',
|
|
422
|
-
note: 'The candle board on Markets and Kiosk.',
|
|
565
|
+
note: 'The exchange feed, and the candle board on Markets and Kiosk.',
|
|
423
566
|
rows: Object.freeze([
|
|
424
|
-
Object.freeze({
|
|
567
|
+
Object.freeze({
|
|
568
|
+
key: 'polling', label: 'Enable market polling', kind: 'toggle',
|
|
569
|
+
hint: 'Let the server ask five exchanges (Coinbase, Kraken, Bitstamp, Bitfinex, OKX) for prices, candles and order books. '
|
|
570
|
+
+ 'Off out of the box: this is the only thing the monitor ever says to anyone but your node, and until it is on, '
|
|
571
|
+
+ 'Markets and Kiosk say so and the explorer shows no dollar figures. On, the server asks only while someone is looking, '
|
|
572
|
+
+ 'and stops ten minutes after the last look. Shared by every screen of this monitor',
|
|
573
|
+
}),
|
|
574
|
+
// right under the polling switch it depends on (operator, 2026-09-15: "Move price line on Overview
|
|
575
|
+
// underneath Enable Market Polling")
|
|
576
|
+
Object.freeze({
|
|
577
|
+
key: 'overviewSummary', label: 'Price line on Overview', kind: 'toggle',
|
|
578
|
+
hint: 'Median, spread, 24 h volume and how many books reported, at the top of Overview. '
|
|
579
|
+
+ 'It needs market polling (above); with that on, leaving this on means this monitor contacts '
|
|
580
|
+
+ 'five exchanges whenever Overview is open, not only on Markets and Kiosk. '
|
|
581
|
+
+ 'Switch it off and the landing page talks to nothing but your node.',
|
|
582
|
+
}),
|
|
583
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the candles: the Galaxy, the Earth, or none. The Kiosk\u2019s right panel is this board', options: SKY_CHOICES }),
|
|
425
584
|
Object.freeze({ key: 'effects', label: 'Board effects', kind: 'toggle', hint: 'The idle effects while the board rests (which of them is the Market effects tab) and the flight when the candles refresh. Off draws the board and leaves it alone' }),
|
|
426
585
|
Object.freeze({
|
|
427
586
|
key: 'exchange', label: 'Exchange', kind: 'choice', hint: 'Whose candles the chart and the 3D board draw. The others stay as overlay lines',
|
|
@@ -431,13 +590,6 @@ export const PANEL = Object.freeze([
|
|
|
431
590
|
key: 'range', label: 'Range', kind: 'choice', hint: 'How many hours the chart covers when the page opens',
|
|
432
591
|
options: Object.freeze([['24', '24 hours'], ['48', '48 hours'], ['168', '7 days']]),
|
|
433
592
|
}),
|
|
434
|
-
Object.freeze({
|
|
435
|
-
key: 'overviewSummary', label: 'Price line on Overview', kind: 'toggle',
|
|
436
|
-
hint: 'Median, spread, 24 h volume and how many books reported, at the top of Overview. '
|
|
437
|
-
+ 'On by default: it needs the exchange feed, so leaving it on means this monitor '
|
|
438
|
-
+ 'contacts five exchanges whenever Overview is open, not only on Markets and Kiosk. '
|
|
439
|
-
+ 'Switch it off and the landing page talks to nothing but your node.',
|
|
440
|
-
}),
|
|
441
593
|
Object.freeze({
|
|
442
594
|
key: 'priceView', label: 'Price view', kind: 'choice', hint: 'Which one the Markets page draws. Only one at a time — they show the same hours, and two tall panels of it filled the screen',
|
|
443
595
|
options: Object.freeze([['2d', 'Flat chart'], ['3d', '3D candle board']]),
|
|
@@ -449,12 +601,7 @@ export const PANEL = Object.freeze([
|
|
|
449
601
|
title: 'Blockout',
|
|
450
602
|
note: 'The Breakout court. These switches are also on the game\u2019s own panel; the sky takes its density, brightness and layers from Sky above.',
|
|
451
603
|
rows: Object.freeze([
|
|
452
|
-
Object.freeze({ key: '
|
|
453
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
454
|
-
Object.freeze({
|
|
455
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy\u2019s centre sits on the panel',
|
|
456
|
-
options: Object.freeze([['center', 'Behind the court'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
457
|
-
}),
|
|
604
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the court: the Galaxy, the Earth, or none. Also a button on the game\u2019s panel', options: SKY_CHOICES }),
|
|
458
605
|
Object.freeze({ key: 'neon', label: 'Neon bricks', kind: 'toggle', hint: 'The wall, the bat and the ball as dim bodies under lit tubes' }),
|
|
459
606
|
Object.freeze({
|
|
460
607
|
key: 'neonSource', label: 'Neon colour from', kind: 'choice', hint: 'Each brick row\u2019s own colour, or all in one colour',
|
|
@@ -473,12 +620,7 @@ export const PANEL = Object.freeze([
|
|
|
473
620
|
title: 'Blockanoid',
|
|
474
621
|
note: 'The Arkanoid court: silver bricks that take more than one hit, gold that takes none, and capsules that fall out of what you break. These switches are also on the game’s own panel.',
|
|
475
622
|
rows: Object.freeze([
|
|
476
|
-
Object.freeze({ key: '
|
|
477
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
478
|
-
Object.freeze({
|
|
479
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy’s centre sits on the panel',
|
|
480
|
-
options: Object.freeze([['center', 'Behind the court'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
481
|
-
}),
|
|
623
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the court: the Galaxy, the Earth, or none. Also a button on the game\u2019s panel', options: SKY_CHOICES }),
|
|
482
624
|
Object.freeze({ key: 'neon', label: 'Neon bricks', kind: 'toggle', hint: 'The wall, Vaus and the ball as dim bodies under lit tubes' }),
|
|
483
625
|
Object.freeze({
|
|
484
626
|
key: 'neonSource', label: 'Neon colour from', kind: 'choice', hint: 'Each brick’s own colour, or all in one colour',
|
|
@@ -503,7 +645,7 @@ export const PANEL = Object.freeze([
|
|
|
503
645
|
note: 'What the Block space board may play while it rests. One is chosen at random every seven to thirteen seconds, never one played within the no-repeat window \u2014 so the more you leave on, the less often you see any one of them. The Markets board has a list of its own, on the next tab.',
|
|
504
646
|
bulk: true,
|
|
505
647
|
rows: fxRows([
|
|
506
|
-
'ripple', 'outline', 'tide', 'cascade', 'twinkle', 'scan', 'lightcycle', 'ball', 'shockwave', 'nova',
|
|
648
|
+
'ripple', 'outline', 'tide', 'cascade', 'twinkle', 'scan', 'xray', 'lightcycle', 'ball', 'shockwave', 'nova',
|
|
507
649
|
'firework', 'flare', 'wave', 'quake', 'rain', 'sparkle', 'checker', 'radar', 'vortex', 'powerup',
|
|
508
650
|
'combo', 'aurora', 'plasma', 'centipede', 'tractor', 'missile', 'boulderdash', 'stormball',
|
|
509
651
|
]),
|
|
@@ -511,10 +653,10 @@ export const PANEL = Object.freeze([
|
|
|
511
653
|
Object.freeze({
|
|
512
654
|
group: 'marketEffects',
|
|
513
655
|
title: 'Market effects',
|
|
514
|
-
note: 'What the candle board on Markets and Kiosk may play while it rests, chosen the same way as on Block space but from the
|
|
656
|
+
note: 'What the candle board on Markets and Kiosk may play while it rests, chosen the same way as on Block space but from the sixteen that translate to a chart: fronts along the hours, bursts from the candle row, the candles lit where they stand, and the price line\u2019s own pulse, bulge and ball lightning. The Board effects switch on the Markets & Price tab is the master.',
|
|
515
657
|
bulk: true,
|
|
516
658
|
rows: fxRows([
|
|
517
|
-
'ripple', 'outline', 'tide', 'cascade', 'twinkle', 'scan', 'pulse', 'bulge', 'firework', 'flare', 'wave', 'stormball',
|
|
659
|
+
'ripple', 'outline', 'tide', 'cascade', 'twinkle', 'scan', 'xray', 'pulse', 'bulge', 'breathe', 'saber', 'blackhole', 'firework', 'flare', 'wave', 'stormball',
|
|
518
660
|
], MARKET_HINT, { landing: false, top: 300 }),
|
|
519
661
|
}),
|
|
520
662
|
Object.freeze({
|
|
@@ -522,12 +664,7 @@ export const PANEL = Object.freeze([
|
|
|
522
664
|
title: 'Tetrust',
|
|
523
665
|
note: 'The game. These switches are also on the game’s own panel; the sky takes the star field’s density, brightness and layers from Sky above.',
|
|
524
666
|
rows: Object.freeze([
|
|
525
|
-
Object.freeze({ key: '
|
|
526
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
527
|
-
Object.freeze({
|
|
528
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy’s centre sits on the panel: behind the title, or a corner',
|
|
529
|
-
options: Object.freeze([['center', 'Behind the title'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
530
|
-
}),
|
|
667
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the well: the Galaxy, the Earth, or none. Also a button on the game\u2019s panel', options: SKY_CHOICES }),
|
|
531
668
|
Object.freeze({ key: 'ghostColour', label: 'Landing marker', kind: 'colour', hint: 'The wireframe on the floor of the well showing where the falling piece will land. Its own colour: the neon finish below never touches it, because the marker is drawn instead of a block rather than over one' }),
|
|
532
669
|
Object.freeze({ key: 'ghostWidth', label: 'Landing marker thickness', kind: 'range', min: 0.3, max: 2.5, step: 0.1, hint: 'How heavy the marker\u2019s lines are. 1 is the shipped weight; below it the outline thins out of the way of the stack behind it' }),
|
|
533
670
|
Object.freeze({ key: 'music', label: 'Music', kind: 'toggle', hint: 'Korobeiniki, on oscillators' }),
|
|
@@ -544,7 +681,58 @@ export const PANEL = Object.freeze([
|
|
|
544
681
|
Object.freeze({ key: 'gridBrightness', label: 'Grid intensity', kind: 'range', min: 0, max: 2, step: 0.05, hint: 'How strongly the grid shows; 1 is the shipped weight, 0 hides it' }),
|
|
545
682
|
]),
|
|
546
683
|
}),
|
|
684
|
+
Object.freeze({
|
|
685
|
+
group: 'scorched',
|
|
686
|
+
title: 'Scorched Yard',
|
|
687
|
+
note: 'The artillery game. The switches are also on the game\u2019s own panel; the rules below take effect at the next new game.',
|
|
688
|
+
rows: Object.freeze([
|
|
689
|
+
Object.freeze({ key: 'sky', label: 'Sky', kind: 'choice', hint: 'Which sky stands behind the field: the Galaxy, the Earth, or none. Earth is the shipped choice here: an artillery duel wants a day and a horizon, and each round draws its own hour of it. Also a button on the game\u2019s panel', options: SKY_CHOICES }),
|
|
690
|
+
Object.freeze({ key: 'sfx', label: 'Sound effects', kind: 'toggle', hint: 'The shot, the blast, a hit, a fall, a death' }),
|
|
691
|
+
Object.freeze({ key: 'music', label: 'Music', kind: 'toggle', hint: 'A march in D minor, on oscillators' }),
|
|
692
|
+
Object.freeze({ key: 'talk', label: 'Talk', kind: 'toggle', hint: 'What the tanks say when they fire, are hit, or die' }),
|
|
693
|
+
Object.freeze({ key: 'roundSky', label: 'A sky per round', kind: 'toggle', hint: 'Under the Living sky, each round draws its own hour: dawn, noon, dusk, night' }),
|
|
694
|
+
Object.freeze({ key: 'fast', label: 'Fast shells', kind: 'toggle', hint: 'Shells fly at three times the pace, for the impatient' }),
|
|
695
|
+
Object.freeze({
|
|
696
|
+
key: 'aimGuide', label: 'Aim guide', kind: 'choice', hint: 'A ghost of the shell\u2019s path while you aim, under this round\u2019s wind and gravity. Short shows the first fifth of the flight: enough to read the lean of the shot without giving the landing away. Off is the original',
|
|
697
|
+
options: Object.freeze([['short', 'Short'], ['full', 'The whole flight'], ['off', 'Off']]),
|
|
698
|
+
}),
|
|
699
|
+
Object.freeze({ key: 'helper', label: 'Correction helper', kind: 'toggle', hint: 'Click an enemy tank to mark it: the panel then says how far your last shot fell short of it or went over, and C corrects your power from that miss. Nothing is solved for you \u2014 the wind and the hills are still yours to read' }),
|
|
700
|
+
Object.freeze({ key: 'confirmLast', label: 'Confirm the last of a weapon', kind: 'toggle', hint: 'The last Nuke, the last Death\u2019s Head \u2014 any weapon sold one at a time \u2014 asks once before it goes: press fire again to send it' }),
|
|
701
|
+
Object.freeze({ key: 'cheat', label: 'Cheat mode', kind: 'toggle', hint: 'Draw the firing solution while you aim: the shell\u2019s own path under this round\u2019s wind and the game\u2019s gravity, clipped where it meets the dirt, with a ring where it lands. It moves as you move' }),
|
|
702
|
+
Object.freeze({ key: 'demo', label: 'Attract mode', kind: 'toggle', hint: 'Every seat is a computer player and the war runs on by itself, for a wall display' }),
|
|
703
|
+
Object.freeze({ key: 'grid', label: 'Grid', kind: 'toggle', hint: 'A quiet grid under the field' }),
|
|
704
|
+
Object.freeze({ key: 'gridColour', label: 'Grid colour', kind: 'colour', hint: 'The colour of that grid' }),
|
|
705
|
+
Object.freeze({ key: 'gridBrightness', label: 'Grid intensity', kind: 'range', min: 0, max: 2, step: 0.05, hint: 'How strongly the grid shows; 0 hides it' }),
|
|
706
|
+
Object.freeze({ key: 'opponents', label: 'Computer players', kind: 'range', min: 1, max: 7, step: 1, hint: 'How many tanks the computer fields against you. Two is the shipped game; seven fills the original\u2019s eight seats' }),
|
|
707
|
+
Object.freeze({
|
|
708
|
+
key: 'opponentKind', label: 'Their kind', kind: 'choice', hint: 'The manual\u2019s personalities: a mix climbs from the easy ones, or every seat the one you name. Moron fires at random; Shooter takes straight shots; Poolshark banks off rubber walls; Tosser lobs and corrects; Chooser picks its method; Spoiler nearly never misses; Cyborg is a Spoiler with a grudge; Unknown is one of them, drawn each round',
|
|
709
|
+
options: Object.freeze([['mix', 'A mix'], ['moron', 'Morons'], ['shooter', 'Shooters'], ['poolshark', 'Poolsharks'], ['tosser', 'Tossers'], ['chooser', 'Choosers'], ['spoiler', 'Spoilers'], ['cyborg', 'Cyborgs'], ['unknown', 'Unknowns']]),
|
|
710
|
+
}),
|
|
711
|
+
Object.freeze({ key: 'rounds', label: 'Rounds', kind: 'range', min: 1, max: 10, step: 1, hint: 'A game is this many rounds; the highest score at the end wins' }),
|
|
712
|
+
Object.freeze({
|
|
713
|
+
key: 'walls', label: 'Walls', kind: 'choice', hint: 'What a shell does at the edge of the field',
|
|
714
|
+
options: Object.freeze([['none', 'None: it is lost (the original\u2019s default)'], ['concrete', 'Concrete: it explodes there'], ['padded', 'Padded: it stops and drops'], ['rubber', 'Rubber: it bounces'], ['spring', 'Spring: it bounces back harder'], ['wrap', 'Wraparound: it comes in the other side']]),
|
|
715
|
+
}),
|
|
716
|
+
Object.freeze({
|
|
717
|
+
key: 'wind', label: 'Wind', kind: 'choice', hint: 'Whether the wind blows, and how often it changes. Once a round holds its direction and strength for the whole round, so the air blows one way while you read it; every turn is the original\u2019s, and it can turn right around between two shots',
|
|
718
|
+
options: Object.freeze([['round', 'Once a round'], ['turn', 'Changes every turn'], ['shot', 'Changes every shot'], ['none', 'No wind']]),
|
|
719
|
+
}),
|
|
720
|
+
Object.freeze({ key: 'gravity', label: 'Gravity', kind: 'range', min: 0.4, max: 2, step: 0.1, hint: 'How hard shells fall; 1 is Earth' }),
|
|
721
|
+
Object.freeze({
|
|
722
|
+
key: 'land', label: 'Landscape', kind: 'choice', hint: 'The shape of the land each round is drawn from',
|
|
723
|
+
options: Object.freeze([['hills', 'Rolling hills'], ['mountains', 'Mountains'], ['valley', 'A valley'], ['flat', 'Flat']]),
|
|
724
|
+
}),
|
|
725
|
+
Object.freeze({ key: 'cash', label: 'Starting cash', kind: 'range', min: 0, max: 100000, step: 5000, hint: 'What every tank has to spend at the first shop; damage and kills earn more' }),
|
|
726
|
+
Object.freeze({ key: 'interest', label: 'Interest', kind: 'range', min: 0, max: 25, step: 1, hint: 'Per cent paid on unspent cash between rounds' }),
|
|
727
|
+
]),
|
|
728
|
+
}),
|
|
547
729
|
]);
|
|
730
|
+
/** The groups in the sheet's order: row by row of TAB_ROWS, then anything a row forgot (a test says nothing is). */
|
|
731
|
+
export const PANEL = Object.freeze([
|
|
732
|
+
...TAB_ROWS.flatMap((row) => row.groups.map((id) => PANEL_GROUPS.find((g) => g.group === id)).filter(Boolean)),
|
|
733
|
+
...PANEL_GROUPS.filter((g) => !TAB_ROWS.some((row) => row.groups.includes(g.group))),
|
|
734
|
+
]);
|
|
735
|
+
|
|
548
736
|
|
|
549
737
|
// "group.key" -> the row that defines it. The bounds live in exactly one place now.
|
|
550
738
|
const ROWS = new Map();
|
|
@@ -700,6 +888,35 @@ const MIGRATIONS = {
|
|
|
700
888
|
const fx = raw.effects && typeof raw.effects === 'object' ? raw.effects : {};
|
|
701
889
|
return { ...raw, marketEffects: { ...fx } };
|
|
702
890
|
},
|
|
891
|
+
// v4 -> v5: the lamp's placements were renamed as six places at three heights (2026-09-16):
|
|
892
|
+
// upper-left is top-left, upper-right top-right; front stays; the height is new (middle)
|
|
893
|
+
4: (raw) => {
|
|
894
|
+
const sp = raw.space && typeof raw.space === 'object' ? raw.space : {};
|
|
895
|
+
const map = { 'upper-left': 'top-left', 'upper-right': 'top-right' };
|
|
896
|
+
return { ...raw, space: { ...sp, light: map[sp.light] ?? sp.light } };
|
|
897
|
+
},
|
|
898
|
+
|
|
899
|
+
// v5 -> v6: ONE SKY PER BOARD (docs/PLAN-SKIES.md). One global `sky.type` (space | living) plus a
|
|
900
|
+
// `stars` toggle on each of six boards becomes a `sky` choice on each board (galaxy | earth |
|
|
901
|
+
// none), and the games' private `galaxy` / `galaxyAt` are dropped in favour of the Sky tab's.
|
|
902
|
+
// The mapping keeps every installation drawing exactly what it drew.
|
|
903
|
+
//
|
|
904
|
+
// IDEMPOTENT ON PURPOSE: settings stored on the server carry no schema version, so this runs on
|
|
905
|
+
// every boot. It acts only on a board that still has the old key and lacks the new one, and a
|
|
906
|
+
// store already in the new shape passes through untouched.
|
|
907
|
+
5: (raw) => {
|
|
908
|
+
const type = raw.sky && typeof raw.sky === 'object' ? raw.sky.type : undefined;
|
|
909
|
+
const out = { ...raw };
|
|
910
|
+
if (out.sky && typeof out.sky === 'object' && 'type' in out.sky) { const { type: _t, ...rest } = out.sky; out.sky = rest; }
|
|
911
|
+
for (const board of ['space', 'markets', 'tetrust', 'blockout', 'blockanoid', 'scorched']) {
|
|
912
|
+
const g = raw[board];
|
|
913
|
+
if (!g || typeof g !== 'object') continue;
|
|
914
|
+
const { stars, galaxy: _g, galaxyAt: _a, ...rest } = g;
|
|
915
|
+
if (g.sky === undefined && stars !== undefined) rest.sky = stars === false ? 'none' : type === 'living' ? 'earth' : 'galaxy';
|
|
916
|
+
out[board] = rest;
|
|
917
|
+
}
|
|
918
|
+
return out;
|
|
919
|
+
},
|
|
703
920
|
};
|
|
704
921
|
|
|
705
922
|
function migrate(raw) {
|
|
@@ -855,13 +1072,71 @@ export function isDefault(s) {
|
|
|
855
1072
|
* Merged OVER the caller's own options, so a mode's resolution and slab still win where they are
|
|
856
1073
|
* the point of the mode; these are the user's preferences about how it is drawn.
|
|
857
1074
|
*/
|
|
1075
|
+
/** The living sky's settings as board options, for every board that draws a sky. */
|
|
1076
|
+
/** Which sky a board draws, from its own key: 'galaxy' | 'earth' | 'none'. */
|
|
1077
|
+
export function skyOf(n, board) {
|
|
1078
|
+
const v = n[board]?.sky;
|
|
1079
|
+
return SKIES.includes(v) ? v : 'galaxy';
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* THE DEEP SKY belongs to the Galaxy: spiral arms, nebulae, dust lanes, halo clusters and distant
|
|
1084
|
+
* galaxies are wrong over a real day and a real night (operator, 2026-09-16: "It's counter
|
|
1085
|
+
* intuitive in the preferences to display a spiral galaxy for a night/day scene"). Gated by the
|
|
1086
|
+
* board's choice, so a board back on the Galaxy gets them all exactly as saved.
|
|
1087
|
+
*/
|
|
1088
|
+
export function deepSky(n, sky = 'galaxy') {
|
|
1089
|
+
const off = sky !== 'galaxy';
|
|
1090
|
+
return {
|
|
1091
|
+
galaxy: n.sky.galaxy && !off,
|
|
1092
|
+
galaxyAt: n.sky.galaxyAt,
|
|
1093
|
+
nebulae: n.sky.nebulae && !off,
|
|
1094
|
+
galaxies: n.sky.galaxies && !off,
|
|
1095
|
+
dust: n.sky.dust && !off,
|
|
1096
|
+
clusters: n.sky.clusters && !off,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** The Earth sky's settings as renderer options. */
|
|
1101
|
+
export function skyExtras(n) {
|
|
1102
|
+
const sky = n.sky;
|
|
1103
|
+
return {
|
|
1104
|
+
skyClock: sky.clock, skyHour: sky.hour, skyWeather: sky.weather,
|
|
1105
|
+
skyCover: sky.cover < 0 ? undefined : sky.cover, skyLat: sky.lat <= -95 ? undefined : sky.lat,
|
|
1106
|
+
skyRays: sky.rays, skyRainbow: sky.rainbow, skyShooting: sky.shooting, skyMoon: sky.moon,
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* ONE ANSWER PER BOARD (docs/PLAN-SKIES.md): everything the renderer needs to draw the sky this
|
|
1112
|
+
* board chose. `sky` is the choice, `stars` whether any sky is drawn at all, `skyType` which one
|
|
1113
|
+
* ('galaxy' or 'earth'); then the Earth's settings and the Galaxy's, the deep layers gated off
|
|
1114
|
+
* under the Earth. Every board's option builder spreads this and nothing else about the sky.
|
|
1115
|
+
*/
|
|
1116
|
+
export function skyFor(n, board) {
|
|
1117
|
+
const sky = skyOf(n, board);
|
|
1118
|
+
return {
|
|
1119
|
+
sky,
|
|
1120
|
+
stars: sky !== 'none',
|
|
1121
|
+
skyType: sky === 'earth' ? 'earth' : 'galaxy',
|
|
1122
|
+
...skyExtras(n),
|
|
1123
|
+
starDensity: n.sky.density,
|
|
1124
|
+
starBrightness: n.sky.brightness,
|
|
1125
|
+
starColours: n.sky.colours, starGlints: n.sky.glints,
|
|
1126
|
+
...deepSky(n, sky),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
858
1130
|
export function spaceOptions(s) {
|
|
859
1131
|
const n = normalise(s);
|
|
860
1132
|
const sp = n.space;
|
|
861
1133
|
const d = DETAIL[sp.detail] ?? DETAIL.full;
|
|
862
1134
|
const out = {
|
|
863
1135
|
shadows: sp.shadows,
|
|
864
|
-
|
|
1136
|
+
// THE SPACE EFFECTS GO OFF UNDER THE LIVING SKY (operator, 2026-09-16: "We have to toggle all
|
|
1137
|
+
// the Space effects off when Living Sky is selected"): supernovae and black holes over a blue
|
|
1138
|
+
// afternoon are wrong. Gated here, not by flipping the switches, so space gets them back as saved.
|
|
1139
|
+
idleFx: sp.idleFx && skyOf(n, 'space') !== 'earth',
|
|
865
1140
|
grid: sp.grid,
|
|
866
1141
|
neon: sp.neon,
|
|
867
1142
|
sheen: sp.sheen,
|
|
@@ -869,18 +1144,12 @@ export function spaceOptions(s) {
|
|
|
869
1144
|
// `space` is the board STYLE (no deck texture, a translucent floor); `stars` is the sky. They
|
|
870
1145
|
// travel together here, which is the block-space board's shipped behaviour, but they are two
|
|
871
1146
|
// options now so the markets board can keep its style while turning its sky off.
|
|
872
|
-
space:
|
|
873
|
-
stars: sp.stars,
|
|
1147
|
+
space: skyOf(n, 'space') !== 'none',
|
|
874
1148
|
dome: sp.dome,
|
|
875
1149
|
facetPx: d.facetPx,
|
|
876
1150
|
crownPx: d.crownPx,
|
|
877
|
-
// the
|
|
878
|
-
|
|
879
|
-
starBrightness: n.sky.brightness,
|
|
880
|
-
galaxy: n.sky.galaxy,
|
|
881
|
-
galaxyAt: n.sky.galaxyAt,
|
|
882
|
-
nebulae: n.sky.nebulae, galaxies: n.sky.galaxies, dust: n.sky.dust, clusters: n.sky.clusters,
|
|
883
|
-
starColours: n.sky.colours, starGlints: n.sky.glints,
|
|
1151
|
+
// the sky this board chose, and what it is made of (skyFor): the Kiosk's left panel is this board
|
|
1152
|
+
...skyFor(n, 'space'),
|
|
884
1153
|
};
|
|
885
1154
|
// the seam belongs to the Stone edges switch at every level of detail: a control that does
|
|
886
1155
|
// nothing in one mode is worse than no control (operator: "stone edges don't work in flat tile
|
|
@@ -891,10 +1160,11 @@ export function spaceOptions(s) {
|
|
|
891
1160
|
if (motion) out.transition = motion;
|
|
892
1161
|
out.departures = sp.departures;
|
|
893
1162
|
out.light = sp.light;
|
|
1163
|
+
out.lightHeight = sp.lightHeight;
|
|
894
1164
|
// merged into the camera by details3d (it owns the oblique constants); 0 leaves it exactly as it
|
|
895
1165
|
// has always been, so the switch costs nothing until someone moves it
|
|
896
1166
|
out.obliqueRise = sp.perspective;
|
|
897
|
-
out.fxKinds = enabledEffects(n);
|
|
1167
|
+
out.fxKinds = skyOf(n, 'space') === 'earth' ? [] : enabledEffects(n);
|
|
898
1168
|
out.fxNoRepeat = n.effects.noRepeat;
|
|
899
1169
|
Object.assign(out, fxCadence(n.effects));
|
|
900
1170
|
out.neonSource = sp.neonSource; out.neonColour = sp.neonColour; out.neonBrightness = sp.neonBrightness;
|
|
@@ -930,9 +1200,8 @@ export function enabledEffects(s, group = 'effects') {
|
|
|
930
1200
|
/** Blockout's switches, with the sky's make-up from the Sky group (as tetrustOptions does). */
|
|
931
1201
|
export function blockoutOptions(s) {
|
|
932
1202
|
const n = normalise(s);
|
|
933
|
-
const sky = spaceOptions(s);
|
|
934
1203
|
return {
|
|
935
|
-
|
|
1204
|
+
sky: skyFor(n, 'blockout'), sfx: n.blockout.sfx,
|
|
936
1205
|
neon: n.blockout.neon,
|
|
937
1206
|
// the engine calls the data-coloured source "temperature"; here that is the brick's own row
|
|
938
1207
|
neonSource: n.blockout.neonSource === 'colour' ? 'colour' : 'temperature',
|
|
@@ -941,43 +1210,45 @@ export function blockoutOptions(s) {
|
|
|
941
1210
|
// composed here rather than in the screen, so the court needs no new import and the renderer
|
|
942
1211
|
// keys stay in one place. 0.18 is the weight this court was hand-tuned at.
|
|
943
1212
|
gridOpts: courtGridColours(n.blockout.gridColour, n.blockout.gridBrightness, 0.18),
|
|
944
|
-
starDensity: sky.starDensity, starBrightness: sky.starBrightness,
|
|
945
|
-
nebulae: sky.nebulae, galaxies: sky.galaxies, dust: sky.dust, clusters: sky.clusters,
|
|
946
|
-
starColours: sky.starColours, starGlints: sky.starGlints,
|
|
947
1213
|
};
|
|
948
1214
|
}
|
|
949
1215
|
|
|
950
1216
|
/** Blockanoid's switches. Blockout's shape, plus the two that are Arkanoid's own. */
|
|
951
1217
|
export function blockanoidOptions(s) {
|
|
952
1218
|
const n = normalise(s);
|
|
953
|
-
const sky = spaceOptions(s);
|
|
954
1219
|
return {
|
|
955
|
-
|
|
1220
|
+
sky: skyFor(n, 'blockanoid'), sfx: n.blockanoid.sfx,
|
|
956
1221
|
capsules: n.blockanoid.capsules, enemies: n.blockanoid.enemies,
|
|
957
1222
|
neon: n.blockanoid.neon,
|
|
958
1223
|
neonSource: n.blockanoid.neonSource === 'colour' ? 'colour' : 'temperature',
|
|
959
1224
|
neonColour: n.blockanoid.neonColour, neonBrightness: n.blockanoid.neonBrightness,
|
|
960
1225
|
grid: n.blockanoid.grid, gridColour: n.blockanoid.gridColour, gridBrightness: n.blockanoid.gridBrightness,
|
|
961
1226
|
gridOpts: courtGridColours(n.blockanoid.gridColour, n.blockanoid.gridBrightness, 0.18),
|
|
962
|
-
starDensity: sky.starDensity, starBrightness: sky.starBrightness,
|
|
963
|
-
nebulae: sky.nebulae, galaxies: sky.galaxies, dust: sky.dust, clusters: sky.clusters,
|
|
964
|
-
starColours: sky.starColours, starGlints: sky.starGlints,
|
|
965
1227
|
};
|
|
966
1228
|
}
|
|
967
1229
|
|
|
968
1230
|
export function tetrustOptions(s) {
|
|
969
1231
|
const n = normalise(s);
|
|
970
|
-
const sky = spaceOptions(s);
|
|
971
1232
|
return {
|
|
972
|
-
|
|
1233
|
+
sky: skyFor(n, 'tetrust'), music: n.tetrust.music, sfx: n.tetrust.sfx,
|
|
973
1234
|
ghostColour: n.tetrust.ghostColour, ghostWidth: n.tetrust.ghostWidth,
|
|
974
1235
|
neon: n.tetrust.neon, neonSource: n.tetrust.neonSource === 'colour' ? 'colour' : 'temperature', neonColour: n.tetrust.neonColour, neonBrightness: n.tetrust.neonBrightness,
|
|
975
1236
|
grid: n.tetrust.grid, gridColour: n.tetrust.gridColour, gridBrightness: n.tetrust.gridBrightness,
|
|
976
1237
|
// 0.06, not 0.18: the well draws its grid fainter than the brick courts do, because the stack
|
|
977
1238
|
// sits on top of it. That difference was hardcoded in tetrust.js; it lives here now.
|
|
978
1239
|
gridOpts: courtGridColours(n.tetrust.gridColour, n.tetrust.gridBrightness, 0.06),
|
|
979
|
-
|
|
980
|
-
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/** Scorched Yard's switches and rules, with the sky's make-up from the Sky group. */
|
|
1244
|
+
export function scorchedOptions(s) {
|
|
1245
|
+
const n = normalise(s);
|
|
1246
|
+
const sc = n.scorched;
|
|
1247
|
+
return {
|
|
1248
|
+
sky: skyFor(n, 'scorched'), sfx: sc.sfx, music: sc.music, talk: sc.talk, roundSky: sc.roundSky, fast: sc.fast, cheat: sc.cheat, aimGuide: sc.aimGuide, helper: sc.helper, confirmLast: sc.confirmLast, demo: sc.demo,
|
|
1249
|
+
grid: sc.grid, gridColour: sc.gridColour, gridBrightness: sc.gridBrightness,
|
|
1250
|
+
gridOpts: courtGridColours(sc.gridColour, sc.gridBrightness, 0.08),
|
|
1251
|
+
opponents: sc.opponents, opponentKind: sc.opponentKind, rounds: sc.rounds, walls: sc.walls, wind: sc.wind, gravity: sc.gravity, land: sc.land, cash: sc.cash, interest: sc.interest,
|
|
981
1252
|
};
|
|
982
1253
|
}
|
|
983
1254
|
|
|
@@ -990,13 +1261,8 @@ export function marketsOptions(s) {
|
|
|
990
1261
|
// as the sky, so driving it from the star switch restyled the whole board when the operator
|
|
991
1262
|
// only wanted the stars gone. The sky has its own option now.
|
|
992
1263
|
space: true,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
starBrightness: n.sky.brightness,
|
|
996
|
-
galaxy: n.sky.galaxy,
|
|
997
|
-
galaxyAt: n.sky.galaxyAt,
|
|
998
|
-
nebulae: n.sky.nebulae, galaxies: n.sky.galaxies, dust: n.sky.dust, clusters: n.sky.clusters,
|
|
999
|
-
starColours: n.sky.colours, starGlints: n.sky.glints,
|
|
1264
|
+
// the sky this board chose (skyFor): the Kiosk's right panel is this board
|
|
1265
|
+
...skyFor(n, 'markets'),
|
|
1000
1266
|
// never, at any setting: the halo under the grid lines is not wanted on this board
|
|
1001
1267
|
neonHalo: 'rgba(0,0,0,0)',
|
|
1002
1268
|
gridGlow: 'rgba(0,0,0,0)',
|
|
@@ -1007,7 +1273,9 @@ export function marketsOptions(s) {
|
|
|
1007
1273
|
...(mk.effects ? {} : { idleFx: false, transition: MOTION.still }),
|
|
1008
1274
|
// ITS OWN SWITCHES (operator, 2026-09-14: "settings specific to market panel"): the Market
|
|
1009
1275
|
// effects group, not the block board's
|
|
1010
|
-
|
|
1276
|
+
// the market effects go off under the Earth the way the Space effects do: a supernova over a
|
|
1277
|
+
// blue afternoon is wrong on this board too
|
|
1278
|
+
fxKinds: skyOf(n, 'markets') === 'earth' ? [] : enabledEffects(n, 'marketEffects'),
|
|
1011
1279
|
fxNoRepeat: n.marketEffects.noRepeat,
|
|
1012
1280
|
...fxCadence(n.marketEffects),
|
|
1013
1281
|
};
|