@maka/maka-cli 5.180.0 → 5.182.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 (22) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +17 -11
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js +14 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +7 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +6 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +6 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +8 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +10 -1
  9. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +69 -23
  10. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +6 -4
  11. package/bundle/typescript/src/commands/game/sideQuest/game.js +47 -39
  12. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +39 -4
  13. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +6 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +44 -17
  15. package/bundle/typescript/src/commands/game/sideQuest/utilities/alarmed-staff.js +3 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-turn.js +91 -23
  17. package/bundle/typescript/src/commands/game/sideQuest/utilities/host-combat.js +107 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-brain.js +84 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.js +84 -51
  20. package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +5 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +5 -0
  22. package/package.json +1 -1
@@ -297,37 +297,37 @@ export function runHostTurn(room, actor, scene) {
297
297
  return ambientPatrol(room, actor);
298
298
  }
299
299
  /**
300
- * ONE COMBAT TURN of the host's ice.
300
+ * THE START OF A HOST'S COMBAT TURN (p.247): "a host can launch one IC
301
+ * program per Combat Turn, at the beginning of each Combat Turn", up to
302
+ * its rating running, never two of a kind. Crashed programs leave the
303
+ * list first and come back the very next turn (p.355-356, "delayed,
304
+ * never destroyed") -- the relaunch takes the turn's one launch.
301
305
  *
302
- * THE TURN MAPPING IS AN ENGINE MAPPING, NOT A RULE, and is labelled so
303
- * deliberately. Canon paces IC in Combat Turns -- one program launched
304
- * per turn, at the beginning of it. sideQuest has no Matrix initiative
305
- * structure to hang that on, so ONE PLAYER ACTION TAKEN INSIDE AN
306
- * ALERTED HOST counts as one Combat Turn. That is the closest structural
307
- * analogue available; it is not something the book says, and it is not
308
- * dressed up as a citation.
306
+ * Launches only; nothing acts here. In play the encounter's newTurn
307
+ * hook calls this (utilities/host-combat.ts) and each program then acts
308
+ * in its own Action Phase (utilities/ic-brain.ts). Returns the actor a
309
+ * launch produced, so the caller can seat it in the fight.
309
310
  */
