@maka/maka-cli 5.218.0 → 5.220.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.218.0",
3
+ "version": "5.220.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.",
@@ -313,10 +313,28 @@ export class SayCommand extends Command {
313
313
  // presence on the grid and is not -- "call" is still the verb that
314
314
  // reaches a body.
315
315
  if (currentPlayer.plane === 'matrix' && !(this.actor instanceof NPC)) {
316
- const heard = speakOnTheGrid(this.scene, currentPlayer, '').map(a => a.name);
317
- return heard.length > 0
318
- ? `Your persona speaks and the grid carries it: ${heard.join(', ')} ${heard.length === 1 ? 'is' : 'are'} on this side of the architecture and hears you. Bodies in the room hear nothing -- you have no voice out there.`
319
- : `Your words go out over the grid and find no persona to land on. Anyone here in the FLESH hears nothing -- a PAN icon is a commlink in somebody's pocket, not a person you can talk to. ("call <name>" reaches a body.)`;
316
+ const { personas, links } = speakOnTheGrid(this.scene, currentPlayer, '');
317
+ const iconNames = personas.map(a => a.name);
318
+ const linkNames = links.map(a => a.name);
319
+ const lines = [];
320
+ if (iconNames.length > 0) {
321
+ lines.push(`Your persona speaks and the grid carries it: ${iconNames.join(', ')} ${iconNames.length === 1 ? 'is' : 'are'} on this side of the architecture and hears you.`);
322
+ }
323
+ // A COMMLINK IS FOR TALKING (FhXadizkMCxAdMaJx). This used to be
324
+ // the refusal -- "a PAN icon is a commlink in somebody's pocket,
325
+ // not a person you can talk to" -- which had the fact right and
326
+ // drew the wrong conclusion. It IS a commlink in their pocket, and
327
+ // that is precisely a thing you can speak out of.
328
+ if (linkNames.length > 0) {
329
+ lines.push(`It comes out of ${linkNames.join(', ')}'s commlink where ${linkNames.length === 1 ? 'they stand' : 'they stand'} in the flesh -- your voice out of ${linkNames.length === 1 ? 'their' : 'their'} pocket, not out of the air.`);
330
+ }
331
+ if (lines.length === 0) {
332
+ return `Your words go out over the grid and land on nothing. No persona on this side of the architecture, and nobody near your body is carrying a live commlink for them to come out of. ("call <name>" reaches somebody further off.)`;
333
+ }
334
+ if (linkNames.length === 0) {
335
+ lines.push(`Bodies in the room hear nothing -- nobody there is carrying a live link.`);
336
+ }
337
+ return lines.join('\n');
320
338
  }
321
339
  return farOff;
322
340
  }
@@ -52,10 +52,21 @@ export class ShoutCommand extends Command {
52
52
  // each other or they do not, and no amount of volume crosses a
53
53
  // host wall. So this routes to the same fan-out `say` uses rather
54
54
  // than inventing a louder one.
55
- const heard = speakOnTheGrid(this.scene, actor, message).map(a => a.name);
56
- return heard.length > 0
57
- ? `You push it out across the grid: "${message}"\n Heard by ${heard.join(', ')}.`
58
- : `You push it out across the grid -- nothing out here is listening.`;
55
+ // Both halves of the fan-out, named apart: a persona beside you and
56
+ // a commlink in somebody's pocket are two different places for your
57
+ // voice to come out of, and only one of them is standing in the
58
+ // same room as your body (FhXadizkMCxAdMaJx).
59
+ const { personas, links } = speakOnTheGrid(this.scene, actor, message);
60
+ const heard = personas.map(a => a.name);
61
+ const onLink = links.map(a => a.name);
62
+ if (heard.length === 0 && onLink.length === 0) {
63
+ return `You push it out across the grid -- nothing out here is listening.`;
64
+ }
65
+ const who = [
66
+ ...(heard.length > 0 ? [`Heard by ${heard.join(', ')}.`] : []),
67
+ ...(onLink.length > 0 ? [`Out of ${onLink.join(', ')}'s commlink, where they stand in the flesh.`] : []),
68
+ ];
69
+ return `You push it out across the grid: "${message}"\n ${who.join('\n ')}`;
59
70
  }
60
71
  const heard = actor.currentLocation.shout(actor, message);
