@maka/maka-cli 5.66.0 → 5.67.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 (71) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/_index.d.ts +1 -0
  3. package/bundle/typescript/src/commands/game/_index.js +1 -0
  4. package/bundle/typescript/src/commands/game/_index.js.map +1 -1
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.d.ts +103 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js +423 -25
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js.map +1 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +15 -1
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +61 -4
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/review.d.ts +39 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/review.js +48 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/review.js.map +1 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +15 -1
  16. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.d.ts +87 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +320 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +32 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
  22. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +8 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +48 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/game.js +91 -7
  26. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  27. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +12 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +23 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +26 -0
  31. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +55 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  33. package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +106 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/types/repro.js +2 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +12 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +9 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  39. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.d.ts +17 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js +250 -36
  41. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js.map +1 -1
  42. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.d.ts +30 -1
  43. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js +58 -0
  44. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js.map +1 -1
  45. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.d.ts +85 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js +100 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js.map +1 -0
  48. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.d.ts +45 -3
  49. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js +45 -3
  50. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js.map +1 -1
  51. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.d.ts +13 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js +22 -0
  53. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js.map +1 -0
  54. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.d.ts +42 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js +57 -0
  56. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js.map +1 -0
  57. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.d.ts +68 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js +147 -0
  59. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js.map +1 -0
  60. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +39 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  62. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.d.ts +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js +46 -0
  64. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js.map +1 -0
  65. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.d.ts +3 -1
  66. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +269 -42
  67. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  68. package/bundle/typescript/src/tools/log/log.class.d.ts +17 -0
  69. package/bundle/typescript/src/tools/log/log.class.js +30 -1
  70. package/bundle/typescript/src/tools/log/log.class.js.map +1 -1
  71. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { Command } from './command.js';
2
2
  import { hint } from '../utilities/hints.js';
3
3
  import { sameSpot, spotOf } from '../utilities/spots.js';
4
4
  import { rollPool, formatRoll } from '../utilities/dice.js';
5
+ import { AFFORDANCE_FLAVOR } from '../utilities/affordances.js';
5
6
  /**
6
7
  * BLUFF IS SOCIAL, AND SOCIAL MEANS A PERSON (player ruling 2026-08-27:
7
8
  * "'bluff' shouldn't work on Barriers like locks/wards/etc. Bluff is a
@@ -58,13 +59,41 @@ export class BluffCommand extends Command {
58
59
  // NAME THE PEOPLE, because "bluff" is now a question about people and
59
60
  // the honest answer to "who?" is a list of who is actually here.
60
61
  if (here.length === 0) {
61
- return `There's nobody here to work. A con needs an audience -- ${room && this.roomHasBarrier() ? `and whatever's blocking you isn't one. It's a THING: work it with "pick", "hack" or "cast", or find the answer and "solve" it.` : `find someone who can say yes.`}`;
62
+ const barrier = this.soleBarrier();
63
+ if (barrier) {
64
+ // The barrier's OWN refusal, per kind, rather than a hand-listed
65
+ // set of verbs here. The hand-listed one named "solve", which
66
+ // was deleted with the Puzzle mechanic -- a refusal that points
67
+ // at a verb the game no longer has is worse than no refusal,
68
+ // because the player types it and is told it does not exist.
69
+ return `There's nobody here to work. A con needs an audience. ${AFFORDANCE_FLAVOR[barrier.kind].resists}`;
70
+ }
71
+ return `There's nobody here to work. A con needs an audience -- find someone who can say yes.`;
62
72
  }
63
73
  const named = args.join(' ').trim();
64
74
  const target = named
65
75
  ? here.find(a => a.name.toLowerCase().includes(named.toLowerCase()))
66
76
  : undefined;
67
77
  if (named && !target) {
78
+ // THEY MAY HAVE NAMED THE BARRIER, and that is the whole point of
79
+ // the ruling this command exists for: "bluff shouldn't work on
80
+ // Barriers like locks/wards/etc."
81
+ //
82
+ // The informative refusal already existed -- and was UNREACHABLE
83
+ // whenever anyone was standing in the room, because it sat behind
84
+ // `here.length === 0`. That is exactly the configuration the
85
+ // report describes ("the real barrier should be Coyote"), so the
86
+ // one case the fix was written for was the one case it could not
87
+ // reach. Typing "bluff maglock" next to a doorman got the generic
88
+ // "you don't see anyone called maglock" -- true, useless, and
89
+ // silent about the maglock being right there.
90
+ //
91
+ // Found by the repro bench built for that very item, on its first
92
+ // run, which is the entire argument for benches.
93
+ const barrier = this.namedBarrier(named);
94
+ if (barrier) {
95
+ return `${AFFORDANCE_FLAVOR[barrier.kind].resists}`;
96
+ }
68
97
  return `You don't see anyone called "${named}" to work. Here: ${here.map(a => a.name).join(', ')}.`;
69
98
  }
70
99
  // Working a mark out loud is the opposite of sneaking (see sneak.ts).
@@ -122,9 +151,37 @@ export class BluffCommand extends Command {
122
151
  /** Is there a barrier in this room at all? Only used to word the
123
152
  * refusal correctly -- a player who typed "bluff" at a lock deserves
124
153
  * to be pointed at the verbs that do work on it. */
