@maka/maka-cli 5.138.0 → 5.139.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.138.0",
3
+ "version": "5.139.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.",
@@ -0,0 +1,88 @@
1
+ import { Command } from './command.js';
2
+ import { hint } from '../utilities/hints.js';
3
+ import { fuzzyPickName } from '../utilities/fuzzy-match.js';
4
+ import { NUDGES_TO_SKIP } from '../utilities/combat-turn.js';
5
+ /**
6
+ * NUDGE -- the table's answer to a runner who is holding the Action
7
+ * Phase and has gone quiet (project owner, 2026-09-07: "three nudges
8
+ * skips the player being nudged").
9
+ *
10
+ * Since the Combat Turn (utilities/combat-turn.ts) a human's phase
11
+ * waits for "end turn", and since the autopilot went (v5.136.0)
12
+ * nothing plays a phase for anyone. On a shared run that leaves one
13
+ * hole: a human who walks away mid-phase stalls the whole table. There
14
+ * is deliberately no clock -- a timer skips the player who was reading
15
+ * the rules -- so the escape is the table's: every nudge tells the
16
+ * nudged runner, in their own log, that people are waiting; the third
17
+ * nudge on one phase ends it for them, whatever they had left forfeit,
18
+ * exactly as "end turn" with nothing spent (SR5 p.158-159; the book
19
+ * gives no refund). The count is the phase's (CombatEncounter.noteNudge)
20
+ * and starts over the next time they act.
21
+ *
22
+ * Off the phase, or out of a fight, a nudge is a tap on the shoulder
23
+ * only the nudged runner sees. Human-only: an NPC acts by rule on its
24
+ * own phase and must never be able to skip a human's.
25
+ */
26
+ export class NudgeCommand extends Command {
27
+ static verb = 'nudge';
28
+ static humanOnly = true;
29
+ static description = 'Prod another runner on a shared run: "nudge <name>". If they are holding the Action Phase, that tells them the table is waiting -- the third nudge on one phase ends it for them, whatever is unspent forfeit, the same as "end turn" with nothing spent. Off the phase, or out of a fight, it is a tap on the shoulder only they see. "initiative" names who is holding the phase.';
30
+ /** The scene's other humans -- the only legal targets. */
31
+ others() {
32
+ return this.scene.getPlayers().filter(p => p !== this.actor);
33
+ }
34
+ async execute(args = []) {
35
+ if (!this.scene.isHumanControlled(this.actor))
36
+ return '';
37
+ const me = this.actor;
38
+ const query = (args ?? []).join(' ').trim();
39
+ const others = this.others();
40
+ if (others.length === 0) {
41
+ return `You're running this one alone -- there's nobody to nudge.${hint(` (Seat a table with "crew hire"; "nudge" is for a shared run.)`)}`;
42
+ }
43
+ if (!query) {
44
+ return `Nudge who? ${others.map(p => p.name).join(', ')}. Usage: "nudge <runner>".`;
45
+ }
46
+ if (query.toLowerCase() === me.name.toLowerCase()) {
47
+ return `Nudging yourself moves nothing. If it's your phase, act -- or "end turn".`;
48
+ }
49
+ const picked = fuzzyPickName(query, others.map(p => p.name));
50
+ const target = picked ? others.find(p => p.name === picked) : undefined;
51
+ if (!target) {
52
+ return `No runner on this job answers to "${query}". Out here with you: ${others.map(p => p.name).join(', ')}. (An NPC acts by rule on its own phase -- nothing to nudge there.)`;
53
+ }
54
+ if (this.scene.getKnockout?.(target.name)) {
55
+ return `${target.name} is out cold -- no nudge reaches them.`;
56
+ }
57
+ const enc = this.scene.encounterFor?.(target);
58
+ const count = enc?.noteNudge(target);
59
+ if (count === undefined) {
60
+ // A TAP ON THE SHOULDER, seen by them alone -- the same per-human
61
+ // scope beginPhase uses for the cyan "your Action Phase" line.
62
+ this.logger.log(`{yellow-fg}${me.name} nudges you.{/yellow-fg}`, { actor: target.name });
63
+ this.logger.write(`${me.name} nudged ${target.name} (no phase to count against).`);
64
+ const holder = enc?.phaseActor;
65
+ const aside = enc && holder && holder !== target
66
+ ? ` It isn't ${target.name}'s Action Phase -- ${holder === me ? `it's yours` : `${holder.name} is holding it`}.`
67
+ : '';
68
+ return `You nudge ${target.name}.${aside}`;
69
+ }
70
+ const room = target.currentLocation;
71
+ if (count >= NUDGES_TO_SKIP) {
72
+ // THE SKIP. Logged, not returned: the phases that follow announce
73
+ // themselves while this command is still running (end-turn.ts has
74
+ // the same note), and a return value would land under them.
75
+ this.logger.log(`{yellow-fg}${target.name}'s Action Phase is skipped -- nudged ${NUDGES_TO_SKIP} times; whatever they had left is forfeit.{/yellow-fg}`, { room: room.name });
76
+ this.logger.write(`${me.name}'s nudge #${count} skips ${target.name}'s Action Phase.`);
77
+ await enc.endPhase(target);
78
+ return '';
79
+ }
80
+ this.logger.log(`{yellow-fg}${me.name} nudges you -- the table is waiting on your Action Phase (${count}/${NUDGES_TO_SKIP}; ${NUDGES_TO_SKIP} and it is skipped). "end turn" when you're done.{/yellow-fg}`, { actor: target.name });
81
+ this.logger.log(`${me.name} nudges ${target.name} (${count}/${NUDGES_TO_SKIP}).`, { room: room.name });
82
+ this.logger.write(`${me.name} nudged ${target.name} (${count}/${NUDGES_TO_SKIP}).`);
83
+ // The room line above already reached the nudger when they share the
84
+ // room; from elsewhere they get the summary instead.
85
+ return me.currentLocation === room ? '' : `You nudge ${target.name} (${count}/${NUDGES_TO_SKIP}).`;
86
+ }
87
+ }
88
+ //# sourceMappingURL=nudge.js.map
@@ -384,5 +384,9 @@
384
384
  // arrival. During a live encounter the ephemeral governor, open-floor
