@maka/maka-cli 5.184.0 → 5.186.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.
Files changed (28) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/disable.js +4 -1
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/download.js +6 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/edit-file.js +132 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +3 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/hide.js +110 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +23 -4
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/jobs.js +15 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/journal.js +17 -3
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +1 -1
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +3 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +16 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +6 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +23 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/factories/cleanup-seed.js +89 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/game.js +220 -6
  17. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +21 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/headless.js +1 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/models/host.js +24 -1
  20. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +43 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/utilities/grids.js +22 -2
  22. package/bundle/typescript/src/commands/game/sideQuest/utilities/host-combat.js +5 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-brain.js +35 -2
  24. package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.js +107 -34
  25. package/bundle/typescript/src/commands/game/sideQuest/utilities/marks.js +3 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +5 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/utilities/surveillance.js +34 -0
  28. package/package.json +1 -1
@@ -140,6 +140,21 @@ const empty = () => ({ lines: [], meta: [], world: [] });
140
140
  * maglocks fall, the vault unseals -- but it stops being an immunity.
141
141
  */
142
142
  export function hostIsHunting(room, actor) {
143
+ return hostSuspectsOrHunts(room, actor)
144
+ // ...AND IT CAN SEE YOU (2026-09-14, "constantly in combat, even
145
+ // running silent"). Alert used to be the whole test, so a host woken
146
+ // by a successful Brute Force -- which canon says leaves it "aware
147
+ // that it is under attack ... but it doesn't automatically spot you"
148
+ // (p.236) -- opened the fight on the next keystroke and the silent
149
+ // test in ambientPatrol was never rolled (it bailed on alert). Ice
150
+ // hunts what the host has SPOTTED (Host.spotted, shared with every
151
+ // program, p.247); an alerted host that has not spotted you sweeps
152
+ // for you every action instead (ambientPatrol, focused).
153
+ && isSpotted(room, actor);
154
+ }
155
+ /** Alert, inside, unsanctioned -- the host is looking, whether or not it
156
+ * has found this persona yet. The two halves below split on that. */
157
+ function hostSuspectsOrHunts(room, actor) {
143
158
  return actor.plane === 'matrix'
144
159
  && room.hasNode
145
160
  // IC RUNS INSIDE THE HOST, AND NOWHERE ELSE (p.246-247; Deditri,
@@ -161,6 +176,27 @@ export function hostIsHunting(room, actor) {
161
176
  && !room.hostSanctioned
162
177
  && room.hostAlert;
163
178
  }
179
+ /** An alerted host that has NOT found this persona: its Patrol is
180
+ * running Matrix Perception every action (Data Trails p.86). */
181
+ export function hostSuspects(room, actor) {
182
+ return hostSuspectsOrHunts(room, actor) && !isSpotted(room, actor);
183
+ }
184
+ /** Has this host (and so all its IC, p.247) got eyes on the persona? */
185
+ export function isSpotted(room, actor) {
186
+ return room.host?.spotted.has(actor.name) ?? false;
187
+ }
188
+ /**
189
+ * THE HOST SEES YOU. One door for every way of being made -- a blown
190
+ * Sleaze's free mark (p.236), a Patrol sighting, Probe's grip -- so the
191
+ * fact that alert and spotted are two things is written in one place.
192
+ */
193
+ export function spot(room, actor) {
194
+ const host = room.host;
195
+ if (!host)
196
+ return;
197
+ host.alert = true;
198
+ host.spotted.add(actor.name);
199
+ }
164
200
  /**
165
201
  * IS THIS PERSONA A LEGITIMATE USER? (p.248, Patrol.)
166
202
  *
@@ -234,39 +270,46 @@ export function ambientPatrol(room, actor) {
234
270
  // back, it is not watching for you. See hostIsHunting.
235
271
  if (room.hostSanctioned)
236
272
  return out;
237
- // Already made: the alerted host's own turn owns that persona
238
- // (runHostTurn dispatches there instead). A CRACKED host is no longer
239
- // exempt -- see hostIsHunting for why that ruling was retired.
240
- if (room.hostAlert)
241
- return out;
242
- if (looksLegitimate(room, actor))
273
+ // Already SEEN: the alerted host's ice owns that persona (hostIsHunting
274
+ // -> the host arena's Combat Turn). A CRACKED host is no longer exempt
275
+ // -- see hostIsHunting for why that ruling was retired.
276
+ if (isSpotted(room, actor))
243
277
  return out;
244
- // The sweep is on a clock (Data Trails p.86) -- it is not looking
245
- // every single moment until it has reason to.
246
- const due = (room.patrolCountdown ?? rollPatrolInterval(room.hostRating)) - 1;
247
- if (due > 0) {
248
- room.patrolCountdown = due;
278
+ // TWO CADENCES, AND THE HOST'S ALERT PICKS BETWEEN THEM (Data Trails
279
+ // p.86, the "constantly in combat" report of 2026-09-14). An
280
+ // unsuspecting host sweeps on the rating interval and lets a marked
281
+ // user be (p.248). A host that KNOWS it has an intruder -- a Brute
282
+ // Force it felt, a Sleaze it caught, a sighting -- runs Matrix
283
+ // Perception "every action", and it is not reassured by a mark: host
284
+ // security stays "vigilant for unauthorized users, even those with
285
+ // marks" (DT p.168-169), and the mark a Brute Force just planted is
286
+ // exactly what it is looking at. This used to return early on alert
287
+ // instead, which is why running silent never got its roll.
288
+ const focused = room.hostAlert;
289
+ // A persona that Hid THIS beat is not re-swept on the same beat: the
290
+ // hide was its action; the host's new perception is its own (p.240).
291
+ if (focused && room.host?.justHid.delete(actor.name))
249
292
  return out;
293
+ if (!focused) {
294
+ if (looksLegitimate(room, actor))
295
+ return out;
296
+ // The sweep is on a clock (Data Trails p.86) -- it is not looking
297
+ // every single moment until it has reason to.
298
+ const due = (room.patrolCountdown ?? rollPatrolInterval(room.hostRating)) - 1;
299
+ if (due > 0) {
300
+ room.patrolCountdown = due;
301
+ return out;
302
+ }
303
+ room.patrolCountdown = rollPatrolInterval(room.hostRating);
250
304
  }
251
- room.patrolCountdown = rollPatrolInterval(room.hostRating);
252
- let spotted;
253
- if (!actor.runningSilent) {
254
- // p.235: no test. A visible icon in the host is simply seen.
255
- spotted = true;
256
- out.meta.push(`Patrol IC -- sweep: icon not running silent, spotted automatically (no test, p.235)`);
257
- }
258
- else {
259
- const look = rollPool(room.hostRating * 2, room.hostAttributes().dataProcessing);
260
- const hide = rollPool(actor.logic + actor.matrixAttribute('sleaze'));
261
- // p.236: a tie goes to the hider.
262
- spotted = look.hits > hide.hits;
263
- out.meta.push(`Patrol IC -- Matrix Perception (Host Rating x2 [Data Processing]): ${formatRoll(look)} v. Logic + Sleaze: ${formatRoll(hide)}`);
264
- }
305
+ const spotted = patrolPerception(room, actor, out);
265
306
  if (!spotted) {
266
- out.lines.push(` Something quarters the datastream around you and moves on. Your silence held.`);
307
+ out.lines.push(focused
308
+ ? ` Patrol IC is HUNTING -- it quarters the datastream around you, hard, and slides past. Your silence held. For now.`
309
+ : ` Something quarters the datastream around you and moves on. Your silence held.`);
267
310
  return out;
268
311
  }
269
- room.hostAlert = true;
312
+ spot(room, actor);
270
313
  actor.sneaking = false;
271
314
  // NAME THE HOST THAT FOUND YOU (rG8LtMoy5DHQJ2NuG: "I got a
272
315
  // notification that an IC has found me, however I haven't entered a
@@ -282,10 +325,32 @@ export function ambientPatrol(room, actor) {
282
325
  // So it says WHICH host, and where it is. That is the whole fix; the
283
326
  // deeper problem -- that this engine has no inside-vs-outside-host
284
327
  // state for the player to have crossed -- is its own item.
285
- out.lines.push(` {red-fg}${hostLabel(room, { capital: true })} makes you -- an unmarked icon where none should be. You're inside its architecture, on ${room.name}'s patch of grid, and it knows it has an intruder.{/red-fg}`);
286
- out.world.push(`${room.name}'s Patrol IC made ${actor.name} on a routine sweep -- alarm flagged, ice hunting.`);
328
+ out.lines.push(focused
329
+ ? ` {red-fg}${hostLabel(room, { capital: true })} makes you -- the hunting Patrol locks onto your icon and every program in the architecture has you now.{/red-fg}`
330
+ : ` {red-fg}${hostLabel(room, { capital: true })} makes you -- an unmarked icon where none should be. You're inside its architecture, on ${room.name}'s patch of grid, and it knows it has an intruder.{/red-fg}`);
331
+ out.world.push(focused
332
+ ? `${room.name}'s hunting Patrol IC found ${actor.name} -- ice closing.`
333
+ : `${room.name}'s Patrol IC made ${actor.name} on a routine sweep -- alarm flagged, ice hunting.`);
287
334
  return out;
288
335
  }
336
+ /**
337
+ * ONE MATRIX PERCEPTION BY THE HOST'S PATROL against one persona, the
338
+ * dice canon prints and nothing more: a non-silent icon is seen with no
339
+ * test (p.235); a silent one is an Opposed Test, Host Rating x2 [Data
340
+ * Processing] (the IC pool, p.247; the perception bracket, p.241) v.
341
+ * Logic + Sleaze, and a tie goes to the hider (p.236). Returns whether
342
+ * the persona was seen; the caller decides what that costs.
343
+ */
344
+ export function patrolPerception(room, actor, out) {
345
+ if (!actor.runningSilent) {
346
+ out.meta.push(`Patrol IC -- sweep: icon not running silent, spotted automatically (no test, p.235)`);
347
+ return true;
348
+ }
349
+ const look = rollPool(room.hostRating * 2, room.hostAttributes().dataProcessing);
350
+ const hide = rollPool(actor.logic + actor.matrixAttribute('sleaze'));
351
+ out.meta.push(`Patrol IC -- Matrix Perception (Host Rating x2 [Data Processing]): ${formatRoll(look)} v. Logic + Sleaze: ${formatRoll(hide)}`);
352
+ return look.hits > hide.hits;
353
+ }
289
354
  /**
290
355
  * THE HOST'S WHOLE TURN: its eyes if it has not made you, its ice if it
291
356
  * has. One entry point, so the caller never has to know which half of
@@ -618,11 +683,17 @@ function dealBiofeedback(actor, dv, type, out) {
618
683
  * pre-alert patrolling exists, the interval table is what it needs.
619
684
  */
620
685
  function patrolActs(room, actor, out) {
621
- // No Attack rating means no bracket to cap the hits with.
622
- const roll = rollPool(room.hostRating * 2);
623
- out.meta.push(`Patrol IC -- Matrix Perception (Host Rating x2): ${formatRoll(roll)}`);
624
- if (roll.hits > 0) {
625
- out.lines.push(` {red-fg}Patrol IC sweeps your icon and hands what it sees to the host.{/red-fg}`);
686
+ // A persona the host can already see (which is what this composition
687
+ // is handed) gets no second roll -- Patrol's perception is the one in
688
+ // patrolPerception, spent on personas it has NOT found (ic-brain.ts
689
+ // runs it against each unspotted icon inside during the fight).
690
+ if (isSpotted(room, actor)) {
691
+ out.lines.push(` {red-fg}Patrol IC keeps your icon in view and hands every move to the host.{/red-fg}`);
692
+ return;
693
+ }
694
+ if (patrolPerception(room, actor, out)) {
695
+ spot(room, actor);
696
+ out.lines.push(` {red-fg}Patrol IC sweeps your icon and hands what it sees to the host -- it has you now.{/red-fg}`);
626
697
  }
627
698
  else {
628
699
  // "Does not take Matrix damage when it fails" -- a whiff costs it
@@ -659,6 +730,8 @@ function probeActs(room, actor, type, out, ice) {
659
730
  }
660
731
  const now = held + 1;
661
732
  room.hostMarksOn.set(actor.name, now);
733
+ // A grip is a sighting: the host and every program see what Probe holds.
734
+ spot(room, actor);
662
735
  out.lines.push(` {red-fg}Probe IC gets a grip -- the host holds ${now} mark${now === 1 ? '' : 's'} on you now${now >= MAX_MARKS ? ', all it can carry' : ''}.{/red-fg}`);
663
736
  out.world.push(`${room.name}'s ice tightened its hold on ${actor.name}.`);
664
737
  }
@@ -44,6 +44,9 @@ export function clearMarksOn(actor, scene) {
44
44
  if ((room.host?.marksOn.get(actor.name) ?? 0) > 0)
45
45
  had++;
46
46
  room.host?.marksOn.delete(actor.name);
47
+ // A rebooted persona is a fresh icon: whatever the host had SPOTTED
48
+ // is gone with the marks (Host.spotted) -- it has to find you again.
49
+ room.host?.spotted.delete(actor.name);
47
50
  }
48
51
  }
49
52
  return had;
@@ -267,6 +267,11 @@ export function leaveMatrix(scene, actor, opts) {
267
267
  actor.bodyRoom = undefined;
268
268
  lines.push(`You're back in your body in ${body.name}.`);
269
269
  }
270
+ // A LOOSE-END CLEANUP ENDS HERE, whichever door the persona left by --
271
+ // a clean jack-out, a torn link-lock, a bricked deck, Scramble, GOD.
272
+ // Every exit is one of this function's callers, so this is the one
273
+ // place the hub takes the scene back (Game.onPersonaLeftMatrix).
274
+ lines.push(...(scene.ownerGame?.onPersonaLeftMatrix?.(actor) ?? []));
270
275
  actor.performAction('jacks out', 'stirring awake', { quiet: scene.isHumanControlled?.(actor) ?? false });
271
276
  scene.addWorldEvent(`${actor.name} jacked out of the Matrix.`);
272
277
  scene.updateStatus();
@@ -97,6 +97,40 @@ export function camerasLive(rooms, room) {
97
97
  return true;
98
98
  return !hosts.some(r => r.hostCracked);
99
99
  }
100
+ /**
101
+ * WHAT THE CAMERAS STILL HOLD WHEN THE RUN ENDS (loose ends, 2026-09-14).
102
+ *
103
+ * `camSeen` is the set of rooms whose cameras were called out during the
104
+ * run (Game._camSeenRooms -- "a camera's dead eye caught ALL of it").
105
+ * The line promised the footage was "already somewhere else", and this
106
+ * is where: the host those cameras are slaved to. Footage survives the
107
+ * run when the room's feeds are STILL live at the end -- its master host
108
+ * never cracked, its cluster never looped or bricked -- and there is a
109
+ * host anywhere to hold it (standalone recorders keep nothing a decker
110
+ * could reach, so they leave no loose end). Grouped under one master
111
+ * (the one with the most rooms, then by name): one run, one record.
112
+ */
113
+ export function liveFootage(rooms, camSeen) {
114
+ const byHost = new Map();
115
+ for (const name of camSeen) {
116
+ const room = Object.values(rooms).find(r => r.name === name);
117
+ if (!room || !camerasLive(rooms, room))
118
+ continue;
119
+ const master = cameraMasterHost(rooms, room);
120
+ if (!master)
121
+ continue;
122
+ const list = byHost.get(master) ?? [];
123
+ list.push(room);
124
+ byHost.set(master, list);
125
+ }
126
+ let best;
127
+ for (const [host, seen] of byHost) {
128
+ if (!best || seen.length > best.rooms.length || (seen.length === best.rooms.length && host.name.localeCompare(best.host.name) < 0)) {
129
+ best = { host, rooms: seen };
130
+ }
131
+ }
132
+ return best;
133
+ }
100
134
  /** Can THIS persona watch the room's feeds remotely? Its own host
101
135
  * cracked or marked; a hostless watched room answers to whoever owns
102
136
  * any of the site's hosts. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.184.0",
3
+ "version": "5.186.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.",