125
- roomHasBarrier() {
126
- const room = this.actor.currentLocation;
127
- return (room?.openableDevices?.() ?? []).length > 0;
154
+ /** The one barrier here, when there is exactly one -- for the case
155
+ * where nobody was named because nobody is here. */
156
+ soleBarrier() {
157
+ const devices = this.actor.currentLocation?.openableDevices?.() ?? [];
158
+ return devices.length === 1 ? devices[0] : undefined;
159
+ }
160
+ /**
161
+ * Did they name a BARRIER rather than a person?
162
+ *
163
+ * Matched loosely, and that is the right call here for the same
164
+ * reason it is right in bypass-command: this is resolving a word a
165
+ * human typed at an object they can see, not an identity already
166
+ * known. The generic-noun fallback is the same one bypass-command
167
+ * uses, so "bluff door" and "pick door" agree about what "door"
168
+ * means -- two commands disagreeing about the same word in the same
169
+ * room is its own bug.
170
+ */
171
+ namedBarrier(named) {
172
+ const devices = this.actor.currentLocation?.openableDevices?.() ?? [];
173
+ if (devices.length === 0)
174
+ return undefined;
175
+ const raw = named.toLowerCase();
176
+ const matches = devices.filter(d => d.name.toLowerCase().includes(raw)
177
+ || d.description.toLowerCase().includes(raw));
178
+ if (matches.length >= 1)
179
+ return matches[0];
180
+ if (devices.length === 1
181
+ && /^(lock|door|ward|barrier|seal|gate|hatch|glyph|field|sigil|way|node|terminal|panel|keypad)$/.test(raw)) {
182
+ return devices[0];
183
+ }
184
+ return undefined;
128
185
  }
129
186
  }
130
187
  //# sourceMappingURL=bluff.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bluff.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/bluff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bluff.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/bluff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { ICommand } from './command.js';
