@maka/maka-cli 5.162.0 → 5.164.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/backlog.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +33 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +147 -61
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +30 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +25 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +35 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +9 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.164.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.",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { hint } from '../utilities/hints.js';
|
|
3
3
|
import { authToken } from '../utilities/cloud-saves.js';
|
|
4
|
+
import { currentSession } from '../utilities/session-context.js';
|
|
4
5
|
import { submitBacklog, fetchBacklog, postBacklogComment, postBacklogSignoff, postBacklogReject, postBacklogClose, BACKLOG_STATUSES, } from '../utilities/backlog.js';
|
|
5
6
|
import { noteBacklogOutcome } from '../utilities/playtester.js';
|
|
6
7
|
import { takeIndex } from '../utilities/fuzzy-match.js';
|
|
@@ -163,6 +164,10 @@ export class BacklogCommand extends Command {
|
|
|
163
164
|
cliVersion: cliVersion(),
|
|
164
165
|
engineVersion: engineVersion(),
|
|
165
166
|
platform: process.platform,
|
|
167
|
+
// The host names the client (session-context.ts): a browser run's
|
|
168
|
+
// engine is the same code on a linux box, and 'linux' told nobody
|
|
169
|
+
// where the report actually came from (wQEyefjzbwtdmdrJo).
|
|
170
|
+
client: currentSession()?.client ?? 'cli',
|
|
166
171
|
});
|
|
167
172
|
// A real call classifies the account for free -- no second request.
|
|
168
173
|
noteBacklogOutcome(outcome);
|
|
@@ -408,6 +408,39 @@ Key Locations: ${json.rooms.map(r => r.name).join(', ')}
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
|
+
// Step 4.95: NO DOOR SHIPS LOCKED WITHOUT A WAY THROUGH
|
|
412
|
+
// (S8WdW7gm9tFGaxeCi, 2026-09-07: "I broke the gate, but the door is
|
|
413
|
+
// locked. It doesn't look like it has a way to open it"). A seed
|
|
414
|
+
// exit's `keyRequired` builds a locked Door with no Device: "pick",
|
|
415
|
+
// "breach" and "hack" all answer "nothing here", the refusal names
|
|
416
|
+
// no route, and the only verb that works ("unlock <dir>" while
|
|
417
|
+
// carrying the key) was never surfaced. The device path was
|
|
418
|
+
// hardened against exactly this (seed-migration synthesises a code
|
|
419
|
+
// rather than ship a routeless barrier); the keyed path never was.
|
|
420
|
+
//
|
|
421
|
+
// Same clamp-don't-throw rule as the keyItem repair above: a key
|
|
422
|
+
// that resolves (exactly or by the same fuzzy match) is written back
|
|
423
|
+
// under its real name so "unlock" finds it; a key that resolves to
|
|
424
|
+
// nothing means the door cannot be opened by anyone, ever, and it
|
|
425
|
+
// ships UNLOCKED with the error logged. Runs on every seed path --
|
|
426
|
+
// static, generated, repro, and the hub seed inside every save.
|
|
427
|
+
for (const roomJson of json.rooms) {
|
|
428
|
+
const room = scene.getRoom(roomJson.name);
|
|
429
|
+
if (!room)
|
|
430
|
+
continue;
|
|
431
|
+
for (const [direction, exit] of room.exits.entries()) {
|
|
432
|
+
if (!(exit instanceof Door) || !exit.checkIfLocked() || exit.heldBy)
|
|
433
|
+
continue;
|
|
434
|
+
const wanted = exit.unlockItemOrSolution;
|
|
435
|
+
const key = wanted ? findItem(wanted) : undefined;
|
|
436
|
+
if (key) {
|
|
437
|
+
exit.unlockItemOrSolution = key.name;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
exit.isLocked = false;
|
|
441
|
+
logger.error(`Scene "${json.name}": the door ${String(direction)} of ${room.name} was locked by "${wanted ?? '(nothing)'}", which no device holds and no item matches -- it ships unlocked rather than as a wall.`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
411
444
|
// Step 4.05: NPCs wear their gear. "heldBy" only puts items in an NPC's
|
|
412
445
|
// inventory, but combat pools (see Player.getWeaponProfile /
|
|
413
446
|
// getArmorValue) read from EQUIPPED slots -- without this, an enforcer
|
|
@@ -2380,6 +2380,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2380
2380
|
* unchanged.
|
|
2381
2381
|
*/
|
|
2382
2382
|
_conditionSink;
|
|
2383
|
+
/** A hosted run's aftermath still owed to this hub (IPendingHomecoming). */
|
|
2384
|
+
_pendingHomecoming;
|
|
2383
2385
|
constructor(player, sceneSeed, conditionSink) {
|
|
2384
2386
|
this._conditionSink = conditionSink;
|
|
2385
2387
|
this.player = player;
|
|
@@ -3043,6 +3045,12 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3043
3045
|
// party-loadable within moments of launch now -- requestSave pushes
|
|
3044
3046
|
// the cloud mirror behind the local write.
|
|
3045
3047
|
if (this._hubScene && this.scene === this._hubScene) {
|
|
3048
|
+
// A HOSTED RUN'S AFTERMATH, SETTLED AT THE DOOR (8izFwokbehjHzkEAn,
|
|
3049
|
+
// 3gFcHfheGSMPtpCFh): the site wrote the runner back with a
|
|
3050
|
+
// homecoming owed; this is the hub entry that pays it.
|
|
3051
|
+
const owed = this.applyHostedHomecoming();
|
|
3052
|
+
if (owed)
|
|
3053
|
+
Logger.getInstance().logWithColor(owed, 'green');
|
|
3046
3054
|
this.requestSave('hub-arrival');
|
|
3047
3055
|
}
|
|
3048
3056
|
// A REGISTERED sprite from the save re-shells at the cot (the
|
|
@@ -3184,6 +3192,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3184
3192
|
}
|
|
3185
3193
|
applyHubOverlay(this.scene, save.hub, this.player, this._homeRoom);
|
|
3186
3194
|
this._tier = save.game.tier;
|
|
3195
|
+
this._pendingHomecoming = save.game.pendingHomecoming;
|
|
3187
3196
|
this.contacts = new Map(save.game.contacts);
|
|
3188
3197
|
// Roles are DERIVED, not earned -- re-derive them all on every
|
|
3189
3198
|
// resume so old saves self-heal (pre-role saves read all "Street
|
|
@@ -3469,6 +3478,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3469
3478
|
return all.length > 0 ? all : undefined;
|
|
3470
3479
|
})(),
|
|
3471
3480
|
crew: this.crew.map(m => ({ ...m, gear: [...m.gear] })),
|
|
3481
|
+
pendingHomecoming: this._pendingHomecoming,
|
|
3472
3482
|
commandRefusals: { ...this.commandRefusals },
|
|
3473
3483
|
fencedCategories: [...this.fencedCategories],
|
|
3474
3484
|
rumorIndex: this._rumorIndex,
|
|
@@ -3843,6 +3853,50 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3843
3853
|
contact.asked = false;
|
|
3844
3854
|
// Fresh material for the buskers too (commands/perform.ts).
|
|
3845
3855
|
this.performedThisCycle = false;
|
|
3856
|
+
// LIFESTYLE upkeep and the High-lifestyle shower: settleLifestyleUpkeep,
|
|
3857
|
+
// shared with the hosted run's owed homecoming (applyHostedHomecoming).
|
|
3858
|
+
const { rentLine, comfortLine } = this.settleLifestyleUpkeep();
|
|
3859
|
+
// Fresh streets, fresh gossip, restocked shelves, new work.
|
|
3860
|
+
this.advanceRumor();
|
|
3861
|
+
this.fenceSellsOn();
|
|
3862
|
+
const restocked = this.restockVendorShelves();
|
|
3863
|
+
const restockLine = restocked > 0 ? `\nThe district has restocked around you -- the wok steams, the bar's shelf is full, the market stalls are loud again.` : '';
|
|
3864
|
+
this.advanceGig();
|
|
3865
|
+
this.repaintForScene();
|
|
3866
|
+
this.refreshLogLabel();
|
|
3867
|
+
Logger.getInstance().write(`Returned to hub "${this._hubName}". Lifestyle ${this.lifestyleTier}: ${this.rentDebt} owed. Restocked ${restocked}. Rumor: ${this.activeRumor ?? '(none)'}`);
|
|
3868
|
+
// Drones spin back up beside the cot.
|
|
3869
|
+
const companionArrivalLines = this.unfoldCompanionsOnArrival();
|
|
3870
|
+
const companionBlock = [...companionFoldLines, ...companionArrivalLines];
|
|
3871
|
+
// The commanded ally's tour ends with the run (RAW's Command lasts
|
|
3872
|
+
// turns; ours lasts the job): the crew disbands at the curb.
|
|
3873
|
+
// The crew stays hired between jobs (the roster persists); their
|
|
3874
|
+
// homecoming lines (obituaries, write-back) rode in above. Fresh
|
|
3875
|
+
// faces on the for-hire board each homecoming.
|
|
3876
|
+
this.rollHireCandidates();
|
|
3877
|
+
// Social settlement: the street hears you're home.
|
|
3878
|
+
this.beatPresence();
|
|
3879
|
+
const allyLine = crewLines.length > 0 ? `\n${crewLines.join('\n')}` : '';
|
|
3880
|
+
const docwagonLine = this.tickDocwagonHomecoming();
|
|
3881
|
+
const deliveryLine = this.deliverProcurements();
|
|
3882
|
+
// The night catches up on the ride (SINs & getting caught): a hot
|
|
3883
|
+
// run means the wire between there and home. Settled AFTER the
|
|
3884
|
+
// payout (the fee comes out of tonight's take) and BEFORE the save
|
|
3885
|
+
// below -- a burned fake or an issued SIN must survive a quit.
|
|
3886
|
+
const checkpointBlock = this.checkpointOnRideHome();
|
|
3887
|
+
// The homecoming save: everything above (payout, rent, restock,
|
|
3888
|
+
// contacts reset, deliveries, the checkpoint) settled first, so the
|
|
3889
|
+
// save reflects the hub as the player will find it.
|
|
3890
|
+
this.requestSave('homecoming');
|
|
3891
|
+
const fateLine = wrappedRun
|
|
3892
|
+
? `The run's wrapped and the street swallows you back up.`
|
|
3893
|
+
: `The run's behind you -- unfinished, unpaid, already someone else's problem. The street doesn't ask.`;
|
|
3894
|
+
return `${checkpointBlock ? `\n${checkpointBlock}\n` : ''}\n${this._hubName.toUpperCase()}\n${fateLine} You're home -- your hideout, one door off ${hub.determineStartRoom().name}.${hint(` Call ${FIXER_NAME} when you're hungry for the next job.`)}${clientLine}${awarenessLine}\n${rentLine}${comfortLine}${restockLine}${deliveryLine}${docwagonLine}${allyLine}${companionBlock.length > 0 ? `\n${companionBlock.join('\n')}` : ''}`;
|
|
3895
|
+
}
|
|
3896
|
+
/** The rent, the slide, the tab, and the High-lifestyle shower --
|
|
3897
|
+
* homecoming's money half, for the solo ride home and the hosted
|
|
3898
|
+
* run's owed homecoming alike. */
|
|
3899
|
+
settleLifestyleUpkeep() {
|
|
3846
3900
|
// LIFESTYLE upkeep comes due with the homecoming: this tier's share
|
|
3847
3901
|
// plus any standing debt, paid from carried cred as far as it goes.
|
|
3848
3902
|
// Coming up short SLIDES YOU DOWN a tier (the district notices) and
|
|
@@ -3887,75 +3941,98 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
3887
3941
|
const cleared = this.player.healStun(this.player.stunTaken);
|
|
3888
3942
|
comfortLine = `\nThe shower runs hot until the run is a rumor -- ${cleared} stun gone before your head hits the pillow.`;
|
|
3889
3943
|
}
|
|
3890
|
-
|
|
3944
|
+
return { rentLine, comfortLine };
|
|
3945
|
+
}
|
|
3946
|
+
/** DOCWAGON's year ticks in homecomings (12 = a street year, player
|
|
3947
|
+
* ruling): the contract lapses quietly unless renewed at Doc's. */
|
|
3948
|
+
tickDocwagonHomecoming() {
|
|
3949
|
+
if (!this.docwagon)
|
|
3950
|
+
return '';
|
|
3951
|
+
this.docwagon.homecomingsLeft -= 1;
|
|
3952
|
+
if (this.docwagon.homecomingsLeft <= 0) {
|
|
3953
|
+
const tier = this.docwagon.tier;
|
|
3954
|
+
this.docwagon = undefined;
|
|
3955
|
+
return `\nYour DocWagon ${tier} contract LAPSES -- the wristband goes dark.${hint(` ("docwagon <tier>" at Doc's renews it.)`)}`;
|
|
3956
|
+
}
|
|
3957
|
+
if (this.docwagon.homecomingsLeft <= 2) {
|
|
3958
|
+
return `\nDocWagon renewal coming due: ${this.docwagon.homecomingsLeft} homecoming${this.docwagon.homecomingsLeft === 1 ? '' : 's'} left on the ${this.docwagon.tier} contract.`;
|
|
3959
|
+
}
|
|
3960
|
+
return '';
|
|
3961
|
+
}
|
|
3962
|
+
/** SWAG deliveries (commands/source.ts): the contact's shopping run
|
|
3963
|
+
* lands with the homecoming -- straight into your gear, paid for at
|
|
3964
|
+
* order time. */
|
|
3965
|
+
deliverProcurements() {
|
|
3966
|
+
if (this.pendingProcurements.length === 0)
|
|
3967
|
+
return '';
|
|
3968
|
+
const delivered = [];
|
|
3969
|
+
for (const order of this.pendingProcurements.splice(0)) {
|
|
3970
|
+
try {
|
|
3971
|
+
const item = restoreItem(order.itemConfig);
|
|
3972
|
+
item.owner = this.player.name;
|
|
3973
|
+
this.player.inventory.addItem(item);
|
|
3974
|
+
delivered.push(`${item.name} (${order.contact} came through)`);
|
|
3975
|
+
}
|
|
3976
|
+
catch (err) {
|
|
3977
|
+
Logger.getInstance().error(`Procurement delivery failed: ${err}`);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
if (delivered.length === 0)
|
|
3981
|
+
return '';
|
|
3982
|
+
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
3983
|
+
return `\nWaiting with your gear: ${delivered.join(', ')}.`;
|
|
3984
|
+
}
|
|
3985
|
+
/**
|
|
3986
|
+
* A HOSTED RUN'S HOMECOMING (8izFwokbehjHzkEAn, 3gFcHfheGSMPtpCFh): the
|
|
3987
|
+
* aftermath returnToHub settles for a solo ride home, applied ONCE for
|
|
3988
|
+
* a run that ended on the site -- from the flag the server left on the
|
|
3989
|
+
* save (IPendingHomecoming), or handed in directly by a CLI that rode a
|
|
3990
|
+
* shared table. What it settles: the loud-run awareness point, the
|
|
3991
|
+
* Johnson's opinion, the crew's tally, contacts' legwork, rent and the
|
|
3992
|
+
* slide, the shower, restock/rumor/gig, the hire board, the DocWagon
|
|
3993
|
+
* year, deliveries. What it does not: the checkpoint on the ride home
|
|
3994
|
+
* (heat is the run's, and the run is gone) and crew gear write-back
|
|
3995
|
+
* (their shells never stood in this process). Clears the flag and
|
|
3996
|
+
* writes the homecoming save. Undefined when nothing is owed, or when
|
|
3997
|
+
* the player is not standing in the hub.
|
|
3998
|
+
*/
|
|
3999
|
+
applyHostedHomecoming(info) {
|
|
4000
|
+
const owed = info ?? this._pendingHomecoming;
|
|
4001
|
+
this._pendingHomecoming = undefined;
|
|
4002
|
+
if (!owed)
|
|
4003
|
+
return undefined;
|
|
4004
|
+
if (!this._hubScene || this.scene !== this._hubScene)
|
|
4005
|
+
return undefined;
|
|
4006
|
+
const awarenessLine = owed.alarmed && this.player.addPublicAwareness(`left "${owed.jobName ?? 'a job'}" screaming`)
|
|
4007
|
+
? `\nYou left the place howling behind you. Somebody filed a report, somebody kept a still -- your face is a little more public than it was this morning. (+1 Public Awareness, now ${this.player.publicAwareness})`
|
|
4008
|
+
: '';
|
|
4009
|
+
this.currentClient = owed.client ? { ...owed.client } : undefined;
|
|
4010
|
+
const clientLine = this.settleClientReputation(owed.wrapped);
|
|
4011
|
+
for (const member of this.crew) {
|
|
4012
|
+
if (owed.wrapped && !member.ghost?.liveOnly)
|
|
4013
|
+
member.jobsRun += 1;
|
|
4014
|
+
member.trainedThisCycle = false;
|
|
4015
|
+
}
|
|
4016
|
+
for (const contact of this.contacts.values())
|
|
4017
|
+
contact.asked = false;
|
|
4018
|
+
this.performedThisCycle = false;
|
|
4019
|
+
const { rentLine, comfortLine } = this.settleLifestyleUpkeep();
|
|
3891
4020
|
this.advanceRumor();
|
|
3892
4021
|
this.fenceSellsOn();
|
|
3893
4022
|
const restocked = this.restockVendorShelves();
|
|
3894
4023
|
const restockLine = restocked > 0 ? `\nThe district has restocked around you -- the wok steams, the bar's shelf is full, the market stalls are loud again.` : '';
|
|
3895
4024
|
this.advanceGig();
|
|
3896
|
-
this.repaintForScene();
|
|
3897
|
-
this.refreshLogLabel();
|
|
3898
|
-
Logger.getInstance().write(`Returned to hub "${this._hubName}". Lifestyle ${this.lifestyleTier}: ${paid} paid, ${this.rentDebt} owed. Restocked ${restocked}. Rumor: ${this.activeRumor ?? '(none)'}`);
|
|
3899
|
-
// Drones spin back up beside the cot.
|
|
3900
|
-
const companionArrivalLines = this.unfoldCompanionsOnArrival();
|
|
3901
|
-
const companionBlock = [...companionFoldLines, ...companionArrivalLines];
|
|
3902
|
-
// The commanded ally's tour ends with the run (RAW's Command lasts
|
|
3903
|
-
// turns; ours lasts the job): the crew disbands at the curb.
|
|
3904
|
-
// The crew stays hired between jobs (the roster persists); their
|
|
3905
|
-
// homecoming lines (obituaries, write-back) rode in above. Fresh
|
|
3906
|
-
// faces on the for-hire board each homecoming.
|
|
3907
4025
|
this.rollHireCandidates();
|
|
3908
|
-
|
|
3909
|
-
this.
|
|
3910
|
-
|
|
3911
|
-
// DOCWAGON's year ticks in homecomings (12 = a street year, player
|
|
3912
|
-
// ruling): the contract lapses quietly unless renewed at Doc's.
|
|
3913
|
-
let docwagonLine = '';
|
|
3914
|
-
if (this.docwagon) {
|
|
3915
|
-
this.docwagon.homecomingsLeft -= 1;
|
|
3916
|
-
if (this.docwagon.homecomingsLeft <= 0) {
|
|
3917
|
-
docwagonLine = `\nYour DocWagon ${this.docwagon.tier} contract LAPSES -- the wristband goes dark.${hint(` ("docwagon <tier>" at Doc's renews it.)`)}`;
|
|
3918
|
-
this.docwagon = undefined;
|
|
3919
|
-
}
|
|
3920
|
-
else if (this.docwagon.homecomingsLeft <= 2) {
|
|
3921
|
-
docwagonLine = `\nDocWagon renewal coming due: ${this.docwagon.homecomingsLeft} homecoming${this.docwagon.homecomingsLeft === 1 ? '' : 's'} left on the ${this.docwagon.tier} contract.`;
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
// SWAG deliveries (commands/source.ts): the contact's shopping run
|
|
3925
|
-
// lands with the homecoming -- straight into your gear, paid for at
|
|
3926
|
-
// order time.
|
|
3927
|
-
let deliveryLine = '';
|
|
3928
|
-
if (this.pendingProcurements.length > 0) {
|
|
3929
|
-
const delivered = [];
|
|
3930
|
-
for (const order of this.pendingProcurements.splice(0)) {
|
|
3931
|
-
try {
|
|
3932
|
-
const item = restoreItem(order.itemConfig);
|
|
3933
|
-
item.owner = this.player.name;
|
|
3934
|
-
this.player.inventory.addItem(item);
|
|
3935
|
-
delivered.push(`${item.name} (${order.contact} came through)`);
|
|
3936
|
-
}
|
|
3937
|
-
catch (err) {
|
|
3938
|
-
Logger.getInstance().error(`Procurement delivery failed: ${err}`);
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
if (delivered.length > 0) {
|
|
3942
|
-
this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
|
|
3943
|
-
deliveryLine = `\nWaiting with your gear: ${delivered.join(', ')}.`;
|
|
3944
|
-
}
|
|
3945
|
-
}
|
|
3946
|
-
// The night catches up on the ride (SINs & getting caught): a hot
|
|
3947
|
-
// run means the wire between there and home. Settled AFTER the
|
|
3948
|
-
// payout (the fee comes out of tonight's take) and BEFORE the save
|
|
3949
|
-
// below -- a burned fake or an issued SIN must survive a quit.
|
|
3950
|
-
const checkpointBlock = this.checkpointOnRideHome();
|
|
3951
|
-
// The homecoming save: everything above (payout, rent, restock,
|
|
3952
|
-
// contacts reset, deliveries, the checkpoint) settled first, so the
|
|
3953
|
-
// save reflects the hub as the player will find it.
|
|
4026
|
+
const docwagonLine = this.tickDocwagonHomecoming();
|
|
4027
|
+
const deliveryLine = this.deliverProcurements();
|
|
4028
|
+
Logger.getInstance().write(`Hosted homecoming applied (${owed.outcome}, ended ${owed.endedAt}): rent ${this.rentDebt} owed, lifestyle ${this.lifestyleTier}.`);
|
|
3954
4029
|
this.requestSave('homecoming');
|
|
3955
|
-
const fateLine =
|
|
4030
|
+
const fateLine = owed.outcome === 'wrapped'
|
|
3956
4031
|
? `The run's wrapped and the street swallows you back up.`
|
|
3957
|
-
:
|
|
3958
|
-
|
|
4032
|
+
: owed.outcome === 'interrupted'
|
|
4033
|
+
? `The run was lost to the static -- the street doesn't ask.`
|
|
4034
|
+
: `The run's behind you -- unfinished, unpaid, already someone else's problem. The street doesn't ask.`;
|
|
4035
|
+
return `${fateLine} The table's settled; home takes its cut.${clientLine}${awarenessLine}\n${rentLine}${comfortLine}${restockLine}${deliveryLine}${docwagonLine}`;
|
|
3959
4036
|
}
|
|
3960
4037
|
/**
|
|
3961
4038
|
* KNIGHT ERRANT CHECKPOINT (SINs & getting caught, ruling 2026-08-24;
|
|
@@ -5307,6 +5384,15 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5307
5384
|
const heat = this.heatBand();
|
|
5308
5385
|
if (heat && player === this.player)
|
|
5309
5386
|
lines.push(heat);
|
|
5387
|
+
// THE ALARM (kqrSCWjj96SP4MRXj, web run 2026-09-11: "I got a
|
|
5388
|
+
// notification that the alarms sound, but I don't see any
|
|
5389
|
+
// indication in my status bar"). The site going loud was a world
|
|
5390
|
+
// event that scrolled away; Scene.alarmRaised has been the standing
|
|
5391
|
+
// fact since 2026-08-27, and it belongs on the HUD for as long as it
|
|
5392
|
+
// holds. A fact about the PLACE, so every player at the table sees
|
|
5393
|
+
// it -- unlike heat, which is the primary's own ledger.
|
|
5394
|
+
if (this.scene?.alarmRaised)
|
|
5395
|
+
lines.push(`{red-fg}🚨 ALARM -- the site is lit{/red-fg}`);
|
|
5310
5396
|
// Line 3: the rest of the readiness read -- armor, and whether the
|
|
5311
5397
|
// commlink is actually live (equipped), the recurring "why can't I
|
|
5312
5398
|
// call anyone" friction surfaced at a glance. The armed state used
|
|
@@ -190,7 +190,7 @@ export function buildPlayerFromSave(save) {
|
|
|
190
190
|
}
|
|
191
191
|
export async function createHeadlessSession(opts) {
|
|
192
192
|
const logger = new EmitterLogger(opts.sink, opts.debug);
|
|
193
|
-
const ctx = { sessionId: opts.sessionId, logger };
|
|
193
|
+
const ctx = { sessionId: opts.sessionId, logger, client: opts.client };
|
|
194
194
|
return runInSession(ctx, async () => {
|
|
195
195
|
const primary = buildPlayerFromSave(opts.players[0]);
|
|
196
196
|
const game = new Game(primary, opts.seed, opts.conditionSink);
|
|
@@ -261,6 +261,26 @@ export async function createHeadlessSession(opts) {
|
|
|
261
261
|
const clientSpot = clientActor ? spotOf(clientActor) : undefined;
|
|
262
262
|
logger.log(`📞 Your link chirps -- {bold}{underline}${clientName}{/underline}{/bold}: "${room ? `I'm at ${room}${clientSpot ? `, the ${clientSpot}` : ''}` : `I'm on-site`} -- waiting on you. Come sit down before somebody else does."`, 'all');
|
|
263
263
|
}
|
|
264
|
+
// THE CREW RIDES ALONG (aCjJiDDJM9jGZJeER, 2026-09-11: "I can't see
|
|
265
|
+
// Hammerhead Joe doing any combat"). Game.spawnCrew has exactly one
|
|
266
|
+
// caller -- the SOLO arrival (activateRunScene) -- and this boot
|
|
267
|
+
// loaded only the leader's DocWagon wristband out of save.game, so a
|
|
268
|
+
// hosted run had no crew at all: a hired face the player had paid
|
|
269
|
+
// for simply was not in the scene. Same filter as Game's own save
|
|
270
|
+
// load (a friend invite rides live or not at all; a dead ghost
|
|
271
|
+
// dissolves), same spawn, same arrival lines, for the primary only
|
|
272
|
+
// -- a late joiner's crew is a table question this does not answer.
|
|
273
|
+
try {
|
|
274
|
+
const primarySave = opts.players[0];
|
|
275
|
+
game.crew = (primarySave?.game?.crew ?? [])
|
|
276
|
+
.filter(m => !(m.ghost && !m.ghost.liveOnly))
|
|
277
|
+
.map(m => ({ ...m, gear: [...m.gear] }));
|
|
278
|
+
for (const line of game.spawnCrew())
|
|
279
|
+
logger.log(line, 'all');
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
logger.write(`spawnCrew (hosted boot) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
283
|
+
}
|
|
264
284
|
const spawnRoom = () => game.scene.determineStartRoom();
|
|
265
285
|
// PER-PLAYER MAP EVENTS (map-overlay wave 2026-08-24): each rider
|
|
266
286
|
// gets their own icon minimap -- the client holds no Room objects
|
|
@@ -368,6 +388,14 @@ export async function createHeadlessSession(opts) {
|
|
|
368
388
|
const res = await game.handleInputAs(actorName, input);
|
|
369
389
|
session.pushStatus();
|
|
370
390
|
pushMap();
|
|
391
|
+
// THE WEB MAP FOLLOWS THE ROOM, NOT JUST YOUR OWN BODY
|
|
392
|
+
// (oHhjxjqKuptDGMHLD: a looted body stayed on the map "until I
|
|
393
|
+
// move"; 9aEMA4APbB6rYtWnT: a killed NPC's dot never changed).
|
|
394
|
+
// The condition beat is what the browser draws actors from, and
|
|
395
|
+
// it fires only on the primary's OWN mutations -- a kill or a
|
|
396
|
+
// loot mutates the room, not the killer, so nothing fired until
|
|
397
|
+
// their next step. One nudge per command; the beat coalesces.
|
|
398
|
+
game.player.noteConditionChanged();
|
|
371
399
|
return res;
|
|
372
400
|
})),
|
|
373
401
|
addPlayer: (save) => runInSession(ctx, async () => {
|
|
@@ -489,6 +517,7 @@ export async function createHeadlessSession(opts) {
|
|
|
489
517
|
game.onSceneChanged = () => {
|
|
490
518
|
session.pushStatus();
|
|
491
519
|
pushMap();
|
|
520
|
+
game.player.noteConditionChanged();
|
|
492
521
|
};
|
|
493
522
|
return session;
|
|
494
523
|
});
|
|
@@ -236,23 +236,44 @@ export function buildConditionReport(player, deps) {
|
|
|
236
236
|
// guessed role): a construct on the matrix plane -- IC, sprite, agent --
|
|
237
237
|
// has no body in the room. Another player's body still shows even
|
|
238
238
|
// while their persona rides the grid.
|
|
239
|
+
// DOWNED, NOT DROPPED (9aEMA4APbB6rYtWnT, 2026-09-11: "when I kill
|
|
240
|
+
// an NPC, their icon on the map should change"): the room panel
|
|
241
|
+
// draws somebody face-down as ☓; this beat used to drop them, so
|
|
242
|
+
// the web dot simply vanished -- or, worse, stood there until the
|
|
243
|
+
// next beat. They ride with kind 'downed' now.
|
|
239
244
|
actors: (player.currentLocation ? visibleActorsIn(player.currentLocation, player) : [])
|
|
240
245
|
.filter(a => !(a.plane === 'matrix' && a !== player && (a.icHost !== undefined || a.companionKind === 'sprite' || a.companionKind === 'agent')))
|
|
241
|
-
.filter(a => a !== player
|
|
246
|
+
.filter(a => a !== player)
|
|
242
247
|
.slice(0, 24)
|
|
243
248
|
.map(a => ({
|
|
244
249
|
name: a.name,
|
|
245
250
|
// THE SAME RED THE ROOM PANEL USES (see IConditionReportDeps
|
|
246
251
|
// .isHostile): stance flag OR live enmity, and never a
|
|
247
252
|
// surrendered one -- hostileByStance owns that clause.
|
|
248
|
-
kind: a.
|
|
249
|
-
:
|
|
253
|
+
kind: a.isIncapacitated() ? 'downed'
|
|
254
|
+
: a.allyOf ? 'ally'
|
|
255
|
+
: (isHostile?.(a) ?? hostileByStance(a)) ? 'hostile' : 'neutral',
|
|
250
256
|
role: actorRole(a),
|
|
251
257
|
spot: a.atSpot,
|
|
252
258
|
cell: a.atCell ?? seats.get(a.name),
|
|
253
259
|
})),
|
|
260
|
+
// THE DEAD (same report): the room's bodies, where they fell -- the
|
|
261
|
+
// panel's 💀. Gone from the list the moment a body is picked clean
|
|
262
|
+
// (loot.ts removeBody), which with the per-command beat is at once.
|
|
263
|
+
bodies: roomBodies(player),
|
|
254
264
|
};
|
|
255
265
|
}
|
|
266
|
+
function roomBodies(player) {
|
|
267
|
+
const room = player.currentLocation;
|
|
268
|
+
if (!room || (player.plane !== 'meat' && player.plane !== 'drone'))
|
|
269
|
+
return [];
|
|
270
|
+
try {
|
|
271
|
+
return room.bodies.slice(0, 24).map(b => ({ name: b.name, cell: { x: b.cell.x, y: b.cell.y, z: b.cell.z } }));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
256
277
|
let lastRoster = '';
|
|
257
278
|
/** One session-log line whenever the room or the roster changes -- the
|
|
258
279
|
* cheapest possible answer to "what did the engine send?" */
|
|
@@ -276,7 +297,7 @@ function noteRoster(report) {
|
|
|
276
297
|
const ADDITIVE_FIELDS = [
|
|
277
298
|
'ar', 'actors', 'deckLine', 'deckDamage', 'astralPerceiving', 'bodyRoom', 'sustainingPenalty',
|
|
278
299
|
'matrix', 'panMarks', 'hostMarksOnYou', 'marksPlaced', 'marksOnYou', 'deckStored', 'droneDamage', 'companions', 'pools', 'initiative',
|
|
279
|
-
'karma', 'nuyen', 'edge', 'grid',
|
|
300
|
+
'karma', 'nuyen', 'edge', 'grid', 'bodies',
|
|
280
301
|
];
|
|
281
302
|
/** Refusal reasons already written to the session log -- one line each. */
|
|
282
303
|
const announcedRefusals = new Map();
|
|
@@ -1188,6 +1188,29 @@ export class SharedRunSession {
|
|
|
1188
1188
|
}
|
|
1189
1189
|
if (!verb)
|
|
1190
1190
|
return '';
|
|
1191
|
+
// PULLING SOMEBODY INTO A LIVE RUN (user 2026-09-11: "ensure that I
|
|
1192
|
+
// can pull in another runner during a live shared run, and the run
|
|
1193
|
+
// won't collapse"). The crew ledger is hub-only (crew.ts), and the
|
|
1194
|
+
// session's party was frozen at createSession -- so out here "crew
|
|
1195
|
+
// invite" had nowhere to go. The server adds the seat to the OPEN
|
|
1196
|
+
// session (sideQuest.inviteToOpen); their commlink buzzes through
|
|
1197
|
+
// the same invite feed, and their taxi seats them through the same
|
|
1198
|
+
// addPlayer a late joiner always used. Nothing about the scene is
|
|
1199
|
+
// touched: the job was sized at draft time and stays that size.
|
|
1200
|
+
const inviteMatch = /^(?:crew|party)\s+invite\s+(.+)$/i.exec(input.trim());
|
|
1201
|
+
if (inviteMatch) {
|
|
1202
|
+
try {
|
|
1203
|
+
const res = await HubLink.call('sideQuest.inviteToOpen', {
|
|
1204
|
+
sessionID: this.sessionID,
|
|
1205
|
+
query: inviteMatch[1].trim(),
|
|
1206
|
+
});
|
|
1207
|
+
return `Invite sent to ${res.memberRunner} -- a seat at this table, mid-job.${res.online ? '' : ` (They're off the street -- it'll wait on their link.)`}`;
|
|
1208
|
+
}
|
|
1209
|
+
catch (err) {
|
|
1210
|
+
const e = err;
|
|
1211
|
+
return `{yellow-fg}${e.reason ?? e.message ?? 'The offer does not go through.'}{/yellow-fg}`;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1191
1214
|
// THE RIDE HOME: on a shared run the taxi is the settlement
|
|
1192
1215
|
// handshake, not an engine verb -- the leader closes the table for
|
|
1193
1216
|
// everyone (snapshots persist, shares already on each stick); a
|
|
@@ -1296,6 +1319,18 @@ export class SharedRunSession {
|
|
|
1296
1319
|
restorePlayer(g.player, cloud.player);
|
|
1297
1320
|
if (g.saveFilePath)
|
|
1298
1321
|
writeSaveAtomic(g.saveFilePath, cloud);
|
|
1322
|
+
// THE HOMECOMING OWED (8izFwokbehjHzkEAn, 3gFcHfheGSMPtpCFh): the
|
|
1323
|
+
// server settled the table and left the aftermath on the save;
|
|
1324
|
+
// this hub Game never went anywhere, so it pays it now -- rent,
|
|
1325
|
+
// the DocWagon year, the Johnson's opinion -- and the homecoming
|
|
1326
|
+
// save it writes clears the flag. After the disk write above, so
|
|
1327
|
+
// the settled state is what lands, not the cloud's pre-settled one.
|
|
1328
|
+
const owed = cloud.game?.pendingHomecoming;
|
|
1329
|
+
if (owed) {
|
|
1330
|
+
const lines = g.applyHostedHomecoming(owed);
|
|
1331
|
+
if (lines)
|
|
1332
|
+
Logger.getInstance().logWithColor(lines, 'green');
|
|
1333
|
+
}
|
|
1299
1334
|
}
|
|
1300
1335
|
}
|
|
1301
1336
|
catch (err) {
|
|
@@ -1241,7 +1241,15 @@ export function lockedRouteHint(room, direction, door) {
|
|
|
1241
1241
|
if (door.heldBy) {
|
|
1242
1242
|
return ` It's barred from ${door.heldBy.room} -- ${door.heldBy.device} is over there, not here.`;
|
|
1243
1243
|
}
|
|
1244
|
-
// A
|
|
1244
|
+
// A PLAIN KEYED DOOR (S8WdW7gm9tFGaxeCi): there IS a route, and the
|
|
1245
|
+
// Door knows it -- the key item it was locked with, and the one verb
|
|
1246
|
+
// that works it. The comment here used to say "no route to invent";
|
|
1247
|
+
// naming a route that exists is not inventing one, and the silence
|
|
1248
|
+
// read as a soft-lock in play. Scene-factory step 4.95 guarantees the
|
|
1249
|
+
// named key is a real item somewhere in the scene.
|
|
1250
|
+
if (door.unlockItemOrSolution) {
|
|
1251
|
+
return ` It takes ${door.unlockItemOrSolution} -- "unlock ${String(direction).toLowerCase()}" with it on you.`;
|
|
1252
|
+
}
|
|
1245
1253
|
return '';
|
|
1246
1254
|
}
|
|
1247
1255
|
export function doorwayKindOf(spotName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.164.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.",
|