385
385
  // re-seating and doorway yielding are frozen: nobody moves off the
386
386
  // player's keystrokes. bluff/persuade wins clear the hostile flag.
387
- export const ENGINE_VERSION = '1.42.0';
387
+ // 1.43.0 (2026-09-07): NUDGE. New verb in shared scenes: "nudge <runner>"
388
+ // tells a human holding the Action Phase that the table is waiting; the
389
+ // third nudge on one phase ends it for them (forfeit, as "end turn" with
390
+ // nothing spent). Off the phase it is a private tap on the shoulder.
391
+ export const ENGINE_VERSION = '1.43.0';
388
392
  //# sourceMappingURL=engine-version.js.map
@@ -80,6 +80,7 @@ import { InitiativeCommand } from './commands/initiative.js';
80
80
  import { AimCommand } from './commands/aim.js';
81
81
  import { DefendCommand } from './commands/defend.js';
82
82
  import { DelayCommand } from './commands/delay.js';
83
+ import { NudgeCommand } from './commands/nudge.js';
83
84
  import { SpellsCommand } from './commands/spells.js';
84
85
  import { InventoryCommand } from './commands/inv.js';
85
86
  import { EquipmentCommand } from './commands/equipment.js';
@@ -2553,6 +2554,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2553
2554
  CommandFactory.registerCommand('aim', AimCommand);
2554
2555
  CommandFactory.registerCommand('defend', DefendCommand);
2555
2556
  CommandFactory.registerCommand('delay', DelayCommand);
2557
+ CommandFactory.registerCommand('nudge', NudgeCommand);
2556
2558
  // 'train' used to alias advance; it belongs to crew instruction now
