@maka/maka-cli 5.150.0 → 5.152.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.150.0",
3
+ "version": "5.152.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.",
@@ -66,6 +66,30 @@ export class LookCommand extends Command {
66
66
  return this.inspectItem(carried.name, this.actor.inventory);
67
67
  }
68
68
  }
69
+ // AN EXACT NAME BEATS A FUZZY ITEM SUBSTRING (Sjjy52mjSqSdjFGdg:
70
+ // "look Jo" resolved to "Certified Credstick (job payments)" --
71
+ // Inventory.resolveKey does deliberately generous substring
72
+ // matching for item-name typos ("jo" sits inside "job payments"),
73
+ // and that ran before the room's actor list was ever consulted.
74
+ // A present person's EXACT name is a far stronger signal than an
75
+ // incidental item substring, so it wins first; an inexact query
76
+ // still falls through to the item/direction ladder below exactly
77
+ // as before.
78
+ // AN EXACT NAME BEATS A FUZZY ITEM SUBSTRING (Sjjy52mjSqSdjFGdg:
79
+ // "look Jo" resolved to "Certified Credstick (job payments)" --
80
+ // Inventory.resolveKey does deliberately generous substring
81
+ // matching for item-name typos ("jo" sits inside "job payments"),
82
+ // and that ran before the room's actor list was ever consulted.
83
+ // A present person's EXACT name is a far stronger signal than an
84
+ // incidental item substring, so it wins first; an inexact query
85
+ // still falls through to the item/direction ladder below exactly
86
+ // as before.
87
+ const exactActorMatch = this.scene
88
+ .getActorsInRoom(this.actor.currentLocation)
89
+ .find(a => a.name.toLowerCase() === target.toLowerCase() && this.actor.canPerceive(a));
90
+ if (exactActorMatch) {
91
+ return this.inspectActor(exactActorMatch.name);
92
+ }
69
93
  if (this.actor.inventory.hasItem(target)) {
70
94
  this.logger.write(`Looking at item '${target}' in ${this.actor.name} inventory.`);
71
95
  return this.inspectItem(target, this.actor.inventory);
@@ -79,14 +103,9 @@ export class LookCommand extends Command {
79
103
  return this.lookOutside(target);
80
104
  }
81
105
  else {
82
- // Check if it's another player or NPC in the room (that this
83
- // actor's plane can perceive -- see Player.canPerceive)
84
- const actorMatch = this.scene
85
- .getActorsInRoom(this.actor.currentLocation)
86
- .find(a => a.name.toLowerCase() === target.toLowerCase() && this.actor.canPerceive(a));
87
- if (actorMatch) {
88
- return this.inspectActor(actorMatch.name);
89
- }
106
+ // The exact-name actor check above already covers a present
107
+ // person; reaching here means the target is neither an item,
108
+ // a direction, nor anyone in the room by that exact name.
90
109
  return `You cannot see ${target}`;
91
110
  }
92
111
  }
@@ -88,6 +88,10 @@ export class TakeCommand extends Command {
88
88
  taken.push(item.name);
89
89
  }
90
90
  }
91
+ // Same durable-change rule as the loose-floor pickups below
92
+ // (RKLbzxQQRJk8eD8Az) -- only when something actually moved.
93
+ if (taken.length > 0)
94
+ this.game?.requestSave('take');
91
95
  const lines = [taken.length > 0 ? `${prefix}You take ${taken.join(', ')} from the ${container.name}.` : `${prefix}You take nothing from the ${container.name}.`, ...left];
92
96
  return lines.join('\n');
93
97
  }
@@ -100,6 +104,7 @@ export class TakeCommand extends Command {
100
104
  if (!container.inventory.transferItemTo(item.name.toLowerCase(), this.actor.inventory)) {
101
105
  return `${prefix}You can't lift the ${item.name} right now.`;
102
106
  }
107
+ this.game?.requestSave('take');
103
108
  this.actor.performAction('takes', `${item.name} from the ${container.name}`);
104
109
  return `${prefix}You take the ${item.name} from the ${container.name}.`;
105
110
  }
@@ -223,6 +228,14 @@ export class TakeCommand extends Command {
223
228
  }
224
229
  if (tookAnything) {
225
230
  this.scene.updateInventory(this.scene.getPlayer().inventory);
231
+ // A PICKUP IS A DURABLE CHANGE (RKLbzxQQRJk8eD8Az: "I picked up
232
+ // the 'Ferret's ledger Service Log', but I don't see it reflected
233
+ // in the character sheet"). buy.ts's own fix comment named this
234
+ // file as a sibling that never learned the lesson (vR8ajvm3wXdexxdF2)
235
+ // -- the web sheet reads the last SAVED snapshot, and nothing here
236
+ // ever asked for one, so a swept haul sat invisible until the next
237
+ // unrelated save beat (homecoming, rest, a trade).
238
+ this.game?.requestSave('take');
226
239
  }