2
+ import { BacklogCommand } from './backlog.js';
3
+ /**
4
+ * `review` -- the short way to answer the item you are standing in.
5
+ *
6
+ * Player ruling 2026-08-28: "I shouldn't have to pick from the list of
7
+ * review items -- I AM reviewing an item. So the commands should be
8
+ * straight forward: 'backlog review reject <message>' or, if we need to
9
+ * fork the command: 'review reject <message>' or 'review accept'."
10
+ *
11
+ * Both forms exist, and this one is the fork. Inside a repro bench the
12
+ * session already knows which report it was stood up for, so the whole
13
+ * `backlog review <n>` ceremony is noise -- the number names something
14
+ * there is only one of.
15
+ *
16
+ * A THIN DELEGATION, not a second implementation. Everything -- the
17
+ * reject keyword, the accept keyword, sign-off routing, the "already
18
+ * answered" case -- stays in BacklogCommand.review, because two code
19
+ * paths that post to the same board are two things that can disagree
20
+ * about what an answer means. This only supplies the word the player
21
+ * reaches for.
22
+ *
23
+ * Outside a bench it is still useful: bare `review` lists what is
24
+ * waiting on you, exactly as `backlog review` does.
25
+ */
26
+ export declare class ReviewCommand extends BacklogCommand implements ICommand {
27
+ protected static verb: string;
28
+ protected static description: string;
29
+ protected static subcommands: ({
30
+ usage: string;
31
+ short: string;
32
+ long?: undefined;
33
+ } | {
34
+ usage: string;
35
+ short: string;
36
+ long: string;
37
+ })[];
38
+ execute(args?: string[]): Promise<string>;
39
+ }
@@ -0,0 +1,48 @@
1
+ import { BacklogCommand } from './backlog.js';
2
+ /**
3
+ * `review` -- the short way to answer the item you are standing in.
4
+ *
5
+ * Player ruling 2026-08-28: "I shouldn't have to pick from the list of
6
+ * review items -- I AM reviewing an item. So the commands should be
7
+ * straight forward: 'backlog review reject <message>' or, if we need to
8
+ * fork the command: 'review reject <message>' or 'review accept'."
9
+ *
10
+ * Both forms exist, and this one is the fork. Inside a repro bench the
11
+ * session already knows which report it was stood up for, so the whole
12
+ * `backlog review <n>` ceremony is noise -- the number names something
13
+ * there is only one of.
14
+ *
15
+ * A THIN DELEGATION, not a second implementation. Everything -- the
16
+ * reject keyword, the accept keyword, sign-off routing, the "already
17
+ * answered" case -- stays in BacklogCommand.review, because two code
18
+ * paths that post to the same board are two things that can disagree
19
+ * about what an answer means. This only supplies the word the player
20
+ * reaches for.
21
+ *
22
+ * Outside a bench it is still useful: bare `review` lists what is
23
+ * waiting on you, exactly as `backlog review` does.
24
+ */
25
+ export class ReviewCommand extends BacklogCommand {
26
+ static verb = 'review';
27
+ static description = 'Answer the backlog item you are reviewing. In a repro bench it needs no number: "review reject <what is still wrong>", "review accept [what you saw]", or "review repro" to re-read the steps. Bare "review" lists what is waiting on you.';
28
+ static subcommands = [
29
+ { usage: 'review', short: 'What the board is waiting on YOU for.' },
30
+ {
31
+ usage: 'review accept [what you saw]',
32
+ short: 'Confirm the fix works.',
33
+ long: 'Signs the item off, which is what closes it -- your word is the thing the board is waiting for. The note is optional; anything you type is recorded against your report.',
34
+ },
35
+ {
36
+ usage: 'review reject <what is still wrong>',
37
+ short: 'Send it back, with your reason.',
38
+ long: 'Reopens the item as open and attaches your reason. The commits stay on it: knowing what was tried and did not work is worth keeping.',
39
+ },
40
+ { usage: 'review repro', short: 'Re-read the steps for this bench.' },
41
+ ];
42
+ async execute(args = []) {
43
+ // Straight through to the backlog verb's review branch, so there is
44
+ // exactly one implementation of "what an answer means".
45
+ return super.execute(['review', ...args]);
46
+ }
47
+ }
48
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/review.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,aAAc,SAAQ,cAAc;IACrC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAC;IACvB,MAAM,CAAC,WAAW,GAAG,4OAA4O,CAAC;IAElQ,MAAM,CAAC,WAAW,GAAG;QAC7B,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,uCAAuC,EAAE;QACnE;YACE,KAAK,EAAE,8BAA8B;YACrC,KAAK,EAAE,wBAAwB;YAC/B,IAAI,EAAE,0KAA0K;SACjL;QACD;YACE,KAAK,EAAE,qCAAqC;YAC5C,KAAK,EAAE,iCAAiC;YACxC,IAAI,EAAE,sIAAsI;SAC7I;QACD,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,mCAAmC,EAAE;KACtE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,OAAiB,EAAE;QAC/B,oEAAoE;QACpE,wDAAwD;QACxD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC"}
@@ -16,4 +16,4 @@
16
16
  * No import.meta here (engine-graph law) -- this file is loaded by the
17
17
  * server.
18
18
  */
19
- export declare const ENGINE_VERSION = "1.14.0";
19
+ export declare const ENGINE_VERSION = "1.15.0";
@@ -55,5 +55,19 @@
55
55
  // types/save-file.ts).
56
56
  // - The engine no longer accepts a seed's puzzles[] as live content;
57
57
  // it migrates them on load (factories/seed-migration.ts).
