@maka/maka-cli 5.68.0 → 5.69.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 (94) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/abandon.d.ts +30 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/abandon.js +54 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/abandon.js.map +1 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +5 -1
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +12 -4
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +28 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +105 -1
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/climb.d.ts +48 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/climb.js +107 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/climb.js.map +1 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js +7 -3
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +2 -2
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +11 -14
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +57 -26
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js +17 -4
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js.map +1 -1
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +12 -2
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +12 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +8 -6
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js.map +1 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/move.d.ts +11 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +107 -14
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.js +5 -4
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/overwatch.js.map +1 -1
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +33 -2
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/standby.d.ts +40 -0
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/standby.js +91 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/standby.js.map +1 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +10 -11
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js.map +1 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +4 -2
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +12 -5
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  48. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +41 -1
  49. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -1
  50. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +4 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
  52. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +2 -0
  53. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +91 -6
  54. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
  55. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +6 -0
  56. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  57. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +85 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/game.js +214 -3
  59. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +16 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +22 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  63. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +67 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +91 -4
  65. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  66. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +85 -1
  67. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +174 -19
  68. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  69. package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +90 -0
  70. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +114 -5
  71. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +1 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +2 -0
  74. package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +42 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +12 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +1 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +5 -1
  78. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  79. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +41 -1
  80. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +52 -4
  81. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -1
  82. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.d.ts +1 -1
  83. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +45 -7
  84. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js.map +1 -1
  85. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.d.ts +7 -0
  86. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js +24 -0
  87. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js.map +1 -1
  88. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.d.ts +46 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js +90 -5
  90. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-grid.js.map +1 -1
  91. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +70 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +168 -22
  93. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  94. package/package.json +1 -1
@@ -8,11 +8,51 @@
8
8
  export interface IDiceRoll {
9
9
  pool: number;
10
10
  rolls: number[];
11
+ /** Hits that COUNT -- after any limit has capped them. */
11
12
  hits: number;
13
+ /** Hits the dice actually showed, before the limit. Equal to `hits`
14
+ * on an unlimited test. */
15
+ rawHits: number;
16
+ /** The cap that applied, when one did. */
17
+ limit?: number;
18
+ /** Whether the cap actually BOUND -- i.e. threw hits away. A limit
19
+ * that never binds is indistinguishable from no limit, so this is
20
+ * what lets a reader (and a play-tester) tell them apart. */
21
+ limited: boolean;
22
+ /** A limit of zero or less reached rollPool. That is a CALLER BUG --
23
+ * no SR5 limit is ever zero -- so the limit is ignored rather than
24
+ * silently making the test unwinnable, and this says so out loud
25
+ * through formatRoll. */
26
+ limitIgnored: boolean;
12
27
  glitch: boolean;
13
28
  criticalGlitch: boolean;
14
29
  }
15
- export declare function rollPool(pool: number): IDiceRoll;
30
+ /**
31
+ * LIMITS (SR5 p.46-47). A limit caps the number of hits you may COUNT
32
+ * from a test; it is never a die added to the pool. The book writes it
33
+ * in brackets -- `Hacking + Logic [Sleaze]` -- and p.244 files the
34
+ * whole Matrix action list by which limit it uses.
35
+ *
36
+ * It caps RAW hits, before an opposed test subtracts the defender's,
37
+ * and net hits are then taken from the capped number (p.46-47, worked
38
+ * through at p.195). It is NOT applied to the defender: limits apply
39
+ * only to pools built from a skill plus an attribute, and a defence
40
+ * like Intuition + Firewall has no skill in it (p.46-47, and the melee
41
+ * example at p.174 -- "no limit because there is no skill involved").
42
+ * So callers pass a limit on the ACTING roll only.
43
+ *
44
+ * GLITCHES ARE UNAFFECTED, and half of that is canon while half is a
45
+ * decision this engine had to make. A glitch is more than half the dice
46
+ * showing 1 (p.45-46) -- nothing to do with hits, so a limit cannot
47
+ * touch it. A CRITICAL glitch is a glitch with no hit achieved, and the
48
+ * book does not say whether a hit thrown away by a limit still counts
49
+ * as achieved. Asked directly and the rulebooks are silent, so rather
50
+ * than invent a rule this reads the RAW dice: you rolled a 5, you
51
+ * achieved a hit, the limit only stopped you counting it. In practice
52
+ * the two readings never diverge -- a real limit is at least 1, so a
53
+ * roll with any raw hit keeps at least one counted hit.
54
+ */
55
+ export declare function rollPool(pool: number, limit?: number): IDiceRoll;
16
56
  /**
17
57
  * One-line render of a roll for the game log, e.g.: "8d6 -> 4 hits"
18
58
  * Pool and hits only. The individual faces have been through two
@@ -5,18 +5,53 @@
5
5
  * (it went ugly AND you failed). Pure functions, no game-state knowledge --
6
6
  * combat resolution lives in commands/attack.ts.
7
7
  */