61
72
  if (!(this.actor instanceof NPC)) {
@@ -1185,5 +1185,55 @@
1185
1185
  // read the win condition without reading completion would make the job
1186
1186
  // unwinnable, which is a worse bug than the one being fixed, and that
1187
1187
  // is the load-bearing test in the file.
1188
- export const ENGINE_VERSION = '1.77.0';
1188
+ // 1.78.0 (2026-09-16): CYBERCOMBAT IS NOT LAYING HANDS ON ANYBODY
1189
+ // (27dAZvLuoaW4PjF6s, Maka, the whole report verbatim):
1190
+ // "[order] Agent (Sony CIY-720): attack patrol ic ,
1191
+ // You can't lay hands on Patrol IC: they are not present in their
1192
+ // body, and there is nobody home to resist you."
1193
+ // The absent-body gate in npc-authorization.ts exists for a good
1194
+ // reason -- somebody grabbing a jacked-in runner's slumped shell is
1195
+ // reaching for a person who is not home -- but it asked the question
1196
+ // of every AI-driven actor on every contact verb. An Agent is a
1197
+ // matrix-plane construct (companions.ts builds it with plane:
1198
+ // 'matrix') and Patrol IC has no body to be absent FROM, so a rule
1199
+ // about hands fired on a program attacking a program and refused the
1200
+ // most ordinary order anyone gives an agent. Attacking an icon is
1201
+ // Cybercombat (Data Spike, p.239): two icons, no meat, nobody's hands
1202
+ // anywhere near it. The question is now asked only of an actor
1203
+ // standing in the meat; canPerceive, which is the rule that actually
1204
+ // knows about planes, still decides whether the two can reach each
1205
+ // other at all. The meat case is pinned in the same test file, because
1206
+ // a fix that traded this report for the older one it was built from
1207
+ // would be no fix.
1208
+ // 1.79.0 (2026-09-16): A COMMLINK IS FOR TALKING (FhXadizkMCxAdMaJx,
1209
+ // Maka, rejecting the first fix: "this looks like it's backwards. If I
1210
+ // jack in, and Grunt's icon is in reach, he SHOULD hear me through his
1211
+ // icon when I do a 'say'"). He is right and the books agree. p.246
1212
+ // names this as the exception to the host wall itself -- from outside
1213
+ // a host "you can't interact directly with icons inside it, ALTHOUGH
1214
+ // you can still send messages, make commcalls, and that sort of thing"
1215
+ // -- and nothing anywhere requires the person on the other end to be
1216
+ // jacked in. The old answer, "a PAN icon is a commlink in somebody's
1217
+ // pocket, not a person you can talk to", had the fact right and drew
1218
+ // the wrong conclusion from it: a commlink in somebody's pocket is
1219
+ // exactly a thing a voice can come out of.
1220
+ // - `say` and `shout` from the grid now reach the PERSONAS beside you
1221
+ // (as always) AND the live commlinks of people standing around your
1222
+ // BODY, delivered with hear()'s existing viaLink so the NPC knows it
1223
+ // came out of their pocket rather than from a mouth in the room.
1224
+ // - SCOPED TO THE VICINITY, not the district. Canon would let a
1225
+ // persona call a commlink anywhere on the grid -- distance is noise,
1226
+ // not range -- but a `say` that rang every commlink in the barrens
1227
+ // is a broadcast nobody asked for. "In reach" means what it means in
1228
+ // the icon list the reporter was reading: the room the body is in.
1229
+ // `call <name>` still reaches further.
1230
+ // - isLinked is the test, the same predicate that decides whether a
1231
+ // body is a ▣ icon at all, so the icon list and the voice agree: no
1232
+ // link, no icon, no pocket for the words to come out of -- and the
1233
+ // refusal says which of the two reasons it was.
1234
+ // The old test asserted the right refusal for the WRONG REASON: its
1235
+ // comment claimed "Krow is HERE, in the flesh, with a PAN icon on the
1236
+ // grid" while its fixture gave him no commlink at all, so there was
1237
+ // never a PAN icon in it. Pinned properly now, both ways.
1238
+ export const ENGINE_VERSION = '1.79.0';
1189
1239
  //# sourceMappingURL=engine-version.js.map
@@ -1,5 +1,6 @@
1
1
  import { roomsInReach } from './grid-reach.js';
2
2
  import { NPC } from '../models/npc.js';
3
+ import { isLinked } from './comm-style.js';
3
4
  /**
4
5
  * WHO A PERSONA CAN REACH (increment 2 of the Matrix position model,
5
6
  * 2026-09-02). The persona's position is `Player.matrixPosition` -- on a
@@ -35,13 +36,62 @@ export function cybercombatCandidates(scene, actor) {
35
36
  export function speechAudience(scene, actor) {
36
37
  return personasOnMySide(scene, actor);
37
38
  }
38
- /** Delivers a persona's words to its audience, the way Room.say delivers a body's. */
39
+ /**
40
+ * THE COMMLINKS IN EARSHOT OF YOUR BODY (FhXadizkMCxAdMaJx, Maka: "this
41
+ * looks like it's backwards. If I jack in, and Grunt's icon is in reach,
42
+ * he SHOULD hear me through his icon when I do a 'say'").
43
+ *
44
+ * He is right, and the books agree. A commlink is a COMMUNICATION
45
+ * device: p.246 says that even across a host wall "you can still send
46
+ * messages, make commcalls, and that sort of thing", and nothing
47
+ * anywhere requires the person on the other end to be jacked in -- a
48
+ * commcall rings a commlink in somebody's pocket, which is what a
49
+ * commlink is for. The engine's old answer, "a PAN icon is a commlink in
50
+ * somebody's pocket, not a person you can talk to", had the fact right
51
+ * and drew the wrong conclusion from it.
52
+ *
53
+ * SCOPED TO THE VICINITY, not the district. Canon would let a persona
54
+ * call a commlink anywhere on the grid -- distance is noise, not range
55
+ * -- but `say` is the verb for speaking to the people around you, and a
56
+ * `say` that rang every commlink in the barrens would be a broadcast
57
+ * nobody asked for. The room your body is slumped in is what "in reach"
58
+ * means in the icon list the reporter was reading, so it is what it
59
+ * means here. `call <name>` is still the verb for reaching somebody
60
+ * further off, and it still works.
61
+ *
62
+ * A PAN IS THE TEST, not a pulse: isLinked is the same predicate that
63
+ * decides whether a body shows up as a ▣ icon at all. Somebody with no
64
+ * commlink, or running silent, has nothing for your voice to come out
65
+ * of -- and does not appear in the icon list either, so the two answers
66
+ * agree.
67
+ */
68
+ export function panAudience(scene, actor) {
69
+ const here = actor.bodyRoom ?? actor.currentLocation;
70
+ if (!here)
71
+ return [];
72
+ return scene.allActors.filter(a => a !== actor
73
+ && a.plane === 'meat'
74
+ && (a.bodyRoom ?? a.currentLocation) === here
75
+ && isLinked(a));
76
+ }
77
+ /**
78
+ * Delivers a persona's words to its audience, the way Room.say delivers
79
+ * a body's -- to the personas beside it, and out of the commlinks of the
80
+ * people standing around its body.
81
+ */
39
82
  export function speakOnTheGrid(scene, actor, message) {
40
- const audience = speechAudience(scene, actor);
41
- for (const recipient of audience) {
83
+ const personas = speechAudience(scene, actor);
84
+ for (const recipient of personas) {
42
85
  void Promise.resolve(recipient.hear(actor, message, { broadcast: true })).catch(() => undefined);
43
86
  }
44
- return audience;
87
+ // viaLink, so the NPC knows this arrived out of their commlink rather
88
+ // than from a mouth in the room -- which is the whole difference, and
89
+ // the thing their reply has to be able to react to.
90
+ const links = panAudience(scene, actor);
91
+ for (const recipient of links) {
92
+ void Promise.resolve(recipient.hear(actor, message, { broadcast: true, viaLink: true })).catch(() => undefined);
93
+ }
94
+ return { personas, links };
45
95
  }
46
96
  /**
47
97
  * The combat exchange's "same place": on the matrix plane it is the
@@ -66,7 +66,26 @@ export function authorizeNpcAction(scene, actor, verb, args) {
66
66
  // which is lying right there in the room and is what the act was
67
67
  // reaching for. For a hands-on verb the absent-body reason is the one
68
68
  // that tells the fiction something it can use.
69
- if (contact && !isPresentInBody(target)) {
69
+ // ...AND IT IS A RULE ABOUT HANDS, SO IT ONLY BINDS A BODY
70
+ // (27dAZvLuoaW4PjF6s, Maka -- the whole report, verbatim):
71
+ //
72
+ // "[order] Agent (Sony CIY-720): attack patrol ic ,
73
+ // You can't lay hands on Patrol IC: they are not present in their
74
+ // body, and there is nobody home to resist you."
75
+ //
76
+ // An Agent is a matrix-plane construct (commands/companions.ts builds
77
+ // it with plane: 'matrix') and Patrol IC has no body to be absent
78
+ // from -- so a gate written for "somebody grabbed a jacked-in
79
+ // runner's slumped shell" fired on a program attacking a program,
80
+ // and refused the single most ordinary order anyone gives an agent.
81
+ //
82
+ // Attacking an icon is CYBERCOMBAT (Data Spike, p.239): two icons,
83
+ // no meat, nobody's hands anywhere near it. So the absent-body
84
+ // question is only asked of an actor standing in the meat. Everything
85
+ // else is still gated -- canPerceive below decides whether these two
86
+ // can reach each other at all, and it is the rule that actually knows
87
+ // about planes.
88
+ if (contact && actor.plane === 'meat' && !isPresentInBody(target)) {
70
89
  return {
71
90
  allowed: false,
72
91
  reason: `You can't lay hands on ${target.name}: they are not present in their body, and there is nobody home to resist you. Describe what you see, or wait for them to come back.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.218.0",
3
+ "version": "5.220.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.",