@maka/maka-cli 5.63.1 → 5.65.0
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/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js +29 -8
- package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.d.ts +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +86 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.d.ts +29 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js +151 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +40 -13
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +107 -25
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/breach.d.ts +11 -15
- package/bundle/typescript/src/commands/game/sideQuest/commands/breach.js +13 -16
- package/bundle/typescript/src/commands/game/sideQuest/commands/breach.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +30 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +40 -49
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +121 -160
- package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +29 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +7 -8
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +13 -13
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +22 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cover.d.ts +30 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cover.js +65 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/cover.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/dispel.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +25 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +2 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/jobs.js +18 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/jobs.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/journal.d.ts +44 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/journal.js +136 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/journal.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +157 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +18 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +2 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js +8 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/players.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/posture.d.ts +45 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/posture.js +115 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/posture.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +46 -12
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +30 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.d.ts +47 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +91 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/time.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/time.js +61 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/time.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +136 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +128 -44
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +551 -254
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +82 -84
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +23 -4
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +95 -79
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/seed-migration.d.ts +30 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/seed-migration.js +176 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/seed-migration.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +104 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +268 -16
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +78 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +188 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.d.ts +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +78 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/_index.d.ts +0 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/_index.js +0 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/_index.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/device.d.ts +121 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js +145 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +27 -8
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -10
- package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +143 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +253 -30
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +68 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +102 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +80 -34
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +126 -54
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +1 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +0 -22
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +78 -109
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +4 -101
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +52 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/legacy-puzzle-seed.d.ts +63 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/legacy-puzzle-seed.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/legacy-puzzle-seed.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +2 -28
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +1 -14
- package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.d.ts +55 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.d.ts +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js +26 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.d.ts +45 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js +116 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.d.ts +73 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js +56 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +37 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/credentials.d.ts +26 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/credentials.js +48 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/credentials.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/devices.d.ts +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/devices.js +55 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/devices.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +57 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +46 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.d.ts +38 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.js +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/log-style.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +72 -17
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/playtester.d.ts +50 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/playtester.js +43 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/playtester.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.d.ts +14 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js +45 -31
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +66 -8
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +81 -15
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +142 -33
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/use-target.d.ts +88 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/use-target.js +113 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/use-target.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/world-clock.d.ts +83 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/world-clock.js +106 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/world-clock.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +54 -5
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/maka.js +1 -1
- package/bundle/typescript/src/maka.js.map +1 -1
- package/bundle/typescript/src/tools/sdk-loader/relocate.d.ts +19 -2
- package/bundle/typescript/src/tools/sdk-loader/relocate.js +22 -2
- package/bundle/typescript/src/tools/sdk-loader/relocate.js.map +1 -1
- package/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.d.ts +0 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +0 -72
- package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +0 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.d.ts +0 -6
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +0 -26
- package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +0 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +0 -143
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +0 -366
- package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +0 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +0 -28
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.js +0 -2
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.js.map +0 -1
|
@@ -49,6 +49,12 @@ export default class Game {
|
|
|
49
49
|
private _quitArmed;
|
|
50
50
|
private _escArmedAt;
|
|
51
51
|
private _statusBox;
|
|
52
|
+
/** The clock gutter left of the prompt, its ticker, and the face it
|
|
53
|
+
* last painted -- compared so a repaint only happens on the minute
|
|
54
|
+
* rollover (see paintClock). */
|
|
55
|
+
private _clockBox?;
|
|
56
|
+
private _clockTimer?;
|
|
57
|
+
private _clockFace;
|
|
52
58
|
private _inputBox;
|
|
53
59
|
/** The floating icon-minimap (map-overlay wave 2026-08-24). Optional:
|
|
54
60
|
* headless sessions never build widgets. */
|
|
@@ -62,6 +68,20 @@ export default class Game {
|
|
|
62
68
|
private _anonymous;
|
|
63
69
|
private _logFilePath;
|
|
64
70
|
private _tier;
|
|
71
|
+
/**
|
|
72
|
+
* The run's difficulty tier. `activateRunScene` sets it from the tier
|
|
73
|
+
* the scene was generated at; a HEADLESS boot never calls that, so a
|
|
74
|
+
* shared run sat at the default 1 no matter what it was actually
|
|
75
|
+
* generated as (audit 2026-08-27).
|
|
76
|
+
*
|
|
77
|
+
* That does not sit still: the stored tier rides into the save
|
|
78
|
+
* snapshot and `continueToNextScene` reads `_tier + 1` for the next
|
|
79
|
+
* draft, so an under-recorded tier propagates forward into content
|
|
80
|
+
* that has not been generated yet. Exposed so the headless boot can
|
|
81
|
+
* state the tier it was handed instead of guessing.
|
|
82
|
+
*/
|
|
83
|
+
get tier(): number;
|
|
84
|
+
set tier(value: number);
|
|
65
85
|
private _draftInProgress;
|
|
66
86
|
private _hubScene?;
|
|
67
87
|
private _hubName;
|
|
@@ -287,6 +307,38 @@ export default class Game {
|
|
|
287
307
|
* setCharacterLocation (the ONLY path that binds room observers), and
|
|
288
308
|
* hand it the scene for its AI context.
|
|
289
309
|
*/
|
|
310
|
+
/**
|
|
311
|
+
* THE FIXER IS REACHABLE FROM THE FIELD (player go 2026-08-27: "on a
|
|
312
|
+
* multi-crew session, I should still be able to TALK to Mr. Krow if I
|
|
313
|
+
* need something").
|
|
314
|
+
*
|
|
315
|
+
* A shared run executes on a server whose Game has no hub scene, so
|
|
316
|
+
* `hubCast()` is empty and NOBODY from home is a call candidate --
|
|
317
|
+
* including the fixer. Solo mid-run they are (see call.ts's own note
|
|
318
|
+
* about "call Ratchet" bouncing while the roster promised a phone
|
|
319
|
+
* icon), so the same fiction behaved differently by mode, invisibly.
|
|
320
|
+
*
|
|
321
|
+
* The ruling that keeps the ROSTER home is untouched, because the
|
|
322
|
+
* roster and the fixer are different KINDS of thing. The roster is
|
|
323
|
+
* per-player relationship state -- your loyalty, your favours, your
|
|
324
|
+
* ledger -- which is not merely awkward on a shared server but
|
|
325
|
+
* meaningless there, since four players have four different Mama Wus.
|
|
326
|
+
* The fixer is world furniture: one fixed identity, the same Krow for
|
|
327
|
+
* everyone at the table, carrying no per-player state. A shared-run
|
|
328
|
+
* Krow is well-defined in a way a shared-run roster is not, so
|
|
329
|
+
* answering yes here costs nothing that ruling was protecting.
|
|
330
|
+
*
|
|
331
|
+
* Deliberately NOT the whole hub cast: that needs per-player contact
|
|
332
|
+
* identity on the server and would reopen the ruling.
|
|
333
|
+
*
|
|
334
|
+
* He is built lazily and kept OFF the scene -- he is a voice on a
|
|
335
|
+
* commlink, not a body in the room, so he must never appear in "look",
|
|
336
|
+
* be attacked, or be looted. Returns undefined in any session that
|
|
337
|
+
* already has a real one (a hub session, or a static scene that ships
|
|
338
|
+
* him as a cast member), so there is only ever one Krow.
|
|
339
|
+
*/
|
|
340
|
+
remoteFixer(): NPC | undefined;
|
|
341
|
+
private _remoteFixer?;
|
|
290
342
|
spawnCompanionShell(opts: {
|
|
291
343
|
name: string;
|
|
292
344
|
description: string;
|
|
@@ -525,6 +577,24 @@ export default class Game {
|
|
|
525
577
|
reported: boolean;
|
|
526
578
|
dead?: boolean;
|
|
527
579
|
};
|
|
580
|
+
/**
|
|
581
|
+
* WHEN THE RUN STARTED -- the one fact the journal cannot derive.
|
|
582
|
+
*
|
|
583
|
+
* commands/journal.ts renders current state rather than recording
|
|
584
|
+
* events, deliberately: a journal that logs what happened can drift
|
|
585
|
+
* from the world it describes, and a derived one cannot disagree with
|
|
586
|
+
* the thing it is reading. Client, pay and objective all already exist
|
|
587
|
+
* elsewhere and are read live. Elapsed time is the exception -- its
|
|
588
|
+
* ORIGIN is not recoverable from any current state -- so this is the
|
|
589
|
+
* single new field the feature adds, said out loud rather than grown
|
|
590
|
+
* quietly into a record.
|
|
591
|
+
*
|
|
592
|
+
* Not saved, and does not need to be: like currentClient above, mid-run
|
|
593
|
+
* state never persists (runs are disposable -- a quit resumes to the
|
|
594
|
+
* hub with the job back on offer), so there is no save-schema change
|
|
595
|
+
* here. Stamped by activateRunScene, cleared by settleClientReputation.
|
|
596
|
+
*/
|
|
597
|
+
runStartedAt?: Date;
|
|
528
598
|
forfeitArmed: boolean;
|
|
529
599
|
/**
|
|
530
600
|
* The meet and the wrap-up, triggered by the player's first/next real
|
|
@@ -694,6 +764,21 @@ export default class Game {
|
|
|
694
764
|
*/
|
|
695
765
|
launchPendingRun(): Promise<string>;
|
|
696
766
|
close(): Promise<void>;
|
|
767
|
+
/**
|
|
768
|
+
* THE CLOCK TICKER (playtest 2026-08-27). The gutter rendered once,
|
|
769
|
+
* whenever the pane happened to paint, and then showed that moment
|
|
770
|
+
* forever. Parked beside a live prompt it is under the player's eye
|
|
771
|
+
* for the whole run, so a stale one is worse than none at all -- and
|
|
772
|
+
* the move made that matter MORE, not less.
|
|
773
|
+
*
|
|
774
|
+
* Repaints on the MINUTE ROLLOVER, not on every tick: it wakes each
|
|
775
|
+
* second but only touches the screen when the rendered string
|
|
776
|
+
* actually changes. A forced once-a-second render would flicker the
|
|
777
|
+
* whole terminal and cost more than the feature is worth.
|
|
778
|
+
*/
|
|
779
|
+
private paintClock;
|
|
780
|
+
private startClockTicker;
|
|
781
|
+
stopClockTicker(): void;
|
|
697
782
|
stopPresenceHeartbeat(): void;
|
|
698
783
|
/**
|
|
699
784
|
* Full per-session teardown (Phase C): everything init/play armed that
|
|
@@ -759,6 +844,10 @@ export default class Game {
|
|
|
759
844
|
* the inner width from it to right-justify the elevation bar. */
|
|
760
845
|
private static readonly ROOM_DOCK_PCT;
|
|
761
846
|
/** Must track the mapBox's `width` below -- used to trim its caption. */
|
|
847
|
+
/** Columns the clock gutter takes from the input field. Wide enough
|
|
848
|
+
* for "HH:MM" inside a border; adjustable if it costs too much
|
|
849
|
+
* typing room in play. */
|
|
850
|
+
private static readonly CLOCK_GUTTER_W;
|
|
762
851
|
private static readonly MAP_DOCK_PCT;
|
|
763
852
|
private initMapBox;
|
|
764
853
|
/** Shifts the Mechanics pane down (a map box is shown) or back up
|
|
@@ -872,8 +961,23 @@ export default class Game {
|
|
|
872
961
|
* answers). Without this, the uncategorized sweep resurrects them
|
|
873
962
|
* in an "other" bucket. */
|
|
874
963
|
private static readonly HIDDEN_VERBS;
|
|
964
|
+
/** Session verbs that only a play-tester (or admin) can actually use
|
|
965
|
+
* -- the server role-gates both and 403s everyone else. Listed in
|
|
966
|
+
* help ONLY for a confirmed play-tester; see helpCategories. `roles`
|
|
967
|
+
* rides along because it is admin-only, a strictly narrower group. */
|
|
968
|
+
private static readonly PLAYTESTER_VERBS;
|
|
875
969
|
/** Descriptions for the verbs handled inline in handleInput() rather than registered. */
|
|
876
970
|
private static readonly BUILTIN_DESCRIPTIONS;
|
|
877
971
|
private describeVerb;
|
|
972
|
+
/**
|
|
973
|
+
* The help index as THIS account should see it (backlog wave 2, item
|
|
974
|
+
* D: "let's remove Seattle Backlog for non playtesters"). The backlog
|
|
975
|
+
* is role-gated on the server and 403s everyone else, so listing it
|
|
976
|
+
* for every player only advertises a refusal. Hidden unless the
|
|
977
|
+
* account is a CONFIRMED play-tester -- see utilities/playtester.ts
|
|
978
|
+
* for why "unknown" (offline, logged out) hides rather than shows,
|
|
979
|
+
* and why the verb itself stays registered and usable regardless.
|
|
980
|
+
*/
|
|
981
|
+
private helpCategories;
|
|
878
982
|
private getHelpMessage;
|
|
879
983
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import blessed from 'blessed';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
3
|
import { capitalCase } from 'change-case';
|
|
4
|
-
import { Category } from './types/shared/item-enum.js';
|
|
4
|
+
import { Category, Size, Rating } from './types/shared/item-enum.js';
|
|
5
5
|
import { Item } from './models/item.js';
|
|
6
6
|
import { Room } from './models/_index.js';
|
|
7
7
|
import { Door } from './models/door.js';
|
|
@@ -27,13 +27,15 @@ import { TakeCommand } from './commands/take.js';
|
|
|
27
27
|
import { LootCommand } from './commands/loot.js';
|
|
28
28
|
import { NoteCommand } from './commands/note.js';
|
|
29
29
|
import { BacklogCommand } from './commands/backlog.js';
|
|
30
|
+
// Whether this account may actually USE the backlog -- gates what the
|
|
31
|
+
// help index offers, never what the verb accepts (item D).
|
|
32
|
+
import { isPlaytester, probePlaytesterStanding } from './utilities/playtester.js';
|
|
30
33
|
import { RolesCommand } from './commands/roles.js';
|
|
31
34
|
import { SinCommand } from './commands/sin.js';
|
|
32
35
|
import { GoCommand } from './commands/go.js';
|
|
33
36
|
import { UseCommand } from './commands/use.js';
|
|
34
37
|
import { EquipCommand } from './commands/equip.js';
|
|
35
38
|
import { UnequipCommand } from './commands/unequip.js';
|
|
36
|
-
import { SolveCommand } from './commands/solve.js';
|
|
37
39
|
import { DropCommand } from './commands/drop.js';
|
|
38
40
|
import { UnlockCommand } from './commands/unlock.js';
|
|
39
41
|
import { LockCommand } from './commands/lock.js';
|
|
@@ -109,6 +111,9 @@ import { SellCommand } from './commands/sell.js';
|
|
|
109
111
|
import { PayCommand } from './commands/pay.js';
|
|
110
112
|
import { LifestyleCommand } from './commands/lifestyle.js';
|
|
111
113
|
import { JobsCommand } from './commands/jobs.js';
|
|
114
|
+
import { JournalCommand } from './commands/journal.js';
|
|
115
|
+
import { CoverCommand } from './commands/cover.js';
|
|
116
|
+
import { PostureCommand, LieDownCommand, StandCommand } from './commands/posture.js';
|
|
112
117
|
import { TreatCommand } from './commands/treat.js';
|
|
113
118
|
import { WorkCommand } from './commands/work.js';
|
|
114
119
|
import { EatCommand } from './commands/eat.js';
|
|
@@ -127,6 +132,10 @@ import { OverwatchCommand } from './commands/overwatch.js';
|
|
|
127
132
|
import { MarkCommand } from './commands/mark.js';
|
|
128
133
|
import { SnoopCommand } from './commands/snoop.js';
|
|
129
134
|
import { TapCommand } from './commands/tap.js';
|
|
135
|
+
import { TimeCommand } from './commands/time.js';
|
|
136
|
+
// The HUD's clock -- read in the engine so a shared run reads the
|
|
137
|
+
// host's wall clock, never each client's own (item B).
|
|
138
|
+
import { clockTime, worldNow } from './utilities/world-clock.js';
|
|
130
139
|
import { AutoFight } from './utilities/auto-fight.js';
|
|
131
140
|
import { CommandFactory } from './factories/command-factory.js';
|
|
132
141
|
import { SceneSynthesizer } from './factories/scene-factory.js';
|
|
@@ -156,6 +165,12 @@ export default class Game {
|
|
|
156
165
|
_quitArmed = false;
|
|
157
166
|
_escArmedAt = 0;
|
|
158
167
|
_statusBox;
|
|
168
|
+
/** The clock gutter left of the prompt, its ticker, and the face it
|
|
169
|
+
* last painted -- compared so a repaint only happens on the minute
|
|
170
|
+
* rollover (see paintClock). */
|
|
171
|
+
_clockBox;
|
|
172
|
+
_clockTimer;
|
|
173
|
+
_clockFace = '';
|
|
159
174
|
_inputBox;
|
|
160
175
|
/** The floating icon-minimap (map-overlay wave 2026-08-24). Optional:
|
|
161
176
|
* headless sessions never build widgets. */
|
|
@@ -172,6 +187,24 @@ export default class Game {
|
|
|
172
187
|
_anonymous = false;
|
|
173
188
|
_logFilePath = '';
|
|
174
189
|
_tier = 1;
|
|
190
|
+
/**
|
|
191
|
+
* The run's difficulty tier. `activateRunScene` sets it from the tier
|
|
192
|
+
* the scene was generated at; a HEADLESS boot never calls that, so a
|
|
193
|
+
* shared run sat at the default 1 no matter what it was actually
|
|
194
|
+
* generated as (audit 2026-08-27).
|
|
195
|
+
*
|
|
196
|
+
* That does not sit still: the stored tier rides into the save
|
|
197
|
+
* snapshot and `continueToNextScene` reads `_tier + 1` for the next
|
|
198
|
+
* draft, so an under-recorded tier propagates forward into content
|
|
199
|
+
* that has not been generated yet. Exposed so the headless boot can
|
|
200
|
+
* state the tier it was handed instead of guessing.
|
|
201
|
+
*/
|
|
202
|
+
get tier() {
|
|
203
|
+
return this._tier;
|
|
204
|
+
}
|
|
205
|
+
set tier(value) {
|
|
206
|
+
this._tier = value;
|
|
207
|
+
}
|
|
175
208
|
// Guards against two overlapping continueToNextScene() calls -- found via
|
|
176
209
|
// a real session where calling the fixer twice in a row (nothing stopped
|
|
177
210
|
// it, and a 20-40+ second draft invites a second, impatient call) fired
|
|
@@ -865,6 +898,80 @@ export default class Game {
|
|
|
865
898
|
* setCharacterLocation (the ONLY path that binds room observers), and
|
|
866
899
|
* hand it the scene for its AI context.
|
|
867
900
|
*/
|
|
901
|
+
/**
|
|
902
|
+
* THE FIXER IS REACHABLE FROM THE FIELD (player go 2026-08-27: "on a
|
|
903
|
+
* multi-crew session, I should still be able to TALK to Mr. Krow if I
|
|
904
|
+
* need something").
|
|
905
|
+
*
|
|
906
|
+
* A shared run executes on a server whose Game has no hub scene, so
|
|
907
|
+
* `hubCast()` is empty and NOBODY from home is a call candidate --
|
|
908
|
+
* including the fixer. Solo mid-run they are (see call.ts's own note
|
|
909
|
+
* about "call Ratchet" bouncing while the roster promised a phone
|
|
910
|
+
* icon), so the same fiction behaved differently by mode, invisibly.
|
|
911
|
+
*
|
|
912
|
+
* The ruling that keeps the ROSTER home is untouched, because the
|
|
913
|
+
* roster and the fixer are different KINDS of thing. The roster is
|
|
914
|
+
* per-player relationship state -- your loyalty, your favours, your
|
|
915
|
+
* ledger -- which is not merely awkward on a shared server but
|
|
916
|
+
* meaningless there, since four players have four different Mama Wus.
|
|
917
|
+
* The fixer is world furniture: one fixed identity, the same Krow for
|
|
918
|
+
* everyone at the table, carrying no per-player state. A shared-run
|
|
919
|
+
* Krow is well-defined in a way a shared-run roster is not, so
|
|
920
|
+
* answering yes here costs nothing that ruling was protecting.
|
|
921
|
+
*
|
|
922
|
+
* Deliberately NOT the whole hub cast: that needs per-player contact
|
|
923
|
+
* identity on the server and would reopen the ruling.
|
|
924
|
+
*
|
|
925
|
+
* He is built lazily and kept OFF the scene -- he is a voice on a
|
|
926
|
+
* commlink, not a body in the room, so he must never appear in "look",
|
|
927
|
+
* be attacked, or be looted. Returns undefined in any session that
|
|
928
|
+
* already has a real one (a hub session, or a static scene that ships
|
|
929
|
+
* him as a cast member), so there is only ever one Krow.
|
|
930
|
+
*/
|
|
931
|
+
remoteFixer() {
|
|
932
|
+
if (this.hasHub || !this.scene)
|
|
933
|
+
return undefined;
|
|
934
|
+
if (this.scene.allActors.some(a => isFixerName(a.name)))
|
|
935
|
+
return undefined;
|
|
936
|
+
if (this._remoteFixer)
|
|
937
|
+
return this._remoteFixer;
|
|
938
|
+
// His own private "room": never added to the scene, so nothing can
|
|
939
|
+
// walk into it. It exists because an NPC needs somewhere to stand
|
|
940
|
+
// for its own AI context to resolve.
|
|
941
|
+
const line = new Room({
|
|
942
|
+
name: 'On The Line',
|
|
943
|
+
description: 'Somewhere across the sprawl, a man takes a call he was expecting.',
|
|
944
|
+
roomType: 'void',
|
|
945
|
+
});
|
|
946
|
+
const npc = new NPC({
|
|
947
|
+
dialog: [
|
|
948
|
+
`Talk to me, chummer -- you're on the clock.`,
|
|
949
|
+
`I broker the work. What happens on site is your business.`,
|
|
950
|
+
],
|
|
951
|
+
description: `The fixer who put this job together. He is not on site and never will be -- he brokers, he does not run. Reachable by commlink because that is the whole point of a fixer. He knows the JOB exists and who hired for it; he does NOT know the inside of the place, the codes, or what the runners are looking at right now.`,
|
|
952
|
+
playerConstructorConfig: {
|
|
953
|
+
playerName: FIXER_NAME,
|
|
954
|
+
startLocation: line,
|
|
955
|
+
combat: { body: 3, agility: 3, reaction: 3, willpower: 4, logic: 4, intuition: 4, charisma: 5, strength: 3, edge: 3 },
|
|
956
|
+
},
|
|
957
|
+
}, new CommandRegistry(this.scene));
|
|
958
|
+
// A fixer with no commlink is a contradiction, and call.ts gates on
|
|
959
|
+
// carrying one (hasCommlink) before it will connect.
|
|
960
|
+
// NOTE the enum VALUES here. A runtime Item validates against the
|
|
961
|
+
// enum's values ('tiny'); a SAVED item uses the enum KEYS ('Tiny').
|
|
962
|
+
// The two conventions are inverses and both are load-bearing.
|
|
963
|
+
npc.inventory.addItem(new Item({
|
|
964
|
+
name: 'Fixer\'s Commlink',
|
|
965
|
+
description: 'The number everyone has and nobody sells.',
|
|
966
|
+
size: Size.Tiny, category: Category.Commlink, shape: 'Rectangle',
|
|
967
|
+
color: 'Black', texture: 'Matte', rating: Rating.Common, weight: 0.1,
|
|
968
|
+
}));
|
|
969
|
+
npc.scene = this.scene;
|
|
970
|
+
this._remoteFixer = npc;
|
|
971
|
+
Logger.getInstance().write(`Remote fixer materialised for a hubless session: ${FIXER_NAME}.`);
|
|
972
|
+
return npc;
|
|
973
|
+
}
|
|
974
|
+
_remoteFixer;
|
|
868
975
|
spawnCompanionShell(opts) {
|
|
869
976
|
if (this.scene.getActor(opts.name)) {
|
|
870
977
|
Logger.getInstance().write(`spawnCompanionShell: name "${opts.name}" already in scene -- refusing.`);
|
|
@@ -1466,6 +1573,24 @@ export default class Game {
|
|
|
1466
1573
|
// state never is (runs are disposable); reconstructed fresh by
|
|
1467
1574
|
// activateRunScene, settled into the roster by returnToHub.
|
|
1468
1575
|
currentClient;
|
|
1576
|
+
/**
|
|
1577
|
+
* WHEN THE RUN STARTED -- the one fact the journal cannot derive.
|
|
1578
|
+
*
|
|
1579
|
+
* commands/journal.ts renders current state rather than recording
|
|
1580
|
+
* events, deliberately: a journal that logs what happened can drift
|
|
1581
|
+
* from the world it describes, and a derived one cannot disagree with
|
|
1582
|
+
* the thing it is reading. Client, pay and objective all already exist
|
|
1583
|
+
* elsewhere and are read live. Elapsed time is the exception -- its
|
|
1584
|
+
* ORIGIN is not recoverable from any current state -- so this is the
|
|
1585
|
+
* single new field the feature adds, said out loud rather than grown
|
|
1586
|
+
* quietly into a record.
|
|
1587
|
+
*
|
|
1588
|
+
* Not saved, and does not need to be: like currentClient above, mid-run
|
|
1589
|
+
* state never persists (runs are disposable -- a quit resumes to the
|
|
1590
|
+
* hub with the job back on offer), so there is no save-schema change
|
|
1591
|
+
* here. Stamped by activateRunScene, cleared by settleClientReputation.
|
|
1592
|
+
*/
|
|
1593
|
+
runStartedAt;
|
|
1469
1594
|
// Taxi warn-once (player ruling): leaving with the pay unclaimed
|
|
1470
1595
|
// arms this; the second "call taxi" forfeits. Mirrors _quitArmed.
|
|
1471
1596
|
forfeitArmed = false;
|
|
@@ -1541,6 +1666,7 @@ export default class Game {
|
|
|
1541
1666
|
settleClientReputation(wrappedRun) {
|
|
1542
1667
|
const client = this.currentClient;
|
|
1543
1668
|
this.currentClient = undefined;
|
|
1669
|
+
this.runStartedAt = undefined;
|
|
1544
1670
|
this.forfeitArmed = false;
|
|
1545
1671
|
if (!client)
|
|
1546
1672
|
return '';
|
|
@@ -1670,7 +1796,6 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1670
1796
|
CommandFactory.registerCommand('use', UseCommand);
|
|
1671
1797
|
CommandFactory.registerCommand('equip', EquipCommand);
|
|
1672
1798
|
CommandFactory.registerCommand('unequip', UnequipCommand);
|
|
1673
|
-
CommandFactory.registerCommand('solve', SolveCommand);
|
|
1674
1799
|
CommandFactory.registerCommand('drop', DropCommand);
|
|
1675
1800
|
CommandFactory.registerCommand('unlock', UnlockCommand);
|
|
1676
1801
|
CommandFactory.registerCommand('lock', LockCommand);
|
|
@@ -1799,6 +1924,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1799
1924
|
CommandFactory.registerCommand('drink', DrinkCommand);
|
|
1800
1925
|
CommandFactory.registerCommand('lifestyle', LifestyleCommand);
|
|
1801
1926
|
CommandFactory.registerCommand('jobs', JobsCommand);
|
|
1927
|
+
CommandFactory.registerCommand('journal', JournalCommand);
|
|
1928
|
+
CommandFactory.registerCommand('cover', CoverCommand);
|
|
1929
|
+
CommandFactory.registerCommand('sit', PostureCommand);
|
|
1930
|
+
CommandFactory.registerCommand('kneel', PostureCommand);
|
|
1931
|
+
CommandFactory.registerCommand('lie', LieDownCommand);
|
|
1932
|
+
CommandFactory.registerCommand('prone', LieDownCommand);
|
|
1933
|
+
CommandFactory.registerCommand('stand', StandCommand);
|
|
1802
1934
|
CommandFactory.registerCommand('treat', TreatCommand);
|
|
1803
1935
|
CommandFactory.registerCommand('work', WorkCommand);
|
|
1804
1936
|
CommandFactory.registerCommand('gig', WorkCommand);
|
|
@@ -1827,6 +1959,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1827
1959
|
// "jackpoint" are what players reach for when they mean the same act.
|
|
1828
1960
|
CommandFactory.registerCommand('tap', TapCommand);
|
|
1829
1961
|
CommandFactory.registerCommand('splice', TapCommand);
|
|
1962
|
+
// The wall clock (item B). "clock" too -- half of us type that.
|
|
1963
|
+
CommandFactory.registerCommand('time', TimeCommand);
|
|
1964
|
+
CommandFactory.registerCommand('clock', TimeCommand);
|
|
1830
1965
|
}
|
|
1831
1966
|
determineStartRoom() {
|
|
1832
1967
|
return this.scene.determineStartRoom();
|
|
@@ -1928,6 +2063,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1928
2063
|
this._saveFilePath = options.saveFilePath;
|
|
1929
2064
|
this._owner = options.owner;
|
|
1930
2065
|
this._anonymous = options.anonymousRunner === true;
|
|
2066
|
+
// Ask the server, once, whether this account is a play-tester, so
|
|
2067
|
+
// the help index can stop advertising a backlog that will only 403
|
|
2068
|
+
// (item D). Fire-and-forget on purpose: it never blocks the boot,
|
|
2069
|
+
// never throws, and an unanswered probe just leaves the menu entry
|
|
2070
|
+
// hidden while the verb stays usable. See utilities/playtester.ts.
|
|
2071
|
+
void probePlaytesterStanding({ anonymous: this._anonymous });
|
|
1931
2072
|
// ONE loud notice the moment the AI provider refuses on ACCOUNT
|
|
1932
2073
|
// grounds (empty credits, bad key). Without it a dead key mutes
|
|
1933
2074
|
// every NPC and makes every job "fall through" invisibly -- a real
|
|
@@ -2041,6 +2182,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2041
2182
|
equipIntoEmptySlots(this.player);
|
|
2042
2183
|
this.warmNpcAppearancesInBackground();
|
|
2043
2184
|
this.startPursuitHeartbeat();
|
|
2185
|
+
this.startClockTicker();
|
|
2044
2186
|
// The banner learns where we are only after the hub/scene bookkeeping
|
|
2045
2187
|
// above settles.
|
|
2046
2188
|
this.refreshLogLabel();
|
|
@@ -2761,7 +2903,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2761
2903
|
// playtest note: being yanked into the run the instant generation
|
|
2762
2904
|
// finished was jarring). The player invokes it on their own time:
|
|
2763
2905
|
// "call Mr. Johnson" delivers the offer (haggle window included --
|
|
2764
|
-
// see call.ts), and "
|
|
2906
|
+
// see call.ts), and "call taxi" actually goes (launchPendingRun).
|
|
2765
2907
|
if (this.hasHub) {
|
|
2766
2908
|
// The mid-generation race (see draftedForParty above): if the live
|
|
2767
2909
|
// party changed while Krow was on the phone, this seed was quoted
|
|
@@ -2795,6 +2937,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2795
2937
|
this.camPingedRooms.clear();
|
|
2796
2938
|
this.sweptRooms.clear();
|
|
2797
2939
|
this.sceneSeed = seed;
|
|
2940
|
+
// The clock starts when the run does (commands/journal.ts).
|
|
2941
|
+
this.runStartedAt = worldNow();
|
|
2798
2942
|
// A call partner from the old scene wouldn't exist in the new one --
|
|
2799
2943
|
// hang up before moving on, rather than leave a dangling reference.
|
|
2800
2944
|
if (this.player.activeCallPartner) {
|
|
@@ -3007,6 +3151,45 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3007
3151
|
});
|
|
3008
3152
|
}
|
|
3009
3153
|
}
|
|
3154
|
+
/**
|
|
3155
|
+
* THE CLOCK TICKER (playtest 2026-08-27). The gutter rendered once,
|
|
3156
|
+
* whenever the pane happened to paint, and then showed that moment
|
|
3157
|
+
* forever. Parked beside a live prompt it is under the player's eye
|
|
3158
|
+
* for the whole run, so a stale one is worse than none at all -- and
|
|
3159
|
+
* the move made that matter MORE, not less.
|
|
3160
|
+
*
|
|
3161
|
+
* Repaints on the MINUTE ROLLOVER, not on every tick: it wakes each
|
|
3162
|
+
* second but only touches the screen when the rendered string
|
|
3163
|
+
* actually changes. A forced once-a-second render would flicker the
|
|
3164
|
+
* whole terminal and cost more than the feature is worth.
|
|
3165
|
+
*/
|
|
3166
|
+
paintClock() {
|
|
3167
|
+
if (!this._clockBox)
|
|
3168
|
+
return;
|
|
3169
|
+
const face = clockTime();
|
|
3170
|
+
if (face === this._clockFace)
|
|
3171
|
+
return;
|
|
3172
|
+
this._clockFace = face;
|
|
3173
|
+
this._clockBox.setContent(`{center}{cyan-fg}${face}{/cyan-fg}{/center}`);
|
|
3174
|
+
this._screen?.render();
|
|
3175
|
+
}
|
|
3176
|
+
startClockTicker() {
|
|
3177
|
+
// No gutter, nothing to repaint. Headless sessions have no screen,
|
|
3178
|
+
// and a server hosting many of them should not spin one interval
|
|
3179
|
+
// per session to call a function that early-returns.
|
|
3180
|
+
if (!this._clockBox || this._clockTimer)
|
|
3181
|
+
return;
|
|
3182
|
+
this._clockTimer = setInterval(() => this.paintClock(), 1000);
|
|
3183
|
+
// Never hold the process open for a clock: a session that is
|
|
3184
|
+
// otherwise finished should exit, not linger for the next minute.
|
|
3185
|
+
this._clockTimer.unref?.();
|
|
3186
|
+
}
|
|
3187
|
+
stopClockTicker() {
|
|
3188
|
+
if (this._clockTimer) {
|
|
3189
|
+
clearInterval(this._clockTimer);
|
|
3190
|
+
this._clockTimer = undefined;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3010
3193
|
stopPresenceHeartbeat() {
|
|
3011
3194
|
if (this._presenceTimer) {
|
|
3012
3195
|
clearInterval(this._presenceTimer);
|
|
@@ -3020,6 +3203,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3020
3203
|
*/
|
|
3021
3204
|
async dispose() {
|
|
3022
3205
|
this.stopPresenceHeartbeat();
|
|
3206
|
+
// A stray interval outliving a closed shared session would be a
|
|
3207
|
+
// nasty thing to find later, so it retires with the rest of them.
|
|
3208
|
+
this.stopClockTicker();
|
|
3023
3209
|
if (this._presenceEquipTimer)
|
|
3024
3210
|
clearTimeout(this._presenceEquipTimer);
|
|
3025
3211
|
this.stopPursuitHeartbeat();
|
|
@@ -3033,10 +3219,39 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3033
3219
|
}
|
|
3034
3220
|
initInputBox(screen) {
|
|
3035
3221
|
// Create an input box for user commands
|
|
3036
|
-
|
|
3222
|
+
// THE CLOCK GUTTER (playtest 2026-08-27: "if it makes sense, the
|
|
3223
|
+
// clock can be to the left of the input box"). A fixed-width pane
|
|
3224
|
+
// at left:0 with the input narrowed to match.
|
|
3225
|
+
//
|
|
3226
|
+
// This is also where the truncation bug goes to die rather than
|
|
3227
|
+
// move house. The clock used to be the LAST field on the Armor
|
|
3228
|
+
// line, so it was the first thing clipped when that pane got tight
|
|
3229
|
+
// -- which is why the player could not find it. A dedicated
|
|
3230
|
+
// fixed-width gutter cannot be squeezed by its neighbours: the
|
|
3231
|
+
// input field yields the columns instead, and long input already
|
|
3232
|
+
// scrolls horizontally. The cost is CLOCK_GUTTER_W columns of
|
|
3233
|
+
// typing room.
|
|
3234
|
+
//
|
|
3235
|
+
// A sibling widget does not touch the textbox's internal cursor --
|
|
3236
|
+
// it just gets a narrower width -- so there is no cursor maths here.
|
|
3237
|
+
const clockBox = blessed.box({
|
|
3238
|
+
id: 'clockBox',
|
|
3037
3239
|
bottom: 0,
|
|
3038
3240
|
left: 0,
|
|
3039
|
-
width:
|
|
3241
|
+
width: Game.CLOCK_GUTTER_W,
|
|
3242
|
+
height: 3,
|
|
3243
|
+
tags: true,
|
|
3244
|
+
border: { type: 'line' },
|
|
3245
|
+
style: { border: { fg: 'cyan' } },
|
|
3246
|
+
content: '',
|
|
3247
|
+
});
|
|
3248
|
+
this._clockBox = clockBox;
|
|
3249
|
+
screen.append(clockBox);
|
|
3250
|
+
this.paintClock();
|
|
3251
|
+
const inputBox = blessed.textbox({
|
|
3252
|
+
bottom: 0,
|
|
3253
|
+
left: Game.CLOCK_GUTTER_W,
|
|
3254
|
+
width: `100%-${Game.CLOCK_GUTTER_W}`,
|
|
3040
3255
|
height: 3,
|
|
3041
3256
|
tags: true,
|
|
3042
3257
|
border: { type: 'line' },
|
|
@@ -3307,6 +3522,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3307
3522
|
* the inner width from it to right-justify the elevation bar. */
|
|
3308
3523
|
static ROOM_DOCK_PCT = 0.23;
|
|
3309
3524
|
/** Must track the mapBox's `width` below -- used to trim its caption. */
|
|
3525
|
+
/** Columns the clock gutter takes from the input field. Wide enough
|
|
3526
|
+
* for "HH:MM" inside a border; adjustable if it costs too much
|
|
3527
|
+
* typing room in play. */
|
|
3528
|
+
static CLOCK_GUTTER_W = 8;
|
|
3310
3529
|
static MAP_DOCK_PCT = 0.12;
|
|
3311
3530
|
initMapBox(screen) {
|
|
3312
3531
|
const mapBox = blessed.box({
|
|
@@ -3554,6 +3773,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3554
3773
|
// decide whether calls land while you are away from your body.
|
|
3555
3774
|
const { overlay, link } = arStatusSlots(player);
|
|
3556
3775
|
const arSlot = player.plane === 'meat' ? ` | ${overlay}` : '';
|
|
3776
|
+
// THE WALL CLOCK rides the readiness line (item B). Rendered HERE,
|
|
3777
|
+
// in the engine, which in a shared run is the server -- so every
|
|
3778
|
+
// player's HUD shows the host's clock and "after midnight" is one
|
|
3779
|
+
// moment for the whole table. utilities/world-clock.ts has the why.
|
|
3780
|
+
// Time only, not the date: the date is what "time" is for.
|
|
3781
|
+
// The clock LEFT this line for the gutter beside the prompt. It was
|
|
3782
|
+
// the last field here, so it was the first thing clipped whenever
|
|
3783
|
+
// the pane got tight -- the reason the player could not find it.
|
|
3557
3784
|
lines.push(`${armorNote}${arSlot} | ${link}`);
|
|
3558
3785
|
// Lines 4+: transient states, present only while active. Future
|
|
3559
3786
|
// contexts (astral projection, VR hot-sim) join here the same way.
|
|
@@ -3899,9 +4126,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3899
4126
|
* future command can't silently fall out of help.
|
|
3900
4127
|
*/
|
|
3901
4128
|
static HELP_CATEGORIES = [
|
|
3902
|
-
{ title: 'moving', entries: [['look'], ['go'], ['move', 'walk', 'approach', 'run', 'sprint'], ['follow', 'unfollow'], ['map', 'exits'], ['search'], ['sneak']] },
|
|
4129
|
+
{ title: 'moving', entries: [['look'], ['go'], ['move', 'walk', 'approach', 'run', 'sprint'], ['sit', 'kneel'], ['lie', 'prone'], ['stand'], ['follow', 'unfollow'], ['map', 'exits'], ['search'], ['sneak']] },
|
|
3903
4130
|
{ title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['drop'], ['give'], ['store'], ['equip'], ['unequip'], ['brandish', 'draw'], ['holster', 'stow'], ['reload'], ['use'], ['read']] },
|
|
3904
|
-
{ title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['stance'], ['surrender'], ['edge'], ['rest']] },
|
|
4131
|
+
{ title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['cover'], ['stance'], ['surrender'], ['edge'], ['rest']] },
|
|
3905
4132
|
// The party layer: everyone who walks (or flies, or manifests) at
|
|
3906
4133
|
// your side answers to these.
|
|
3907
4134
|
// "players" folded back under crew after playtesting ("crew
|
|
@@ -3913,10 +4140,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3913
4140
|
// resonance" -- matrix is the place, Resonance is the talent).
|
|
3914
4141
|
{ title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['register']] },
|
|
3915
4142
|
{ title: 'social', entries: [['say'], ['emote', 'me'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
|
|
3916
|
-
{ title: 'street', entries: [['browse', 'buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['jobs'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
|
|
3917
|
-
{ title: 'barriers', entries: [['
|
|
4143
|
+
{ title: 'street', entries: [['browse', 'buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['jobs'], ['journal'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
|
|
4144
|
+
{ title: 'barriers', entries: [['pick'], ['breach'], ['dispel', 'dispell'], ['bluff'], ['unlock'], ['lock']] },
|
|
3918
4145
|
{ title: 'character', entries: [['sheet', 'stats'], ['advance'], ['qualities', 'quality'], ['sin', 'sins', 'papers'], ['initiate', 'submerge'], ['install', 'chrome']] },
|
|
3919
|
-
{ title: 'session', entries: [['help'], ['hints'], ['note'], ['backlog'], ['roles'], ['clear'], ['quit', 'exit']] },
|
|
4146
|
+
{ title: 'session', entries: [['help'], ['hints'], ['time', 'clock'], ['note'], ['backlog'], ['roles'], ['clear'], ['quit', 'exit']] },
|
|
3920
4147
|
];
|
|
3921
4148
|
/** Registered but deliberately UNLISTED: reachable as subcommands or
|
|
3922
4149
|
* muscle-memory ("players" folded back under crew after playtesting
|
|
@@ -3924,6 +4151,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3924
4151
|
* answers). Without this, the uncategorized sweep resurrects them
|
|
3925
4152
|
* in an "other" bucket. */
|
|
3926
4153
|
static HIDDEN_VERBS = ['players'];
|
|
4154
|
+
/** Session verbs that only a play-tester (or admin) can actually use
|
|
4155
|
+
* -- the server role-gates both and 403s everyone else. Listed in
|
|
4156
|
+
* help ONLY for a confirmed play-tester; see helpCategories. `roles`
|
|
4157
|
+
* rides along because it is admin-only, a strictly narrower group. */
|
|
4158
|
+
static PLAYTESTER_VERBS = new Set(['backlog', 'roles']);
|
|
3927
4159
|
/** Descriptions for the verbs handled inline in handleInput() rather than registered. */
|
|
3928
4160
|
static BUILTIN_DESCRIPTIONS = {
|
|
3929
4161
|
help: 'This overview. "help <category>" for one group with full descriptions; "help <command>" for one command.',
|
|
@@ -3937,17 +4169,37 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3937
4169
|
const CommandClass = CommandFactory.getAllRegisteredCommands()[verb];
|
|
3938
4170
|
return CommandClass?.getDescription() ?? 'No description available.';
|
|
3939
4171
|
}
|
|
4172
|
+
/**
|
|
4173
|
+
* The help index as THIS account should see it (backlog wave 2, item
|
|
4174
|
+
* D: "let's remove Seattle Backlog for non playtesters"). The backlog
|
|
4175
|
+
* is role-gated on the server and 403s everyone else, so listing it
|
|
4176
|
+
* for every player only advertises a refusal. Hidden unless the
|
|
4177
|
+
* account is a CONFIRMED play-tester -- see utilities/playtester.ts
|
|
4178
|
+
* for why "unknown" (offline, logged out) hides rather than shows,
|
|
4179
|
+
* and why the verb itself stays registered and usable regardless.
|
|
4180
|
+
*/
|
|
4181
|
+
helpCategories() {
|
|
4182
|
+
if (isPlaytester())
|
|
4183
|
+
return Game.HELP_CATEGORIES;
|
|
4184
|
+
return Game.HELP_CATEGORIES.map(c => c.title !== 'session' ? c : {
|
|
4185
|
+
...c,
|
|
4186
|
+
entries: c.entries.filter(e => !Game.PLAYTESTER_VERBS.has(e[0])),
|
|
4187
|
+
});
|
|
4188
|
+
}
|
|
3940
4189
|
getHelpMessage(args = []) {
|
|
3941
4190
|
const registered = Object.keys(CommandFactory.getAllRegisteredCommands());
|
|
4191
|
+
const helpCategories = this.helpCategories();
|
|
4192
|
+
// The uncategorized sweep must not resurrect what helpCategories
|
|
4193
|
+
// just hid, so the restricted verbs stay "covered" either way.
|
|
3942
4194
|
const covered = new Set([...Game.HELP_CATEGORIES.flatMap(c => c.entries.flat()), ...Game.HIDDEN_VERBS]);
|
|
3943
4195
|
const uncategorized = registered.filter(v => !covered.has(v));
|
|
3944
4196
|
const query = args.join(' ').trim().toLowerCase();
|
|
3945
4197
|
// "help <category>": that group, with full descriptions.
|
|
3946
4198
|
if (query) {
|
|
3947
|
-
const categoryNames =
|
|
4199
|
+
const categoryNames = helpCategories.map(c => c.title);
|
|
3948
4200
|
const pickedCategory = fuzzyPickName(query, categoryNames);
|
|
3949
4201
|
if (pickedCategory) {
|
|
3950
|
-
const category =
|
|
4202
|
+
const category = helpCategories.find(c => c.title === pickedCategory);
|
|
3951
4203
|
const lines = [`${category.title.toUpperCase()}`];
|
|
3952
4204
|
for (const [primary, ...aliases] of category.entries) {
|
|
3953
4205
|
lines.push(`${BULLET}${primary}${aliases.length > 0 ? ` (${aliases.join(', ')})` : ''}: ${this.describeVerb(primary)}`);
|
|
@@ -3962,7 +4214,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3962
4214
|
const pickedVerb = fuzzyPickName(words[0], [...covered, ...uncategorized])
|
|
3963
4215
|
?? fuzzyPickName(query, [...covered, ...uncategorized]);
|
|
3964
4216
|
if (pickedVerb) {
|
|
3965
|
-
const entry =
|
|
4217
|
+
const entry = helpCategories.flatMap(c => c.entries).find(e => e.includes(pickedVerb));
|
|
3966
4218
|
const primary = entry?.[0] ?? pickedVerb;
|
|
3967
4219
|
const aliases = entry?.slice(1) ?? [];
|
|
3968
4220
|
const CommandClass = CommandFactory.getAllRegisteredCommands()[primary];
|
|
@@ -4018,7 +4270,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4018
4270
|
// line outgrows the Mechanics column (help renders there now --
|
|
4019
4271
|
// blessed's mid-list wrap lost the left-column alignment).
|
|
4020
4272
|
const lines = ['COMMANDS'];
|
|
4021
|
-
const pad = Math.max(...
|
|
4273
|
+
const pad = Math.max(...helpCategories.map(c => c.title.length));
|
|
4022
4274
|
const pushWrapped = (title, verbs) => {
|
|
4023
4275
|
const rows = [];
|
|
4024
4276
|
let cur = '';
|
|
@@ -4037,7 +4289,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4037
4289
|
for (const r of rows.slice(1))
|
|
4038
4290
|
lines.push(`${' '.repeat(pad)} ${r}`);
|
|
4039
4291
|
};
|
|
4040
|
-
for (const category of
|
|
4292
|
+
for (const category of helpCategories) {
|
|
4041
4293
|
pushWrapped(category.title, category.entries.map(e => e[0]));
|
|
4042
4294
|
}
|
|
4043
4295
|
if (uncategorized.length > 0) {
|