58
- export const ENGINE_VERSION = '1.14.0';
58
+ // 1.15.0 (2026-08-28): A PICKET HOLDS ITS CELL. Two shared-session
59
+ // surfaces move, both about a guarding NPC:
60
+ // - COMMAND SEMANTICS: walking onto a guard's doorway no longer
61
+ // displaces them, so "go <dir>" is now refused where it used to
62
+ // succeed. Grid seats are derived in passes and an exact cell used
63
+ // to win, so stepping into the doorway re-seated the guard one
64
+ // square over and doorwayBlocker stopped seeing them (reported as
65
+ // "I was able to just, push Wraith away by moving to the vault
66
+ // door"). A shared table can now be blocked by a body that
67
+ // previously waved everyone through.
68
+ // - NPC BRIEFS, which the hosting server builds: a guarding NPC is
69
+ // told they ARE the barrier. They were told about every barrier
70
+ // around them and never about that, so a picket beside an open
71
+ // door offered to let people past while the geometry refused.
72
+ export const ENGINE_VERSION = '1.15.0';
59
73
  //# sourceMappingURL=engine-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../../../../../src/commands/game/sideQuest/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,6CAA6C;AAC7C,iEAAiE;AACjE,qCAAqC;AACrC,oEAAoE;AACpE,6DAA6D;AAC7D,uEAAuE;AACvE,kDAAkD;AAClD,iEAAiE;AACjE,mEAAmE;AACnE,gEAAgE;AAChE,8DAA8D;AAC9D,kEAAkE;AAClE,kDAAkD;AAClD,kEAAkE;AAClE,4DAA4D;AAC5D,qEAAqE;AACrE,yDAAyD;AACzD,sEAAsE;AACtE,2CAA2C;AAC3C,qEAAqE;AACrE,yDAAyD;AACzD,iEAAiE;AACjE,qEAAqE;AACrE,qEAAqE;AACrE,uEAAuE;AACvE,wEAAwE;AACxE,oEAAoE;AACpE,wEAAwE;AACxE,6DAA6D;AAC7D,uEAAuE;AACvE,sEAAsE;AACtE,0DAA0D;AAC1D,2BAA2B;AAC3B,uEAAuE;AACvE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../../../../../src/commands/game/sideQuest/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,6CAA6C;AAC7C,iEAAiE;AACjE,qCAAqC;AACrC,oEAAoE;AACpE,6DAA6D;AAC7D,uEAAuE;AACvE,kDAAkD;AAClD,iEAAiE;AACjE,mEAAmE;AACnE,gEAAgE;AAChE,8DAA8D;AAC9D,kEAAkE;AAClE,kDAAkD;AAClD,kEAAkE;AAClE,4DAA4D;AAC5D,qEAAqE;AACrE,yDAAyD;AACzD,sEAAsE;AACtE,2CAA2C;AAC3C,qEAAqE;AACrE,yDAAyD;AACzD,iEAAiE;AACjE,qEAAqE;AACrE,qEAAqE;AACrE,uEAAuE;AACvE,wEAAwE;AACxE,oEAAoE;AACpE,wEAAwE;AACxE,6DAA6D;AAC7D,uEAAuE;AACvE,sEAAsE;AACtE,0DAA0D;AAC1D,2BAA2B;AAC3B,uEAAuE;AACvE,8DAA8D;AAC9D,mEAAmE;AACnE,4CAA4C;AAC5C,kEAAkE;AAClE,oEAAoE;AACpE,uEAAuE;AACvE,mEAAmE;AACnE,sEAAsE;AACtE,mEAAmE;AACnE,+DAA+D;AAC/D,yCAAyC;AACzC,qEAAqE;AACrE,oEAAoE;AACpE,mEAAmE;AACnE,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { Direction } from '../types/shared/direction-enum.js';