8
- export function rollPool(pool) {
8
+ /**
9
+ * LIMITS (SR5 p.46-47). A limit caps the number of hits you may COUNT
10
+ * from a test; it is never a die added to the pool. The book writes it
11
+ * in brackets -- `Hacking + Logic [Sleaze]` -- and p.244 files the
12
+ * whole Matrix action list by which limit it uses.
13
+ *
14
+ * It caps RAW hits, before an opposed test subtracts the defender's,
15
+ * and net hits are then taken from the capped number (p.46-47, worked
16
+ * through at p.195). It is NOT applied to the defender: limits apply
17
+ * only to pools built from a skill plus an attribute, and a defence
18
+ * like Intuition + Firewall has no skill in it (p.46-47, and the melee
19
+ * example at p.174 -- "no limit because there is no skill involved").
20
+ * So callers pass a limit on the ACTING roll only.
21
+ *
22
+ * GLITCHES ARE UNAFFECTED, and half of that is canon while half is a
23
+ * decision this engine had to make. A glitch is more than half the dice
24
+ * showing 1 (p.45-46) -- nothing to do with hits, so a limit cannot
25
+ * touch it. A CRITICAL glitch is a glitch with no hit achieved, and the
26
+ * book does not say whether a hit thrown away by a limit still counts
27
+ * as achieved. Asked directly and the rulebooks are silent, so rather
28
+ * than invent a rule this reads the RAW dice: you rolled a 5, you
29
+ * achieved a hit, the limit only stopped you counting it. In practice
30
+ * the two readings never diverge -- a real limit is at least 1, so a
31
+ * roll with any raw hit keeps at least one counted hit.
32
+ */
33
+ export function rollPool(pool, limit) {
9
34
  const size = Math.max(0, Math.floor(pool));
10
35
  const rolls = Array.from({ length: size }, () => 1 + Math.floor(Math.random() * 6));
11
- const hits = rolls.filter(r => r >= 5).length;
36
+ const rawHits = rolls.filter(r => r >= 5).length;
12
37
  const ones = rolls.filter(r => r === 1).length;
13
38
  const glitch = size > 0 && ones > size / 2;
39
+ const capped = limit !== undefined && Number.isFinite(limit) && limit > 0;
40
+ const limitIgnored = limit !== undefined && !capped;
41
+ const hits = capped ? Math.min(rawHits, Math.floor(limit)) : rawHits;
14
42
  return {
15
43
  pool: size,
16
44
  rolls,
17
45
  hits,
46
+ rawHits,
47
+ limit: capped ? Math.floor(limit) : undefined,
48
+ limited: capped && hits < rawHits,
49
+ limitIgnored,
50
+ // RAW hits on purpose -- see the note above: the book defines a
51
+ // critical glitch as a glitch with no hit achieved and is silent on
52
+ // whether a limit un-achieves one.
53
+ criticalGlitch: glitch && rawHits === 0,
18
54
  glitch,
19
- criticalGlitch: glitch && hits === 0,
20
55
  };
21
56
  }
22
57
  /**
@@ -32,6 +67,19 @@ export function formatRoll(roll) {
32
67
  if (roll.pool === 0)
33
68
  return '0d6 -> no dice to roll';
34
69
  const glitchTag = roll.criticalGlitch ? ' -- CRITICAL GLITCH' : roll.glitch ? ' -- glitch' : '';
35
- return `${roll.pool}d6 -> ${roll.hits} hit${roll.hits === 1 ? '' : 's'}${glitchTag}`;
70
+ // THE LIMIT HAS TO BE VISIBLE, and especially when it BOUND. A cap
71
+ // that never shows is indistinguishable from no cap, which is exactly
72
+ // the check-whose-two-sides-fail-together shape: a play-tester could
73
+ // not tell a limited engine from an unlimited one, and neither could
74
+ // a bench. So an unbound limit is shown quietly and a bound one says
75
+ // what it threw away.
76
+ const limitTag = roll.limitIgnored
77
+ ? ` [LIMIT IGNORED -- a caller computed one that is not a limit]`
78
+ : roll.limited
79
+ ? ` -- LIMITED to ${roll.limit} (${roll.rawHits} rolled)`
80
+ : roll.limit !== undefined
81
+ ? ` [limit ${roll.limit}]`
82
+ : '';
83
+ return `${roll.pool}d6 -> ${roll.hits} hit${roll.hits === 1 ? '' : 's'}${limitTag}${glitchTag}`;
36
84
  }
37
85
  //# sourceMappingURL=dice.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dice.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/dice.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAE3C,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK;QACL,IAAI;QACJ,MAAM;QACN,cAAc,EAAE,MAAM,IAAI,IAAI,KAAK,CAAC;KACrC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,OAAO,GAAG,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;AACvF,CAAC"}
1
+ {"version":3,"file":"dice.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/dice.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAc;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACjD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;IAC1E,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAErE,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK;QACL,IAAI;QACJ,OAAO;QACP,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7C,OAAO,EAAE,MAAM,IAAI,IAAI,GAAG,OAAO;QACjC,YAAY;QACZ,gEAAgE;QAChE,oEAAoE;QACpE,mCAAmC;QACnC,cAAc,EAAE,MAAM,IAAI,OAAO,KAAK,CAAC;QACvC,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,mEAAmE;IACnE,sEAAsE;IACtE,qEAAqE;IACrE,qEAAqE;IACrE,qEAAqE;IACrE,sBAAsB;IACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY;QAChC,CAAC,CAAC,+DAA+D;QACjE,CAAC,CAAC,IAAI,CAAC,OAAO;YACZ,CAAC,CAAC,kBAAkB,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,UAAU;YACzD,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS;gBACxB,CAAC,CAAC,WAAW,IAAI,CAAC,KAAK,GAAG;gBAC1B,CAAC,CAAC,EAAE,CAAC;IACX,OAAO,GAAG,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,GAAG,SAAS,EAAE,CAAC;AAClG,CAAC"}
@@ -9,7 +9,7 @@ import { Scene } from '../models/scene.js';
9
9
  * exits, prose places -- is deliberately absent: if it isn't
10
10
  * broadcasting, it isn't here. Shared by look.ts (gridLook) and go.ts
11
11
  * (grid-jump arrivals). Glyphs match the grid map: ● host, ■
12
- * air-gapped, ◆ persona, ▣ PAN, ◇ data.
12
+ * air-gapped, ◆ persona, ▣ PAN, ▲ dangerous device, ◇ data.
13
13
  */
14
14
  export declare function gridIcons(scene: Scene, actor: Player, room: Room): string[];
15
15
  /**
@@ -9,20 +9,26 @@ import { isWatched, camerasLive, canSnoopFeeds } from './surveillance.js';
9
9
  * exits, prose places -- is deliberately absent: if it isn't
10
10
  * broadcasting, it isn't here. Shared by look.ts (gridLook) and go.ts
11
11
  * (grid-jump arrivals). Glyphs match the grid map: ● host, ■
12
- * air-gapped, ◆ persona, ▣ PAN, ◇ data.
12
+ * air-gapped, ◆ persona, ▣ PAN, ▲ dangerous device, ◇ data.
13
13
  */
14
14
  export function gridIcons(scene, actor, room) {
15
15
  const icons = [];
16
16
  if (room.hasNode) {
17
+ // A HOST IS DESCRIBED BY ITS RATING (SR5 p.247; Deditri,
18
+ // BTyfgybBnD2hjDmoN). It rides every read of a host, including the
19
+ // air-gapped one -- you can see that a host is there and how heavy
20
+ // it is without having a way in, which is exactly the decision the
21
+ // rating is for.
22
+ const hr = ` {bold}[HR ${room.hostRating}]{/bold}`;
17
23
  if (!canReachHost(actor, room)) {
18
- icons.push(`■ The ${room.name} host -- DARK. Air-gapped: it only opens to a hard line ("jack in" from inside this room).`);
24
+ icons.push(`■ The ${room.name} host${hr} -- DARK. Air-gapped: it only opens to a hard line ("jack in" from inside this room).`);
19
25
  }
20
26
  else if (room.hostCracked) {
21
- icons.push(`● The ${room.name} host -- cracked open, security in shreds. Whatever it guarded is unsealed.`);
27
+ icons.push(`● The ${room.name} host${hr} -- cracked open, security in shreds. Whatever it guarded is unsealed.`);
22
28
  }
23
29
  else {
24
30
  const myMarks = room.hostMarksBy.get(actor.name) ?? 0;
25
- icons.push(`● The ${room.name} host -- sculpted ice geometry over the room's systems.${room.hostAlert ? ' Walls UP -- it holds a mark on an intruder.' : ''}${myMarks > 0 ? ` [your marks: ${myMarks}/3]` : ''} ("hack" slips in on sleaze; "hack loud" smashes; "mark" softens it first)`);
31
+ icons.push(`● The ${room.name} host${hr} -- sculpted ice geometry over the room's systems.${room.hostAlert ? ' Walls UP -- it holds a mark on an intruder.' : ''}${myMarks > 0 ? ` [your marks: ${myMarks}/3]` : ''} ("hack" slips in on sleaze; "hack loud" smashes; "mark" softens it first)`);
26
32
  }
27
33
  }
28
34
  for (const a of scene.getActorsInRoom(room)) {
@@ -46,9 +52,16 @@ export function gridIcons(scene, actor, room) {
46
52
  // into a single PAN icon, and that icon "often looks similar to
47
53
  // the physical device that serves as the master" (p.219). The PAN
48
54
  // is exactly what you see; the master is exactly what it is
49
- // shaped like. (The book's own exception -- an interesting or
50
- // dangerous device, a wireless gun, breaking out as its own icon
51
- // -- is NOT modelled here and is on the board, not smuggled in.)
55
+ // shaped like.
56
+ //
57
+ // THE BOOK'S EXCEPTION IS NOW MODELLED, one icon down: "Some
58
+ // devices are not merged into the single PAN icon; if an
59
+ // individual is carrying a wireless-enabled gun -- or any other
60
+ // wireless device that might kill you -- it will show up
61
+ // separately so that it can be identified rapidly" (p.219, and
62
+ // p.217-218 "exceptions for interesting or dangerous devices in
63
+ // that network, such as a gun"). Confirmed against the book
64
+ // directly rather than taken secondhand (AKfemBJJWJP99nx8K).
52
65
  //
53
66
  // What the report was right about is that this line never SAID
54
67
  // any of that, so an icon labelled with someone's whole network
@@ -60,6 +73,31 @@ export function gridIcons(scene, actor, room) {
60
73
  if (master) {
61
74
  const myMarks = a.panMarksBy.get(actor.name) ?? 0;
62
75
  icons.push(`▣ ${a.name}'s PAN -- one icon, sculpted like the ${master.name} that masters it, everything else they carry folded inside.${myMarks > 0 ? ` [your marks: ${myMarks}/3]` : ''} ("look ${a.name}" reads the shape; "hack ${a.name}" cracks it open)`);
76
+ // THE GUN BREAKS OUT (p.219). Deliberately NOT gated behind a
77
+ // mark, unlike the PAN's contents: the whole point of the
78
+ // exception is that a dangerous device "can be identified
79
+ // rapidly", and a decker who has to crack a network first to
80
+ // learn the man across the room is armed has been told the
81
+ // opposite of what the book promises. Everything else that
82
+ // person carries stays folded inside the icon above.
83
+ //
84
+ // SAME SELECTOR AS hack.ts's top-priority PAN effect, on
85
+ // purpose: the icon must name exactly the device the verb will
86
+ // seize, or "identified rapidly" points at the wrong thing.
87
+ // This engine treats a held firearm as smartlinked -- there is
88
+ // no separate wireless flag on Item -- so held-and-a-firearm is
89
+ // the whole test, and the two surfaces agree by construction.
90
+ //
91
+ // Sitting INSIDE the isLinked branch is what makes running
92
+ // silent hide it, which is canon too: the book notes a user can
93
+ // hide these icons, and isLinked() is already false while
94
+ // silent. No second rule needed.
95
+ const gun = a.equipment.rightHand?.weapon ?? a.equipment.leftHand?.weapon;
96
+ if (gun?.isFirearm()) {
97
+ icons.push(gun.jammed
98
+ ? `▲ ${a.name}'s ${gun.name} -- its own icon beside the PAN, smartlink DEAD. Already seized; it will not fire until they reboot.`
99
+ : `▲ ${a.name}'s ${gun.name} -- its own icon beside the PAN. A wireless gun does not hide inside a network: it is flagged so it can be seen fast. ("hack ${a.name}" seizes the smartlink)`);
100
+ }
63
101
  }
64
102
  }
65
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"grid-view.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/grid-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grid-view.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/grid-view.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,12 @@
6
6
  * street-level open ground counts, anything underground or interior
7
7
  * doesn't. Keeping both callers on the same predicate is the point --
8
8
  * a curb the generator guarantees must be a curb the dispatcher accepts.
9
+ *
10
+ * A NAME HEURISTIC STANDING IN FOR A SPATIAL FACT, and it cannot be
11
+ * made correct by a better regex -- only narrower. Whether a room has a
12
+ * roof is a property of the room, and the durable fix is for a seed to
13
+ * SAY so rather than for this to guess from an English word. That is
14
+ * filed; what is here is the guessing made less wrong, in the direction
15
+ * that fails safely.
9
16
  */
10
17
  export declare function isOutdoorsPlace(name: string, roomType?: string): boolean;
@@ -6,11 +6,35 @@
6
6
  * street-level open ground counts, anything underground or interior
7
7
  * doesn't. Keeping both callers on the same predicate is the point --
8
8
  * a curb the generator guarantees must be a curb the dispatcher accepts.
9
+ *
10
+ * A NAME HEURISTIC STANDING IN FOR A SPATIAL FACT, and it cannot be
11
+ * made correct by a better regex -- only narrower. Whether a room has a
12
+ * roof is a property of the room, and the durable fix is for a seed to
13
+ * SAY so rather than for this to guess from an English word. That is
14
+ * filed; what is here is the guessing made less wrong, in the direction
15
+ * that fails safely.
9
16
  */
10
17
  export function isOutdoorsPlace(name, roomType) {
11
18
  const haystack = `${roomType ?? ''} ${name}`.toLowerCase();
12
19
  if (/under|tunnel|crypt|basement|cellar|sewer|vault|sub-|subterran/.test(haystack))
13
20
  return false;
21
+ // ROOFED PLACES THAT WEAR AN OUTDOOR WORD (rhYnX2eaWgTkDnJ4a: "I was
22
+ // able to call a taxi from inside, I think").
23
+ //
24
+ // The positive list below contains words that are perfectly happy
25
+ // indoors -- market, court, gate, plaza -- so a "Market Hall", a
26
+ // "Food Court" or a "Gate House" read as open street and the
27
+ // dispatcher sent a cab into a building. Checked BEFORE the positive
28
+ // pass, the same way the underground guard is, because the roof is
29
+ // the deciding fact and the outdoor word is incidental.
30
+ //
31
+ // THIS ERRS TOWARD INDOORS ON PURPOSE, and the asymmetry is real: a
32
+ // false "outdoors" hails a cab inside a building, which is the bug.
33
+ // A false "indoors" only tells a runner to step out, and the
34
+ // generator's own use is self-correcting -- normalizeSkeleton simply
35
+ // injects a curb when the start room fails this test.
36
+ if (/hall|lobby|foyer|atrium|concourse|arcade|indoor|interior|food court|mall\b/.test(haystack))
37
+ return false;
14
38
  return /street|alley|strip|yard|dock|gate|road|plaza|lot\b|court|bridge|park|curb|waterfront|pier|rooftop|exterior|outside|market/.test(haystack);
15
39
  }
16
40
  //# sourceMappingURL=outdoors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"outdoors.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/outdoors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,QAAiB;IAC7D,MAAM,QAAQ,GAAG,GAAG,QAAQ,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,+DAA+D,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACjG,OAAO,2HAA2H,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACpJ,CAAC"}
1
+ {"version":3,"file":"outdoors.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/utilities/outdoors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,QAAiB;IAC7D,MAAM,QAAQ,GAAG,GAAG,QAAQ,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,+DAA+D,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACjG,qEAAqE;IACrE,8CAA8C;IAC9C,EAAE;IACF,kEAAkE;IAClE,iEAAiE;IACjE,6DAA6D;IAC7D,qEAAqE;IACrE,mEAAmE;IACnE,wDAAwD;IACxD,EAAE;IACF,oEAAoE;IACpE,oEAAoE;IACpE,6DAA6D;IAC7D,qEAAqE;IACrE,sDAAsD;IACtD,IAAI,4EAA4E,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9G,OAAO,2HAA2H,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACpJ,CAAC"}
@@ -29,7 +29,45 @@ export interface GridCoord {
29
29
  z: number;
30
30
  }
31
31
  export declare const METERS_PER_CELL = 2;
32
+ /**
33
+ * How tall one storey is. A CHOICE, not a citation -- SR5 never says how
34
+ * high a catwalk sits -- and it is load-bearing in three places now, so
35
+ * it is written down rather than left as a number three files read:
36
+ *
37
+ * - falling damage (fallDamageDV): canon takes no damage at or under
38
+ * three metres (p.172), so at exactly 3 a one-storey fall is the
39
+ * hardest landing that still hurts nobody. That is deliberate: a
40
+ * catwalk should bruise your pride.
41
+ * - how many hits a free climb needs (climbHitsFor, below).
42
+ * - straight-line distance for perception and ranges (distanceMeters).
43
+ *
44
+ * Three metres is about a real industrial catwalk over a warehouse
45
+ * floor. Moving it re-prices all three at once, which is the reason it
46
+ * is one constant.
47
+ */
32
48
  export declare const METERS_PER_LEVEL = 3;
49
+ /**
50
+ * Hits needed to free-climb `meters` (SR5 p.134, Climbing Table:
51
+ * "Unassisted climbing upward -- 1 meter per 2 hits").
52
+ *
53
+ * ASSISTED climbing is 1 metre per hit and wants rope and harness; this
54
+ * engine has no climbing gear, so every climb it rolls is unassisted.
55
+ * When gear arrives, this is the one function that changes.
56
+ *
57
+ * The other rows are recorded here rather than implemented, because
58
+ * nothing in the engine yet distinguishes a surface: assisted
59
+ * horizontal is 1m/2 hits, assisted upside-down 1m/3, unassisted DOWN
60
+ * 1m per hit, and rappelling is 20m + 1m per hit. The dice-pool
61
+ * modifiers for surface (+1 chain-link, 0 broken, -3 flat, -5 sheer,
62
+ * -2 wet, -4 greased) are likewise real canon we do not model -- said
63
+ * plainly so the next reader knows they are missing, not forgotten.
64
+ */
65
+ export declare function climbHitsFor(meters: number): number;
66
+ /** How far up a climb actually got, from the hits it rolled -- the
67
+ * inverse of climbHitsFor, used to price a fall from a climb that
68
+ * fell short. Canon buys distance with hits whether or not you reach
69
+ * the top; you do not teleport back to the floor to fall from it. */
70
+ export declare function climbMetersFrom(hits: number): number;
33
71
  export declare const key: (c: GridCoord) => string;
34
72
  export type RoomSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge';
35
73
  export type WallSide = 'north' | 'south' | 'east' | 'west';
@@ -193,6 +231,14 @@ export declare function blockingRegionFor(grid: IRoomGrid, from: GridCoord, to:
193
231
  /** SR5 p.172 (RAG-verified): DV = meters fallen, only beyond 3m; AP-4
194
232
  * applies at the caller (resisted with Body+Armor -- move.ts's job). */
195
233
  export declare function fallDamageDV(zSteps: number): number;
234
+ /**
235
+ * The same rule stated in METRES, for a fall from part-way up a climb
236
+ * (SR5 p.172: DV equals metres fallen, and a fall of three metres or
237
+ * less does nothing). fallDamageDV delegates here rather than repeating
238
+ * the comparison -- one rule, one place, so a change to the threshold
239
+ * cannot land on storeys and miss climbs.
240
+ */
241
+ export declare function fallDamageDVMeters(meters: number): number;
196
242
  /** Does this path cross a 'climb' vertical link (the riskier route --
197
243
  * stairs/ladder never roll anything)? Returns the z-delta of the
198
244
  * riskiest crossing (for fallDamageDV), or undefined when the path
@@ -1,6 +1,48 @@
1
1
  import { Direction } from '../types/shared/direction-enum.js';
2
2
  export const METERS_PER_CELL = 2;
3
+ /**
4
+ * How tall one storey is. A CHOICE, not a citation -- SR5 never says how
5
+ * high a catwalk sits -- and it is load-bearing in three places now, so
6
+ * it is written down rather than left as a number three files read:
7
+ *
8
+ * - falling damage (fallDamageDV): canon takes no damage at or under
9
+ * three metres (p.172), so at exactly 3 a one-storey fall is the
10
+ * hardest landing that still hurts nobody. That is deliberate: a
11
+ * catwalk should bruise your pride.
12
+ * - how many hits a free climb needs (climbHitsFor, below).
13
+ * - straight-line distance for perception and ranges (distanceMeters).
14
+ *
15
+ * Three metres is about a real industrial catwalk over a warehouse
16
+ * floor. Moving it re-prices all three at once, which is the reason it
17
+ * is one constant.
18
+ */
3
19
  export const METERS_PER_LEVEL = 3;
20
+ /**
21
+ * Hits needed to free-climb `meters` (SR5 p.134, Climbing Table:
22
+ * "Unassisted climbing upward -- 1 meter per 2 hits").
23
+ *
24
+ * ASSISTED climbing is 1 metre per hit and wants rope and harness; this
25
+ * engine has no climbing gear, so every climb it rolls is unassisted.
26
+ * When gear arrives, this is the one function that changes.
27
+ *
28
+ * The other rows are recorded here rather than implemented, because
29
+ * nothing in the engine yet distinguishes a surface: assisted
30
+ * horizontal is 1m/2 hits, assisted upside-down 1m/3, unassisted DOWN
31
+ * 1m per hit, and rappelling is 20m + 1m per hit. The dice-pool
32
+ * modifiers for surface (+1 chain-link, 0 broken, -3 flat, -5 sheer,
33
+ * -2 wet, -4 greased) are likewise real canon we do not model -- said
34
+ * plainly so the next reader knows they are missing, not forgotten.
35
+ */
36
+ export function climbHitsFor(meters) {
37
+ return Math.max(1, Math.ceil(meters * 2));
38
+ }
39
+ /** How far up a climb actually got, from the hits it rolled -- the
40
+ * inverse of climbHitsFor, used to price a fall from a climb that
41
+ * fell short. Canon buys distance with hits whether or not you reach
42
+ * the top; you do not teleport back to the floor to fall from it. */
43
+ export function climbMetersFrom(hits) {
44
+ return hits / 2;
45
+ }
4
46
  export const key = (c) => `${c.x},${c.y},${c.z}`;
5
47
  /**
6
48
  * x,y cell counts per size class -- 2 m/cell.
@@ -291,8 +333,18 @@ export function blockingRegionFor(grid, from, to) {
291
333
  /** SR5 p.172 (RAG-verified): DV = meters fallen, only beyond 3m; AP-4
292
334
  * applies at the caller (resisted with Body+Armor -- move.ts's job). */
293
335
  export function fallDamageDV(zSteps) {
294
- const meters = zSteps * METERS_PER_LEVEL;
295
- return meters > 3 ? meters : 0;
336
+ return fallDamageDVMeters(zSteps * METERS_PER_LEVEL);
337
+ }
338
+ /**
339
+ * The same rule stated in METRES, for a fall from part-way up a climb
340
+ * (SR5 p.172: DV equals metres fallen, and a fall of three metres or
341
+ * less does nothing). fallDamageDV delegates here rather than repeating
342
+ * the comparison -- one rule, one place, so a change to the threshold
343
+ * cannot land on storeys and miss climbs.
344
+ */
345
+ export function fallDamageDVMeters(meters) {
346
+ const m = Math.round(meters);
347
+ return m > 3 ? m : 0;
296
348
  }
297
349
  /** Does this path cross a 'climb' vertical link (the riskier route --
298
350
  * stairs/ladder never roll anything)? Returns the z-delta of the
@@ -501,14 +553,40 @@ export function synthesizeGrid(input) {
501
553
  grid.spotCells.set(spot.name, cell);
502
554
  }
503
555
  // --- Unplaced spots: spread out (max-min distance, rejection sampled). ---
556
+ //
557
+ // A SPOT'S LEVEL IS HONOURED WITHOUT A PLACE HINT. This sampler used
558
+ // to hardcode `z: 0`, so `level: 1` only reached the grid when the
559
+ // spot ALSO carried a `place` -- the placed branch above reads it,
560
+ // this one silently dropped it. A catwalk spot with no wall hint was
561
+ // therefore built on the floor.
562
+ //
563
+ // It is the two-sources-of-truth shape again, and it was invisible
564
+ // because the two sources disagreed QUIETLY: describeSpotRoster reads
565
+ // the seed and says "catwalk (up a level)", while everything spatial
566
+ // reads the grid and saw a ground-floor cell. The player was told the
567
+ // place was above their head and `climb` answered that there was
568
+ // nothing up there -- both surfaces honest, one of them wrong.
569
+ //
570
+ // Found by the repro bench, which builds its level spot without a
571
+ // place hint. That is the bench earning its keep: no unit test in the
572
+ // suite happened to omit the hint.
504
573
  const onPerimeter = (c) => c.x === 0 || c.y === 0 || c.x === dims.x - 1 || c.y === dims.y - 1;
505
574
  for (const spot of unplaced) {
575
+ const spotZ = clamp(spot.level ?? 0, 0, dims.z - 1);
576
+ // An upper storey is only its declared strip, so sample from the
577
+ // cells that actually exist up there rather than the whole
578
+ // footprint -- a random x,y on level 1 is usually thin air.
579
+ const levelCellKeys = spotZ === 0
580
+ ? undefined
581
+ : new Set(levelFootprint(grid, spotZ).map(key));
506
582
  let best;
507
583
  let bestScore = -1;
508
584
  for (let attempt = 0; attempt < 8; attempt++) {
509
585
  const x = Math.floor(rng() * dims.x);
510
586
  const y = Math.floor(rng() * dims.y);
511
- const c = { x, y, z: 0 };
587
+ const c = { x, y, z: spotZ };
588
+ if (levelCellKeys && !levelCellKeys.has(key(c)))
589
+ continue;
512
590
  if (occupied.has(key(c)))
513
591
  continue;
514
592
  // A DOOR IS A HOLE IN A WALL (item A, 2026-08-27). This sampler
@@ -528,10 +606,17 @@ export function synthesizeGrid(input) {
528
606
  }
529
607
  // Rejection sampling can miss a free perimeter cell in 8 tries, so
530
608
  // sweep for one before giving up on the constraint entirely.
531
- if (!best && spot.preferPerimeter) {
609
+ if (!best && spot.preferPerimeter && spotZ === 0) {
532
610
  best = perimeterFreeCell(dims, occupied, exists);
533
611
  }
534
- if (!best)
612
+ // BOTH FALLBACKS BUILD ON THE GROUND, so a level spot that the
613
+ // sampler missed must sweep its own storey instead -- otherwise the
614
+ // rescue path quietly reintroduces exactly the bug the loop above
615
+ // just fixed, and only for the crowded rooms where it matters.
616
+ if (!best && levelCellKeys) {
617
+ best = levelFootprint(grid, spotZ).find(c => !occupied.has(key(c)));
618
+ }
619
+ if (!best && spotZ === 0)
535
620
  best = fallbackFreeCell(dims, occupied);
536
621
  if (best) {
537
622
  claim(best);