@maka/maka-cli 5.172.0 → 5.174.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 +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +43 -16
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +13 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +18 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js +27 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js +24 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +29 -8
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.174.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.",
|
|
@@ -160,7 +160,14 @@ export class BacklogCommand extends Command {
|
|
|
160
160
|
// different timestamps on different machines -- so without this
|
|
161
161
|
// two reports about the same moment cannot be tied together. Seen
|
|
162
162
|
// for real: one run produced items stamped 00-18-43 and 00-18-15.
|
|
163
|
-
|
|
163
|
+
// A HOSTED RUN NAMES ITSELF (2026-09-12, the reporter's note on
|
|
164
|
+
// ZdhkP8mAP9QhpqFvA): the browser client's session has no log
|
|
165
|
+
// file to upload, but its id is the key to the event stream the
|
|
166
|
+
// site keeps -- and the site reads the transcript off that
|
|
167
|
+
// (game-backlog-vetting.ts sessionEventsExcerpt). The CLI's own
|
|
168
|
+
// shared-run link keeps its id; the headless host's is the same
|
|
169
|
+
// GameSessions id under another name.
|
|
170
|
+
sharedRunId: game?.remoteSession?.id ?? currentSession()?.sessionId,
|
|
164
171
|
cliVersion: cliVersion(),
|
|
165
172
|
engineVersion: engineVersion(),
|
|
166
173
|
platform: process.platform,
|
|
@@ -490,14 +490,32 @@ export class HackCommand extends BypassCommand {
|
|
|
490
490
|
const found = hostsInReach(rooms, actor)
|
|
491
491
|
.map(r => ({ room: r, matches: matching(r) }))
|
|
492
492
|
.filter(x => x.matches.length === 1);
|
|
493
|
-
if (found.length
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
493
|
+
if (found.length === 1) {
|
|
494
|
+
host = found[0].room;
|
|
495
|
+
device = found[0].matches[0];
|
|
496
|
+
viaWan = true;
|
|
497
|
+
const onHost = host.hostMarksBy.get(actor.name) ?? 0;
|
|
498
|
+
if (onHost < 1) {
|
|
499
|
+
return `${device.name} is on the map, but it hangs off ${hostLabel(host)}'s WAN -- slaved, and a host's devices answer nobody who holds no mark on the host itself (p.233).${hint(` ("hack ${hostLabel(host)}" for a mark first; "enter" once you have one and the lock defends with its own DR ${device.rating} alone.)`)}`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
// A LOOSE DEVICE, NO HOST OVER IT (ZdhkP8mAP9QhpqFvA, the half
|
|
504
|
+
// the bench exposed 2026-09-12): the reporter's own fixture -- a
|
|
505
|
+
// wireless DR 2 maglock on a door in a room with NO host -- still
|
|
506
|
+
// fell through to "nothing on the grid answers", because this
|
|
507
|
+
// path only ever searched hosts' WANs. Every wireless device is
|
|
508
|
+
// its own icon on the grid (p.232), and an unslaved one defends
|
|
509
|
+
// with nothing but its own Device Rating (p.237-238: the rating
|
|
510
|
+
// stands in for the Mental attribute it lacks, and its Firewall
|
|
511
|
+
// is its rating) -- deviceDefensePool. It is found where the
|
|
512
|
+
// body stands: that is the room whose AR overlay drew the icon.
|
|
513
|
+
// A device with its wireless off has no icon and stays unfound.
|
|
514
|
+
const here = gridVicinity(actor);
|
|
515
|
+
const loose = hostOver(here) ? [] : matching(here).filter(d => d.wireless);
|
|
516
|
+
if (loose.length !== 1)
|
|
517
|
+
return null;
|
|
518
|
+
device = loose[0];
|
|
501
519
|
}
|
|
502
520
|
}
|
|
503
521
|
const held = device.marksBy.get(actor.name) ?? 0;
|
|
@@ -532,11 +550,17 @@ export class HackCommand extends BypassCommand {
|
|
|
532
550
|
if (mode === 'sleaze') {
|
|
533
551
|
// A blown Sleaze is NOTICED (p.236) -- and a slaved device that
|
|
534
552
|
// notices tells the host it hangs off.
|
|
535
|
-
host.hostAlert = true;
|
|
536
553
|
actor.sneaking = false;
|
|
537
|
-
|
|
554
|
+
if (host) {
|
|
555
|
+
host.hostAlert = true;
|
|
556
|
+
this.scene.addWorldEvent(`${device.name} flags a bad handshake -- ${hostLabel(host, { capital: true })} is awake.`);
|
|
557
|
+
this.scene.updateStatus();
|
|
558
|
+
return [`${device.name} refuses the key and SAYS SO -- the node it answers to knows something is in here.`, ...godLines].join('\n');
|
|
559
|
+
}
|
|
560
|
+
// A loose device has no host to wake; its owner still gets the
|
|
561
|
+
// mark on you a blown Sleaze hands over (p.240).
|
|
538
562
|
this.scene.updateStatus();
|
|
539
|
-
return [`${device.name} refuses the key and SAYS SO --
|
|
563
|
+
return [`${device.name} refuses the key and SAYS SO -- whoever owns it has your icon on file now.`, ...godLines].join('\n');
|
|
540
564
|
}
|
|
541
565
|
return [`${device.name} holds against the smash -- its firewall never even flexes. Nobody upstairs noticed.`, ...godLines].join('\n');
|
|
542
566
|
}
|
|
@@ -547,10 +571,12 @@ export class HackCommand extends BypassCommand {
|
|
|
547
571
|
// p.87-88 works it: "1 mark on the device and its master (the
|
|
548
572
|
// host)"). One, not `declared` -- the declaration bought marks on
|
|
549
573
|
// the lock; the host gets the one canon hands over with it.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
574
|
+
if (host) {
|
|
575
|
+
const onHost = host.hostMarksBy.get(actor.name) ?? 0;
|
|
576
|
+
if (onHost < MAX_MARKS)
|
|
577
|
+
host.hostMarksBy.set(actor.name, onHost + 1);
|
|
578
|
+
}
|
|
579
|
+
this.logger.write(`Mark: ${actor.name} -> device ${device.name} (${now}/${MAX_MARKS}, ${mode}${host ? '' : ', loose'}).`);
|
|
554
580
|
const lines = [`${device.name} takes your key -- ${now} of ${MAX_MARKS} mark${now === 1 ? '' : 's'} on it now.`];
|
|
555
581
|
// BRUTE FORCE BURNS WHAT IT FORCES (p.238): 1 DV per two full net
|
|
556
582
|
// hits, resisted by Device Rating + Firewall. Unlike a host, a
|
|
@@ -566,7 +592,8 @@ export class HackCommand extends BypassCommand {
|
|
|
566
592
|
lines.push(` It dies open. Whatever it was holding shut is not shut any more.`);
|
|
567
593
|
}
|
|
568
594
|
}
|
|
569
|
-
host
|
|
595
|
+
if (host)
|
|
596
|
+
host.hostAlert = true;
|
|
570
597
|
actor.sneaking = false;
|
|
571
598
|
}
|
|
572
599
|
else {
|
|
@@ -410,5 +410,17 @@
|
|
|
410
410
|
// in Medicine aftercare (+hits as recovery dice, once per set of wounds,
|
|
411
411
|
// persisted), the street doc has the Medicine skill, and a doc asked
|
|
412
412
|
// about wounds names the verb and the price.
|
|
413
|
-
|
|
413
|
+
// 1.48.0 (2026-09-12): THE ROOM AND THE REPORT. A hosted run's backlog
|
|
414
|
+
// report names its session (sharedRunId) so the site can vet it with the
|
|
415
|
+
// run's own transcript; an exit-serving spot that is furniture (a booth,
|
|
416
|
+
// a counter) sits beside its doorway instead of on it; a job's objective
|
|
417
|
+
// is clamped to a carriable weight at scene build.
|
|
418
|
+
// 1.49.0 (2026-09-12): A LOOSE LOCK IS HACKABLE. A wireless device in a
|
|
419
|
+
// room with no host over it is its own icon on the grid: "hack <it>"
|
|
420
|
+
// from the Matrix finds it where the body stands and it defends with its
|
|
421
|
+
// Device Rating alone (p.232, p.237-238) -- no more "nothing on the grid
|
|
422
|
+
// answers" for the reporter's DR 2 maglock. The bench fixture grows
|
|
423
|
+
// home / clinic / wounded / nuyen and npcs[].doc, so healing can be
|
|
424
|
+
// benched without a fight.
|
|
425
|
+
export const ENGINE_VERSION = '1.49.0';
|
|
414
426
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -295,7 +295,9 @@ export function buildReproScene(fixture) {
|
|
|
295
295
|
+ `${device.name} is fixed beside the doorway${barrier.open ? ', standing open' : ', holding it shut'}. `
|
|
296
296
|
+ `There is nothing else here, which is the point -- whatever is wrong is wrong at that door.`
|
|
297
297
|
: `A short concrete run with a door ${direction} of you and nothing else in it.`,
|
|
298
|
-
|
|
298
|
+
// fixture.home: the barrier room is the runner's own bed, so a rest
|
|
299
|
+
// here is the night rest.ts counts (vFMhSGwLhsfpiY7bs).
|
|
300
|
+
roomType: fixture.home ? 'Residence' : fixture.clinic ? 'Clinic' : 'Street',
|
|
299
301
|
size: level ? 'large' : 'medium',
|
|
300
302
|
spots,
|
|
301
303
|
levels: level ? [{ name: level.name, side: level.side ?? 'north', kind: level.kind ?? 'catwalk', climbOnly: level.climbOnly === true }] : undefined,
|
|
@@ -386,6 +388,18 @@ export function buildReproScene(fixture) {
|
|
|
386
388
|
},
|
|
387
389
|
};
|
|
388
390
|
});
|
|
391
|
+
// THE DOC'S HANDS (npcs[].doc): the medical skills treat.ts and
|
|
392
|
+
// street-doc.ts gate on, laid over the generic block above.
|
|
393
|
+
npcs.forEach((npc, i) => {
|
|
394
|
+
if (!npc.doc || npc.ice)
|
|
395
|
+
return;
|
|
396
|
+
const seed = npcSeeds[i];
|
|
397
|
+
seed.player.combat = {
|
|
398
|
+
...seed.player.combat,
|
|
399
|
+
logic: 6,
|
|
400
|
+
skills: { ...(seed.player.combat?.skills ?? {}), 'first-aid': 6, medicine: 6 },
|
|
401
|
+
};
|
|
402
|
+
});
|
|
389
403
|
const itemSeeds = items.map(it => ({
|
|
390
404
|
name: it.name,
|
|
391
405
|
description: it.description,
|
|
@@ -5,6 +5,9 @@ import { Door } from '../models/door.js';
|
|
|
5
5
|
import { RoomFactory } from './room-factory.js';
|
|
6
6
|
import { NPCFactory } from './npc-factory.js';
|
|
7
7
|
import { ItemFactory } from './item-factory.js';
|
|
8
|
+
/** The heaviest a job's prize may be: a Strength 1 runner carries 30 kg
|
|
9
|
+
* (player.ts maxCarryingWeight), and they still need room for gear. */
|
|
10
|
+
export const OBJECTIVE_MAX_WEIGHT_KG = 15;
|
|
8
11
|
import { Category } from '../types/shared/item-enum.js';
|
|
9
12
|
import { Logger } from '../utilities/logger.js';
|
|
10
13
|
import { migrateLegacySeed } from './seed-migration.js';
|
|
@@ -262,6 +265,21 @@ Key Locations: ${json.rooms.map(r => r.name).join(', ')}
|
|
|
262
265
|
const itemMap = new Map();
|
|
263
266
|
for (const itemJson of json.items ?? []) {
|
|
264
267
|
const item = await itemFactory.createItemFromJson(itemJson);
|
|
268
|
+
// THE PRIZE HAS TO BE CARRIABLE (KSfzQzuHTx7kHjohi, 2026-09-12:
|
|
269
|
+
// "I shouldn't have an item I need to return, that I can't carry
|
|
270
|
+
// -- this case is too heavy"). The seed's items are written by a
|
|
271
|
+
// model that does not know the carry cap (20 + Strength x 10 kg,
|
|
272
|
+
// player.ts maxCarryingWeight); a "heavy case" at 60 kg is a job
|
|
273
|
+
// no runner can finish. The win condition's item is clamped to a
|
|
274
|
+
// weight the weakest runner can lift -- an objective you cannot
|
|
275
|
+
// pick up is a seed error, not a challenge. Other items keep
|
|
276
|
+
// whatever the seed said.
|
|
277
|
+
if (json.winCondition?.item === item.name) {
|
|
278
|
+
if (item.weight > OBJECTIVE_MAX_WEIGHT_KG) {
|
|
279
|
+
console.warn(`[scene] objective "${item.name}" weighs ${item.weight} kg -- clamped to ${OBJECTIVE_MAX_WEIGHT_KG} so it can be carried.`);
|
|
280
|
+
item.weight = OBJECTIVE_MAX_WEIGHT_KG;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
265
283
|
itemMap.set(item.name, item);
|
|
266
284
|
// Step 4.5: Distribute Items
|
|
267
285
|
if (itemJson.heldBy) {
|
|
@@ -237,6 +237,33 @@ export async function dryRunRepro(repro) {
|
|
|
237
237
|
players: [saveForArchetype(REPRO_ACTOR, reproArchetypeName(fixture))],
|
|
238
238
|
events,
|
|
239
239
|
});
|
|
240
|
+
// START WOUNDED (fixture.wounded): the boxes go on before the first
|
|
241
|
+
// step, so the transcript opens on a runner who already needs the
|
|
242
|
+
// verb under test.
|
|
243
|
+
if (fixture.wounded) {
|
|
244
|
+
const runner = session.game.scene.getPlayerByName(REPRO_ACTOR);
|
|
245
|
+
if (runner) {
|
|
246
|
+
if (fixture.wounded.physical)
|
|
247
|
+
runner.takeDamage(fixture.wounded.physical);
|
|
248
|
+
if (fixture.wounded.stun)
|
|
249
|
+
runner.takeStun(fixture.wounded.stun);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// NUYEN (fixture.nuyen): a credstick in the pack, because money lives
|
|
253
|
+
// on a carrier item (Player.currency sums them) and the pregen has none.
|
|
254
|
+
if (fixture.nuyen && fixture.nuyen > 0) {
|
|
255
|
+
const runner = session.game.scene.getPlayerByName(REPRO_ACTOR);
|
|
256
|
+
if (runner) {
|
|
257
|
+
const { ItemFactory } = await import('../factories/item-factory.js');
|
|
258
|
+
const stick = await new ItemFactory().createItemFromJson({
|
|
259
|
+
name: 'Certified Credstick', description: 'Cash that does not ask questions.',
|
|
260
|
+
size: 'Tiny', category: 'Credstick', shape: 'Slab', color: 'Black', texture: 'Plastic',
|
|
261
|
+
rating: 'Common', weight: 0.1, transferable: true,
|
|
262
|
+
});
|
|
263
|
+
stick.addCurrency(fixture.nuyen);
|
|
264
|
+
runner.addInventory(stick);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
240
267
|
const lines = [];
|
|
241
268
|
// THE FIRST PAINT IS NOT PART OF STEP ONE. bootSession pushes every
|
|
242
269
|
// player's panels the moment the session comes up (B7NNNesy5gvRSbZpp),
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Direction } from '../types/shared/direction-enum.js';
|
|
2
|
+
export function doorwayKindOf(spotName) {
|
|
3
|
+
if (/stair|\bsteps?\b|ladder|hatch|shaft|chute|\bramp\b|\blift\b|elevator|escalator/i.test(spotName))
|
|
4
|
+
return 'vertical';
|
|
5
|
+
if (/door|entrance|entry|threshold|gate|arch|portal|airlock/i.test(spotName))
|
|
6
|
+
return 'lateral';
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
2
9
|
export const METERS_PER_CELL = 2;
|
|
3
10
|
/**
|
|
4
11
|
* How tall one storey is. A CHOICE, not a citation -- SR5 never says how
|
|
@@ -609,7 +616,23 @@ export function synthesizeGrid(input) {
|
|
|
609
616
|
// own walls; the hub sits where all of them are plausibly reachable.
|
|
610
617
|
for (const [spotName, served] of exitCellsBySpot.entries()) {
|
|
611
618
|
if (served.length === 1) {
|
|
612
|
-
|
|
619
|
+
// FURNITURE BESIDE THE DOOR, NOT IN IT (XanDMPmRrbJQ8ta8e,
|
|
620
|
+
// 2026-09-12: "the corner booth is right in front of the door
|
|
621
|
+
// north ... doors and booths should not be on top of each
|
|
622
|
+
// other"). A seed may name any spot as the one an exit is
|
|
623
|
+
// reached from -- "the booth by the north door". Only a spot
|
|
624
|
+
// that IS a doorway (a door, a gate, the stairs) takes the
|
|
625
|
+
// doorway cell; a booth, a counter or a bar takes the nearest
|
|
626
|
+
// free cell beside it instead. The doorway and its approach are
|
|
627
|
+
// already claimed above, so "nearest free" is next to the door,
|
|
628
|
+
// never on it or in front of it.
|
|
629
|
+
if (doorwayKindOf(spotName)) {
|
|
630
|
+
grid.spotCells.set(spotName, served[0]);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
const beside = nearestFree(served[0], occupied, dims, exists);
|
|
634
|
+
claim(beside);
|
|
635
|
+
grid.spotCells.set(spotName, beside);
|
|
613
636
|
continue;
|
|
614
637
|
}
|
|
615
638
|
const mid = {
|
|
@@ -2,7 +2,10 @@ import { Direction } from '../types/shared/direction-enum.js';
|
|
|
2
2
|
import { significantWords, wordsAreCloseEnough } from './fuzzy-match.js';
|
|
3
3
|
import { hint } from './hints.js';
|
|
4
4
|
import { placeName } from './log-style.js';
|
|
5
|
-
import { findPath, distanceMeters, coverBetween, blockingRegionFor, pathClimbSteps, levelFootprint, key, isCellBlocked, pathSqueezes, METERS_PER_CELL, } from './room-grid.js';
|
|
5
|
+
import { findPath, distanceMeters, coverBetween, blockingRegionFor, pathClimbSteps, levelFootprint, key, isCellBlocked, pathSqueezes, METERS_PER_CELL, doorwayKindOf, } from './room-grid.js';
|
|
6
|
+
// The doorway vocabulary moved to room-grid.ts (the grid seats exit
|
|
7
|
+
// spots by it); every caller of spots.ts keeps its import.
|
|
8
|
+
export { doorwayKindOf };
|
|
6
9
|
/**
|
|
7
10
|
* Intra-room locations ("at" spots -- player request: every room felt
|
|
8
11
|
* like everything stood at your elbow; walking into the bar had both
|
|
@@ -1283,13 +1286,31 @@ export function lockedRouteHint(room, direction, door) {
|
|
|
1283
1286
|
}
|
|
1284
1287
|
return '';
|
|
1285
1288
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1289
|
+
/**
|
|
1290
|
+
* WHAT KIND OF WAY THROUGH a spot's name describes, or undefined when
|
|
1291
|
+
* it isn't a way through at all (a bar, a workbench, the stalls).
|
|
1292
|
+
*
|
|
1293
|
+
* THE ONE DOORWAY PREDICATE (item A, 2026-08-27). There were two
|
|
1294
|
+
* separately-written copies of this regex -- here in entrySpotFor and
|
|
1295
|
+
* in Room.namedDoorwayFor -- free to drift apart, and neither knew the
|
|
1296
|
+
* distinction that actually matters: a "back door" is a LATERAL way
|
|
1297
|
+
* through and a "way down" is a VERTICAL one. Kind-blindness is what
|
|
1298
|
+
* let namedDoorwayFor bind a spot called "the back door" to a DOWN
|
|
1299
|
+
* exit, which put the basement's barrier at the back door (the player's
|
|
1300
|
+
* report) and, because vertical exits are centre-placed, simultaneously
|
|
1301
|
+
* put the back door in the middle of the room (the older report). One
|
|
1302
|
+
* defect, two bug reports.
|
|
1303
|
+
*
|
|
1304
|
+
* This is a PERMANENT component, not a migration shim. Generation is
|
|
1305
|
+
* moving toward binding every exit to a spot explicitly, but that data
|
|
1306
|
+
* is model-written: validation can make it right more often, never
|
|
1307
|
+
* always. And every scene seeded before that lands has nothing but
|
|
1308
|
+
* names, forever. So this is the honest floor under a seed that is
|
|
1309
|
+
* silent or lying, and it is the LAST resort -- an explicit
|
|
1310
|
+
* room.exitSpots binding always wins.
|
|
1311
|
+
*
|
|
1312
|
+
* (Definition now in room-grid.ts -- see the re-export at the top.)
|
|
1313
|
+
*/
|
|
1293
1314
|
/** The kind of way through a given direction NEEDS. */
|
|
1294
1315
|
export function exitDoorwayKind(direction) {
|
|
1295
1316
|
return direction === Direction.UP || direction === Direction.DOWN ? 'vertical' : 'lateral';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.174.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.",
|