@maka/maka-cli 5.175.0 → 5.176.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/engine-version.js +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +19 -24
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +13 -3
- package/bundle/typescript/src/commands/game/sideQuest/game.js +91 -12
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +336 -275
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js +4 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.js +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js +35 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +5 -19
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/hub-seed.js +284 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/site-transport.js +61 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +53 -6
- package/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +0 -1413
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.176.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|
|
@@ -430,5 +430,12 @@
|
|
|
430
430
|
// the purpose. A `hub: true` seed is stripped of hosts, ice and files on
|
|
431
431
|
// load -- the shipped hub (scene2) no longer stands one over the Bazaar
|
|
432
432
|
// or the Sim Den.
|
|
433
|
-
|
|
433
|
+
// 1.51.0 (2026-09-12): THE HUB LIVES ON THE SITE. The hub seed is served
|
|
434
|
+
// by maka-cli.com (/api/v1/game-hub) and cached like the catalog; the
|
|
435
|
+
// package ships no scene2.json. Save-shape field the server writes
|
|
436
|
+
// back: hubSeedRef {id, version, contentHash} beside the (for one
|
|
437
|
+
// release) still-embedded hubSeed. Hosted hub sessions
|
|
438
|
+
// (createHeadlessHub) run the solo hub loop in the site's process with
|
|
439
|
+
// a save sink; a browser player can enter the hub.
|
|
440
|
+
export const ENGINE_VERSION = '1.51.0';
|
|
434
441
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -12,7 +12,8 @@ import { findCatalogItem, itemFromCatalog, loadCatalog, augFor, spellFor, adeptP
|
|
|
12
12
|
import { SKILL_GROUPS } from '../skill-groups.js';
|
|
13
13
|
import { serializePlayer, serializeItem } from '../utilities/persistence.js';
|
|
14
14
|
import { canonicalLifestyleTier, LIFESTYLE_TIERS, canonicalLifestyleOption, lifestyleOptionAllowed, LIFESTYLE_OPTIONS, } from '../utilities/commerce.js';
|
|
15
|
-
import { SAVE_VERSION } from '../types/save-file.js';
|
|
15
|
+
import { SAVE_VERSION, DUAL_WRITE_HUB_SEED } from '../types/save-file.js';
|
|
16
|
+
import { hubSeedRefOf } from '../utilities/hub-seed.js';
|
|
16
17
|
/**
|
|
17
18
|
* THE ENGINE-SIDE CHARACTER FACTORY -- one payload in, one bootable save
|
|
18
19
|
* out, so the maka-cli.com web generator and the CLI cannot produce
|
|
@@ -527,32 +528,25 @@ export function buildFromChargen(payload) {
|
|
|
527
528
|
return { player, garage, lifestyleTier, lifestyleOptions };
|
|
528
529
|
}
|
|
529
530
|
/**
|
|
530
|
-
* The payload's save
|
|
531
|
-
* it is embedded rather than referenced so a resume never depends on
|
|
532
|
-
* scene2.json still existing or still matching.
|
|
533
|
-
*/
|
|
534
|
-
/**
|
|
535
|
-
* THE SHIPPED HUB SEED -- the default home for a character born without one.
|
|
531
|
+
* The payload's save, born into `hubSeed`.
|
|
536
532
|
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
533
|
+
* THE HUB SEED IS AN ARGUMENT NOW, AND A REQUIRED ONE (2026-09-12). The
|
|
534
|
+
* shipped scene2.json is gone: the hub lives on maka-cli.com
|
|
535
|
+
* (utilities/hub-seed.ts fetches and caches it for a terminal; the site
|
|
536
|
+
* reads its own GameHubSeeds collection for a web birth), and a factory
|
|
537
|
+
* that quietly reached for a bundled copy would be the second source of
|
|
538
|
+
* truth this migration removes. Nothing in this repo creates a
|
|
539
|
+
* character without first holding the served seed.
|
|
542
540
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
* (
|
|
541
|
+
* The save records the seed by REFERENCE (hubSeedRef, from the stamp
|
|
542
|
+
* the server put on it) and, for one release, still embeds it
|
|
543
|
+
* (hubSeed) so a binary from before the migration can resume the
|
|
544
|
+
* runner -- see ISaveFileV1.hubSeed and DUAL_WRITE_HUB_SEED in game.ts.
|
|
546
545
|
*/
|
|
547
|
-
import { createRequire } from 'module';
|
|
548
|
-
const requireSeed = createRequire(import.meta.url);
|
|
549
|
-
function shippedHubSeed() {
|
|
550
|
-
return requireSeed('../scenes/scene2.json');
|
|
551
|
-
}
|
|
552
546
|
export function createNewCharacterSave(payload, hubSeed) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
547
|
+
if (!hubSeed || typeof hubSeed !== 'object' || !Array.isArray(hubSeed.rooms)) {
|
|
548
|
+
throw new ChargenPayloadError('A runner needs a home district: createNewCharacterSave requires the served hub seed.');
|
|
549
|
+
}
|
|
556
550
|
const { player, garage, lifestyleTier, lifestyleOptions } = buildFromChargen(payload);
|
|
557
551
|
// WHAT A BIRTH CERTIFICATE CARRIES, AND WHAT IT DOES NOT (2026-09-05).
|
|
558
552
|
// Game.applyResumeState restores the PLAYER, applies the LIFESTYLE and
|
|
@@ -578,7 +572,8 @@ function createNewCharacterSaveWithSeed(payload, hubSeed) {
|
|
|
578
572
|
lifestyleTier,
|
|
579
573
|
hubName: hubSeed.name ?? 'the hub',
|
|
580
574
|
},
|
|
581
|
-
hubSeed,
|
|
575
|
+
hubSeedRef: hubSeedRefOf(hubSeed),
|
|
576
|
+
...(DUAL_WRITE_HUB_SEED ? { hubSeed } : {}),
|
|
582
577
|
player: serializePlayer(player),
|
|
583
578
|
game: {
|
|
584
579
|
lifestyleTier, lifestyleOptions, rentDebt: 0, tier: 1, contacts: [],
|
|
@@ -278,7 +278,7 @@ export function buildReproScene(fixture) {
|
|
|
278
278
|
* the room seed -- see types/seed/room-seed.ts. Nothing is invented
|
|
279
279
|
* here, so a bench cannot describe a host the engine cannot build. */
|
|
280
280
|
const hostFields = (side) => (host && hostSide === side
|
|
281
|
-
? { hasNode: true, nodeAccess: host.access ?? 'wireless', hostRating: host.rating }
|
|
281
|
+
? { hasNode: true, nodeAccess: host.access ?? 'wireless', hostRating: host.rating, hostPurpose: host.purpose, hostSculpt: host.sculpt }
|
|
282
282
|
: {});
|
|
283
283
|
// Cameras WITHOUT a host. Never emitted alongside one: a host is
|
|
284
284
|
// always watched already, and saying it twice invites the next reader
|
|
@@ -400,6 +400,13 @@ export function buildReproScene(fixture) {
|
|
|
400
400
|
skills: { ...(seed.player.combat?.skills ?? {}), 'first-aid': 6, medicine: 6 },
|
|
401
401
|
};
|
|
402
402
|
});
|
|
403
|
+
// A FILE NEEDS A HOST TO SIT IN (items[].file). Same refusal as
|
|
404
|
+
// homeless ice, for the same reason: a builder that quietly invented
|
|
405
|
+
// the host would be inventing the thing under test.
|
|
406
|
+
const homelessFile = items.find(it => it.file) && !host;
|
|
407
|
+
if (homelessFile) {
|
|
408
|
+
throw new ReproFixtureError(`This fixture has a file but no host. A file lives inside a host -- add "host": {} so there is something for it to be in.`);
|
|
409
|
+
}
|
|
403
410
|
const itemSeeds = items.map(it => ({
|
|
404
411
|
name: it.name,
|
|
405
412
|
description: it.description,
|
|
@@ -411,8 +418,11 @@ export function buildReproScene(fixture) {
|
|
|
411
418
|
texture: 'Plain',
|
|
412
419
|
rating: 'Common',
|
|
413
420
|
weight: 0.1,
|
|
414
|
-
room
|
|
415
|
-
|
|
421
|
+
// A FILE rides the host's room on the matrix plane and takes no
|
|
422
|
+
// spot -- a spot is a meat position (see the ice rule above).
|
|
423
|
+
...(it.file
|
|
424
|
+
? { plane: 'matrix', room: roomFor(hostSide) }
|
|
425
|
+
: { room: roomFor(it.side ?? 'barrier'), atSpot: it.atDoorway ? REPRO_DOORWAY_SPOT : REPRO_FLOOR_SPOT }),
|
|
416
426
|
}));
|
|
417
427
|
// INVARIANT 4: the key is on the side the runner starts on. A key
|
|
418
428
|
// behind the door it opens is a bench nobody can work, and it reads
|
|
@@ -18,7 +18,8 @@ import { Category, Size, Rating } from './types/shared/item-enum.js';
|
|
|
18
18
|
import { Item } from './models/item.js';
|
|
19
19
|
import { Room, Container } from './models/_index.js';
|
|
20
20
|
import { Door } from './models/door.js';
|
|
21
|
-
import { SAVE_VERSION } from './types/save-file.js';
|
|
21
|
+
import { SAVE_VERSION, DUAL_WRITE_HUB_SEED } from './types/save-file.js';
|
|
22
|
+
import { hubSeedRefOf } from './utilities/hub-seed.js';
|
|
22
23
|
import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, applyHomeBlock, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, spillPathFor, } from './utilities/persistence.js';
|
|
23
24
|
import { heartbeatPresence, heartbeatPresenceNow } from './utilities/social.js';
|
|
24
25
|
import { presenceBeatAllowed } from './utilities/presence-gate.js';
|
|
@@ -665,13 +666,15 @@ export default class Game {
|
|
|
665
666
|
* push one right now and say so.
|
|
666
667
|
*/
|
|
667
668
|
async pushPresenceNow() {
|
|
668
|
-
|
|
669
|
+
// A hosted hub never dials the site from inside the site: the box's
|
|
670
|
+
// own `maka login`, if any, is not this player's.
|
|
671
|
+
if (this._anonymous || this._bench || this._hostedHub)
|
|
669
672
|
return 'offline';
|
|
670
673
|
return heartbeatPresenceNow(this.presencePayload());
|
|
671
674
|
}
|
|
672
675
|
startPresenceHeartbeat() {
|
|
673
|
-
if (this._anonymous || this._bench)
|
|
674
|
-
return; // account-less, or
|
|
676
|
+
if (this._anonymous || this._bench || this._hostedHub)
|
|
677
|
+
return; // account-less, a bench, or hosted on the site: never on the street
|
|
675
678
|
if (this._presenceTimer)
|
|
676
679
|
return;
|
|
677
680
|
this.beatPresence();
|
|
@@ -1285,7 +1288,12 @@ export default class Game {
|
|
|
1285
1288
|
_saveFilePath; // from options.saveFilePath; unset = never save
|
|
1286
1289
|
_dead = false; // set in close(); suppresses any later save
|
|
1287
1290
|
_resume; // present when this session continues a save
|
|
1288
|
-
_hubSeed; // the seed
|
|
1291
|
+
_hubSeed; // the served hub seed this session plays (its stamp is the save's hubSeedRef)
|
|
1292
|
+
/** A HUB HOSTED ON THE SITE (IMainGameConfig.hostedHub): no presence,
|
|
1293
|
+
* no DDP hub link, no auto-draft, and every save beat goes to the
|
|
1294
|
+
* sink instead of a file. */
|
|
1295
|
+
_hostedHub = false;
|
|
1296
|
+
_saveSink;
|
|
1289
1297
|
/**
|
|
1290
1298
|
* The one correct runtime NPC spawn (mirrors scene-factory's seed
|
|
1291
1299
|
* path): construct with a scene-bound registry, add to the scene
|
|
@@ -2898,6 +2906,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2898
2906
|
this._useAi = options.useAi;
|
|
2899
2907
|
this._logFilePath = options.logFilePath;
|
|
2900
2908
|
this._saveFilePath = options.saveFilePath;
|
|
2909
|
+
this._hostedHub = options.hostedHub === true;
|
|
2910
|
+
this._saveSink = options.saveSink;
|
|
2901
2911
|
this._owner = options.owner;
|
|
2902
2912
|
this._anonymous = options.anonymousRunner === true;
|
|
2903
2913
|
this._bench = options.reproBench === true;
|
|
@@ -3027,9 +3037,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3027
3037
|
// and the DDP hub link (run invites, shared sessions -- Phase C)
|
|
3028
3038
|
// warms up behind them. All fire-and-forget.
|
|
3029
3039
|
this.startPresenceHeartbeat();
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3040
|
+
// NOT FROM A HOSTED HUB: the DDP hub link is a terminal's line to
|
|
3041
|
+
// the site, and this game already runs inside it (2026-09-12).
|
|
3042
|
+
if (!this._hostedHub) {
|
|
3043
|
+
void import('./utilities/shared-run.js')
|
|
3044
|
+
.then(m => m.ensureHubLink(this))
|
|
3045
|
+
.catch(() => undefined);
|
|
3046
|
+
}
|
|
3033
3047
|
// The next run starts cooking the moment you're home (player
|
|
3034
3048
|
// request): generation takes 20-40s, and waiting for the player
|
|
3035
3049
|
// to CALL first meant "give me a bit" every time. Draft now --
|
|
@@ -3043,7 +3057,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3043
3057
|
// player explicitly calls Krow (or takes from the jobs board),
|
|
3044
3058
|
// never automatically on load. Solo drafts on the player's own
|
|
3045
3059
|
// key keep cooking at arrival as always.
|
|
3046
|
-
|
|
3060
|
+
// NEVER FROM A HOSTED HUB (2026-09-12): on the site a draft is a
|
|
3061
|
+
// generation on the site's own key, and it happens only when the
|
|
3062
|
+
// player calls Krow -- not on every browser entry.
|
|
3063
|
+
if (!this._resume && !this._hostedHub && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
|
|
3047
3064
|
this.draftNextSceneInBackground();
|
|
3048
3065
|
}
|
|
3049
3066
|
}
|
|
@@ -3073,7 +3090,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3073
3090
|
// Krow's "1 new message" ping lands mid-session as usual.
|
|
3074
3091
|
// Same live-seat gate as the fresh-start draft above: crew work
|
|
3075
3092
|
// is commissioned by CALLING, never by loading in.
|
|
3076
|
-
if (!this._pendingRunSeed && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
|
|
3093
|
+
if (!this._pendingRunSeed && !this._hostedHub && this.livePartyFingerprint() === '' && this.canDraftNextScene().allowed) {
|
|
3077
3094
|
this.draftNextSceneInBackground();
|
|
3078
3095
|
}
|
|
3079
3096
|
}
|
|
@@ -3385,7 +3402,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3385
3402
|
// GUARDS BEFORE THE LOGGER: the "move" beat fires from any room change,
|
|
3386
3403
|
// including ones outside a logging session (harnesses, benches). A
|
|
3387
3404
|
// no-op beat must cost nothing and touch nothing.
|
|
3388
|
-
if (!this.initialized || this._dead || !this._saveFilePath)
|
|
3405
|
+
if (!this.initialized || this._dead || (!this._saveFilePath && !this._saveSink))
|
|
3389
3406
|
return;
|
|
3390
3407
|
if (!this.hasHub || this.scene !== this._hubScene || !this._hubSeed)
|
|
3391
3408
|
return;
|
|
@@ -3394,6 +3411,25 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3394
3411
|
if (this.scene.isPlayerExchangeActive())
|
|
3395
3412
|
return;
|
|
3396
3413
|
const logger = Logger.getInstance();
|
|
3414
|
+
// A HOSTED HUB SAVES INTO THE SINK (2026-09-12): the site writes the
|
|
3415
|
+
// GameSaves doc itself. No file, no spill, no push queue -- those are
|
|
3416
|
+
// a terminal's answer to a network it does not own. The sink's
|
|
3417
|
+
// promise is not awaited here (a beat must not block a command);
|
|
3418
|
+
// flushBoundSave awaits it for quit/evict.
|
|
3419
|
+
if (this._saveSink && !this._saveFilePath) {
|
|
3420
|
+
try {
|
|
3421
|
+
const save = this.buildSaveFile(reason);
|
|
3422
|
+
this._lastSinkWrite = Promise.resolve(this._saveSink(save, reason))
|
|
3423
|
+
.catch(err => logger.write(`Saved (${reason}) -> sink FAILED: ${err instanceof Error ? err.message : String(err)}`));
|
|
3424
|
+
logger.write(`Saved (${reason}) -> sink.`);
|
|
3425
|
+
}
|
|
3426
|
+
catch (err) {
|
|
3427
|
+
logger.write(`requestSave(${reason}) failed to build: ${err instanceof Error ? err.message : String(err)}`);
|
|
3428
|
+
}
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
if (!this._saveFilePath)
|
|
3432
|
+
return;
|
|
3397
3433
|
try {
|
|
3398
3434
|
const save = this.buildSaveFile(reason);
|
|
3399
3435
|
// Carry the cloud-sync stamp FORWARD through local writes: the
|
|
@@ -3453,6 +3489,13 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3453
3489
|
* nothing to seal and must never be made to wait on a network.
|
|
3454
3490
|
*/
|
|
3455
3491
|
async flushBoundSave(reason) {
|
|
3492
|
+
// A HOSTED HUB: one final beat into the sink, awaited -- the caller
|
|
3493
|
+
// (quit, evict, drain) needs the write to have landed.
|
|
3494
|
+
if (this._saveSink && !this._saveFilePath) {
|
|
3495
|
+
this.requestSave(reason);
|
|
3496
|
+
await this._lastSinkWrite;
|
|
3497
|
+
return true;
|
|
3498
|
+
}
|
|
3456
3499
|
if (!this.boundRunner)
|
|
3457
3500
|
return true;
|
|
3458
3501
|
// Take a final beat first, so the thing we seal is the state the
|
|
@@ -3490,7 +3533,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3490
3533
|
lifestyleTier: this.lifestyleTier,
|
|
3491
3534
|
hubName: this._hubName,
|
|
3492
3535
|
},
|
|
3493
|
-
|
|
3536
|
+
// BY REFERENCE (engine 1.51.0): the stamp maka-cli.com put on the
|
|
3537
|
+
// seed this session plays. An unstamped seed (a hand-written
|
|
3538
|
+
// fixture, a scene handed straight to Game.getInstance) records no
|
|
3539
|
+
// ref; the dual-written embed below still carries it this release.
|
|
3540
|
+
hubSeedRef: hubSeedRefOf(this._hubSeed),
|
|
3541
|
+
...(DUAL_WRITE_HUB_SEED ? { hubSeed: this._hubSeed } : {}),
|
|
3494
3542
|
player: serializePlayer(this.player),
|
|
3495
3543
|
game: {
|
|
3496
3544
|
location: this.hubLocationSnapshot(),
|
|
@@ -5784,6 +5832,37 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5784
5832
|
}
|
|
5785
5833
|
return Game.instance;
|
|
5786
5834
|
}
|
|
5835
|
+
/**
|
|
5836
|
+
* A HUB HOSTED ON maka-cli.com (headless.ts createHeadlessHub): the
|
|
5837
|
+
* same boot as a terminal resume -- a Game over the served hub seed
|
|
5838
|
+
* continuing `save` -- but NEVER through the singleton: one server
|
|
5839
|
+
* process hosts many runners' hubs at once, and Game.instance is the
|
|
5840
|
+
* one terminal's game. Pair with initEngine({ hostedHub: true, ... }).
|
|
5841
|
+
*/
|
|
5842
|
+
static forHostedHub(player, hubSeed, save, conditionSink) {
|
|
5843
|
+
const game = new Game(player, hubSeed, conditionSink);
|
|
5844
|
+
game._resume = save;
|
|
5845
|
+
return game;
|
|
5846
|
+
}
|
|
5847
|
+
/** Standing in the hub (as opposed to out on a run). */
|
|
5848
|
+
get onHubScene() {
|
|
5849
|
+
return this._hubScene !== undefined && this.scene === this._hubScene;
|
|
5850
|
+
}
|
|
5851
|
+
/**
|
|
5852
|
+
* THE SAVE AS IT STANDS, for a hosting server's quit/evict/drain --
|
|
5853
|
+
* the same file requestSave would write, or undefined when there is
|
|
5854
|
+
* nothing to save (out on a run, dead, down). The pending run rides
|
|
5855
|
+
* the last hub save, exactly as it does when a terminal quits mid-job.
|
|
5856
|
+
*/
|
|
5857
|
+
snapshotSave(reason) {
|
|
5858
|
+
if (!this.initialized || this._dead || !this.onHubScene || !this._hubSeed)
|
|
5859
|
+
return undefined;
|
|
5860
|
+
if (this.player.isDown() || this.player.isUnconscious())
|
|
5861
|
+
return undefined;
|
|
5862
|
+
return this.buildSaveFile(reason);
|
|
5863
|
+
}
|
|
5864
|
+
/** The last sink write in flight, awaited by flushBoundSave. */
|
|
5865
|
+
_lastSinkWrite;
|
|
5787
5866
|
/**
|
|
5788
5867
|
* The verb the LAST handleInput actually ran, after typo correction
|
|
5789
5868
|
* ("sjeet" -> "sheet"). ui.ts routes menu output to the Mechanics
|