2557
2559
  // (commands/crew.ts) -- self-training is "advance".
2558
2560
  CommandFactory.registerCommand('advance', AdvanceCommand);
@@ -6040,7 +6042,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
6040
6042
  static HELP_CATEGORIES = [
6041
6043
  { title: 'moving', entries: [['look'], ['go'], ['move', 'walk', 'approach'], ['run'], ['sprint'], ['climb', 'mantle', 'scale', 'clamber'], ['descend'], ['sit', 'kneel'], ['lie', 'prone'], ['stand'], ['follow'], ['unfollow'], ['map', 'exits'], ['search'], ['sneak']] },
6042
6044
  { title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['loot'], ['drop'], ['give'], ['store'], ['open'], ['close'], ['put'], ['equip'], ['unequip'], ['fit'], ['unfit'], ['brandish', 'draw'], ['holster'], ['reload'], ['use'], ['read']] },
6043
- { title: 'combat', entries: [['attack'], ['kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['cover'], ['aim'], ['defend'], ['delay'], ['end-turn', 'endturn', 'done', 'pass'], ['initiative', 'tracker', 'turn'], ['stance'], ['surrender'], ['edge'], ['rest'], ['heal', 'firstaid', 'bandage', 'patch']] },
6045
+ { title: 'combat', entries: [['attack'], ['kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['cover'], ['aim'], ['defend'], ['delay'], ['end-turn', 'endturn', 'done', 'pass'], ['initiative', 'tracker', 'turn'], ['nudge'], ['stance'], ['surrender'], ['edge'], ['rest'], ['heal', 'firstaid', 'bandage', 'patch']] },
6044
6046
  // The party layer: everyone who walks (or flies, or manifests) at
6045
6047
  // your side answers to these.
6046
6048
  // "players" folded back under crew after playtesting ("crew
@@ -1,6 +1,71 @@
1
1
  import { rollInitiativeScore, formatInitiative, INITIATIVE_PASS_DROP, rollPool, formatRoll } from './dice.js';
2
2
  import { ActionBudget } from './action-budget.js';
3
3
  import { runNpcActionPhase } from './npc-combat-brain.js';
4
+ /**
5
+ * THE COMBAT TURN (SR5 p.158-161, RAG-checked 2026-09-06).
6
+ *
7
+ * This engine used to resolve a fight as EXCHANGES: one typed "attack"
8
+ * rolled a two-actor initiative, both sides struck, and a >10 gap bought
9
+ * one extra strike. That was the closest thing it had to the book's
10
+ * turn, and combat-exchange.ts said so. What it could never express was
11
+ * the thing the book is built on -- a sequence of Action Phases in
12
+ * which every participant spends a budget of actions, in an order the
13
+ * dice decided, until nobody has Initiative Score left.
14
+ *
15
+ * The sequence, as the book gives it:
16
+ * 1. ROLL INITIATIVE: Initiative attribute + Initiative Dice, summed
17
+ * (p.159). Highest acts first; ties break Edge, Reaction,
18
+ * Intuition, coin (p.159 "ERIC").
19
+ * 2-4. INITIATIVE PASS: each participant, highest score first, takes
20
+ * an ACTION PHASE -- two Simple Actions or one Complex, plus one
21
+ * Free (p.158). When everyone has acted, EVERY score drops by 10
22
+ * (p.159), and anyone still above zero goes again.
23
+ * 5. When nobody is above zero the Combat Turn ends and a new one
24
+ * begins at step 1 (p.159).
25
+ *
26
+ * Also carried here because the score lives here:
27
+ * - INTERRUPT ACTIONS (p.167-168) spend Initiative Score: Full
28
+ * Defense -10 for the Combat Turn, Block/Dodge/Parry -5 each for
29
+ * one test. Only affordable "if he has enough Initiative Score
30
+ * left" -- a score already at or below zero buys nothing (the p.191
31
+ * worked example).
32
+ * - WOUND MODIFIERS CHANGE THE SCORE AS THEY LAND (p.160 Changing
33
+ * Initiative): the difference is applied immediately, and it can
34
+ * reorder the pass. It never grants an extra action.
35
+ * - ENTERING LATE (p.160): roll as normal, then subtract 10 for every
36
+ * pass already gone.
37
+ * - DELAYING (p.161): an actor may hold their Action Phase and take
38
+ * it later in the same pass. Modelled as "act after everyone else
39
+ * this pass, keeping your score"; the pass-end -10 still applies.
40
+ * - SURPRISE (p.192): a fight opened on the unaware makes every other
41
+ * participant roll Reaction + Intuition (3); failure is -10 and no
42
+ * defense against, nor action against, the surprising side until
43
+ * their next Action Phase. +3 for anyone already alert.
44
+ * - MOVEMENT is a WHOLE-TURN total (p.161-162, Player.movedMetersThisTurn)
45
+ * and comes back at step 5, not per pass.
46
+ *
47
+ * SCOPE: one encounter per ROOM, on the physical planes (meat and a
48
+ * jumped-in drone). The Matrix keeps its own initiative structure
49
+ * (utilities/ic.ts maps IC to Combat Turns per player action) and
50
+ * astral combat still rides the exchange engine -- both are noted as
51
+ * gaps rather than folded in half-right.
52
+ *
53
+ * WHO DRIVES IT: a human's Action Phase waits for them to type actions
54
+ * and then "end turn" (commands/end-turn.ts). An NPC's phase is played
55
+ * by a deterministic brain (utilities/npc-combat-brain.ts) -- the LLM
56
+ * reflect chain is suspended for participants while the encounter runs,
57
+ * because a model answering "what next?" in a second or two cannot be
58
+ * sequenced against a dice-ordered pass, and was the reason NPCs used
59
+ * to shoot from between the beats of the player's own exchange.
60
+ */
61
+ /**
62
+ * Nudges on one Action Phase before the table ends it for the holder
63
+ * (commands/nudge.ts, owner ruling 2026-09-07). A ruling, not canon --
64
+ * the book has no idle player -- kept as a count rather than a clock so
65
+ * that a runner reading the rules is never skipped by a timer, only by
66
+ * three people saying so.
67
+ */
68
+ export const NUDGES_TO_SKIP = 3;
4
69
  export class CombatEncounter {
5
70
  scene;
6
71
  logger;
@@ -317,6 +382,7 @@ export class CombatEncounter {
317
382
  this.budget = new ActionBudget();
318
383
  p.surprised = false;
319
384
  p.delayed = false;
385
+ p.nudges = 0;
320
386
  p.actor.clearAim();
321
387
  // Recoil resets the moment an Action Phase passes without firing --
322
388
  // tracked by the phase, settled by attack.ts (p.175-176).
@@ -342,6 +408,19 @@ export class CombatEncounter {
342
408
  const walkLeft = Math.max(0, actor.walkRateMeters - actor.movedMetersThisTurn);
343
409
  return `${left} m of movement left this turn (${walkLeft} m at a walk)`;
344
410
  }
411
+ /**
412
+ * Another human nudged the phase holder (commands/nudge.ts): the
413
+ * running count for this phase, or undefined when `target` is not
414
+ * the one holding it. Bookkeeping only -- the verb owns the wording
415
+ * and calls endPhase() itself at NUDGES_TO_SKIP.
416
+ */
417
+ noteNudge(target) {
418
+ const p = this.participantOf(target);
419
+ if (!p || this.ended || this.phaseActor !== target)
420
+ return undefined;
421
+ p.nudges = (p.nudges ?? 0) + 1;
422
+ return p.nudges;
423
+ }
345
424
  /** The human typed "end turn" (or lost the phase -- fled, dropped). */
346
425
  async endPhase(actor) {
347
426
  const p = this.participantOf(actor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.138.0",
3
+ "version": "5.139.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.",