@maka/maka-cli 5.132.0 → 5.134.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.132.0",
3
+ "version": "5.134.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.",
@@ -38,10 +38,23 @@ export class JumpCommand extends Command {
38
38
  // (utilities/owned-drones.ts): a frame parked at home jumps from
39
39
  // home and stays parked while you ride it.
40
40
  const owned = ownedDrones(actor, this.game?.homeRoom, { reach: 'here' });
41
- const drones = owned.map(d => d.item);
41
+ // ...AND EVERY DEPLOYED FRAME, WHEREVER IT FLIES (transcript
42
+ // 2026-09-06: a Direktionssekretar deployed from the garage could
43
+ // not be jumped into once the rigger left the hideout -- "Nothing
44
+ // to jump into -- no drone in your pack" -- while this verb's own
45
+ // description promised seizure "wherever it flies"). Jumping in
46
+ // rides the link, not your hands: the hands rule above is for
47
+ // frames still stowed; a frame already out is in reach by definition.
48
+ const deployed = (this.game?.companions ?? [])
49
+ .filter(c => c.kind === 'drone' && c.boundDevice && !owned.some(d => d.item === c.boundDevice));
50
+ const drones = [...owned.map(d => d.item), ...deployed.map(c => c.boundDevice)];
51
+ const listing = [
52
+ ...droneListLines(owned),
53
+ ...deployed.map(c => ` • ${c.boundDevice.name.padEnd(26)} deployed -- ${c.npc.currentLocation.name}${c.boundDevice.maxDroneBoxes > 0 ? ` hull ${c.boundDevice.droneSummary()}` : ''}`),
54
+ ];
42
55
  const atHome = this.game?.homeRoom !== undefined && actor.currentLocation === this.game.homeRoom;
43
56
  if (drones.length === 0) {
44
- return `Nothing to jump into -- no drone in your pack${atHome ? ' or your garage' : ''}.${hint(` (Ratchet's Circuit Bazaar stocks airframes.)`)}`;
57
+ return `Nothing to jump into -- no drone in your pack${atHome ? ' or your garage' : ''}, none deployed.${hint(` (Ratchet's Circuit Bazaar stocks airframes.)`)}`;
45
58
  }
46
59
  // "jump hot" / "jump <drone> hot": strip the mode word, pick the frame.
47
60
  const words = args.map(w => w.toLowerCase());
@@ -52,11 +65,11 @@ export class JumpCommand extends Command {
52
65
  const picked = fuzzyPickName(nameWords.join(' '), drones.map(d => d.name));
53
66
  drone = picked ? drones.find(d => d.name === picked) : undefined;
54
67
  if (!drone) {
55
- return [`No drone by that name in reach. Yours:`, ...droneListLines(owned)].join('\n');
68
+ return [`No drone by that name in reach. Yours:`, ...listing].join('\n');
56
69
  }
57
70
  }
58
71
  if (!drone) {
59
- return [`Which airframe? ("jump <name>", add "hot" for hot-sim)`, ...droneListLines(owned)].join('\n');
72
+ return [`Which airframe? ("jump <name>", add "hot" for hot-sim)`, ...listing].join('\n');
60
73
  }
61
74
  if (drone.isWrecked) {
62
75
  return `The ${drone.name} is a wreck -- ${droneWreckFragment(drone)} -- and a wreck jumps nowhere. It repairs while you rest somewhere safe.`;
@@ -7,13 +7,14 @@ import { Direction } from '../types/shared/direction-enum.js';
7
7
  import { Door } from '../models/door.js';
8
8
  import { fuzzyPickName } from '../utilities/fuzzy-match.js';
9
9
  import { heldBy, holding, physicalLimit } from '../utilities/grapple.js';
10
- import { spotsActive, resolveSpot, spotOf, describeSpotRoster, OPEN_FLOOR, pathBetweenSpots, spotDistanceMeters, spotClimbSteps, blockingRegion, terrainRefusal, reseatOpenFloor, routeForMove, stepFrom, spotForCell, actorCell, standingRoomNear, furnitureCells, approachCellFor, exitSpot, concealedFrom, revealActor, onTheFloor } from '../utilities/spots.js';
10
+ import { spotsActive, resolveSpot, spotOf, describeSpotRoster, OPEN_FLOOR, pathBetweenSpots, spotDistanceMeters, spotClimbSteps, blockingRegion, terrainRefusal, reseatOpenFloor, routeForMove, stepFrom, spotForCell, actorCell, standingRoomNear, yieldSquareToMaster, furnitureCells, approachCellFor, exitSpot, concealedFrom, revealActor, onTheFloor } from '../utilities/spots.js';
11
11
  // fallDamageDVMeters, not fallDamageDV: a climb that fell short drops
12
12
  // you from where you GOT to, which is a distance in metres, not a whole
13
13
  // storey. The storey-based wrapper still exists for callers that really
14
14
  // do fall a level.
15
15
  import { fallDamageDVMeters, climbHitsFor, climbMetersFrom, climbSurfaceModifier, METERS_PER_CELL, METERS_PER_LEVEL, key, distanceMeters } from '../utilities/room-grid.js';
16
16
  import { rollPool, formatRoll } from '../utilities/dice.js';
17
+ import { trailMaster } from '../utilities/companion-heel.js';
17
18
  /**
18
19
  * "walk to <spot>" / "run to <spot>" / "sprint to <spot>" -- crossing
19
20
  * the CURRENT room to one of its "at" spots (see utilities/spots.ts;
@@ -101,6 +102,15 @@ export class MoveCommand extends Command {
101
102
  }
102
103
  return `Spots are a meat-world concern -- on the grid there is no distance at all, so "go <room>" puts your persona anywhere by name.`;
103
104
  }
105
+ /** Your shells trail you across the room (companion-heel.ts
106
+ * trailMaster): after every in-room move they re-place themselves
107
+ * beside you. An NPC mover has no Game in its context and drags
108
+ * nobody. */
109
+ trailCompanions() {
110
+ if (!this.game)
111
+ return;
112
+ trailMaster(this.actor, this.game.companions);
113
+ }
104
114
  async execute(args = []) {
105
115
  const actor = this.actor;
106
116
  const room = actor.currentLocation;
@@ -633,6 +643,7 @@ export class MoveCommand extends Command {
633
643
  // an NPC's own spot is a social event for them (the bartender greets
634
644
  // you when you reach the bar).
635
645
  actor.performAction('moves to', `the ${spot.name}`);
646
+ this.trailCompanions();
636
647
  this.logger.write(`MoveCommand: ${actor.name} ${this.mode()}s to ${spot.name} in ${room.name}.`);
637
648
  // Companions drift along with their master (go.ts follow precedent).
638
649
  if (actor === this.scene.getPlayer() && this.game) {
@@ -759,7 +770,10 @@ export class MoveCommand extends Command {
759
770
  const wasAt = actor.atSpot;
760
771
  actor.atCell = cell;
761
772
  actor.atSpot = spotForCell(room, cell);
773
+ // A shell of yours on that square steps aside (spots.ts heelsTo).
774
+ yieldSquareToMaster(room, actor);
762
775
  actor.performAction('steps', `${taken} ${taken === 1 ? 'pace' : 'paces'} ${direction}`);
776
+ this.trailCompanions();
763
777
  this.scene.updateStatus();
764
778
  this.scene.updateExits(room);
765
779
  // QUIET STEPS (user, 2026-09-01: "You step 1 pace east" is spammy now
@@ -865,6 +879,7 @@ export class MoveCommand extends Command {
865
879
  actor.atCell = cell;
866
880
  actor.atSpot = spotForCell(room, cell);
867
881
  actor.performAction('steps up beside', target.name);
882
+ this.trailCompanions();
868
883
  this.scene.updateStatus();
869
884
  this.scene.updateExits(room);
870
885
  const pace = actor.isRunningThisTurn ? ` You're running now -- everything else this turn is at -2.` : '';
@@ -1014,6 +1029,7 @@ export class MoveCommand extends Command {
1014
1029
  actor.atCell = cell;
1015
1030
  actor.atSpot = spotForCell(room, cell);
1016
1031
  actor.performAction('moves to', `the ${direction} doorway`);
1032
+ this.trailCompanions();
1017
1033
  this.scene.updateStatus();
1018
1034
  this.scene.updateExits(room);
1019
1035
  return `You cross to the ${direction} way out.${hint(` ("go ${direction}" to step through.)`)}`;
@@ -345,5 +345,19 @@
345
345
  // while "order <drone> fold/stow" packs. A frame you jump out of HOLDS
346
346
  // its position (Game.companions.holding) instead of teleporting to your
347
347
  // heel on your next step; recall clears the hold. HUD shows "holding at".
348
- export const ENGINE_VERSION = '1.37.0';
348
+ // 1.38.0 (2026-09-06): YOUR OWN SHELL IS NEVER IN YOUR WAY. COMMAND SEMANTICS
349
+ // in shared scenes: a companion shell (drone, spirit, sprite, agent) at its
350
+ // master's heel no longer blocks the master's "move <direction>" -- it
351
+ // yields the square (spots.ts heelsTo / yieldSquareToMaster); hired crew
352
+ // still hold theirs. "jump" reaches every DEPLOYED frame wherever it flies,
353
+ // garage-born or not (it used to answer "no drone in your pack" away from
354
+ // the hideout). An NPC with no dialogue and no AI no longer greets you
355
+ // with "<name>: undefined".
356
+ // 1.39.0 (2026-09-06): COMPANIONS TRAIL YOU ACROSS THE ROOM. COMMAND SEMANTICS
357
+ // in shared scenes: every in-room move ("move <direction>", "move to
358
+ // <spot>", the doorway) re-places your shells on the nearest free square
359
+ // beside you (companion-heel.ts trailMaster) -- they used to heel only on
360
+ // a room change and sat in the doorway while you paced. "recall" judges
361
+ // "already at your side" by cells, not spot names.
362
+ export const ENGINE_VERSION = '1.39.0';
349
363
  //# sourceMappingURL=engine-version.js.map
@@ -1546,7 +1546,13 @@ ${worldEventsSummary}
1546
1546
  this._triedThisStimulus = [];
1547
1547
  if (!AI.isConfigured()) {
1548
1548
  this.logger.write(`No AI available`);
1549
- await actor.hear(this, this.speak());
1549
+ // A shell with no dialogue -- every companion is built with
1550
+ // `dialog: []` -- has nothing to say, and used to say so as
1551
+ // "<name>: undefined" (transcript 2026-09-06: a drone waking as
1552
+ // its rigger walked in). Silence is the right line.
1553
+ const line = this.speak();
1554
+ if (line)
1555
+ await actor.hear(this, line);
1550
1556
  return;
1551
1557
  }
1552
1558
  // ---------- helpers ----------
@@ -2679,6 +2679,10 @@ export class Player extends AbstractPlayer {
2679
2679
  const overLink = opts?.viaLink || this.activeCallPartner === actor;
2680
2680
  if (!overLink && !this.canPerceive(actor))
2681
2681
  return;
2682
+ // Nothing said is nothing heard: a caller passing an empty line
2683
+ // (an NPC with no dialogue, AI off) must not print "<name>: undefined".
2684
+ if (!message)
2685
+ return;
2682
2686
  this.logger.write(`${this.name} heard ${actor.name}`);
2683
2687
  // A voice arriving over an active CALL from someone who isn't in the
2684
2688
  // room wears the comm dressing (player note: Mr. Johnson -- who lives
@@ -1,22 +1,80 @@
1
+ import { actorCell, standingRoomNear, onTheFloor, heelsTo } from './spots.js';
2
+ /** Chebyshev distance in cells, or undefined when either has no cell. */
3
+ function cellGap(room, a, b) {
4
+ const ca = actorCell(room, a);
5
+ const cb = actorCell(room, b);
6
+ if (!ca || !cb)
7
+ return undefined;
8
+ return Math.max(Math.abs(ca.x - cb.x), Math.abs(ca.y - cb.y), Math.abs(ca.z - cb.z));
9
+ }
10
+ /** Is the shell beside its master -- same room, same spot, and at most
11
+ * one square away (or on a room with no grid to measure)? */
12
+ export function atMastersSide(entry, master) {
13
+ const room = master.currentLocation;
14
+ if (entry.npc.currentLocation !== room || entry.npc.atSpot !== master.atSpot)
15
+ return false;
16
+ const gap = cellGap(room, entry.npc, master);
17
+ return gap === undefined || gap <= 1;
18
+ }
19
+ /** Put the shell on the nearest free square beside its master, at the
20
+ * master's spot. */
21
+ export function placeBeside(entry, master) {
22
+ const room = master.currentLocation;
23
+ if (entry.npc.currentLocation !== room)
24
+ entry.npc.currentLocation = room;
25
+ entry.npc.atSpot = master.atSpot;
26
+ const here = actorCell(room, master);
27
+ entry.npc.atCell = here ? standingRoomNear(room, here, entry.npc) : undefined;
28
+ }
1
29
  /**
2
30
  * BRING A COMPANION TO HEEL (player ruling 2026-09-06: "recall" is
3
31
  * "return to me" -- the frame comes to your side and follows again;
4
32
  * putting it away is "stow"). The shell crosses to the master's room
5
- * and spot, its cell is released so seating finds it a place beside
6
- * them, and any HOLDING flag clears so the next "go" drags it along
7
- * like any companion. Returns whether it had anywhere to come from.
33
+ * and spot, takes the nearest free square beside them, and any HOLDING
34
+ * flag clears so the next move drags it along like any companion.
35
+ * Returns whether it had anywhere to come from -- judged by CELLS, not
36
+ * spot names: a frame eight squares off on the same open floor shares
37
+ * the spot name and is not "at your side" (transcript 2026-09-06).
8
38
  *
9
39
  * One function, used by Game.heelCompanion and by the tests that pin
10
40
  * recall, so a mock Game and the real one cannot disagree about it.
11
41
  */
12
42
  export function bringToHeel(entry, master) {
13
- const here = master.currentLocation;
14
- const moved = entry.npc.currentLocation !== here || entry.npc.atSpot !== master.atSpot || !!entry.holding;
15
- if (entry.npc.currentLocation !== here)
16
- entry.npc.currentLocation = here;
17
- entry.npc.atSpot = master.atSpot;
18
- entry.npc.atCell = undefined;
43
+ const moved = !!entry.holding || !atMastersSide(entry, master);
44
+ placeBeside(entry, master);
19
45
  entry.holding = false;
20
46
  return { moved };
21
47
  }
48
+ /**
49
+ * COMPANIONS TRAIL THEIR MASTER ACROSS THE ROOM (playtest 2026-09-06:
50
+ * "It will follow to another room, but then just sit at the door").
51
+ * go.ts heels every shell on a ROOM change; nothing did on an in-room
52
+ * move, so a rigger who paced eight squares across the Neon Strip left
53
+ * the frame parked in the doorway. Called after every successful
54
+ * in-room move (move.ts): each shell of the mover's that is on the
55
+ * floor here, not HOLDING and not hired crew, is re-placed beside them.
56
+ * The frame answers to the meat body only (go.ts HEELS_ON): a rigger
57
+ * flying one frame does not drag the others around.
58
+ */
59
+ export function trailMaster(master, entries) {
60
+ if (master.plane !== 'meat')
61
+ return [];
62
+ const room = master.currentLocation;
63
+ const moved = [];
64
+ for (const entry of entries) {
65
+ if (entry.holding || entry.kind === 'ally')
66
+ continue;
67
+ if (!heelsTo(entry.npc, master))
68
+ continue;
69
+ if (entry.npc.currentLocation !== room || !onTheFloor(entry.npc))
70
+ continue;
71
+ if (entry.npc.isIncapacitated() || entry.npc.inExchange)
72
+ continue;
73
+ if (atMastersSide(entry, master))
74
+ continue;
75
+ placeBeside(entry, master);
76
+ moved.push(entry.npc);
77
+ }
78
+ return moved;
79
+ }
22
80
  //# sourceMappingURL=companion-heel.js.map
@@ -858,6 +858,15 @@ export function standingOccupants(room, mover) {
858
858
  const offPlane = new Set(room.getActors()
859
859
  .filter(a => !onTheFloor(a) && a.bodyRoom !== room)
860
860
  .map(a => a.name));
861
+ // YOUR OWN SHELL IS NEVER IN YOUR WAY (transcript 2026-09-06: a drone
862
+ // at heel sat on its master's next square -- "You can't step east --
863
+ // S-K Direktionssekretar (autopilot)'s in the way", a dozen times --
864
+ // and read as the frame refusing to follow). A companion shell heels
865
+ // AT its master's spot by construction (go.ts), so of course it is
866
+ // standing where the master wants to go next; it yields instead
867
+ // (yieldSquareToMaster, called by the step). Hired crew are people
868
+ // (ruling 2026-08-25) and still hold their square.
869
+ const heels = new Set(mover ? room.getActors().filter(a => heelsTo(a, mover)).map(a => a.name) : []);
861
870
  for (const [name, c] of seatingIn(room).entries()) {
862
871
  if (mover && name === mover.name)
863
872
  continue;
@@ -865,10 +874,40 @@ export function standingOccupants(room, mover) {
865
874
  continue;
866
875
  if (offPlane.has(name))
867
876
  continue;
877
+ if (heels.has(name))
878
+ continue;
868
879
  out.set(key(c), name);
869
880
  }
870
881
  return out;
871
882
  }
883
+ /** Is `a` a companion SHELL of `master` -- a drone, spirit, sprite or
884
+ * agent riding their bond? Hired crew (kind 'ally') are people and
885
+ * answer no. */
886
+ export function heelsTo(a, master) {
887
+ const shell = a;
888
+ return shell.allyOf === master.name && shell.companionKind !== undefined && shell.companionKind !== 'ally';
889
+ }
890
+ /**
891
+ * After `master` steps onto a square one of their own shells was
892
+ * holding, the shell gives it up: its cell is released so seatingIn
893
+ * finds it the nearest free square at the master's spot. Returns the
894
+ * shells that moved. */
895
+ export function yieldSquareToMaster(room, master) {
896
+ const here = master.atCell ? key(master.atCell) : undefined;
897
+ if (!here)
898
+ return [];
899
+ const moved = [];
900
+ for (const a of room.getActors()) {
901
+ if (!heelsTo(a, master))
902
+ continue;
903
+ if (!a.atCell || key(a.atCell) !== here)
904
+ continue;
905
+ a.atCell = undefined;
906
+ a.atSpot = master.atSpot;
907
+ moved.push(a);
908
+ }
909
+ return moved;
910
+ }
872
911
  /** The cell `mover` would end up in on arriving at `spotName` -- their
873
912
  * seat around that fixture, with everyone else's seats already taken.
874
913
  * This, not the fixture's anchor, is where movement actually paths to:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.132.0",
3
+ "version": "5.134.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.",