@maka/maka-cli 5.233.0 → 5.235.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.233.0",
3
+ "version": "5.235.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.",
@@ -1455,5 +1455,55 @@
1455
1455
  // the roll shows what the network lends it. Those are two different
1456
1456
  // facts on purpose, and there is a test pinning them apart so nobody
1457
1457
  // "fixes" one to match the other.
1458
- export const ENGINE_VERSION = '1.84.0';
1458
+ // 1.85.0 (2026-09-16): SPAMMING `look` COSTS SOMETHING NOW (p.49)
1459
+ // Asked directly, and it was the right question: "what is the penalty
1460
+ // of just spamming 'look' to eventually get the details?"
1461
+ // THE ANSWER WAS NONE. `look` on the grid is billed as the Complex
1462
+ // Action it is (p.241) -- but billAction only charges inside an
1463
+ // encounter, so out of combat a sweep cost nothing at all. A decker
1464
+ // could stand in the dark typing `look` until the dice came up, which
1465
+ // means every hidden icon in the game was on a TIMER rather than
1466
+ // behind a test, and the whole of 1.80-1.84 was theatre for anyone
1467
+ // willing to press a key four times. My own find-rate measurements
1468
+ // said "found within 1-2 looks" and I read that as the mechanic
1469
+ // working rather than as the mechanic being free.
1470
+ // - TRYING AGAIN (p.49): "each attempt after the first takes a
1471
+ // cumulative -2 dice pool penalty." Counted per room on
1472
+ // Player.sweepAttempts, and only FAILED sweeps count -- pinning
1473
+ // what you contested is not a retry. A 7-dice sweeper gets 7, 5, 3,
1474
+ // 1, then nothing.
1475
+ // - AND IT REACHES ZERO. The base pool floors at 1 so wounds and
1476
+ // noise never lock you out, but the retry penalty is allowed
1477
+ // through that floor, because that is the part that has to stop the
1478
+ // grind: a pool of nothing cannot be rolled. The line says so
1479
+ // rather than printing a mystery 0d6.
1480
+ // - THE BREAK is the one piece p.49 hands to the table ("left to
1481
+ // gamemaster discretion"), so it was asked rather than guessed.
1482
+ // Ruling: JACKING OUT, and nothing less -- not moving node, not
1483
+ // spending a turn on something else. So clearing a stack of failed
1484
+ // sweeps costs your position, your part-made marks and a fresh
1485
+ // entry.
1486
+ // - NO OVERWATCH COST, deliberately. OS rises on Attack and Sleaze
1487
+ // actions (p.231-232) and Matrix Perception is neither. Charging
1488
+ // one would be inventing a rule to fix a balance problem canon
1489
+ // already fixes.
1490
+ // Nothing here is new to this engine: clue-reading has used p.49 and
1491
+ // the same -2 since 1.40. It had simply never been pointed at the one
1492
+ // test a player can repeat by holding down a key.
1493
+ // 1.85.1 (2026-09-16): THE GRID WAS ANNOUNCED BUT NEVER CHARGED
1494
+ // Asked while reading a sweep transcript: "is the grid type being
1495
+ // applied?" The banner says it plainly --
1496
+ // "Riding the public grid (-2 on everything you do, p.233)"
1497
+ // -- and the sweep was not paying it. Player.matrixActionPenalty is
1498
+ // ONLY the running-silent -2; the grid modifier is a separate helper
1499
+ // (grids.ts gridActionModifier) that every other Matrix verb calls
1500
+ // and spotHiddenIcons never did. So the -2 was printed on screen and
1501
+ // then left out of the one roll the player was watching, which is the
1502
+ // same class of defect as the Firewall label a version ago: the text
1503
+ // made a claim the dice did not honour.
1504
+ // The sweep now takes it, and the mechanics line shows '[public grid
1505
+ // -2]' where it applied. Inside a host it is correctly nothing -- a
1506
+ // host's interior is not on a grid -- and it stacks with p.49's retry
1507
+ // penalty, so a public-grid decker gets 5, 3, 1, none.
1508
+ export const ENGINE_VERSION = '1.85.1';
1459
1509
  //# sourceMappingURL=engine-version.js.map