2
+ import { ISceneSeedJson } from '../types/seed/scene-seed.js';
3
+ import { IReproFixture } from '../types/repro.js';
4
+ /**
5
+ * BUILDING THE BENCH A REVIEW ITEM SHIPS WITH.
6
+ *
7
+ * Two rooms and one sealed door between them. That is the whole scene,
8
+ * on purpose: a repro is a bench, not a run, and every room you have to
9
+ * walk through to reach the thing under test is a room the reproduction
10
+ * can go wrong in.
11
+ *
12
+ * THIS FILE OWNS THE INVARIANTS, and that is the reason it exists
13
+ * rather than the specs carrying raw seed JSON. The author of a repro
14
+ * states INTENT -- "a guard in the doorway", "a keypad sealing north" --
15
+ * and everything that intent implies is derived here, once, for every
16
+ * repro that will ever be written.
17
+ *
18
+ * The invariant that motivated the design: an NPC marked `guarding`
19
+ * whose startSpot is not the sealed exit's spot. Nothing throws.
20
+ * `holdsGround` is simply never consulted, because doorwayOccupants
21
+ * reads the exit cell and the guard is not standing in it. The scene
22
+ * builds, the walkthrough passes, and the repro demonstrates the
23
+ * opposite of what it claims -- a check whose two sides fail together
24
+ * cannot see the failure, which is the dominant defect shape in this
25
+ * codebase's history. An author cannot make that mistake here, because
26
+ * an author never supplies the spot.
27
+ *
28
+ * The other invariants are refusals rather than derivations, and they
29
+ * refuse LOUDLY rather than degrading. A repro that silently does less
30
+ * than it says is the failure mode this whole feature is built against,
31
+ * so "drop the field and carry on" -- which is what the runtime does
32
+ * with a code on a lock -- is exactly the wrong behaviour here.
33
+ */
34
+ /** The room the device stands in, which is also the room that declares
35
+ * the exit it seals. */
36
+ export declare const REPRO_BARRIER_ROOM = "The Approach";
37
+ /** Through the door. */
38
+ export declare const REPRO_FAR_ROOM = "The Far Side";
39
+ /** The one spot that matters, and the one an author never has to name.
40
+ * Both rooms have it: a doorway has two sides, and a guard posted on
41
+ * the far side stands in their own. */
42
+ export declare const REPRO_DOORWAY_SPOT = "doorway";
43
+ /**
44
+ * Somewhere to stand that is NOT the doorway, so "move to the door" is
45
+ * a real move with a real before-state. Without it the runner starts in
46
+ * the doorway and the whole class of "I walked onto the guard's cell"
47
+ * items cannot be reproduced at all.
48
+ *
49
+ * NOT called "open floor", which was the first name and was wrong for a
50
+ * reason worth recording: "open floor" is the ENGINE's phrase for
51
+ * having no named spot at all (see move.ts -- "You peel off to the open
52
+ * floor"). Naming a real spot that made a positioned NPC render as
53
+ * "Grier at the open floor, Grier on the open floor" -- one clause from
54
+ * the spot, one from the generic fallback, reading exactly like a
55
+ * duplicate-render bug. Every reviewer sent to a bench would have filed
56
+ * it. A fixture must not borrow the engine's words for "none".
57
+ */
58
+ export declare const REPRO_FLOOR_SPOT = "back wall";
59
+ /** A fixture that cannot be built into an honest bench. Thrown, never
60
+ * swallowed -- the caller turns it into a sentence for whoever wrote
61
+ * the spec. */
62
+ export declare class ReproFixtureError extends Error {
63
+ constructor(message: string);
64
+ }
65
+ export interface IReproBuild {
66
+ seed: ISceneSeedJson;
67
+ /** What the builder actually named things, so a caller can print
68
+ * steps that refer to them without guessing. */
69
+ names: {
70
+ device?: string;
71
+ npcs: string[];
72
+ direction: Direction;
73
+ barrierRoom: string;
74
+ farRoom: string;
75
+ startRoom: string;
76
+ };
77
+ /** Things worth saying that are not refusals -- a mundane sent to
78
+ * dispel a ward, a key stranded behind its own door. */
79
+ warnings: string[];
80
+ }
81
+ export declare function buildReproScene(fixture: IReproFixture): IReproBuild;
82
+ /** The archetype a fixture asks for, resolved. Separate from the build
83
+ * because the launcher hands the NAME to the game (which rolls the
84
+ * character itself) while the headless walkthrough needs the spread to
85
+ * make a save out of. */
86
+ export declare function reproArchetypeName(fixture: IReproFixture): string;
87
+ export declare function reproArchetype(fixture: IReproFixture): import("../archetypes.js").IArchetype;
@@ -0,0 +1,320 @@
1
+ import { Direction } from '../types/shared/direction-enum.js';
2
+ import { ARCHETYPES, getArchetype } from '../archetypes.js';
3
+ /**
4
+ * BUILDING THE BENCH A REVIEW ITEM SHIPS WITH.
5
+ *
6
+ * Two rooms and one sealed door between them. That is the whole scene,
7
+ * on purpose: a repro is a bench, not a run, and every room you have to
8
+ * walk through to reach the thing under test is a room the reproduction
9
+ * can go wrong in.
10
+ *
11
+ * THIS FILE OWNS THE INVARIANTS, and that is the reason it exists
12
+ * rather than the specs carrying raw seed JSON. The author of a repro
13
+ * states INTENT -- "a guard in the doorway", "a keypad sealing north" --
14
+ * and everything that intent implies is derived here, once, for every
15
+ * repro that will ever be written.
16
+ *
17
+ * The invariant that motivated the design: an NPC marked `guarding`
18
+ * whose startSpot is not the sealed exit's spot. Nothing throws.
19
+ * `holdsGround` is simply never consulted, because doorwayOccupants
20
+ * reads the exit cell and the guard is not standing in it. The scene
21
+ * builds, the walkthrough passes, and the repro demonstrates the
22
+ * opposite of what it claims -- a check whose two sides fail together
23
+ * cannot see the failure, which is the dominant defect shape in this
24
+ * codebase's history. An author cannot make that mistake here, because
25
+ * an author never supplies the spot.
26
+ *
27
+ * The other invariants are refusals rather than derivations, and they
28
+ * refuse LOUDLY rather than degrading. A repro that silently does less
29
+ * than it says is the failure mode this whole feature is built against,
30
+ * so "drop the field and carry on" -- which is what the runtime does
31
+ * with a code on a lock -- is exactly the wrong behaviour here.
32
+ */
33
+ /** The room the device stands in, which is also the room that declares
34
+ * the exit it seals. */
35
+ export const REPRO_BARRIER_ROOM = 'The Approach';
36
+ /** Through the door. */
37
+ export const REPRO_FAR_ROOM = 'The Far Side';
38
+ /** The one spot that matters, and the one an author never has to name.
39
+ * Both rooms have it: a doorway has two sides, and a guard posted on
40
+ * the far side stands in their own. */
41
+ export const REPRO_DOORWAY_SPOT = 'doorway';
42
+ /**
43
+ * Somewhere to stand that is NOT the doorway, so "move to the door" is
44
+ * a real move with a real before-state. Without it the runner starts in
45
+ * the doorway and the whole class of "I walked onto the guard's cell"
46
+ * items cannot be reproduced at all.
47
+ *
48
+ * NOT called "open floor", which was the first name and was wrong for a
49
+ * reason worth recording: "open floor" is the ENGINE's phrase for
50
+ * having no named spot at all (see move.ts -- "You peel off to the open
51
+ * floor"). Naming a real spot that made a positioned NPC render as
52
+ * "Grier at the open floor, Grier on the open floor" -- one clause from
53
+ * the spot, one from the generic fallback, reading exactly like a
54
+ * duplicate-render bug. Every reviewer sent to a bench would have filed
55
+ * it. A fixture must not borrow the engine's words for "none".
56
+ */
57
+ export const REPRO_FLOOR_SPOT = 'back wall';
58
+ const DEFAULT_DEVICE_NAME = {
59
+ lock: 'Maglock',
60
+ panel: 'Keypad',
61
+ ward: 'Chalk Ward',
62
+ };
63
+ const DEFAULT_DEVICE_DESCRIPTION = {
64
+ lock: 'A heavy maglock, seated in the frame and holding the door shut.',
65
+ panel: 'A keypad on a short steel stalk, its display waiting for a code.',
66
+ ward: 'A ward drawn in chalk and old ash across the whole doorway, humming against your teeth.',
67
+ };
68
+ const OPPOSITE = {
69
+ [Direction.NORTH]: Direction.SOUTH,
70
+ [Direction.SOUTH]: Direction.NORTH,
71
+ [Direction.EAST]: Direction.WEST,
72
+ [Direction.WEST]: Direction.EAST,
73
+ [Direction.UP]: Direction.DOWN,
74
+ [Direction.DOWN]: Direction.UP,
75
+ };
76
+ /** Archetypes that can work a ward. `dispel` needs the Art; nothing
77
+ * else in the game does. */
78
+ const AWAKENED = new Set(['Adept', 'Combat Mage', 'Street Shaman']);
79
+ /** Bounds. A repro is a bench: more than this and it is a scene, and a
80
+ * scene is a place a reproduction gets lost in. */
81
+ const MAX_NPCS = 4;
82
+ const MAX_ITEMS = 6;
83
+ /** A fixture that cannot be built into an honest bench. Thrown, never
84
+ * swallowed -- the caller turns it into a sentence for whoever wrote
85
+ * the spec. */
86
+ export class ReproFixtureError extends Error {
87
+ constructor(message) {
88
+ super(message);
89
+ this.name = 'ReproFixtureError';
90
+ }
91
+ }
92
+ function roomFor(side) {
93
+ return side === 'far' ? REPRO_FAR_ROOM : REPRO_BARRIER_ROOM;
94
+ }
95
+ /** A guard who is not in the doorway guards nothing, so `guarding`
96
+ * implies `atDoorway` rather than sitting beside it as a second flag
97
+ * the two could disagree about. */
98
+ function standsInDoorway(npc) {
99
+ return npc.guarding === true || npc.atDoorway === true;
100
+ }
101
+ export function buildReproScene(fixture) {
102
+ const warnings = [];
103
+ // ARCHETYPE. getArchetype falls back to ARCHETYPES[0] on a name it
104
+ // does not know, which is right for a launcher menu and wrong here:
105
+ // a ward repro silently handed a mundane Runner is a repro that
106
+ // cannot be run, and it would look like the fix had failed.
107
+ const archetypeName = fixture.archetype ?? 'Covert Ops';
108
+ if (!ARCHETYPES.some(a => a.name === archetypeName)) {
109
+ throw new ReproFixtureError(`No archetype called "${archetypeName}". One of: ${ARCHETYPES.map(a => a.name).join(', ')}.`);
110
+ }
111
+ const npcs = fixture.npcs ?? [];
112
+ const items = fixture.items ?? [];
113
+ if (npcs.length > MAX_NPCS) {
114
+ throw new ReproFixtureError(`A repro carries at most ${MAX_NPCS} NPCs; this one has ${npcs.length}.`);
115
+ }
116
+ if (items.length > MAX_ITEMS) {
117
+ throw new ReproFixtureError(`A repro carries at most ${MAX_ITEMS} items; this one has ${items.length}.`);
118
+ }
119
+ const duplicateNpc = npcs.find((n, i) => npcs.findIndex(o => o.name === n.name) !== i);
120
+ if (duplicateNpc) {
121
+ throw new ReproFixtureError(`Two NPCs are both called "${duplicateNpc.name}" -- a name has to pick one body out.`);
122
+ }
123
+ const barrier = fixture.barrier;
124
+ const direction = barrier?.sealsExit ?? Direction.NORTH;
125
+ const startSide = fixture.start ?? 'barrier';
126
+ const startRoom = roomFor(startSide);
127
+ let device;
128
+ if (barrier) {
129
+ // INVARIANT 1: a code belongs to a panel and nowhere else. The
130
+ // Device constructor DROPS it on a lock or a ward, silently and
131
+ // correctly -- that is the runtime honouring "you can't talk at a
132
+ // mechanical lock". Inheriting that drop here would produce a bench
133
+ // whose stated credential does nothing, and the reviewer would read
134
+ // the refusal as the bug still being present.
135
+ if (barrier.code && barrier.kind !== 'panel') {
136
+ throw new ReproFixtureError(`A ${barrier.kind} has no code -- only a panel does. Give it a keyItem, or let it be picked or dispelled.`);
137
+ }
138
+ // A panel with no code cannot be opened the way a panel opens.
139
+ if (barrier.kind === 'panel' && !barrier.code && !barrier.keyItem && !barrier.open) {
140
+ warnings.push('This panel has neither a code nor a key, so the only way through it is force.');
141
+ }
142
+ if (barrier.kind === 'ward' && !AWAKENED.has(archetypeName)) {
143
+ warnings.push(`A ward answers to the Art, and ${archetypeName} is mundane -- fine if the refusal is the point, otherwise use Combat Mage.`);
144
+ }
145
+ device = {
146
+ name: barrier.name ?? DEFAULT_DEVICE_NAME[barrier.kind],
147
+ description: DEFAULT_DEVICE_DESCRIPTION[barrier.kind],
148
+ kind: barrier.kind,
149
+ code: barrier.code,
150
+ keyItem: barrier.keyItem,
151
+ // INVARIANT 2: the device stands AT the doorway it seals, so
152
+ // reaching it is reaching the door. bypass-command resolves the
153
+ // spot as exitSpot(room, opensExit) ?? atSpot, and a device
154
+ // sitting at neither is a device you cannot walk up to.
155
+ atSpot: REPRO_DOORWAY_SPOT,
156
+ // INVARIANT 3: naming the exit is what locks the door.
157
+ // scene-factory only LOGS when a device names something that is
158
+ // not a door, which on a bench means an unsealed doorway and a
159
+ // reviewer concluding the barrier never worked.
160
+ opensExit: direction,
161
+ // The walkthrough test caught this being dropped, which is the
162
+ // point of having one: `open: true` was accepted here and went
163
+ // nowhere, because the seed type had no such field. A fixture
164
+ // that quietly does less than it says is the exact failure this
165
+ // whole feature exists to prevent -- so the field was added to
166
+ // IDeviceSeedJson rather than the intent silently discarded.
167
+ open: barrier.open === true,
168
+ };
169
+ }
170
+ const spots = [
171
+ { name: REPRO_DOORWAY_SPOT },
172
+ { name: REPRO_FLOOR_SPOT },
173
+ ];
174
+ // THE BARRIER IS NAMED IN THE PROSE, and where it stands, and which
175
+ // way the door runs.
176
+ //
177
+ // A device is only surfaced by `search`, which is right for a real
178
+ // scene -- finding things is play. On a bench it is a trap: the whole
179
+ // promise is "you start standing at the thing", and the first player
180
+ // through one reported exactly that ("There is no location for the
181
+ // door, so I cannot get to it"). They looked, saw two spots and no
182
+ // door, and concluded the fixture was broken. Nothing here should
183
+ // have to be discovered.
184
+ const barrierRoom = {
185
+ name: REPRO_BARRIER_ROOM,
186
+ description: barrier
187
+ ? `A short concrete run ending at one door, ${direction} of you. `
188
+ + `${device.name} is fixed beside the doorway${barrier.open ? ', standing open' : ', holding it shut'}. `
189
+ + `There is nothing else here, which is the point -- whatever is wrong is wrong at that door.`
190
+ : `A short concrete run with a door ${direction} of you and nothing else in it.`,
191
+ roomType: 'Street',
192
+ size: 'medium',
193
+ spots,
194
+ isStartRoom: startSide === 'barrier',
195
+ exits: [{ direction, targetRoom: REPRO_FAR_ROOM, atSpot: REPRO_DOORWAY_SPOT }],
196
+ devices: device ? [device] : undefined,
197
+ };
198
+ const farRoom = {
199
+ name: REPRO_FAR_ROOM,
200
+ description: `The other side of the door, which lies ${OPPOSITE[direction]} of you`
201
+ + `${barrier ? ` and is held from the far side by ${device.name}` : ''}. Bare, and no further out.`,
202
+ roomType: 'Street',
203
+ size: 'medium',
204
+ spots,
205
+ isStartRoom: startSide === 'far',
206
+ // INVARIANT 3, other half: a door has two sides. Without the return
207
+ // exit the far room is a one-way pocket, and "go back and try it
208
+ // again" -- which is most of what reviewing a fix consists of --
209
+ // would not work.
210
+ exits: [{ direction: OPPOSITE[direction], targetRoom: REPRO_BARRIER_ROOM, atSpot: REPRO_DOORWAY_SPOT }],
211
+ };
212
+ const npcSeeds = npcs.map(npc => {
213
+ const side = npc.side ?? 'barrier';
214
+ const inDoorway = standsInDoorway(npc);
215
+ return {
216
+ description: npc.concept,
217
+ dialogue: [],
218
+ player: {
219
+ name: npc.name,
220
+ startLocation: roomFor(side),
221
+ // THE INVARIANT THIS FILE EXISTS FOR. Derived from the
222
+ // direction the barrier seals, never supplied -- a guarding NPC
223
+ // standing anywhere else holds nothing, silently, and the
224
+ // walkthrough would still pass.
225
+ startSpot: inDoorway ? REPRO_DOORWAY_SPOT : REPRO_FLOOR_SPOT,
226
+ guarding: npc.guarding === true,
227
+ lifestyleChoices: [],
228
+ combat: {
229
+ body: 4, agility: 4, reaction: 4, combatSkill: 3, strength: 4,
230
+ willpower: 3, logic: 3, intuition: 3, charisma: 3, edge: 2,
231
+ skills: { unarmed: 3, perception: 3, negotiation: 3 },
232
+ },
233
+ },
234
+ };
235
+ });
236
+ const itemSeeds = items.map(it => ({
237
+ name: it.name,
238
+ description: it.description,
239
+ details: it.details,
240
+ size: 'Tiny',
241
+ category: 'Document',
242
+ shape: 'Rectangle',
243
+ color: 'Gray',
244
+ texture: 'Plain',
245
+ rating: 'Common',
246
+ weight: 0.1,
247
+ room: roomFor(it.side ?? 'barrier'),
248
+ atSpot: REPRO_FLOOR_SPOT,
249
+ }));
250
+ // INVARIANT 4: the key is on the side the runner starts on. A key
251
+ // behind the door it opens is a bench nobody can work, and it reads
252
+ // as the fix having failed rather than the fixture being wrong.
253
+ if (barrier?.keyItem) {
254
+ itemSeeds.push({
255
+ name: barrier.keyItem,
256
+ description: `The thing that opens the ${device?.name ?? 'door'}.`,
257
+ size: 'Tiny',
258
+ category: 'Key',
259
+ shape: 'Rectangle',
260
+ color: 'Steel',
261
+ texture: 'Milled',
262
+ rating: 'Common',
263
+ weight: 0.1,
264
+ room: startRoom,
265
+ atSpot: REPRO_FLOOR_SPOT,
266
+ });
267
+ }
268
+ if (barrier && !barrier.open && startSide === 'far') {
269
+ warnings.push(`The runner starts on the far side, so the ${device?.name} is through a shut door and cannot be worked from there -- right for testing what someone SAYS about the door, wrong for testing the door.`);
270
+ }
271
+ const seed = {
272
+ name: 'Repro Bench',
273
+ story: fixture.premise
274
+ ?? 'A bench, not a job. One door, whatever is standing at it, and nothing else to be distracted by.',
275
+ currency: { type: 'nuyen', authorityRoomTags: [] },
276
+ residenceTiers: ['Street', 'Squatter', 'Low'],
277
+ rooms: [barrierRoom, farRoom],
278
+ npcs: npcSeeds,
279
+ items: itemSeeds,
280
+ puzzles: [],
281
+ player: {
282
+ name: 'Testbed',
283
+ startLocation: startRoom,
284
+ // NO RESIDENCE. Copied from paritySeed originally, where it is
285
+ // right -- that fixture models a real run. A bench models nothing
286
+ // and nobody lives in it; declaring a home here only invites the
287
+ // lifestyle machinery to have opinions about a two-room scene.
288
+ lifestyleChoices: [],
289
+ startingItems: [],
290
+ },
291
+ // NO winCondition AND NO client, deliberately. A bench cannot be
292
+ // won, because there is nothing to win -- adding a payout would put
293
+ // a wrap-up, a settlement and an offer of more work in the way of a
294
+ // reviewer who came to look at one door. scene1 already proves a
295
+ // scene boots without a client.
296
+ };
297
+ return {
298
+ seed,
299
+ names: {
300
+ device: device?.name,
301
+ npcs: npcs.map(n => n.name),
302
+ direction,
303
+ barrierRoom: REPRO_BARRIER_ROOM,
304
+ farRoom: REPRO_FAR_ROOM,
305
+ startRoom,
306
+ },
307
+ warnings,
308
+ };
309
+ }
310
+ /** The archetype a fixture asks for, resolved. Separate from the build
311
+ * because the launcher hands the NAME to the game (which rolls the
312
+ * character itself) while the headless walkthrough needs the spread to
313
+ * make a save out of. */
314
+ export function reproArchetypeName(fixture) {
315
+ return fixture.archetype ?? 'Covert Ops';
316
+ }
317
+ export function reproArchetype(fixture) {
318
+ return getArchetype(reproArchetypeName(fixture));
319
+ }
320
+ //# sourceMappingURL=repro-scene.js.map