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.
- package/CHANGELOG.md +108 -0
- package/README.md +13 -11
- package/SECURITY.md +2 -2
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +36 -5
- package/docs/CONFIGURATION.md +6 -4
- package/docs/DEFECTS.md +4 -1
- package/docs/GETTING-STARTED.md +14 -7
- package/docs/INSTALL.md +7 -4
- 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 +26 -7
- package/docs/TROUBLESHOOTING.md +10 -5
- package/docs/USER-GUIDE.md +239 -9
- package/package.json +4 -2
- package/public/css/app.css +87 -0
- package/public/index.html +58 -6
- package/public/js/app.js +60 -19
- package/public/js/blockanoid.js +15 -7
- package/public/js/blockout.js +15 -7
- package/public/js/blockscene3d.js +51 -11
- package/public/js/depthchart.js +1 -1
- package/public/js/details3d.js +25 -2
- package/public/js/explorer.js +7 -1
- package/public/js/livingsky.js +494 -0
- package/public/js/login.js +3 -2
- package/public/js/mining.js +4 -4
- package/public/js/panels.js +27 -18
- 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 +266 -80
- package/public/js/tetrust.js +15 -6
- package/public/js/tetsound.js +35 -5
- package/scripts/check.js +46 -0
- package/scripts/index-build.js +9 -2
- package/scripts/pool-map.js +152 -36
- package/scripts/setup.js +108 -10
- package/scripts/shots.mjs +21 -0
- package/scripts/smoke.sh +6 -5
- 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 +432 -56
- 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 +24 -11
- package/server/collect/network.js +19 -9
- package/server/config.js +7 -0
- package/server/http/api.js +70 -13
- package/server/http/server.js +22 -5
- package/server/http/sse.js +53 -7
- package/server/main.js +13 -3
- 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/systemd/blockyard.service +34 -3
package/public/js/settings.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
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
25
|
// APPEARANCE (operator, 2026-09-16: "add an appearance section in preferences to change the colors
|
|
@@ -77,7 +77,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
77
77
|
// ON (operator, 2026-09-13: "the current settings I have saved out should be the shipping
|
|
78
78
|
// defaults"). It was opt-in because a twinkling field means the board never stops repainting.
|
|
79
79
|
// That cost is now paid deliberately rather than avoided: it is the look that was chosen.
|
|
80
|
-
|
|
80
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none' -- which sky this board draws (docs/PLAN-SKIES.md)
|
|
81
81
|
dome: 5, // how far the board bows toward the viewer, 0 = flat
|
|
82
82
|
// HEIGHT THAT FORESHORTENS (operator, 2026-09-13: "I'm not seeing the bottom or the front face
|
|
83
83
|
// changing at all during movement ... fix camera stuff"). The oblique camera is parallel: height
|
|
@@ -99,6 +99,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
99
99
|
// ten stops. A stored value above it clamps on load.
|
|
100
100
|
perspective: 0, // 0 = the parallel camera; 0.001 the most allowed
|
|
101
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'
|
|
102
103
|
detail: 'simple', // 'full' | 'simple' | 'flat' -- facet and crown thresholds below; simple by default
|
|
103
104
|
motion: 'full', // 'full' | 'quick' | 'still' -- the refresh choreography
|
|
104
105
|
// HOW CUBES LEAVE AND ARRIVE (operator, 2026-09-13: "all the left and right side blocks are
|
|
@@ -118,11 +119,24 @@ export const DEFAULTS = Object.freeze({
|
|
|
118
119
|
// unknown choice to this default, so those boards land on 'normal' rather than on nothing.
|
|
119
120
|
departures: 'normal', // 'normal' | 'arcing'
|
|
120
121
|
}),
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
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.
|
|
125
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)
|
|
126
140
|
density: 3, // multiplies the star count (0.2 .. 3) -- the shipped look sits at the top of the range
|
|
127
141
|
brightness: 1, // multiplies each star's alpha (0.2 .. 1.5)
|
|
128
142
|
galaxy: true, // the same stars laid on spiral arms, turning once a quarter hour
|
|
@@ -145,7 +159,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
145
159
|
// of the box, so a fresh monitor makes no outbound connection but to the node. The server
|
|
146
160
|
// reads it from the shared settings file on every market request (http/api.js).
|
|
147
161
|
polling: false,
|
|
148
|
-
|
|
162
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
149
163
|
effects: true, // the idle effects and the flight when the candles refresh
|
|
150
164
|
// THE TOOLBAR REMEMBERS (operator, 2026-09-12: "We need to remember the user settings for the
|
|
151
165
|
// Markets page"). Which exchange and how many hours were a click that survived until the tab
|
|
@@ -235,9 +249,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
235
249
|
// in another tab, using our engine"). The same shape as the Tetrust group: the game says whether
|
|
236
250
|
// there is a sky and a galaxy; what the sky is MADE of comes from the Sky group.
|
|
237
251
|
blockout: Object.freeze({
|
|
238
|
-
|
|
239
|
-
galaxy: true,
|
|
240
|
-
galaxyAt: 'center',
|
|
252
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
241
253
|
neon: false, // the bricks as dim bodies under lit tubes
|
|
242
254
|
neonSource: 'brick', // 'brick' (each row's own colour) | 'colour'
|
|
243
255
|
neonColour: '#3d8bff',
|
|
@@ -252,9 +264,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
252
264
|
// engine, and make it a new Diversion called 'Blockanoid'"). Blockout's shape plus the two
|
|
253
265
|
// switches Arkanoid earns: whether capsules fall at all, and whether the minions turn up.
|
|
254
266
|
blockanoid: Object.freeze({
|
|
255
|
-
|
|
256
|
-
galaxy: true,
|
|
257
|
-
galaxyAt: 'center',
|
|
267
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none'
|
|
258
268
|
neon: false,
|
|
259
269
|
neonSource: 'brick', // 'brick' (the brick's own colour) | 'colour'
|
|
260
270
|
neonColour: '#3d8bff',
|
|
@@ -271,9 +281,7 @@ export const DEFAULTS = Object.freeze({
|
|
|
271
281
|
// those settings in the game and have them persistent in settings"). The sky is the panel's own
|
|
272
282
|
// canvas behind the well, so it costs the game nothing per key press.
|
|
273
283
|
tetrust: Object.freeze({
|
|
274
|
-
|
|
275
|
-
galaxy: true, // the spiral galaxy in it
|
|
276
|
-
galaxyAt: 'center', // where its centre sits: behind the title
|
|
284
|
+
sky: 'galaxy', // 'galaxy' | 'earth' | 'none' -- the panel's own canvas behind the well
|
|
277
285
|
// the landing marker's colour (operator: "I want to be able to set the ghost wireframe
|
|
278
286
|
// color"). Its own setting, not the neon tubes': the wireframe is drawn instead of a block,
|
|
279
287
|
// so the neon finish never touches it.
|
|
@@ -289,6 +297,33 @@ export const DEFAULTS = Object.freeze({
|
|
|
289
297
|
gridColour: '#1844bf', // blue rather than the engine green
|
|
290
298
|
gridBrightness: 1.55, // and above full, which the blue needs to read at all
|
|
291
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
|
+
}),
|
|
292
327
|
});
|
|
293
328
|
|
|
294
329
|
const DETAIL = {
|
|
@@ -390,8 +425,24 @@ export function fxCadence(g) {
|
|
|
390
425
|
export const TAB_ROWS = Object.freeze([
|
|
391
426
|
Object.freeze({ label: 'Boards', groups: Object.freeze(['appearance', 'space', 'sky', 'markets']) }),
|
|
392
427
|
Object.freeze({ label: 'Effects', groups: Object.freeze(['effects', 'marketEffects']) }),
|
|
393
|
-
Object.freeze({ label: 'Diversions', groups: Object.freeze(['tetrust', 'blockout', 'blockanoid']) }),
|
|
428
|
+
Object.freeze({ label: 'Diversions', groups: Object.freeze(['tetrust', 'blockout', 'blockanoid', 'scorched']) }),
|
|
394
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
|
+
|
|
395
446
|
const PANEL_GROUPS = Object.freeze([
|
|
396
447
|
Object.freeze({
|
|
397
448
|
group: 'appearance',
|
|
@@ -440,7 +491,7 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
440
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',
|
|
441
492
|
options: Object.freeze([['chrome', 'Chrome'], ['satin', 'Satin']]),
|
|
442
493
|
}),
|
|
443
|
-
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 }),
|
|
444
495
|
Object.freeze({
|
|
445
496
|
key: 'detail', label: 'Level of detail', kind: 'choice', hint: 'Simpler cubes draw fewer polygons at the same size',
|
|
446
497
|
options: Object.freeze([['full', 'Full'], ['simple', 'Simple cubes'], ['flat', 'Flat tiles']]),
|
|
@@ -458,29 +509,54 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
458
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' }),
|
|
459
510
|
Object.freeze({
|
|
460
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',
|
|
461
|
-
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']]),
|
|
462
517
|
}),
|
|
463
518
|
]),
|
|
464
519
|
}),
|
|
465
520
|
Object.freeze({
|
|
466
521
|
group: 'sky',
|
|
467
522
|
title: 'Sky',
|
|
468
|
-
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.',
|
|
469
524
|
rows: Object.freeze([
|
|
470
|
-
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' }),
|
|
471
528
|
Object.freeze({
|
|
472
|
-
key: 'galaxyAt', label: '
|
|
473
|
-
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',
|
|
474
531
|
options: Object.freeze([['center', 'Behind the board'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
475
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' }),
|
|
476
537
|
Object.freeze({ key: 'nebulae', label: 'Nebulae', kind: 'toggle', hint: 'Clouds of gas along the spiral arms, in the colours of star-forming lanes' }),
|
|
477
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' }),
|
|
478
|
-
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' }),
|
|
479
540
|
Object.freeze({ key: 'galaxies', label: 'Distant galaxies', kind: 'toggle', hint: 'Other galaxies, small and faint and far, behind everything else' }),
|
|
480
|
-
Object.freeze({ key: '
|
|
481
|
-
Object.freeze({
|
|
482
|
-
|
|
483
|
-
|
|
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
|
+
}),
|
|
484
560
|
]),
|
|
485
561
|
}),
|
|
486
562
|
Object.freeze({
|
|
@@ -504,7 +580,7 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
504
580
|
+ 'five exchanges whenever Overview is open, not only on Markets and Kiosk. '
|
|
505
581
|
+ 'Switch it off and the landing page talks to nothing but your node.',
|
|
506
582
|
}),
|
|
507
|
-
Object.freeze({ key: '
|
|
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 }),
|
|
508
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' }),
|
|
509
585
|
Object.freeze({
|
|
510
586
|
key: 'exchange', label: 'Exchange', kind: 'choice', hint: 'Whose candles the chart and the 3D board draw. The others stay as overlay lines',
|
|
@@ -525,12 +601,7 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
525
601
|
title: 'Blockout',
|
|
526
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.',
|
|
527
603
|
rows: Object.freeze([
|
|
528
|
-
Object.freeze({ key: '
|
|
529
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
530
|
-
Object.freeze({
|
|
531
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy\u2019s centre sits on the panel',
|
|
532
|
-
options: Object.freeze([['center', 'Behind the court'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
533
|
-
}),
|
|
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 }),
|
|
534
605
|
Object.freeze({ key: 'neon', label: 'Neon bricks', kind: 'toggle', hint: 'The wall, the bat and the ball as dim bodies under lit tubes' }),
|
|
535
606
|
Object.freeze({
|
|
536
607
|
key: 'neonSource', label: 'Neon colour from', kind: 'choice', hint: 'Each brick row\u2019s own colour, or all in one colour',
|
|
@@ -549,12 +620,7 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
549
620
|
title: 'Blockanoid',
|
|
550
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.',
|
|
551
622
|
rows: Object.freeze([
|
|
552
|
-
Object.freeze({ key: '
|
|
553
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
554
|
-
Object.freeze({
|
|
555
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy’s centre sits on the panel',
|
|
556
|
-
options: Object.freeze([['center', 'Behind the court'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
557
|
-
}),
|
|
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 }),
|
|
558
624
|
Object.freeze({ key: 'neon', label: 'Neon bricks', kind: 'toggle', hint: 'The wall, Vaus and the ball as dim bodies under lit tubes' }),
|
|
559
625
|
Object.freeze({
|
|
560
626
|
key: 'neonSource', label: 'Neon colour from', kind: 'choice', hint: 'Each brick’s own colour, or all in one colour',
|
|
@@ -598,12 +664,7 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
598
664
|
title: 'Tetrust',
|
|
599
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.',
|
|
600
666
|
rows: Object.freeze([
|
|
601
|
-
Object.freeze({ key: '
|
|
602
|
-
Object.freeze({ key: 'galaxy', label: 'Spiral galaxy', kind: 'toggle', hint: 'The galaxy in that sky, turning' }),
|
|
603
|
-
Object.freeze({
|
|
604
|
-
key: 'galaxyAt', label: 'Galaxy centre', kind: 'choice', hint: 'Where the galaxy’s centre sits on the panel: behind the title, or a corner',
|
|
605
|
-
options: Object.freeze([['center', 'Behind the title'], ['top-left', 'Top left'], ['top-right', 'Top right'], ['bottom-left', 'Bottom left'], ['bottom-right', 'Bottom right']]),
|
|
606
|
-
}),
|
|
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 }),
|
|
607
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' }),
|
|
608
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' }),
|
|
609
670
|
Object.freeze({ key: 'music', label: 'Music', kind: 'toggle', hint: 'Korobeiniki, on oscillators' }),
|
|
@@ -620,6 +681,51 @@ const PANEL_GROUPS = Object.freeze([
|
|
|
620
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' }),
|
|
621
682
|
]),
|
|
622
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
|
+
}),
|
|
623
729
|
]);
|
|
624
730
|
/** The groups in the sheet's order: row by row of TAB_ROWS, then anything a row forgot (a test says nothing is). */
|
|
625
731
|
export const PANEL = Object.freeze([
|
|
@@ -782,6 +888,35 @@ const MIGRATIONS = {
|
|
|
782
888
|
const fx = raw.effects && typeof raw.effects === 'object' ? raw.effects : {};
|
|
783
889
|
return { ...raw, marketEffects: { ...fx } };
|
|
784
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
|
+
},
|
|
785
920
|
};
|
|
786
921
|
|
|
787
922
|
function migrate(raw) {
|
|
@@ -937,13 +1072,71 @@ export function isDefault(s) {
|
|
|
937
1072
|
* Merged OVER the caller's own options, so a mode's resolution and slab still win where they are
|
|
938
1073
|
* the point of the mode; these are the user's preferences about how it is drawn.
|
|
939
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
|
+
|
|
940
1130
|
export function spaceOptions(s) {
|
|
941
1131
|
const n = normalise(s);
|
|
942
1132
|
const sp = n.space;
|
|
943
1133
|
const d = DETAIL[sp.detail] ?? DETAIL.full;
|
|
944
1134
|
const out = {
|
|
945
1135
|
shadows: sp.shadows,
|
|
946
|
-
|
|
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',
|
|
947
1140
|
grid: sp.grid,
|
|
948
1141
|
neon: sp.neon,
|
|
949
1142
|
sheen: sp.sheen,
|
|
@@ -951,18 +1144,12 @@ export function spaceOptions(s) {
|
|
|
951
1144
|
// `space` is the board STYLE (no deck texture, a translucent floor); `stars` is the sky. They
|
|
952
1145
|
// travel together here, which is the block-space board's shipped behaviour, but they are two
|
|
953
1146
|
// options now so the markets board can keep its style while turning its sky off.
|
|
954
|
-
space:
|
|
955
|
-
stars: sp.stars,
|
|
1147
|
+
space: skyOf(n, 'space') !== 'none',
|
|
956
1148
|
dome: sp.dome,
|
|
957
1149
|
facetPx: d.facetPx,
|
|
958
1150
|
crownPx: d.crownPx,
|
|
959
|
-
// the
|
|
960
|
-
|
|
961
|
-
starBrightness: n.sky.brightness,
|
|
962
|
-
galaxy: n.sky.galaxy,
|
|
963
|
-
galaxyAt: n.sky.galaxyAt,
|
|
964
|
-
nebulae: n.sky.nebulae, galaxies: n.sky.galaxies, dust: n.sky.dust, clusters: n.sky.clusters,
|
|
965
|
-
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'),
|
|
966
1153
|
};
|
|
967
1154
|
// the seam belongs to the Stone edges switch at every level of detail: a control that does
|
|
968
1155
|
// nothing in one mode is worse than no control (operator: "stone edges don't work in flat tile
|
|
@@ -973,10 +1160,11 @@ export function spaceOptions(s) {
|
|
|
973
1160
|
if (motion) out.transition = motion;
|
|
974
1161
|
out.departures = sp.departures;
|
|
975
1162
|
out.light = sp.light;
|
|
1163
|
+
out.lightHeight = sp.lightHeight;
|
|
976
1164
|
// merged into the camera by details3d (it owns the oblique constants); 0 leaves it exactly as it
|
|
977
1165
|
// has always been, so the switch costs nothing until someone moves it
|
|
978
1166
|
out.obliqueRise = sp.perspective;
|
|
979
|
-
out.fxKinds = enabledEffects(n);
|
|
1167
|
+
out.fxKinds = skyOf(n, 'space') === 'earth' ? [] : enabledEffects(n);
|
|
980
1168
|
out.fxNoRepeat = n.effects.noRepeat;
|
|
981
1169
|
Object.assign(out, fxCadence(n.effects));
|
|
982
1170
|
out.neonSource = sp.neonSource; out.neonColour = sp.neonColour; out.neonBrightness = sp.neonBrightness;
|
|
@@ -1012,9 +1200,8 @@ export function enabledEffects(s, group = 'effects') {
|
|
|
1012
1200
|
/** Blockout's switches, with the sky's make-up from the Sky group (as tetrustOptions does). */
|
|
1013
1201
|
export function blockoutOptions(s) {
|
|
1014
1202
|
const n = normalise(s);
|
|
1015
|
-
const sky = spaceOptions(s);
|
|
1016
1203
|
return {
|
|
1017
|
-
|
|
1204
|
+
sky: skyFor(n, 'blockout'), sfx: n.blockout.sfx,
|
|
1018
1205
|
neon: n.blockout.neon,
|
|
1019
1206
|
// the engine calls the data-coloured source "temperature"; here that is the brick's own row
|
|
1020
1207
|
neonSource: n.blockout.neonSource === 'colour' ? 'colour' : 'temperature',
|
|
@@ -1023,43 +1210,45 @@ export function blockoutOptions(s) {
|
|
|
1023
1210
|
// composed here rather than in the screen, so the court needs no new import and the renderer
|
|
1024
1211
|
// keys stay in one place. 0.18 is the weight this court was hand-tuned at.
|
|
1025
1212
|
gridOpts: courtGridColours(n.blockout.gridColour, n.blockout.gridBrightness, 0.18),
|
|
1026
|
-
starDensity: sky.starDensity, starBrightness: sky.starBrightness,
|
|
1027
|
-
nebulae: sky.nebulae, galaxies: sky.galaxies, dust: sky.dust, clusters: sky.clusters,
|
|
1028
|
-
starColours: sky.starColours, starGlints: sky.starGlints,
|
|
1029
1213
|
};
|
|
1030
1214
|
}
|
|
1031
1215
|
|
|
1032
1216
|
/** Blockanoid's switches. Blockout's shape, plus the two that are Arkanoid's own. */
|
|
1033
1217
|
export function blockanoidOptions(s) {
|
|
1034
1218
|
const n = normalise(s);
|
|
1035
|
-
const sky = spaceOptions(s);
|
|
1036
1219
|
return {
|
|
1037
|
-
|
|
1220
|
+
sky: skyFor(n, 'blockanoid'), sfx: n.blockanoid.sfx,
|
|
1038
1221
|
capsules: n.blockanoid.capsules, enemies: n.blockanoid.enemies,
|
|
1039
1222
|
neon: n.blockanoid.neon,
|
|
1040
1223
|
neonSource: n.blockanoid.neonSource === 'colour' ? 'colour' : 'temperature',
|
|
1041
1224
|
neonColour: n.blockanoid.neonColour, neonBrightness: n.blockanoid.neonBrightness,
|
|
1042
1225
|
grid: n.blockanoid.grid, gridColour: n.blockanoid.gridColour, gridBrightness: n.blockanoid.gridBrightness,
|
|
1043
1226
|
gridOpts: courtGridColours(n.blockanoid.gridColour, n.blockanoid.gridBrightness, 0.18),
|
|
1044
|
-
starDensity: sky.starDensity, starBrightness: sky.starBrightness,
|
|
1045
|
-
nebulae: sky.nebulae, galaxies: sky.galaxies, dust: sky.dust, clusters: sky.clusters,
|
|
1046
|
-
starColours: sky.starColours, starGlints: sky.starGlints,
|
|
1047
1227
|
};
|
|
1048
1228
|
}
|
|
1049
1229
|
|
|
1050
1230
|
export function tetrustOptions(s) {
|
|
1051
1231
|
const n = normalise(s);
|
|
1052
|
-
const sky = spaceOptions(s);
|
|
1053
1232
|
return {
|
|
1054
|
-
|
|
1233
|
+
sky: skyFor(n, 'tetrust'), music: n.tetrust.music, sfx: n.tetrust.sfx,
|
|
1055
1234
|
ghostColour: n.tetrust.ghostColour, ghostWidth: n.tetrust.ghostWidth,
|
|
1056
1235
|
neon: n.tetrust.neon, neonSource: n.tetrust.neonSource === 'colour' ? 'colour' : 'temperature', neonColour: n.tetrust.neonColour, neonBrightness: n.tetrust.neonBrightness,
|
|
1057
1236
|
grid: n.tetrust.grid, gridColour: n.tetrust.gridColour, gridBrightness: n.tetrust.gridBrightness,
|
|
1058
1237
|
// 0.06, not 0.18: the well draws its grid fainter than the brick courts do, because the stack
|
|
1059
1238
|
// sits on top of it. That difference was hardcoded in tetrust.js; it lives here now.
|
|
1060
1239
|
gridOpts: courtGridColours(n.tetrust.gridColour, n.tetrust.gridBrightness, 0.06),
|
|
1061
|
-
|
|
1062
|
-
|
|
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,
|
|
1063
1252
|
};
|
|
1064
1253
|
}
|
|
1065
1254
|
|
|
@@ -1072,13 +1261,8 @@ export function marketsOptions(s) {
|
|
|
1072
1261
|
// as the sky, so driving it from the star switch restyled the whole board when the operator
|
|
1073
1262
|
// only wanted the stars gone. The sky has its own option now.
|
|
1074
1263
|
space: true,
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
starBrightness: n.sky.brightness,
|
|
1078
|
-
galaxy: n.sky.galaxy,
|
|
1079
|
-
galaxyAt: n.sky.galaxyAt,
|
|
1080
|
-
nebulae: n.sky.nebulae, galaxies: n.sky.galaxies, dust: n.sky.dust, clusters: n.sky.clusters,
|
|
1081
|
-
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'),
|
|
1082
1266
|
// never, at any setting: the halo under the grid lines is not wanted on this board
|
|
1083
1267
|
neonHalo: 'rgba(0,0,0,0)',
|
|
1084
1268
|
gridGlow: 'rgba(0,0,0,0)',
|
|
@@ -1089,7 +1273,9 @@ export function marketsOptions(s) {
|
|
|
1089
1273
|
...(mk.effects ? {} : { idleFx: false, transition: MOTION.still }),
|
|
1090
1274
|
// ITS OWN SWITCHES (operator, 2026-09-14: "settings specific to market panel"): the Market
|
|
1091
1275
|
// effects group, not the block board's
|
|
1092
|
-
|
|
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'),
|
|
1093
1279
|
fxNoRepeat: n.marketEffects.noRepeat,
|
|
1094
1280
|
...fxCadence(n.marketEffects),
|
|
1095
1281
|
};
|