@maka/maka-cli 5.375.0 → 5.377.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.375.0",
3
+ "version": "5.377.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.",
@@ -828,9 +828,19 @@ export function outfitArchetype(player, archetype) {
828
828
  * as a manual equip would.
829
829
  */
830
830
  export function equipIntoEmptySlots(player) {
831
+ // A DECK IS ALREADY THE COMMLINK (RsnjkLpgLcSmsrzKa, Maka: "I have a
832
+ // cyberdeck, not a commlink, yet the commlink section still shows").
833
+ // A deck "performs all commlink functions" (p.223) -- calls ride it
834
+ // (call.ts liveLink) -- so a commlink looted mid-run and left in the
835
+ // bag got WORN on the next boot with no word said, and the sheet then
836
+ // honestly showed gear the runner never chose to put on. Only a runner
837
+ // with no working deck or RCC gets one slotted for them.
838
+ const coveredLink = !!player.getCyberdeck() || !!player.getRcc();
831
839
  for (const item of [...player.inventory.getAllItems()]) {
832
840
  if (!EQUIP_ON_START.has(item.category))
833
841
  continue;
842
+ if (item.category === Category.Commlink && coveredLink)
843
+ continue;
834
844
  const slotInfo = player.determineEquipmentSlot(item.category);
835
845
  if (!slotInfo)
836
846
  continue;
@@ -429,17 +429,27 @@ export class DisableCommand extends Command {
429
429
  }
430
430
  else {
431
431
  // A gun is a Rating 2 device with a monitor of 9 (p.228, the
432
- // book's own smartgun example): a spike that lands past its
433
- // firewall for that much bricks it outright.
434
- const monitor = 8 + 1;
435
- if (dealt >= monitor) {
432
+ // book's own smartgun example).
433
+ //
434
+ // THE BOXES STAY (AD8TzWT89yFGDHMAE, Maka: "the gun I bricked
435
+ // never actually gets bricked"). Each spike used to be judged
436
+ // alone -- 9 in one hit or nothing -- so the Ancient's Browning
437
+ // ate 7, 6, 6 and 8 boxes and was still "one more spike from
438
+ // bricked" after all four. A condition monitor FILLS (p.228), the
439
+ // same as a camera's or a deck's; it goes on the gun's own track,
440
+ // and a reboot unlocks the smartlink but mends none of it.
441
+ const monitor = 8 + Math.ceil(this.deviceRatingOf(t) / 2);
442
+ t.gun.takeDeckDamage(dealt);
443
+ const filled = Math.min(monitor, t.gun.deckDamage);
444
+ if (filled >= monitor) {
436
445
  t.gun.bricked = true;
437
- lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- it sparks, crackles and smokes in their hand. BRICKED: it will not fire again until somebody's bench work.`);
446
+ lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- ${dealt} box${dealt === 1 ? '' : 'es'} (${filled}/${monitor}): it sparks, crackles and smokes in their hand. BRICKED: it will not fire again until somebody's bench work.`);
438
447
  t.owner.performAction('suffers a PAN crack', `their ${t.gun.name} smoking in their hand`);
439
448
  }
440
449
  else {
441
450
  t.gun.jammed = true;
442
- lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- ${dealt} box${dealt === 1 ? '' : 'es'}: the smartlink seizes and LOCKS. It boots again on a reboot; another spike like that bricks it.`);
451
+ const left = monitor - filled;
452
+ lines.push(`The spike lands on ${t.owner.name}'s ${t.gun.name} -- ${dealt} box${dealt === 1 ? '' : 'es'} (${filled}/${monitor}): the smartlink seizes and LOCKS. A reboot unlocks it; ${left} more box${left === 1 ? '' : 'es'} bricks it.`);
443
453
  t.owner.performAction('suffers a PAN crack', `their ${t.gun.name} locking up in their hand`);
444
454
  }
445
455
  this.scene.addWorldEvent(`${actor.name} spiked ${t.owner.name}'s ${t.gun.name}.`);
@@ -1103,8 +1103,22 @@ export class HackCommand extends BypassCommand {
1103
1103
  const now = Math.min(MAX_MARKS, heldMarks + 1);
1104
1104
  target.panMarksBy.set(actor.name, now);
1105
1105
  actor.noteConditionChanged();
1106
- lines.push(` IN -- and that is all it is yet. You have a MARK on ${target.name}'s PAN (${now}/${MARKS_TO_COMMAND_A_PAN} to command it), not a hand on the controls.`);
1107
- lines.push(` A mark is the key; making their gear DO something is the next action, and it wants ${MARKS_TO_COMMAND_A_PAN}.${hint(` ("hack ${target.name}" again, or "mark ${target.name}" -- both place keys.)`)}`);
1106
+ // THE SECOND KEY SAYS SO (WbCPspahquHnXX3wF, Maka: "this is
1107
+ // confusing"). The line that bought mark 2 used to read the same as
1108
+ // the one that bought mark 1 -- "that is all it is yet ... (2/2 to
1109
+ // command it)" and "hack again" -- so a player holding everything
1110
+ // Control Device asks for was told they were still short. Same
1111
+ // wording as the device hack's (wan-device-hack.test.ts).
1112
+ if (now >= MARKS_TO_COMMAND_A_PAN) {
1113
+ const held = target.equipment.rightHand?.weapon ?? target.equipment.leftHand?.weapon;
1114
+ lines.push(held?.isFirearm()
1115
+ ? ` IN -- a second key settles on ${target.name}'s PAN (${now}/${MARKS_TO_COMMAND_A_PAN} marks). That is enough to command it: "disable ${target.name}" locks their ${held.name} (Control Device), and "brick ${target.name}" spikes it at +${2 * now} DV for the marks.`
1116
+ : ` IN -- a second key settles on ${target.name}'s PAN (${now}/${MARKS_TO_COMMAND_A_PAN} marks). That is enough to command it.`);
1117
+ }
1118
+ else {
1119
+ lines.push(` IN -- and that is all it is yet. You have a MARK on ${target.name}'s PAN (${now} of the ${MARKS_TO_COMMAND_A_PAN} it takes to command it), not a hand on the controls.`);
1120
+ lines.push(` A mark is the key; making their gear DO something is the next action, and it wants ${MARKS_TO_COMMAND_A_PAN}.${hint(` ("hack ${target.name}" again, or "mark ${target.name}" -- both place keys.)`)}`);
1121
+ }
1108
1122
  target.performAction('is marked', 'an unfamiliar key settling on their PAN');
1109
1123
  lines.push(...godLines);
1110
1124
  this.scene.updateStatus();
@@ -813,7 +813,14 @@ export class MoveCommand extends Command {
813
813
  // gap, not answer "you're already there".
814
814
  if (spotOf(actor) === theirSpot)
815
815
  return this.crossToActor(target);
816
- spot = room.spots.find(s => s.name === theirSpot) ?? { name: theirSpot };
816
+ // THE OPEN FLOOR IS NOT A PLACE TO WALK TO (prod X82wWvdwR3pPjZyR3,
817
+ // 2026-10-03: every phase of a street gang crashed here). It is
818
+ // bare ground in every room, named spots or none -- a street has
819
+ // no `spots` at all -- so someone standing on it is at their CELL,
820
+ // and walking to them is walking to that cell.
821
+ if (theirSpot === OPEN_FLOOR)
822
+ return this.crossToActor(target);
823
+ spot = room.spots?.find(s => s.name === theirSpot) ?? { name: theirSpot };
817
824
  toWhom = target.name;
818
825
  }
819
826
  }
@@ -24,7 +24,9 @@ export class OverwatchCommand extends Command {
24
24
  if (actor.plane !== 'matrix' && !actor.isTechnomancer() && !deck) {
25
25
  return `Checking your overwatch takes a live persona -- jack in, or put a working deck in hand.`;
26
26
  }
27
- if (actor.overwatchScore <= 0) {
27
+ // No file means no illegal action since the last boot -- not a score
28
+ // of 0, which a defender who rolled nothing leaves on a running tally.
29
+ if (actor.overwatchLastTick === 0) {
28
30
  return `You listen for the grid's counting... nothing. Fresh boot, clean slate -- GOD has no file on this persona yet.`;
29
31
  }
30
32
  const skill = actor.skillRating('electronic-warfare');
@@ -31,7 +31,7 @@ export class RebootCommand extends Command {
31
31
  }
32
32
  }
33
33
  const hadArDown = actor.arOffline;
34
- const hadOverwatch = actor.overwatchScore > 0;
34
+ const hadOverwatch = actor.overwatchLastTick > 0;
35
35
  const marksOnMe = marksOnYou(this.scene, actor).length;
36
36
  if (jammedGear.length === 0 && !hadArDown && !hadOverwatch && marksOnMe === 0) {
37
37
  return `Your PAN is clean -- nothing needs a reboot.`;
@@ -72,7 +72,12 @@ export class RestCommand extends Command {
72
72
  // like a deck does. This filtered on isCyberdeck(), so the link
73
73
  // panel's "BRICKED (rest to repair)" sent that rider to bed and the
74
74
  // link stayed bricked with no way out. The bench takes the whole bag.
75
- const damagedDevices = actor.inventory.getAllItems().filter(i => i.deckDamage > 0 && !i.permanentlyBricked);
75
+ // A spiked gun carries boxes on the same track (disable.ts), and the
76
+ // one in your hand is not always in the bag.
77
+ const damagedDevices = [...new Set([
78
+ ...actor.inventory.getAllItems(),
79
+ actor.equipment.rightHand?.weapon, actor.equipment.leftHand?.weapon,
80
+ ])].filter((i) => !!i && i.deckDamage > 0 && !i.permanentlyBricked);
76
81
  // Any implant with a track of its own (every piece of cyberware --
77
82
  // a housed deck is in the inventory above). p.228 bench work mends
78
83
  // it the same way; the datajack end of a toolkit reaches it.
@@ -253,7 +258,7 @@ export class RestCommand extends Command {
253
258
  if (cannotBench)
254
259
  lines.push(` The bench waits: ${cannotBench}`);
255
260
  for (const deck of cannotBench ? [] : damagedDevices) {
256
- const wasBricked = deck.isBricked;
261
+ const wasBricked = deck.isBricked || deck.bricked;
257
262
  const bench = benchRoll(actor);
258
263
  lines.push(` Bench work on the ${deck.name}: ${formatRoll(bench.roll)}`);
259
264
  if (bench.permanent) {
@@ -264,14 +269,18 @@ export class RestCommand extends Command {
264
269
  continue;
265
270
  }
266
271
  const repaired = deck.repairDeck(bench.boxes);
272
+ // A spiked gun bricks at its own monitor (disable.ts) and carries
273
+ // the flag attack.ts reads; one box off a full track works again.
274
+ if (repaired > 0 && deck.bricked)
275
+ deck.bricked = false;
267
276
  if (repaired > 0)
268
277
  actor.noteConditionChanged(); // the deck track on the live sheet moves with the bench
269
278
  this.logger.write(`RestCommand: ${actor.name} repaired ${repaired} boxes on ${deck.name} (${deck.deckDamage}/${deck.maxDeckBoxes}).`);
270
- lines.push(` ${repaired} box${repaired === 1 ? '' : 'es'} repaired: ${deck.deckSummary()}${wasBricked && !deck.isBricked ? ' -- it boots again.' : ''}`);
279
+ lines.push(` ${repaired} box${repaired === 1 ? '' : 'es'} repaired: ${deck.deckSummary()}${wasBricked && !deck.isBricked && !deck.bricked ? ' -- it boots again.' : ''}`);
271
280
  }
272
- // BRICKED GEAR carries a flag, not a count: a Data Spike that bricks
273
- // a gun fills its track, and one box off a full track is a device
274
- // that works again -- so one hit brings it back, none leaves it dead.
281
+ // FLAG-ONLY BRICKS (a gun saved before spikes kept a count): one box
282
+ // off a full track is a device that works again -- so one hit brings
283
+ // it back, none leaves it dead.
275
284
  for (const gear of cannotBench ? [] : brickedGear) {
276
285
  const bench = benchRoll(actor);
277
286
  lines.push(` Bench work on the ${gear.name}: ${formatRoll(bench.roll)}`);
@@ -3212,5 +3212,21 @@
3212
3212
  // drops onto a weapon or garment to "fit", not onto the body.
3213
3213
  // - A currency carrier reports its balance (IGearItem.currencyAmount), so
3214
3214
  // a credstick stashed at home still says what it holds.
3215
- export const ENGINE_VERSION = '1.187.0';
3215
+ // 1.188.0 (2026-10-03): A GANG THAT WALKS TO YOU.
3216
+ // - "move to <person>" on someone standing on the OPEN FLOOR walks to
3217
+ // their cell. It looked the name up in `room.spots`, which a street
3218
+ // does not have, and threw -- so every phase of a street gang closing
3219
+ // on a runner failed and the gang stood still (prod X82wWvdwR3pPjZyR3).
3220
+ // - A failed NPC phase logs its stack, not just the message.
3221
+ // 1.189.0 (2026-10-03): THE SPIKE THAT STAYS, THE SECOND KEY, GOD'S BILL.
3222
+ // - A Data Spike's boxes on a gun ACCUMULATE on its Matrix monitor
3223
+ // (p.228); each was judged alone against 9, so a gun ate 27 boxes and
3224
+ // never bricked (AD8TzWT89yFGDHMAE). The bench mends a gun's boxes.
3225
+ // - The hack that places a PAN's second mark says it is enough and names
3226
+ // "disable" / "brick", instead of "that is all it is yet" (WbCPspahquHnXX3wF).
3227
+ // - Overwatch adds the defender's hits with no floor of 1 (p.231-232);
3228
+ // a 0-hit defence starts the tally but adds nothing (XR9xDyyxygCGzTLCz).
3229
+ // - A runner carrying a working deck or RCC no longer has a looted
3230
+ // commlink worn for them at session load (p.223, RsnjkLpgLcSmsrzKa).
3231
+ export const ENGINE_VERSION = '1.189.0';
3216
3232
  //# sourceMappingURL=engine-version.js.map
@@ -600,7 +600,7 @@ export class CombatEncounter {
600
600
  await runNpcActionPhase(this, next.actor);
601
601
  }
602
602
  catch (err) {
603
- this.logger.error(`Encounter: ${next.actor.name}'s phase failed: ${err}`);
603
+ this.logger.error(`Encounter: ${next.actor.name}'s phase failed: ${err instanceof Error && err.stack ? err.stack : err}`);
604
604
  }
605
605
  this.closePhase(next);
606
606
  }
@@ -30,7 +30,8 @@ function d6() {
30
30
  }
31
31
  /** The canon time bill (+2d6 per 15 min while tallying), settled lazily. */
32
32
  function billTime(actor) {
33
- if (actor.overwatchScore <= 0 || actor.overwatchLastTick === 0)
33
+ // The tally runs from the first illegal action, even one that scored 0.
34
+ if (actor.overwatchLastTick === 0)
34
35
  return;
35
36
  const ticks = Math.min(MAX_BACKLOG_TICKS, Math.floor((Date.now() - actor.overwatchLastTick) / TICK_MS));
36
37
  if (ticks <= 0)
@@ -60,9 +61,16 @@ export function accrueOverwatch(scene, actor, defenseHits, why) {
60
61
  if (actor.overwatchLastTick === 0)
61
62
  actor.overwatchLastTick = Date.now();
62
63
  billTime(actor);
64
+ // NO FLOOR (XR9xDyyxygCGzTLCz): "your OS increases by the number of
65
+ // hits the target gets on its defense test" (p.231-232) -- a defender
66
+ // who rolls nothing adds nothing. This was max(1, hits), and two 0-hit
67
+ // Data Spikes' worth of it was the difference between 39 and a
68
+ // convergence. The action still STARTS the tally (the clock above):
69
+ // "the moment you perform an illegal action, you get an OS".
63
70
  const before = actor.overwatchScore;
64
- actor.overwatchScore += Math.max(1, defenseHits);
65
- logger.write(`Overwatch: ${actor.name} +${Math.max(1, defenseHits)} (${why}) -> OS ${actor.overwatchScore}.`);
71
+ const added = Math.max(0, defenseHits);
72
+ actor.overwatchScore += added;
73
+ logger.write(`Overwatch: ${actor.name} +${added} (${why}) -> OS ${actor.overwatchScore}.`);
66
74
  if (actor.overwatchScore >= CONVERGENCE_AT) {
67
75
  return { lines: converge(scene, actor), converged: true };
68
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.375.0",
3
+ "version": "5.377.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.",