227
240
  // Win-condition checks run AFTER the sweep summary renders: the check
228
241
  // logs the JOB COMPLETE banner itself, and running it per-item used to
@@ -363,6 +376,15 @@ export class TakeCommand extends Command {
363
376
  room.inventory.removeItem(itemName);
364
377
  room.plainSightItems.delete(item.name.toLowerCase());
365
378
  this.logger.write(`Successfully removed "${itemName}" from ${room.name} room's inventory during 'Take' command.`);
379
+ // A PICKUP IS A DURABLE CHANGE (RKLbzxQQRJk8eD8Az: "I picked up
380
+ // the 'Ferret's ledger Service Log', but I don't see it reflected
381
+ // in the character sheet"). buy.ts's own fix comment named this
382
+ // file as a sibling that never learned the lesson (vR8ajvm3wXdexxdF2)
383
+ // -- the web sheet reads the last SAVED snapshot, and nothing here
384
+ // ever asked for one. Placed after the item has actually changed
385
+ // hands (the try's own catch below handles a too-heavy rollback
386
+ // before this line is ever reached), same rule as buy.ts/unequip.ts.
387
+ this.game?.requestSave('take');
366
388
  // Possession-based win condition: if some OTHER actor ends up with
367
389
  // the item the win condition's target NPC wants, that counts just as
368
390
  // much as the player handing it over directly (emergent delivery).
@@ -552,6 +552,33 @@ export default class Game {
552
552
  return `{yellow-fg}🔥 Heat: ears up all over the block{/yellow-fg}`;
553
553
  return `{yellow-fg}🔥 Heat: somebody heard something{/yellow-fg}`;
554
554
  }
555
+ /**
556
+ * THE SAME BAND, FOR AN NPC'S MOUTH (YE9G3Q5P85y5xcP7n: "Mr. Ibis said
557
+ * 'Quiet as promised' however, there's a status of: 🔥 Heat: sirens
558
+ * circling closer"). heatBand() paints the player's own HUD; nothing
559
+ * fed the run's actual heat into NPC.respondTo's prompt at all, so an
560
+ * NPC narrating "the coast is clear" had no way to know it wasn't --
561
+ * the same shape as payoutSummary's fix for an invented payout, here
562
+ * for an invented all-clear.
563
+ *
564
+ * DELIBERATELY NOT ITS OWN THRESHOLDS. heat-band-honesty.test.ts pins
565
+ * the top band's heat check to the ONE line inside heatBand(), on
566
+ * purpose: the HUD's promise and checkpointOnRideHome's verifierDice
567
+ * escalation share that number so they cannot drift apart. A second
568
+ * copy of the same comparison here for the AI prompt would be exactly
569
+ * the drift that guard exists to catch. So this reads heatBand()'s OWN
570
+ * text -- strip the color codes and the HUD's "🔥 Heat: " lede, and
571
+ * what is left is plain enough for a prompt -- rather than re-deriving
572
+ * the bands.
573
+ */
574
+ heatSummaryForDialogue() {
575
+ if (!this.onRun)
576
+ return undefined;
577
+ const band = this.heatBand();
578
+ if (!band)
579
+ return undefined;
580
+ return band.replace(/\{[^}]*\}/g, '').replace(/^🔥\s*Heat:\s*/, '');
581
+ }
555
582
  // THE FOR-HIRE BOARD: archetype-built candidates, fresh faces each
556
583
  // homecoming (the gig-pool rotation school). Not saved -- the street's
557
584
  // talent pool churns; whoever you HIRED is the persistent part.
@@ -1660,6 +1660,15 @@ ${worldEventsSummary}
1660
1660
  const payoutSummary = payout
1661
1661
  ? `The agreed payout for the current job is EXACTLY ${payout} nuyen, paid as ONE LUMP SUM on delivery -- there is no advance and no half up front; never speak as if part has already been paid. Any time you mention payment, reward, or what the job is worth, use exactly this figure -- never invent a different one. Payment transfers automatically when the job completes; you never need to hand it over.`
1662
1662
  : '';
1663
+ // HEAT IS MECHANICALLY REAL TOO (YE9G3Q5P85y5xcP7n: an NPC said
1664
+ // "Quiet as promised" while the player's own HUD read "Heat: sirens
1665
+ // circling closer"). Same shape as payoutSummary above -- nothing
1666
+ // told the model the run's actual heat, so a reassuring line had
1667
+ // nothing to check itself against.
1668
+ const heat = this._scene?.ownerGame?.heatSummaryForDialogue?.();
1669
+ const heatSummary = heat
1670
+ ? `The district's current HEAT on this run is ${heat}. If you would naturally reassure them that things are quiet, clean, or under control, that claim has to be TRUE against this -- never tell them the coast is clear, that nobody noticed, or that things went quiet if heat says otherwise. Speak honestly about how hot it actually is instead.`
1671
+ : '';
1663
1672
  // If nothing else has happened to/around this NPC yet this scene, this
