@maka/maka-cli 5.182.0 → 5.184.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 (37) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js +6 -6
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +3 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/delay.js +1 -1
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/disable.js +331 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/download.js +3 -3
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/end-turn.js +1 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js +11 -13
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +17 -17
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +2 -2
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +5 -5
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +1 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +1 -1
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +13 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +78 -31
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/snoop.js +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +7 -7
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +1 -1
  20. package/bundle/typescript/src/commands/game/sideQuest/game.js +7 -4
  21. package/bundle/typescript/src/commands/game/sideQuest/models/device.js +40 -1
  22. package/bundle/typescript/src/commands/game/sideQuest/models/host.js +4 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +6 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +2 -1
  25. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +6 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js +1 -1
  27. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +2 -2
  28. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.js +1 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +44 -18
  30. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-style.js +8 -0
  31. package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.js +2 -2
  32. package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js +50 -21
  33. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +6 -2
  34. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +3 -3
  35. package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +2 -1
  36. package/bundle/typescript/src/commands/game/sideQuest/utilities/surveillance.js +4 -0
  37. package/package.json +1 -1
@@ -16,6 +16,25 @@ import { isWatched, camerasLive, canSnoopFeeds } from './surveillance.js';
16
16
  * ▣ PAN, ▲ dangerous device, ◇ data. (There is no air-gapped-host glyph
17
17
  * any more -- see hhSWzLSXECFtfoAGa and canReachHost.)
18
18
  */