@@ -717,6 +717,37 @@ export class Player extends AbstractPlayer {
717
717
  * (-2 per retry). In memory only: a save and reload is the "break"
718
718
  * the rule says clears it. See utilities/clue-reading.ts. */
719
719
  readAttempts = new Map();
720
+ /**
721
+ * FAILED MATRIX PERCEPTION SWEEPS, by room name -- p.49's Trying Again
722
+ * pointed at the one test a player can repeat for free.
723
+ *
724
+ * Asked directly (2026-09-16): "what is the penalty of just spamming
725
+ * 'look' to eventually get the details?" The answer was NONE. `look`
726
+ * is billed as the Complex Action it is (p.241), but billAction only
727
+ * charges inside an encounter, so out of combat a sweep cost nothing
728
+ * at all and a decker could stand in the dark typing `look` until the
729
+ * dice came up. Every hidden icon in the game was on a timer rather
730
+ * than behind a test.
731
+ *
732
+ * p.49: "you can try again, but each attempt after the first takes a
733
+ * cumulative -2 dice pool penalty ... a sufficient break lets you
734
+ * begin again with no penalty." Not invented here and not new to this
735
+ * engine either -- clue-reading has used the same rule and the same
736
+ * citation since 1.40. A sweeper at 7 dice gets 7, 5, 3, 1: four looks
737
+ * and brute force is spent, which is the point.
738
+ *
739
+ * NO OVERWATCH COST, deliberately. OS rises on Attack and Sleaze
740
+ * actions (p.231-232) and Matrix Perception is neither, so charging
741
+ * one would be inventing a rule to solve a balance problem that canon
742
+ * already solves.
743
+ *
744
+ * WHAT COUNTS AS THE BREAK is the one part p.49 hands to the table
745
+ * ("left to gamemaster discretion"), so it was asked and ruled on
746
+ * rather than guessed: JACKING OUT, and nothing less (user ruling
747
+ * 2026-09-16). Not moving node, not spending a turn elsewhere. See
748
+ * planes.ts leaveMatrix, which is the only thing that clears this.
749
+ */
750
+ sweepAttempts = new Map();
720
751
  /** True for the one Electronics item that is a trode net (p.439-440). */
721
752
  static isTrodeNet(item) {
722
753
  // The item's OWN row, not Item.effects: that getter folds fitted
@@ -222,6 +222,19 @@ export function leaveMatrix(scene, actor, opts) {
222
222
  // bricked, Scramble, convergence -- is no longer a participant. Before
223
223
  // endPersona clears the position the arena is read off.
224
224
  scene.encounterFor?.(actor)?.leave(actor);
225
+ // THE BREAK p.49 DESCRIBES, and the book hands this one to the table:
226
+ // "if the character takes a sufficient break from the task -- the
227
+ // length of which is left to gamemaster discretion." Asked directly
228
+ // (user ruling 2026-09-16, "what is a break?"), and the answer was the
229
+ // strictest of the four on offer: NOTHING SHORT OF JACKING OUT.
230
+ //
231
+ // Not moving to another node, not spending a turn on something else.
232
+ // So a stack of failed sweeps costs you your position on the grid,
233
+ // whatever marks you were part-way through, and a fresh entry -- which
234
+ // makes a dark room genuinely expensive rather than a typing exercise.
235
+ // Optional-chained because leaveMatrix is reached from a dozen places
236
+ // with stub actors that are not full Players.
237
+ actor.sweepAttempts?.clear();
225
238
  if (opts?.forced) {
226
239
  lines.push(...applyDumpshock(actor, opts.reason ?? `The connection is severed from the other side.`));
227
240
  }
@@ -1,6 +1,7 @@
1
1
  import { rollPool, formatRoll } from './dice.js';
2
2
  import { deviceHidePool, wanHidePool } from './matrix-intrusion.js';
3
3
  import { isWatched, cameraMasterHost } from './surveillance.js';
4
+ import { gridActionModifier, gridNote } from './grids.js';
4
5
  function hiddenDevice(device, master) {
5
6
  // p.235-236: the hider rolls LOGIC + SLEAZE. Not Firewall -- that
6
7
  // resists an intrusion (p.238/p.240) and has nothing to do with being
@@ -116,8 +117,30 @@ export function spotHiddenIcons(scene, actor, room) {
116
117
  // sweeper's own silence, wounds and sustaining the way every other
117
118
  // Matrix action in this engine does.
118
119
  const computer = actor.skillRating('computer');
119
- const pool = Math.max(1, actor.intuition + (computer > 0 ? computer : -1)
120
- + actor.matrixActionPenalty + actor.woundModifier - actor.sustainingPenalty);
120
+ // TRYING AGAIN (p.49): "each attempt after the first takes a
121
+ // cumulative -2 dice pool penalty." Without it, spamming `look` was
122
+ // free outside a fight -- billAction charges a Complex Action only
123
+ // inside an encounter -- so every hidden icon was on a timer instead
124
+ // of behind a test. Counted per room and cleared by a break
125
+ // (Player.sweepAttempts).
126
+ const retries = actor.sweepAttempts.get(room.name) ?? 0;
127
+ // The base floors at 1 -- wounds and noise grind you down but never
128
+ // out. The RETRY penalty is allowed through that floor to zero,
129
+ // because that is what stops the grind: a pool reduced to nothing
130
+ // cannot be rolled, so after enough failures you are not looking
131
+ // harder, you are done looking until you take the break p.49 names.
132
+ // THE GRID YOU ARE RIDING (p.233), which the banner has been promising
133
+ // and the dice were not paying: "Riding the public grid (-2 on
134
+ // everything you do)". matrixActionPenalty is ONLY the running-silent
135
+ // -2 -- the grid modifier is a separate helper that every other Matrix
136
+ // verb calls and this sweep never did, so the public grid's -2 was
137
+ // announced on screen and then quietly left out of the one roll the
138
+ // player was watching. Inside a host it is correctly nothing: a host's
139
+ // interior is not on a grid.
140
+ const grid = gridActionModifier(actor.currentGrid, !!actor.hostInside, actor.currentGrid);
141
+ const base = Math.max(1, actor.intuition + (computer > 0 ? computer : -1)
142
+ + actor.matrixActionPenalty + grid.total + actor.woundModifier - actor.sustainingPenalty);
143
+ const pool = Math.max(0, base - 2 * retries);
121
144
  const roll = rollPool(pool, actor.matrixAttribute('dataProcessing'), { gremlins: actor.deckGremlins });
122
145
  const found = [];
123
146
  let missed = 0;
@@ -139,7 +162,14 @@ export function spotHiddenIcons(scene, actor, room) {
139
162
  against.push({ roll: resist });
140
163
  }
141
164
  }
142
- return { found, missed, roll, against };
165
+ // p.49 counts FAILED attempts. A sweep that pinned everything it
166
+ // contested was not a failure, so it costs the next one nothing --
167
+ // and an icon you already found is not in `hidden` to be re-rolled.
168
+ if (missed > 0)
169
+ actor.sweepAttempts.set(room.name, retries + 1);
170
+ else
171
+ actor.sweepAttempts.delete(room.name);
172
+ return { found, missed, roll, retries, grid: gridNote(grid), against };
143
173
  }
144
174
  /**
145
175
  * The mechanics lines for a sweep.
@@ -153,8 +183,15 @@ export function spotHiddenIcons(scene, actor, room) {
153
183
  * Perception HIT buys).
154
184
  */
155
185
  export function spotMechanics(outcome) {
186
+ // Said out loud, because a pool that quietly shrinks every time you
187
+ // type the same word reads as a bug rather than as p.49.
188
+ const again = outcome.roll.pool === 0
189
+ ? ` -- NO DICE LEFT (p.49: -${outcome.retries * 2} for ${outcome.retries} failed attempts). Staring harder will not do it; break off and come back.`
190
+ : outcome.retries > 0
191
+ ? ` -- trying again, -${outcome.retries * 2} (p.49: -2 per failed attempt; a break clears it)`
192
+ : '';
156
193
  return [
157
- `Matrix Perception, silent icons (Computer + Intuition [Data Processing]): ${formatRoll(outcome.roll)}`,
194
+ `Matrix Perception, silent icons (Computer + Intuition [Data Processing]): ${formatRoll(outcome.roll)}${outcome.grid}${again}`,
158
195
  ...outcome.against.map(a => (a.name
159
196
  ? ` ${a.name} hides (${a.label}): ${formatRoll(a.roll)}`
160
197
  : ` something holds its cover: ${formatRoll(a.roll)}`)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.233.0",
3
+ "version": "5.235.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.",