1664
1673
  // is the first time anyone's reached out -- nudge toward volunteering
1665
1674
  // the job rather than waiting to be asked, which real playtesting
@@ -2103,6 +2112,7 @@ Your objective is to drive the story line.`}
2103
2112
  ${eph ? '' : objectiveSummary}
2104
2113
  ${dialogueSummary}
2105
2114
  ${eph ? '' : payoutSummary}
2115
+ ${eph ? '' : heatSummary}
2106
2116
 
2107
2117
  ${roomDesc}${spotBlock}${combatSpatialBlock}
2108
2118
  ${formattedExitsStr}
@@ -652,7 +652,37 @@ export class Player extends AbstractPlayer {
652
652
  return true;
653
653
  if (this.arOffline)
654
654
  return false;
655
- return !!this.getPanMaster();
655
+ if (!this.getPanMaster())
656
+ return false;
657
+ return this.hasARDisplay();
658
+ }
659
+ /**
660
+ * THE MISSING HALF OF AR (canon p.53-54/221-222, RAG-checked
661
+ * 2026-09-10, acBi46ZKKJCPyBKXb: "it looks like I'm able to see AR
662
+ * with the Hermes Ikon, however, I don't have a method to interface
663
+ * to it -- no trode, or commlink jack. I think this is a gap"). It
664
+ * was: "a commlink alone does not automatically give you AR -- the
665
+ * commlink processes and sends the information, but you still need a
666
+ * display device to receive it." hasMatrixEyes gated ONLY on a
667
+ * PAN master existing, never on having any way to actually SEE what
668
+ * it sends -- so a runner with a commlink in their pocket and nothing
669
+ * on their face or in their skull got the full overlay regardless.
670
+ *
671
+ * Two independent roads in, matching what the rulebook actually lists:
672
+ * a DNI (trodes/datajack/technomancer -- Player.hasDirectNeuralInterface,
673
+ * "you can use all your senses to view AR without any extra devices"),
674
+ * or a worn visual display (cybereyes, glasses, goggles, contacts --
675
+ * same head.eyes slot and same two categories smartlink detection
676
+ * already reads, Category.Glasses / Category.EyeCyberwere). Audio-only
677
+ * and haptic-only AROs have their own separate gear (earbuds/cyberear,
678
+ * AR gloves, p.222/439) and are not modelled here -- this predicate is
679
+ * specifically the VISUAL overlay hasMatrixEyes has always been about.
680
+ */
681
+ hasARDisplay() {
682
+ if (this.hasDirectNeuralInterface())
683
+ return true;
684
+ const onFace = this._equipment.head?.eyes;
685
+ return !!onFace && (onFace.category === Category.Glasses || onFace.category === Category.EyeCyberwere);
656
686
  }
657
687
  /**
658
688
  * A DIRECT NEURAL INTERFACE -- what VR needs and AR does not (SR5 core
@@ -225,10 +225,17 @@ export function arStatusSlots(actor) {
225
225
  ? `{white-fg}◌ AR OFF{/white-fg}`
226
226
  : !actor.getPanMaster() && !actor.isTechnomancer()
227
227
  ? `{white-fg}◌ no AR rig{/white-fg}`
228
- // Off-plane, or any future state that puts the overlay down
229
- // without being one of the above. game.ts drops the slot
230
- // entirely while off-plane -- a persona has its own HUD line.
231
- : `{white-fg}◌ AR dark{/white-fg}`;
228
+ // A processor with nothing to paint it on (acBi46ZKKJCPyBKXb):
229
+ // a commlink sends the overlay, but nothing here receives it
230
+ // (Player.hasARDisplay -- no DNI, no worn glasses/goggles/
231
+ // cybereyes). Distinct from "no AR rig" -- the player HAS a
232
+ // rig, it just has nowhere to show up.
233
+ : !actor.hasARDisplay()
234
+ ? `{white-fg}◌ no display -- trodes or eyewear{/white-fg}`
235
+ // Off-plane, or any future state that puts the overlay down
236
+ // without being one of the above. game.ts drops the slot
237
+ // entirely while off-plane -- a persona has its own HUD line.
238
+ : `{white-fg}◌ AR dark{/white-fg}`;
232
239
  // The link slot asks for the LIVE LINK -- worn commlink or working
233
240
  // deck -- because that is what call.ts now asks for (p.223: a deck
234
241
  // performs all commlink functions). The old "AR ON | Link offline"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.150.0",
3
+ "version": "5.152.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.",