19
+ /**
20
+ * A DEVICE ICON, AND WHAT WORKS IT (commands/disable.ts, 2026-09-13).
21
+ * Control Device (p.238) needs 2 marks on the device -- "disable" -- and
22
+ * a Data Spike (p.239) needs none -- "brick". A bricked lock stays
23
+ * locked (p.228), so its line says so rather than offering a way in.
24
+ */
25
+ export function deviceIconLine(device, actor) {
26
+ const dr = `{bold}[DR ${device.rating}]{/bold}`;
27
+ if (device.isOpen())
28
+ return `▤ ${device.name} ${dr} -- its icon standing open, nothing left to hold.`;
29
+ if (device.isBricked) {
30
+ return `▤ ${device.name} ${dr} -- BRICKED, dead electronics${device.kind === 'lock' ? '; the lock stays locked (p.228)' : ''}.`;
31
+ }
32
+ const marks = device.marksBy.get(actor.name) ?? 0;
33
+ const way = marks >= 2
34
+ ? `"disable ${device.name}" (Control Device, ${marks} marks held)`
35
+ : `"hack ${device.name}" for marks (${marks}/2 to command it), or "brick ${device.name}"`;
36
+ return `▤ ${device.name} ${dr} -- a device icon shaped like the thing itself, shut. (${way})`;
37
+ }
19
38
  export function gridIcons(scene, actor, room) {
20
39
  // INSIDE A HOST IS A DIFFERENT PLACE (SR5 p.246, and Deditri's
21
40
  // eJANPpm7qCaCAJBJw). "The virtual space inside a host is separate
@@ -202,9 +221,7 @@ export function gridIcons(scene, actor, room) {
202
221
  for (const device of room.devices) {
203
222
  if (!device.broadcastsAro())
204
223
  continue;
205
- icons.push(device.isOpen()
206
- ? `▤ ${device.name} {bold}[DR ${device.rating}]{/bold} -- its icon standing open, nothing left to hold.`
207
- : `▤ ${device.name} {bold}[DR ${device.rating}]{/bold} -- a device icon shaped like the thing itself, shut. ("hack ${device.name}")`);
224
+ icons.push(deviceIconLine(device, actor));
208
225
  }
209
226
  // The cameras, as a THING (surveillance.ts): live for security, looped,
210
227
  // or yours to ride.
@@ -306,7 +323,16 @@ function hostInteriorIcons(scene, actor, room) {
306
323
  const data = room.offlineServer
307
324
  ? []
308
325
  : room.inventory.getAllItems().filter(i => i.plane === 'matrix');
309
- for (const item of data) {
326
+ // THE ARCHIVE IS NOT A GLANCE (p.241, player ruling 2026-09-13): a
327
+ // persona sees the host's files after a Matrix Search turns them up
328
+ // (commands/search.ts, Host.searchedBy), or when the host is cracked
329
+ // and they spill open. Until then a look says only that there is an
330
+ // archive to search.
331
+ const found = room.hostCracked || (room.host?.searchedBy.has(actor.name) ?? false);
332
+ if (data.length > 0 && !found) {
333
+ icons.push(`◇ The host's archive -- files in here somewhere, and a glance does not read an archive. ("search" runs a Matrix Search, p.241)`);
334
+ }
335
+ for (const item of found ? data : []) {
310
336
  icons.push(room.hostCracked
311
337
  ? `◇ ${item.name} [data] -- unsealed, sitting open in the host's archive. ("take" it)`
312
338
  : `◇ ${item.name} [data] -- SEALED in the host's archive. ("hack" the host to break the seal)`);
@@ -414,20 +440,20 @@ export function gridSculpt(actor, room) {
414
440
  const purpose = room.host?.purpose;
415
441
  const sculpt = room.host?.sculpt;
416
442
  return [
417
- `{cyan-fg}INSIDE :: ${hostLabel(room, { capital: true }).toUpperCase()} [HR ${room.hostRating}]${purpose ? ` -- ${purpose}` : ''}{/cyan-fg}`,
418
- `{cyan-fg}${sculpt ?? 'Architecture all the way around -- the outside grid is a wall away and might as well be weather.'}{/cyan-fg}`,
443
+ `{lightblue-fg}INSIDE :: ${hostLabel(room, { capital: true }).toUpperCase()} [HR ${room.hostRating}]${purpose ? ` -- ${purpose}` : ''}{/lightblue-fg}`,
444
+ `{lightblue-fg}${sculpt ?? 'Architecture all the way around -- the outside grid is a wall away and might as well be weather.'}{/lightblue-fg}`,
419
445
  ].join('\n');
420
446
  }
421
447
  // ON THE GRID (SR5 pp.217-218): no street under the persona -- a black
422
448
  // flatland, the body's room named only because that is where the
423
449
  // signal comes from. A host directly overhead gets a line of its own.
424
450
  const body = actor.bodyRoom ?? room;
425
- const header = `{cyan-fg}THE GRID :: jacked in from ${body.name.toUpperCase()}{/cyan-fg}`;
451
+ const header = `{lightblue-fg}THE GRID :: jacked in from ${body.name.toUpperCase()}{/lightblue-fg}`;
426
452
  const grid = actor.currentGrid;
427
- const flat = `{cyan-fg}Black flatland under a black sky -- the icons are the only things out here.${grid ? ` Riding ${grid.name}${grid.userPenalty ? ' (-2 on everything you do, p.233)' : ''}.` : ''}{/cyan-fg}`;
453
+ const flat = `{lightblue-fg}Black flatland under a black sky -- the icons are the only things out here.${grid ? ` Riding ${grid.name}${grid.userPenalty ? ' (-2 on everything you do, p.233)' : ''}.` : ''}{/lightblue-fg}`;
428
454
  if (!room.hasNode)
429
455
  return `${header}\n${flat}`;
430
- return `${header}\n${flat}\n{cyan-fg}${hostLabel(room, { capital: true })} hangs directly overhead, sealed. What it holds is inside it.{/cyan-fg}`;
456
+ return `${header}\n${flat}\n{lightblue-fg}${hostLabel(room, { capital: true })} hangs directly overhead, sealed. What it holds is inside it.{/lightblue-fg}`;
431
457
  }
432
458
  /**
433
459
  * The icons rendered as the standard cyan block, or the thin-grid line.
@@ -456,19 +482,19 @@ export function gridIconBlock(scene, actor, room, rooms) {
456
482
  if (actor.insideHost || !rooms) {
457
483
  const icons = gridIcons(scene, actor, room);
458
484
  if (icons.length === 0) {
459
- return `{cyan-fg}Thin grid out here -- background noise, nothing worth cracking.{/cyan-fg}`;
485
+ return `{lightblue-fg}Thin grid out here -- background noise, nothing worth cracking.{/lightblue-fg}`;
460
486
  }
461
- return `{cyan-fg}ICONS IN REACH:{/cyan-fg}\n${icons.map(i => ` {cyan-fg}${i}{/cyan-fg}`).join('\n')}`;
487
+ return `{lightblue-fg}ICONS IN REACH:{/lightblue-fg}\n${icons.map(i => ` {lightblue-fg}${i}{/lightblue-fg}`).join('\n')}`;
462
488
  }
463
489
  const out = [];
464
490
  const hosts = hostsInReach(rooms, actor);
465
491
  out.push(hosts.length > 0
466
- ? `{cyan-fg}HOSTS OVERHEAD:{/cyan-fg}\n${hosts.map(h => ` {cyan-fg}${hostLine(h, actor)}{/cyan-fg}`).join('\n')}`
467
- : `{cyan-fg}Nothing overhead -- no host stands over this district.{/cyan-fg}`);
492
+ ? `{lightblue-fg}HOSTS OVERHEAD:{/lightblue-fg}\n${hosts.map(h => ` {lightblue-fg}${hostLine(h, actor)}{/lightblue-fg}`).join('\n')}`
493
+ : `{lightblue-fg}Nothing overhead -- no host stands over this district.{/lightblue-fg}`);
468
494
  const near = gridIcons(scene, actor, gridVicinity(actor)).filter(i => !isHostLine(i));
469
495
  out.push(near.length > 0
470
- ? `{cyan-fg}ICONS IN REACH:{/cyan-fg}\n${near.map(i => ` {cyan-fg}${i}{/cyan-fg}`).join('\n')}`
471
- : `{cyan-fg}Nothing near -- background noise around your signal.{/cyan-fg}`);
496
+ ? `{lightblue-fg}ICONS IN REACH:{/lightblue-fg}\n${near.map(i => ` {lightblue-fg}${i}{/lightblue-fg}`).join('\n')}`
497
+ : `{lightblue-fg}Nothing near -- background noise around your signal.{/lightblue-fg}`);
472
498
  // FARTHER OUT, WITHOUT AN ADDRESS (SR5 p.235, p.243): Matrix Perception
473
499
  // never tells you where a device physically sits -- that is Trace Icon,
474
500
  // two marks and an opposed test. So far icons are listed by the noise
@@ -493,9 +519,9 @@ export function gridIconBlock(scene, actor, room, rooms) {
493
519
  byBand.set(noise, list);
494
520
  }
495
521
  const far = [...byBand.entries()].sort((a, b) => a[0] - b[0])
496
- .map(([noise, names]) => ` {cyan-fg}· dim, noise -${noise}: ${names.join(', ')}{/cyan-fg}`);
522
+ .map(([noise, names]) => ` {lightblue-fg}· dim, noise -${noise}: ${names.join(', ')}{/lightblue-fg}`);
497
523
  if (far.length > 0)
498
- out.push(`{cyan-fg}FARTHER OUT -- dimmer with distance:{/cyan-fg}\n${far.join('\n')}`);
524
+ out.push(`{lightblue-fg}FARTHER OUT -- dimmer with distance:{/lightblue-fg}\n${far.join('\n')}`);
499
525
  // A blank line between the distance bands (user, 2026-09-02): three
500
526
  // sections read as three when they do not touch.
501
527
  return out.join('\n\n');
@@ -510,7 +536,7 @@ export function gridIconBlock(scene, actor, room, rooms) {
510
536
  * the other personas, and the files. Cut to the dock's rows and columns.
511
537
  */
512
538
  export function hostInteriorPanel(scene, actor, host, rows, cols) {
513
- const cut = (raw) => `{cyan-fg}${[...raw].slice(0, Math.max(4, cols)).join('')}{/cyan-fg}`;
539
+ const cut = (raw) => `{lightblue-fg}${[...raw].slice(0, Math.max(4, cols)).join('')}{/lightblue-fg}`;
514
540
  const lines = [];
515
541
  lines.push(cut(`INSIDE ${host.name}`));
516
542
  if (host.purpose)
@@ -0,0 +1,8 @@
1
+ export const MATRIX_COLOR = 'lightblue';
2
+ export const FEED_COLOR = 'white';
3
+ export const TURN_COLOR = 'yellow';
4
+ /** Matrix-plane prose, tagged. */
5
+ export function mx(text) {
6
+ return `{${MATRIX_COLOR}-fg}${text}{/${MATRIX_COLOR}-fg}`;
7
+ }
8
+ //# sourceMappingURL=matrix-style.js.map
@@ -58,10 +58,10 @@ export function accrueOverwatch(scene, actor, defenseHits, why) {
58
58
  // The grid's built-in warning system (p.221): subtle ripples, once
59
59
  // per band -- the only free hint the player ever gets.
60
60
  if (before < 30 && actor.overwatchScore >= 30) {
61
- return { lines: [`{cyan-fg}The grid RIPPLES -- hard, close, and looking. Something vast has nearly found the thread you're hanging by. ("reboot" is a clean slate.){/cyan-fg}`], converged: false };
61
+ return { lines: [`{lightblue-fg}The grid RIPPLES -- hard, close, and looking. Something vast has nearly found the thread you're hanging by. ("reboot" is a clean slate.){/lightblue-fg}`], converged: false };
62
62
  }
63
63
  if (before < 20 && actor.overwatchScore >= 20) {
64
- return { lines: [`{cyan-fg}A ripple crosses the grid -- subtle, wrong, deliberate. Somewhere above, an eye has started counting your fingerprints.{/cyan-fg}`], converged: false };
64
+ return { lines: [`{lightblue-fg}A ripple crosses the grid -- subtle, wrong, deliberate. Somewhere above, an eye has started counting your fingerprints.{/lightblue-fg}`], converged: false };
65
65
  }
66
66
  return { lines: [], converged: false };
67
67
  }
@@ -39,36 +39,65 @@ import { mentalLimit } from './limits.js';
39
39
  * an active search mechanically different from a passive glance. */
40
40
  export const ACTIVELY_LOOKING = 3;
41
41
  /**
42
- * ON THE GRID IT IS A DIFFERENT TEST WITH A DIFFERENT BRACKET. Matrix
43
- * Search is Computer + Logic [Data Processing], so the Mental limit does
44
- * NOT apply out there -- capping a decker's sweep by their Willpower
45
- * would be a fresh deviation dressed as a fix. The engine takes the
46
- * best of Perception and Computer so pre-Computer deckers lose nothing
47
- * (a standing allowance, older than this function).
42
+ * THE MEAT PERCEPTION TEST. The Matrix used to ride through here as a
43
+ * "different test with a different bracket" -- Logic + the better of
44
+ * Perception and Computer [Data Processing] -- which was neither Matrix
45
+ * Perception nor Matrix Search. It is matrixSearchTest now, below; the
46
+ * grid never comes through this door.
47
+ */
48
+ export function perceptionTest(actor, opts = {}) {
49
+ const skill = actor.skillRating('perception');
50
+ // Unskilled defaults at -1 (p.130), exactly as both callers already did.
51
+ const pool = Math.max(1, actor.intuition
52
+ + (skill > 0 ? skill : -1)
53
+ + (opts.active ? ACTIVELY_LOOKING : 0)
54
+ + actor.bonus('perception')
55
+ + actor.woundModifier
56
+ - actor.sustainingPenalty);
57
+ return { pool, limit: mentalLimit(actor) };
58
+ }
59
+ /**
60
+ * MATRIX SEARCH (SR5 p.241, RAG-checked 2026-09-13): a Special action,
61
+ * "Computer + Intuition [Data Processing]" -- INTUITION, not Logic; the
62
+ * old grid branch above rolled Logic and called it canon. The threshold
63
+ * and the base time come from what is being looked for (the Matrix
64
+ * Search Table: public 1 / 1 minute, limited 3 / 30 minutes; inside a
65
+ * host the base time is one minute whatever the information), net hits
66
+ * over the threshold divide the time, and a failure still spends all
67
+ * of it. A Browse program "cuts the base time in half" (Splintered
68
+ * State p.54) -- time, not dice. Running silent costs its -2 like every
69
+ * Matrix action (p.235-236).
48
70
  *
49
71
  * Data Processing reads 0 for an actor with no deck at all; that is a
50
72
  * caller's missing gate rather than a real limit, so it degrades to
51
73
  * unlimited here instead of handing rollPool a zero it would reject.
52
74
  */
53
- export function perceptionTest(actor, opts = {}) {
54
- const onGrid = actor.plane === 'matrix';
55
- const skill = onGrid
56
- ? Math.max(actor.skillRating('perception'), actor.skillRating('computer'))
57
- : actor.skillRating('perception');
58
- const attribute = onGrid ? actor.logic : actor.intuition;
59
- // Unskilled defaults at -1 (p.130), exactly as both callers already did.
60
- const pool = Math.max(1, attribute
75
+ export function matrixSearchTest(actor) {
76
+ const skill = actor.skillRating('computer');
77
+ const pool = Math.max(1, actor.intuition
61
78
  + (skill > 0 ? skill : -1)
62
- + (opts.browse ?? 0)
63
- + (opts.active ? ACTIVELY_LOOKING : 0)
64
79
  + actor.bonus('perception')
80
+ + (typeof actor.matrixActionPenalty === 'number' ? actor.matrixActionPenalty : 0)
65
81
  + actor.woundModifier
66
82
  - actor.sustainingPenalty);
67
- const dataProcessing = onGrid ? actor.matrixAttribute('dataProcessing') : 0;
68
- const limit = onGrid
69
- ? (dataProcessing > 0 ? dataProcessing : undefined)
70
- : mentalLimit(actor);
71
- return { pool, limit };
83
+ const dataProcessing = actor.matrixAttribute('dataProcessing');
84
+ return { pool, limit: dataProcessing > 0 ? dataProcessing : undefined };
85
+ }
86
+ /** The Matrix Search Table (p.241-242): threshold and base time in
87
+ * seconds. Inside a host the base time is one minute regardless. */
88
+ export const MATRIX_SEARCH = {
89
+ public: { threshold: 1, seconds: 60 },
90
+ inHost: { threshold: 3, seconds: 60 },
91
+ };
92
+ /** How long a search takes once rolled: the base time divided by the
93
+ * net hits over the threshold, never under one Combat Turn (3s); a
94
+ * failure spends the whole base time. Browse halves the base. */
95
+ export function matrixSearchSeconds(baseSeconds, hits, threshold, browse) {
96
+ const base = browse ? Math.ceil(baseSeconds / 2) : baseSeconds;
97
+ if (hits < threshold)
98
+ return base;
99
+ const net = hits - threshold;
100
+ return net > 0 ? Math.max(3, Math.ceil(base / net)) : base;
72
101
  }
73
102
  /**
74
103
  * PERCEPTION THRESHOLDS (p.136). Named because the numbers appear in
@@ -164,6 +164,8 @@ export function serializeItem(item) {
164
164
  out.loadedAmmo = item.loadedAmmo;
165
165
  if (item.jammed)
166
166
  out.jammed = true;
167
+ if (item.bricked)
168
+ out.bricked = true;
167
169
  if (item.deckDamage > 0)
168
170
  out.deckDamage = item.deckDamage;
169
171
  if (item.droneDamage > 0)
@@ -219,6 +221,8 @@ export function restoreItem(json) {
219
221
  item.loadedAmmo = json.loadedAmmo;
220
222
  if (json.jammed)
221
223
  item.jammed = true;
224
+ if (json.bricked)
225
+ item.bricked = true;
222
226
  if (json.deckDamage)
223
227
  item.takeDeckDamage(json.deckDamage);
224
228
  if (json.droneDamage)
@@ -663,7 +667,7 @@ export function captureHubOverlay(scene, hubSeed, player, homeRoom) {
663
667
  .map(n => n.player.name)
664
668
  .filter(name => !scene.getActor(name));
665
669
  const devices = [...collectDevices(scene).entries()]
666
- .map(([name, d]) => ({ name, open: d.isOpen(), ...(d.barrierDamage > 0 ? { damage: d.barrierDamage } : {}) }));
670
+ .map(([name, d]) => ({ name, open: d.isOpen(), ...(d.barrierDamage > 0 ? { damage: d.barrierDamage } : {}), ...(d.isBricked ? { bricked: true } : {}) }));
667
671
  // THE TOMBSTONE (see ISavedHub.puzzles): always [], never read here.
668
672
  // Omitting it crashes v5.64.0's unguarded for-of on resume, and
669
673
  // saves move between builds via the cloud mirror.
@@ -824,7 +828,7 @@ export function applyHubOverlay(scene, saved, player, homeRoom) {
824
828
  for (const d of saved.devices ?? []) {
825
829
  const live = liveDevices.get(d.name);
826
830
  if (live)
827
- live.restoreState({ open: d.open, damage: d.damage });
831
+ live.restoreState({ open: d.open, damage: d.damage, bricked: d.bricked });
828
832
  }
829
833
  // A SAVE WRITTEN BEFORE DEVICES EXISTED has no `devices` key at all,
830
834
  // so every device would read shut -- while the door it holds stays
@@ -107,7 +107,7 @@ export function enterMatrix(scene, actor, mode) {
107
107
  // half-done, which would be a fresh canon violation wearing a
108
108
  // feature's clothes. See the PR for CgJ6uTfEvTd8JXdDN.
109
109
  actor.activeDeck = actor.isTechnomancer() ? undefined : actor.getCyberdeck();
110
- actor.performAction('jacks in', `slumping where they stand, eyes flickering (${mode}-sim)`);
110
+ actor.performAction('jacks in', `slumping where they stand, eyes flickering (${mode}-sim)`, { quiet: scene.isHumanControlled?.(actor) ?? false });
111
111
  scene.addWorldEvent(`${actor.name} jacked into the Matrix in ${actor.bodyRoom.name}.`);
112
112
  Logger.getInstance().write(`${actor.name} entered the Matrix (${mode}-sim, ${actor.activeDeck?.name ?? (actor.isTechnomancer() ? 'living persona' : 'no deck?!')}) from ${actor.bodyRoom.name}.`);
113
113
  const hotWarning = mode === 'hot'
@@ -267,7 +267,7 @@ 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
- actor.performAction('jacks out', 'stirring awake');
270
+ actor.performAction('jacks out', 'stirring awake', { quiet: scene.isHumanControlled?.(actor) ?? false });
271
271
  scene.addWorldEvent(`${actor.name} jacked out of the Matrix.`);
272
272
  scene.updateStatus();
273
273
  return lines;
@@ -449,7 +449,7 @@ export function fileReach(actor, room) {
449
449
  */
450
450
  export function planeTintItemName(item, viewerPlane) {
451
451
  if (item.plane === 'matrix') {
452
- return `{cyan-fg}${item.name} [data]{/cyan-fg}`;
452
+ return `{lightblue-fg}${item.name} [data]{/lightblue-fg}`;
453
453
  }
454
454
  if (item.plane === 'astral') {
455
455
  return `{magenta-fg}${item.name} [astral]{/magenta-fg}`;
@@ -24,6 +24,7 @@ import { join, dirname } from 'node:path';
24
24
  import { Logger } from './logger.js';
25
25
  import { hint } from './hints.js';
26
26
  import { CALL_ICON, CALL_COLOR, END_CALL_SENTINEL } from './comm-style.js';
27
+ import { FEED_COLOR } from './matrix-style.js';
27
28
  import { ENGINE_VERSION } from '../engine-version.js';
28
29
  import { authToken, fetchSave } from './cloud-saves.js';
29
30
  import { saveSlug, restorePlayer, writeSaveAtomic } from './persistence.js';
@@ -1143,7 +1144,7 @@ export class SharedRunSession {
1143
1144
  break;
1144
1145
  }
1145
1146
  for (const line of ev.text.split('\n'))
1146
- g.logBox?.pushLine(`{cyan-fg}${line}{/cyan-fg}`);
1147
+ g.logBox?.pushLine(`{${FEED_COLOR}-fg}${line}{/${FEED_COLOR}-fg}`);
1147
1148
  Logger.getInstance().transcript(ev.text);
1148
1149
  g.followLog?.();
1149
1150
  if (ev.kind === 'death' && ev.data?.player === g.player.name) {
@@ -85,6 +85,10 @@ export function directConnectionBlocker(actor, room) {
85
85
  export function camerasLive(rooms, room) {
86
86
  if (!isWatched(room))
87
87
  return false;
88
+ // Looped by Control Device or bricked by a Data Spike (commands/
89
+ // disable.ts): the house sees nothing from this cluster either way.
90
+ if (room.camerasLooped || room.camerasBricked)
91
+ return false;
88
92
  if (room.hasNode)
89
93
  return !room.hostCracked;
90
94
  const hosts = Object.values(rooms).filter(r => r.hasNode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.182.0",
3
+ "version": "5.184.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.",