@maka/maka-cli 5.173.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.173.0",
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.",
@@ -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 !== 1)
494
- return null;
495
- host = found[0].room;
496
- device = found[0].matches[0];
497
- viaWan = true;
498
- const onHost = host.hostMarksBy.get(actor.name) ?? 0;
499
- if (onHost < 1) {
500
- 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.)`)}`;
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
- this.scene.addWorldEvent(`${device.name} flags a bad handshake -- ${hostLabel(host, { capital: true })} is awake.`);
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 -- the node it answers to knows something is in here.`, ...godLines].join('\n');
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
- const onHost = host.hostMarksBy.get(actor.name) ?? 0;
551
- if (onHost < MAX_MARKS)
552
- host.hostMarksBy.set(actor.name, onHost + 1);
553
- this.logger.write(`Mark: ${actor.name} -> device ${device.name} (${now}/${MAX_MARKS}, ${mode}).`);
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.hostAlert = true;
595
+ if (host)
596
+ host.hostAlert = true;
570
597
  actor.sneaking = false;
571
598
  }
572
599
  else {
@@ -415,5 +415,12 @@
415
415
  // run's own transcript; an exit-serving spot that is furniture (a booth,
416
416
  // a counter) sits beside its doorway instead of on it; a job's objective
417
417
  // is clamped to a carriable weight at scene build.
418
- export const ENGINE_VERSION = '1.48.0';
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';
419
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
- roomType: 'Street',
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,
@@ -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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.173.0",
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.",