@maka/maka-cli 5.66.0 → 5.68.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/_index.d.ts +1 -0
- package/bundle/typescript/src/commands/game/_index.js +1 -0
- package/bundle/typescript/src/commands/game/_index.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.d.ts +103 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js +423 -25
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +61 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.d.ts +39 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js +85 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.d.ts +37 -17
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +53 -115
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.d.ts +55 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js +203 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +42 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +28 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js +69 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/order.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.d.ts +15 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +32 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.d.ts +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.js +48 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.d.ts +35 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js +108 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.d.ts +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +7 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.d.ts +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.js +162 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +31 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.d.ts +87 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +320 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +32 -0
- 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 +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +98 -11
- package/bundle/typescript/src/commands/game/sideQuest/game.js +239 -35
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +63 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +57 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +62 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +10 -3
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.d.ts +108 -0
- package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.js +186 -0
- package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.d.ts +88 -0
- package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.js +137 -0
- package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +106 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +32 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.d.ts +17 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js +250 -36
- 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 +30 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.d.ts +85 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js +100 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.d.ts +45 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js +45 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.d.ts +13 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +36 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.d.ts +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +14 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.d.ts +68 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js +147 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js +46 -0
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.d.ts +3 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +352 -42
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/log/log.class.d.ts +17 -0
- package/bundle/typescript/src/tools/log/log.class.js +30 -1
- package/bundle/typescript/src/tools/log/log.class.js.map +1 -1
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ import { BacklogCommand } from './commands/backlog.js';
|
|
|
32
32
|
// help index offers, never what the verb accepts (item D).
|
|
33
33
|
import { isPlaytester, probePlaytesterStanding } from './utilities/playtester.js';
|
|
34
34
|
import { RolesCommand } from './commands/roles.js';
|
|
35
|
+
import { ReviewCommand } from './commands/review.js';
|
|
35
36
|
import { SinCommand } from './commands/sin.js';
|
|
36
37
|
import { GoCommand } from './commands/go.js';
|
|
37
38
|
import { UseCommand } from './commands/use.js';
|
|
@@ -95,9 +96,12 @@ import { ContactsCommand } from './commands/contacts.js';
|
|
|
95
96
|
import { JumpCommand } from './commands/jump.js';
|
|
96
97
|
import { InitiateCommand } from './commands/initiate.js';
|
|
97
98
|
import { SummonCommand } from './commands/summon.js';
|
|
98
|
-
import { CompileCommand, SPRITE_TYPES } from './commands/compile.js';
|
|
99
|
+
import { CompileCommand, SPRITE_TYPES, uniqueSpriteName } from './commands/compile.js';
|
|
100
|
+
import { DecompileCommand } from './commands/decompile.js';
|
|
99
101
|
import { ThreadCommand } from './commands/thread.js';
|
|
100
102
|
import { RegisterCommand } from './commands/register.js';
|
|
103
|
+
import { SpritesCommand } from './commands/sprites.js';
|
|
104
|
+
import { UnravelCommand } from './commands/unravel.js';
|
|
101
105
|
import { OrderCommand } from './commands/order.js';
|
|
102
106
|
import { DeployCommand, AgentCommand, RecallCommand } from './commands/companions.js';
|
|
103
107
|
import { DeckCommand, LoadProgramCommand, UnloadProgramCommand } from './commands/deck.js';
|
|
@@ -186,6 +190,9 @@ export default class Game {
|
|
|
186
190
|
// social layer stays dark (presence, cloud mirror, street, drafts).
|
|
187
191
|
_owner;
|
|
188
192
|
_anonymous = false;
|
|
193
|
+
_bench = false;
|
|
194
|
+
_reproItemId;
|
|
195
|
+
_closing = false;
|
|
189
196
|
_logFilePath = '';
|
|
190
197
|
_tier = 1;
|
|
191
198
|
/**
|
|
@@ -254,6 +261,66 @@ export default class Game {
|
|
|
254
261
|
get isAnonymous() {
|
|
255
262
|
return this._anonymous;
|
|
256
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* A REPRO BENCH: a scratch session stood up to reproduce one backlog
|
|
266
|
+
* item. Not a life -- no save, no street presence, no shared table.
|
|
267
|
+
*
|
|
268
|
+
* SEPARATE FROM `isAnonymous`, and the separation is the point. The
|
|
269
|
+
* bench first reused the anonymous flag, since both want the street
|
|
270
|
+
* left alone -- and that quietly took the BACKLOG with it, because
|
|
271
|
+
* anonymity is a promise about a RUNNER ("nothing about them leaves
|
|
272
|
+
* this machine") and the backlog is one of the things it forbids. So
|
|
273
|
+
* a play-tester standing in the bench built for their own report was
|
|
274
|
+
* refused when they tried to answer it, which is the one thing the
|
|
275
|
+
* bench exists for.
|
|
276
|
+
*
|
|
277
|
+
* Two different concerns wearing one flag: "this character is
|
|
278
|
+
* private" and "this session is scratch". A bench is not private --
|
|
279
|
+
* it is the opposite, a place to file and confirm from.
|
|
280
|
+
*/
|
|
281
|
+
get isBench() {
|
|
282
|
+
return this._bench;
|
|
283
|
+
}
|
|
284
|
+
/** The backlog item this bench was stood up for, if any.
|
|
285
|
+
*
|
|
286
|
+
* The review commands read it so an answer needs no index. Player
|
|
287
|
+
* ruling 2026-08-28: "I shouldn't have to pick from the list of
|
|
288
|
+
* review items -- I AM reviewing an item." Making someone name which
|
|
289
|
+
* report they mean, while they are standing in the scene built for
|
|
290
|
+
* that report, is asking a question the session already answered. */
|
|
291
|
+
get reproItemId() {
|
|
292
|
+
return this._reproItemId;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* CLOSE THE SESSION SHORTLY, leaving the last message on screen.
|
|
296
|
+
*
|
|
297
|
+
* For a bench that has just been answered (player request 2026-08-28:
|
|
298
|
+
* "after accepting or rejecting, give a 3s timeout and then close the
|
|
299
|
+
* session"). The pause is the point -- exiting the instant the post
|
|
300
|
+
* lands would wipe the confirmation before it could be read, and the
|
|
301
|
+
* confirmation is the only proof the answer went anywhere.
|
|
302
|
+
*
|
|
303
|
+
* Exits code 0, which is the same door `quit` goes through: the save
|
|
304
|
+
* seal in ui.ts fires (a no-op for a bench, which has no save path)
|
|
305
|
+
* and the launcher parent, which is the terminal's guaranteed
|
|
306
|
+
* janitor, restores the terminal exactly as it would after any other
|
|
307
|
+
* exit. No second teardown path.
|
|
308
|
+
*
|
|
309
|
+
* Idempotent: the first request wins, so answering twice in three
|
|
310
|
+
* seconds cannot stack timers.
|
|
311
|
+
*/
|
|
312
|
+
closeAfter(ms, reason) {
|
|
313
|
+
if (this._closing)
|
|
314
|
+
return;
|
|
315
|
+
this._closing = true;
|
|
316
|
+
const timer = setTimeout(() => {
|
|
317
|
+
Logger.getInstance().write(`<<<<<<<<--------- Closing session: ${reason}`);
|
|
318
|
+
process.exit(0);
|
|
319
|
+
}, ms);
|
|
320
|
+
// Never hold the process open on its own account -- if everything
|
|
321
|
+
// else has finished, going now is right.
|
|
322
|
+
timer.unref?.();
|
|
323
|
+
}
|
|
257
324
|
get sessionDir() {
|
|
258
325
|
return this._logFilePath ? dirname(this._logFilePath) : undefined;
|
|
259
326
|
}
|
|
@@ -452,13 +519,13 @@ export default class Game {
|
|
|
452
519
|
* push one right now and say so.
|
|
453
520
|
*/
|
|
454
521
|
async pushPresenceNow() {
|
|
455
|
-
if (this._anonymous)
|
|
522
|
+
if (this._anonymous || this._bench)
|
|
456
523
|
return 'offline';
|
|
457
524
|
return heartbeatPresenceNow(this.presencePayload());
|
|
458
525
|
}
|
|
459
526
|
startPresenceHeartbeat() {
|
|
460
|
-
if (this._anonymous)
|
|
461
|
-
return; // account-less: never on the street
|
|
527
|
+
if (this._anonymous || this._bench)
|
|
528
|
+
return; // account-less, or a bench: never on the street
|
|
462
529
|
if (this._presenceTimer)
|
|
463
530
|
return;
|
|
464
531
|
this.beatPresence();
|
|
@@ -883,9 +950,98 @@ export default class Game {
|
|
|
883
950
|
// death both remove the entry; persistent state (the frame, the deck)
|
|
884
951
|
// lives on Items in the player's possession, never here.
|
|
885
952
|
companions = [];
|
|
886
|
-
/**
|
|
887
|
-
*
|
|
888
|
-
|
|
953
|
+
/** REGISTERED SPRITES WAITING IN THE RESONANCE (SR5 p.254-256):
|
|
954
|
+
* folded for travel, or staged from a resumed save, waiting to
|
|
955
|
+
* re-shell (the drone _pendingRedeploys school).
|
|
956
|
+
*
|
|
957
|
+
* An ARRAY since 2026-08-28. It was a single slot back when one
|
|
958
|
+
* sprite was the hard maximum; the canon cap is one COMPILED sprite
|
|
959
|
+
* with registered ones exempt, up to LOGIC of them (register.ts), so
|
|
960
|
+
* a single slot would have quietly dropped every registered sprite
|
|
961
|
+
* but one at the next save or journey -- the same "your permanent
|
|
962
|
+
* companion vanished" the fix is for, moved one step downstream. */
|
|
963
|
+
_pendingSpriteRedeploy = [];
|
|
964
|
+
/** How many registrations are spent: the ones standing here plus the
|
|
965
|
+
* ones waiting in the Resonance. register.ts bills this against
|
|
966
|
+
* Logic. */
|
|
967
|
+
registeredSpriteCount() {
|
|
968
|
+
return this.companions.filter(c => c.kind === 'sprite' && c.registered).length
|
|
969
|
+
+ this._pendingSpriteRedeploy.length;
|
|
970
|
+
}
|
|
971
|
+
// THE STASH AND THE LIVE ROSTER ARE DISJOINT, and every mover below
|
|
972
|
+
// must keep them that way: a registered sprite is EITHER standing in
|
|
973
|
+
// `companions` OR waiting here, never both and never neither.
|
|
974
|
+
// foldCompanionsForTravel pushes here and dismisses the shell in the
|
|
975
|
+
// same pass; both drains splice(0); dismissSpriteToResonance and
|
|
976
|
+
// callSpriteFromResonance are the two halves of Call/Dismiss and move
|
|
977
|
+
// a sprite across in one step each.
|
|
978
|
+
//
|
|
979
|
+
// This is why decompile.ts needs no "forget this registration" call,
|
|
980
|
+
// and why one written there could not be made to fail under mutation:
|
|
981
|
+
// decompile only reaches actors in the room, and a sprite waiting
|
|
982
|
+
// HERE is not in the room. That stays true now that Call/Dismiss can
|
|
983
|
+
// put one here mid-run -- dismissing does not make it decompilable,
|
|
984
|
+
// it makes it absent.
|
|
985
|
+
/** Registered sprites waiting in the Resonance, for the surfaces that
|
|
986
|
+
* have to tell a player what they still have. A copy: the stash is
|
|
987
|
+
* maintained by the movers above and nowhere else. */
|
|
988
|
+
registeredSpritesWaiting() {
|
|
989
|
+
return this._pendingSpriteRedeploy.map(s => ({ ...s }));
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* DISMISS half of Call/Dismiss Sprite (SR5 p.250, Simple Action):
|
|
993
|
+
* "send a sprite back to the Resonance, releasing it from any tasks it
|
|
994
|
+
* still owes you."
|
|
995
|
+
*
|
|
996
|
+
* The task release is canon and it is the expensive part -- p.256
|
|
997
|
+
* offers STANDBY (costs 1 task, keeps the rest) precisely because
|
|
998
|
+
* dismissing burns them all. So tasks go to 0 here rather than being
|
|
999
|
+
* carried, and the command that calls this says so out loud before a
|
|
1000
|
+
* player spends a companion's usefulness to tidy the grid.
|
|
1001
|
+
*
|
|
1002
|
+
* Only a REGISTERED sprite has anywhere to go. A compiled one that is
|
|
1003
|
+
* sent away is simply gone, which is decompile's job, not this one --
|
|
1004
|
+
* so this refuses it rather than quietly destroying it.
|
|
1005
|
+
*/
|
|
1006
|
+
dismissSpriteToResonance(entry) {
|
|
1007
|
+
if (entry.kind !== 'sprite' || !entry.registered || !entry.spriteType)
|
|
1008
|
+
return false;
|
|
1009
|
+
this._pendingSpriteRedeploy.push({
|
|
1010
|
+
spriteType: entry.spriteType,
|
|
1011
|
+
level: entry.force,
|
|
1012
|
+
tasks: 0,
|
|
1013
|
+
name: entry.npc.name,
|
|
1014
|
+
});
|
|
1015
|
+
this.dismissCompanion(entry, 'dismissed to the Resonance');
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* CALL half of Call/Dismiss Sprite (p.250): "You call a registered
|
|
1020
|
+
* sprite you have waiting in the Resonance."
|
|
1021
|
+
*
|
|
1022
|
+
* `name` picks one when several wait; without it, the first. Returns
|
|
1023
|
+
* the arrival line, or undefined when nothing answered -- the caller
|
|
1024
|
+
* owns the refusal wording, because "you have none" and "you have
|
|
1025
|
+
* some but not that one" are different things to be told.
|
|
1026
|
+
*/
|
|
1027
|
+
callSpriteFromResonance(name) {
|
|
1028
|
+
const idx = name === undefined
|
|
1029
|
+
? (this._pendingSpriteRedeploy.length > 0 ? 0 : -1)
|
|
1030
|
+
: this._pendingSpriteRedeploy.findIndex(s => s.name === name);
|
|
1031
|
+
if (idx < 0)
|
|
1032
|
+
return undefined;
|
|
1033
|
+
// Removed BEFORE the re-shell, so a failed spawn cannot leave the
|
|
1034
|
+
// same sprite both waiting and standing.
|
|
1035
|
+
const [spec] = this._pendingSpriteRedeploy.splice(idx, 1);
|
|
1036
|
+
const line = this.respawnRegisteredSprite(spec);
|
|
1037
|
+
if (line === undefined) {
|
|
1038
|
+
// It could not land. Put it back rather than losing it: the
|
|
1039
|
+
// registration is the player's property, not this call's.
|
|
1040
|
+
this._pendingSpriteRedeploy.splice(idx, 0, spec);
|
|
1041
|
+
return undefined;
|
|
1042
|
+
}
|
|
1043
|
+
return line;
|
|
1044
|
+
}
|
|
889
1045
|
// Drones deployed at travel time re-shell on arrival ("the rotors spin
|
|
890
1046
|
// back up at the curb") -- tracked as their items across the swap.
|
|
891
1047
|
_pendingRedeploys = [];
|
|
@@ -1164,7 +1320,7 @@ export default class Game {
|
|
|
1164
1320
|
// A REGISTERED sprite persists (register.ts): it folds into
|
|
1165
1321
|
// your PAN for the ride and re-shells at the curb.
|
|
1166
1322
|
if (entry.registered && entry.spriteType) {
|
|
1167
|
-
this._pendingSpriteRedeploy
|
|
1323
|
+
this._pendingSpriteRedeploy.push({ spriteType: entry.spriteType, level: entry.force, tasks: entry.services ?? 0, name: entry.npc.name });
|
|
1168
1324
|
lines.push(`${entry.npc.name} folds into your PAN for the ride -- registered code travels with you.`);
|
|
1169
1325
|
}
|
|
1170
1326
|
else {
|
|
@@ -1196,10 +1352,11 @@ export default class Game {
|
|
|
1196
1352
|
if (shell)
|
|
1197
1353
|
lines.push(`The ${item.name}'s rotors spin back up at the curb.`);
|
|
1198
1354
|
}
|
|
1199
|
-
//
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1355
|
+
// EVERY registered sprite re-shells from the PAN (register.ts).
|
|
1356
|
+
// splice(0) drains the stash exactly once, the way the drone loop
|
|
1357
|
+
// above does -- a sprite that fails to land is not left staged to
|
|
1358
|
+
// try again at the next curb.
|
|
1359
|
+
for (const spec of this._pendingSpriteRedeploy.splice(0)) {
|
|
1203
1360
|
const line = this.respawnRegisteredSprite(spec);
|
|
1204
1361
|
if (line)
|
|
1205
1362
|
lines.push(line);
|
|
@@ -1214,7 +1371,11 @@ export default class Game {
|
|
|
1214
1371
|
if (!type)
|
|
1215
1372
|
return undefined;
|
|
1216
1373
|
const shell = this.spawnCompanionShell({
|
|
1217
|
-
name
|
|
1374
|
+
// The name it left with, so "order <name>" still works after the
|
|
1375
|
+
// ride -- but never on top of a live actor, which is how two
|
|
1376
|
+
// registered sprites of the same type and Level used to collide
|
|
1377
|
+
// and silently lose the second one.
|
|
1378
|
+
name: this.scene.getActor(spec.name) ? uniqueSpriteName(this.scene, type.name, spec.level) : spec.name,
|
|
1218
1379
|
description: `A registered ${type.name.toLowerCase()} sprite riding ${this.player.name}'s PAN -- ${type.blurb}. It serves until its ${spec.tasks} task${spec.tasks === 1 ? '' : 's'} are spent.`,
|
|
1219
1380
|
kind: 'sprite',
|
|
1220
1381
|
combat: type.combat(spec.level),
|
|
@@ -1512,8 +1673,8 @@ export default class Game {
|
|
|
1512
1673
|
/** The transport, hooks-first, CLI default second; undefined =
|
|
1513
1674
|
* offline/logged out/classic mode (callers degrade quietly). */
|
|
1514
1675
|
async streetTransport() {
|
|
1515
|
-
if (this._anonymous)
|
|
1516
|
-
return undefined; // account-less: no street
|
|
1676
|
+
if (this._anonymous || this._bench)
|
|
1677
|
+
return undefined; // account-less, or a bench: no street
|
|
1517
1678
|
if (this.listStreetPlayers && this.sendPage) {
|
|
1518
1679
|
return { list: this.listStreetPlayers, send: this.sendPage };
|
|
1519
1680
|
}
|
|
@@ -1805,6 +1966,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1805
1966
|
CommandFactory.registerCommand('note', NoteCommand);
|
|
1806
1967
|
CommandFactory.registerCommand('backlog', BacklogCommand);
|
|
1807
1968
|
CommandFactory.registerCommand('roles', RolesCommand);
|
|
1969
|
+
// The short form of "backlog review" -- see commands/review.ts.
|
|
1970
|
+
CommandFactory.registerCommand('review', ReviewCommand);
|
|
1808
1971
|
CommandFactory.registerCommand('sin', SinCommand);
|
|
1809
1972
|
CommandFactory.registerCommand('sins', SinCommand);
|
|
1810
1973
|
CommandFactory.registerCommand('papers', SinCommand);
|
|
@@ -1910,9 +2073,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
1910
2073
|
CommandFactory.registerCommand('summon', SummonCommand);
|
|
1911
2074
|
CommandFactory.registerCommand('conjure', SummonCommand);
|
|
1912
2075
|
CommandFactory.registerCommand('compile', CompileCommand);
|
|
1913
|
-
CommandFactory.registerCommand('decompile',
|
|
2076
|
+
CommandFactory.registerCommand('decompile', DecompileCommand);
|
|
1914
2077
|
CommandFactory.registerCommand('thread', ThreadCommand);
|
|
1915
2078
|
CommandFactory.registerCommand('register', RegisterCommand);
|
|
2079
|
+
// The sprite database (p.256-259) -- reference, the way "spells" is.
|
|
2080
|
+
CommandFactory.registerCommand('sprites', SpritesCommand);
|
|
2081
|
+
// Kill Complex Form (p.250) -- the Resonance counterpart to dispel.
|
|
2082
|
+
CommandFactory.registerCommand('unravel', UnravelCommand);
|
|
1916
2083
|
// "At" spots (utilities/spots.ts): walk/move/approach are one
|
|
1917
2084
|
// crossing; run and sprint are the fast modes (combat semantics in
|
|
1918
2085
|
// the spot-move phase).
|
|
@@ -2078,6 +2245,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2078
2245
|
this._saveFilePath = options.saveFilePath;
|
|
2079
2246
|
this._owner = options.owner;
|
|
2080
2247
|
this._anonymous = options.anonymousRunner === true;
|
|
2248
|
+
this._bench = options.reproBench === true;
|
|
2249
|
+
this._reproItemId = options.reproItemId;
|
|
2081
2250
|
// Ask the server, once, whether this account is a play-tester, so
|
|
2082
2251
|
// the help index can stop advertising a backlog that will only 403
|
|
2083
2252
|
// (item D). Fire-and-forget on purpose: it never blocks the boot,
|
|
@@ -2130,7 +2299,23 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2130
2299
|
// spawned in a phantom hideout and read it as a kick back to the
|
|
2131
2300
|
// hub). The room object still exists for any code that touches
|
|
2132
2301
|
// _homeRoom; it's just not in the scene.
|
|
2133
|
-
|
|
2302
|
+
// A BENCH GETS NO HIDEOUT EITHER, for the same reason a shared run
|
|
2303
|
+
// does not: nobody lives here. It is two rooms and a door.
|
|
2304
|
+
//
|
|
2305
|
+
// FOUND FROM A LIVE SESSION LOG (2026-08-28). The bench promises
|
|
2306
|
+
// "you start standing at the barrier", and it did not: the hideout
|
|
2307
|
+
// was added and the player was moved INTO it, so the first thing a
|
|
2308
|
+
// reviewer had to do was "go up" out of a home the bench had built
|
|
2309
|
+
// them. They reported the steps as wrong; the steps were right and
|
|
2310
|
+
// the scene had grown a room.
|
|
2311
|
+
//
|
|
2312
|
+
// WORTH RECORDING WHY THE DRY RUN MISSED IT. The headless harness
|
|
2313
|
+
// boots with sharedRun: true, so it took the branch that skips all
|
|
2314
|
+
// of this -- the replay I had been calling proof of the bench was
|
|
2315
|
+
// quietly proving a DIFFERENT scene from the one a player gets.
|
|
2316
|
+
// Adding reproBench here is what makes the two agree, which is the
|
|
2317
|
+
// property the dry run was supposed to have all along.
|
|
2318
|
+
if (!options.sharedRun && !options.reproBench) {
|
|
2134
2319
|
this.scene.addRoom(this._homeRoom);
|
|
2135
2320
|
this.connectHomeRoom(this.player.currentLocation);
|
|
2136
2321
|
// Every session starts AT HOME -- the hideout, one door from the
|
|
@@ -2230,12 +2415,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2230
2415
|
}
|
|
2231
2416
|
// A REGISTERED sprite from the save re-shells at the cot (the
|
|
2232
2417
|
// technomancer's persistent companion -- register.ts).
|
|
2233
|
-
if (this._pendingSpriteRedeploy && this.scene === this._hubScene) {
|
|
2234
|
-
const spec
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2418
|
+
if (this._pendingSpriteRedeploy.length > 0 && this.scene === this._hubScene) {
|
|
2419
|
+
for (const spec of this._pendingSpriteRedeploy.splice(0)) {
|
|
2420
|
+
const line = this.respawnRegisteredSprite(spec);
|
|
2421
|
+
if (line)
|
|
2422
|
+
Logger.getInstance().logWithColor(line, 'cyan');
|
|
2423
|
+
}
|
|
2239
2424
|
}
|
|
2240
2425
|
if (this._resume) {
|
|
2241
2426
|
const pendingNote = this._pendingRunSeed
|
|
@@ -2319,9 +2504,22 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2319
2504
|
if (this.crew.length > 0 && this.crewForHire.some(c => this.crew.some(m => m.name === c.name))) {
|
|
2320
2505
|
this.rollHireCandidates();
|
|
2321
2506
|
}
|
|
2322
|
-
//
|
|
2323
|
-
|
|
2324
|
-
|
|
2507
|
+
// Registered sprites stage for their re-shell at hub arrival. The
|
|
2508
|
+
// array is the live field; the singular `registeredSprite` is only
|
|
2509
|
+
// read when it is absent, which is exactly the pre-2026-08-28 saves
|
|
2510
|
+
// written before a technomancer could hold more than one. Reading
|
|
2511
|
+
// BOTH would double a sprite on the first save that carries each.
|
|
2512
|
+
if (save.game.registeredSprites && save.game.registeredSprites.length > 0) {
|
|
2513
|
+
this._pendingSpriteRedeploy = save.game.registeredSprites.map(s => ({ ...s }));
|
|
2514
|
+
}
|
|
2515
|
+
else if (save.game.registeredSprite) {
|
|
2516
|
+
const legacy = save.game.registeredSprite;
|
|
2517
|
+
const type = SPRITE_TYPES[legacy.spriteType];
|
|
2518
|
+
this._pendingSpriteRedeploy = [{
|
|
2519
|
+
...legacy,
|
|
2520
|
+
// Old saves carry no name; rebuild the one it would have had.
|
|
2521
|
+
name: `${type?.name ?? 'Courier'} Sprite (Level ${legacy.level})`,
|
|
2522
|
+
}];
|
|
2325
2523
|
}
|
|
2326
2524
|
if (this.crew.length === 0 && save.game.enlistedAlly) {
|
|
2327
2525
|
const legacy = save.game.enlistedAlly;
|
|
@@ -2430,13 +2628,19 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2430
2628
|
contacts: [...this.contacts.entries()],
|
|
2431
2629
|
procurements: [...this.pendingProcurements],
|
|
2432
2630
|
docwagon: this.docwagon ? { ...this.docwagon } : undefined,
|
|
2433
|
-
|
|
2434
|
-
//
|
|
2435
|
-
//
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2631
|
+
registeredSprites: (() => {
|
|
2632
|
+
// Registered sprites ride the save from wherever they are:
|
|
2633
|
+
// STANDING here (a live companion, whose services are the
|
|
2634
|
+
// truth for its task count) or WAITING in the redeploy stash
|
|
2635
|
+
// after a fold. Both sources, because a technomancer can have
|
|
2636
|
+
// some of each -- taking only one of the two lists is how a
|
|
2637
|
+
// sprite that happened to be manifest at save time would
|
|
2638
|
+
// survive while its folded sibling silently did not.
|
|
2639
|
+
const live = this.companions
|
|
2640
|
+
.filter(c => c.kind === 'sprite' && c.registered && c.spriteType)
|
|
2641
|
+
.map(c => ({ spriteType: c.spriteType, level: c.force, tasks: c.services ?? 0, name: c.npc.name }));
|
|
2642
|
+
const all = [...live, ...this._pendingSpriteRedeploy.map(s => ({ ...s }))];
|
|
2643
|
+
return all.length > 0 ? all : undefined;
|
|
2440
2644
|
})(),
|
|
2441
2645
|
crew: this.crew.map(m => ({ ...m, gear: [...m.gear] })),
|
|
2442
2646
|
commandRefusals: { ...this.commandRefusals },
|
|
@@ -2796,7 +3000,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2796
3000
|
// offer flow (his call, accept/decline, the taxi) works unchanged.
|
|
2797
3001
|
// Offline owners fall back to the Phase B ghost riders below.
|
|
2798
3002
|
let sharedDeclineNote = '';
|
|
2799
|
-
if (this.hasHub && !this._anonymous) {
|
|
3003
|
+
if (this.hasHub && !this._anonymous && !this._bench) {
|
|
2800
3004
|
const srMod = await import('./utilities/shared-run.js').catch(() => undefined);
|
|
2801
3005
|
const preferred = this.preferredDraftSessionID;
|
|
2802
3006
|
this.preferredDraftSessionID = undefined;
|
|
@@ -4163,7 +4367,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
4163
4367
|
{ title: 'matrix', entries: [['jack'], ['deck'], ['load'], ['unload'], ['hack'], ['mark'], ['snoop'], ['overwatch', 'os'], ['pan'], ['ar'], ['aros'], ['silent'], ['reboot'], ['agent'], ['drone'], ['jump', 'rig']] },
|
|
4164
4368
|
// The Emerged get their own shelf (player request: "there MUST be
|
|
4165
4369
|
// resonance" -- matrix is the place, Resonance is the talent).
|
|
4166
|
-
{ title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['register']] },
|
|
4370
|
+
{ title: 'resonance', entries: [['compile', 'decompile'], ['thread'], ['unravel'], ['register'], ['sprites']] },
|
|
4167
4371
|
{ title: 'social', entries: [['say'], ['emote', 'me'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
|
|
4168
4372
|
{ title: 'street', entries: [['browse', 'buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['jobs'], ['journal'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
|
|
4169
4373
|
{ title: 'barriers', entries: [['pick'], ['breach'], ['dispel', 'dispell'], ['bluff'], ['unlock'], ['lock']] },
|