310
- export function runICTurn(room, actor, scene) {
311
- if (!hostIsHunting(room, actor))
312
- return empty();
313
- const out = empty();
311
+ export function hostLaunchStep(room, scene, out) {
314
312
  const host = room.host;
315
- // CRASHED IC LEAVES, AND COMES BACK (p.355-356): a program whose monitor
316
- // filled since the last turn is off the list now, and the host
317
- // relaunches it the very next turn -- "delayed, never destroyed".
313
+ let launched;
318
314
  if (host) {
319
- // Last turn's crashes come back FIRST (p.355-356: "reactivated the very
320
- // next turn"); what crashes this turn waits for the next call.
315
+ // Crashes first, then the relaunch: a program whose monitor filled
316
+ // during the last turn is off the list at the start of this one and
317
+ // comes straight back -- "reactivated the very next turn" -- taking
318
+ // the turn's one launch.
319
+ for (const kind of crashedIC(host)) {
320
+ crashIC(scene, host, kind);
321
+ out.lines.push(` {red-fg}${kind} shears apart -- crashed. The host is already recompiling it.{/red-fg}`);
322
+ }
321
323
  if (host.relaunchQueue.length > 0 && room.runningIC.length < room.hostRating) {
322
324
  const kind = host.relaunchQueue.shift();
323
325
  room.runningIC.push(kind);
324
326
  if (scene)
325
- spawnIC(scene, host, kind);
327
+ launched = spawnIC(scene, host, kind);
326
328
  out.lines.push(` {red-fg}The host relaunches ${kind}.{/red-fg}`);
327
- }
328
- for (const kind of crashedIC(host)) {
329
- crashIC(scene, host, kind);
330
- out.lines.push(` {red-fg}${kind} shears apart -- crashed. The host is already recompiling it.{/red-fg}`);
329
+ // ONE launch per turn (p.247): the relaunch was it.
330
+ return launched;
331
331
  }
332
332
  }
333
333
  if (room.runningIC.length < room.hostRating) {
@@ -335,26 +335,49 @@ export function runICTurn(room, actor, scene) {
335
335
  if (next) {
336
336
  room.runningIC.push(next.name);
337
337
  if (host && scene)
338
- spawnIC(scene, host, next.name);
338
+ launched = spawnIC(scene, host, next.name) ?? launched;
339
339
  out.lines.push(next.kind === 'patrol'
340
340
  ? ` {red-fg}${next.name} is already walking the host -- it never had to be launched.{/red-fg}`
341
341
  : ` {red-fg}The host launches ${next.name}.{/red-fg}`);
342
342
  }
343
343
  }
344
- // Every running program acts. An IC attack is a Complex Action
345
- // (p.247), which is one program's whole turn.
344
+ return launched;
345
+ }
346
+ /**
347
+ * ONE PROGRAM'S ACTION -- an IC attack is a Complex Action (p.247), which
348
+ * is one program's whole Action Phase. `ice` is the program's own actor
349
+ * when the scene has one: it is what a failed attack damages.
350
+ */
351
+ export function icActs(room, actor, type, out, ice) {
352
+ if (type.kind === 'patrol')
353
+ patrolActs(room, actor, out);
354
+ else if (type.kind === 'probe')
355
+ probeActs(room, actor, type, out, ice);
356
+ else if (type.kind === 'damage')
357
+ damageActs(room, actor, type, out, ice);
358
+ else
359
+ reducerOrConditionalActs(room, actor, type, out, ice);
360
+ }
361
+ /**
362
+ * ONE WHOLE COMBAT TURN of the host's ice, as a single call: the launch
363
+ * step, then every running program acts once. This is the shape the
364
+ * tests drive and the shape the engine used to run after EVERY player
365
+ * command ("one action inside an alerted host = one Combat Turn" -- an
366
+ * engine mapping that was never a rule, and that had four programs
367
+ * hitting a persona for typing "look"). In play the Combat Turn owns the
368
+ * pacing now (utilities/host-combat.ts): the launch at newTurn, each
369
+ * program in its own phase.
370
+ */
371
+ export function runICTurn(room, actor, scene) {
372
+ if (!hostIsHunting(room, actor))
373
+ return empty();
374
+ const out = empty();
375
+ hostLaunchStep(room, scene, out);
346
376
  for (const name of room.runningIC) {
347
377
  const type = IC_TYPES.find(t => t.name === name);
348
378
  if (!type)
349
379
  continue;
350
- if (type.kind === 'patrol')
351
- patrolActs(room, actor, out);
352
- else if (type.kind === 'probe')
353
- probeActs(room, actor, out);
354
- else if (type.kind === 'damage')
355
- damageActs(room, actor, type, out);
356
- else
357
- reducerOrConditionalActs(room, actor, type, out);
380
+ icActs(room, actor, type, out, room.host?.iceActors.get(name));
358
381
  }
359
382
  return out;
360
383
  }
@@ -363,13 +386,29 @@ export function runICTurn(room, actor, scene) {
363
386
  * v. Intuition + Firewall (p.248). Returns the net hits; 0 or less is a
364
387
  * miss. Shared so the four reducers, Track and Scramble cannot drift
365
388
  * from Killer's version of the same roll.
389
+ *
390
+ * A FAILED ATTACK ACTION HURTS THE ATTACKER (p.247 "as with all Attack
391
+ * actions, a failed attack causes damage to the IC"; the amount is the
392
+ * general rule, Data Trails p.181: one box of Matrix damage per net hit
393
+ * the defender got, unresisted). Patrol makes no Attack action and is
394
+ * never here (hurtByFailure false). Applied to the program's own actor,
395
+ * which is its condition monitor (ic-actors.ts); a program crashed this
396
+ * way leaves at the next launch step like any other.
366
397
  */
367
- function icAttack(room, actor, type, out) {
398
+ function icAttack(room, actor, type, out, ice) {
368
399
  const { pool, limit } = icAttackPool(room);
369
400
  const attack = rollPool(pool, limit);
370
401
  const defence = rollPool(icDefencePool(actor));
371
402
  const net = attack.hits - defence.hits;
372
403
  out.meta.push(`${type.name} -- Host Rating x2 [Attack ${limit}]: ${formatRoll(attack)} v. Intuition + Firewall: ${formatRoll(defence)} (net ${net})`);
404
+ if (net < 0 && type.hurtByFailure && ice && !ice.isDown()) {
405
+ const rebound = -net;
406
+ ice.takeDamage(rebound);
407
+ out.meta.push(`${type.name} -- failed Attack action: ${rebound} box${rebound === 1 ? '' : 'es'} back on the program, unresisted (p.247)`);
408
+ out.lines.push(ice.isDown()
409
+ ? ` {red-fg}${ice.name}'s own attack code rejects and tears it apart -- ${rebound} box${rebound === 1 ? '' : 'es'}, and its monitor fills.{/red-fg}`
410
+ : ` ${ice.name} eats its own rejected code -- ${rebound} box${rebound === 1 ? '' : 'es'} (${ice.conditionSummary()}).`);
411
+ }
373
412
  return net;
374
413
  }
375
414
  /**
@@ -389,8 +428,8 @@ function icAttack(room, actor, type, out) {
389
428
  * against the next IC, and Jammer really does lower the ceiling on your
390
429
  * own Brute Force.
391
430
  */
392
- function reducerOrConditionalActs(room, actor, type, out) {
393
- const net = icAttack(room, actor, type, out);
431
+ function reducerOrConditionalActs(room, actor, type, out, ice) {
432
+ const net = icAttack(room, actor, type, out, ice);
394
433
  if (net <= 0) {
395
434
  out.lines.push(` ${type.name} probes at your icon and finds no purchase.`);
396
435
  return;
@@ -467,17 +506,13 @@ function applyMatrixDamage(actor, dealt, type, out) {
467
506
  * Probe IC spends its turns building. That coupling is canon's own, and
468
507
  * it is why Probe is worth a slot on a host that also fields a Killer.
469
508
  *
470
- * A FAILED IC ATTACK SHOULD DAMAGE THE IC (p.247) AND DOES NOT YET,
471
- * stated here rather than left for the next reader to discover. Modelling
472
- * it needs a condition monitor per program AND a way for the player to
473
- * attack ice, and neither exists: there is no cybercombat path against
474
- * IC in this engine at all. Half of it -- ice that hurts itself while
475
- * the player still cannot swing back -- would be worse than neither
476
- * half. It lands with the verb.
509
+ * A FAILED IC ATTACK DAMAGES THE IC (p.247) -- see icAttack, where it
510
+ * lands for every attacking program at once. The player's side of it,
511
+ * cybercombat against ice, is `attack <program>` inside the host.
477
512
  */
478
- function damageActs(room, actor, type, out) {
513
+ function damageActs(room, actor, type, out, ice) {
479
514
  const { limit } = icAttackPool(room);
480
- const net = icAttack(room, actor, type, out);
515
+ const net = icAttack(room, actor, type, out, ice);
481
516
  if (net <= 0) {
482
517
  out.lines.push(` ${type.name} lunges at your icon and closes on nothing.`);
483
518
  return;
@@ -608,12 +643,10 @@ function patrolActs(room, actor, out) {
608
643
  * read in Phase 4. The engine previously modelled the host's side as the
609
644
  * single boolean `hostAlert`, which cannot answer "how many".
610
645
  */
611
- function probeActs(room, actor, out) {
612
- const { pool, limit } = icAttackPool(room);
613
- const attack = rollPool(pool, limit);
614
- const defence = rollPool(icDefencePool(actor));
615
- const net = attack.hits - defence.hits;
616
- out.meta.push(`Probe IC -- Host Rating x2 [Attack ${limit}]: ${formatRoll(attack)} v. Intuition + Firewall: ${formatRoll(defence)} (net ${net})`);
646
+ function probeActs(room, actor, type, out, ice) {
647
+ // The same opposed roll as every attacking program (icAttack), so a
648
+ // failed probe rebounds on the program like any failed Attack action.
649
+ const net = icAttack(room, actor, type, out, ice);
617
650
  const held = room.hostMarksOn.get(actor.name) ?? 0;
618
651
  if (net <= 0) {
619
652
  out.lines.push(` Probe IC tests your edges and finds nothing to hold onto.`);
@@ -1,6 +1,7 @@
1
1
  import { Category } from '../types/shared/item-enum.js';
2
2
  import { inMeleeReach, isInCover, coverAvailableFor, actorDistanceMeters } from './spots.js';
3
3
  import { Logger } from './logger.js';
4
+ import { runIcActionPhase } from './ic-brain.js';
4
5
  /**
5
6
  * AN NPC'S ACTION PHASE, PLAYED BY RULE (SR5 p.163-167).
6
7
  *
@@ -25,6 +26,10 @@ import { Logger } from './logger.js';
25
26
  * Nothing here rolls a die of its own: the verbs do.
26
27
  */
27
28
  export async function runNpcActionPhase(enc, npc) {
29
+ // A HOST'S PROGRAM HAS ITS OWN BRAIN (utilities/ic-brain.ts): no
30
+ // weapon to draw, no ground to close -- one test, by type.
31
+ if (npc.icKind !== undefined)
32
+ return runIcActionPhase(enc, npc);
28
33
  const logger = Logger.getInstance();
29
34
  if (npc.isIncapacitated() || npc.surrendered)
30
35
  return;
@@ -209,6 +209,11 @@ export const DUMPSHOCK_DV = 6;
209
209
  export function leaveMatrix(scene, actor, opts) {
210
210
  const lines = [];
211
211
  const body = actor.bodyRoom;
212
+ // OUT OF THE MATRIX IS OUT OF THE FIGHT (utilities/host-combat.ts): a
213
+ // persona leaving its host arena -- jack out, link-lock broken, deck
214
+ // bricked, Scramble, convergence -- is no longer a participant. Before
215
+ // endPersona clears the position the arena is read off.
216
+ scene.encounterFor?.(actor)?.leave(actor);
212
217
  if (opts?.forced) {
213
218
  lines.push(...applyDumpshock(actor, opts.reason ?? `The connection is severed from the other side.`));
214
219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.180.0",
3
+ "version": "5.182.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.",