@maka/maka-cli 5.66.0 → 5.68.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 (123) 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/archetypes.js +6 -1
  6. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js +4 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js.map +1 -1
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.d.ts +103 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js +423 -25
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js.map +1 -1
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +15 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +61 -4
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.d.ts +39 -3
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js +85 -4
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js.map +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.d.ts +37 -17
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +53 -115
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.d.ts +55 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js +203 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js.map +1 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +23 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +42 -7
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +27 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +28 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/order.js +69 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/order.js.map +1 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/register.d.ts +15 -2
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +32 -6
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/register.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/review.d.ts +39 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/review.js +48 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/review.js.map +1 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.d.ts +35 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js +108 -0
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js.map +1 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.d.ts +27 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +7 -2
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.d.ts +57 -0
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.js +162 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/unravel.js.map +1 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  48. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +31 -1
  49. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  50. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.d.ts +87 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +320 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -0
  53. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +32 -0
  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 +8 -1
  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 +98 -11
  58. package/bundle/typescript/src/commands/game/sideQuest/game.js +239 -35
  59. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +24 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +63 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
  63. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +9 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +9 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  66. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +57 -1
  67. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +62 -1
  68. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  69. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +10 -3
  70. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +5 -1
  71. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.d.ts +108 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.js +186 -0
  74. package/bundle/typescript/src/commands/game/sideQuest/resonance-actions.js.map +1 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.d.ts +88 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.js +137 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/sprite-powers.js.map +1 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +106 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/types/repro.js +2 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +32 -1
  82. package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +12 -0
  83. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +9 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/ui.js +3 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  86. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.d.ts +17 -1
  87. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js +250 -36
  88. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js.map +1 -1
  89. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.d.ts +30 -1
  90. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js +58 -0
  91. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js.map +1 -1
  92. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.d.ts +85 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js +100 -0
  94. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js.map +1 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.d.ts +45 -3
  96. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js +45 -3
  97. package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js.map +1 -1
  98. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.d.ts +13 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js +22 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js.map +1 -0
  101. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.d.ts +13 -1
  102. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +36 -3
  103. package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js.map +1 -1
  104. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.d.ts +42 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js +57 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js.map +1 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +14 -0
  108. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
  109. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.d.ts +68 -0
  110. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js +147 -0
  111. package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js.map +1 -0
  112. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +39 -0
  113. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  114. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.d.ts +1 -0
  115. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js +46 -0
  116. package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js.map +1 -0
  117. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.d.ts +3 -1
  118. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +352 -42
  119. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  120. package/bundle/typescript/src/tools/log/log.class.d.ts +17 -0
  121. package/bundle/typescript/src/tools/log/log.class.js +30 -1
  122. package/bundle/typescript/src/tools/log/log.class.js.map +1 -1
  123. package/package.json +1 -1
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repro-scene.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/factories/repro-scene.ts"],"names":[],"mappings":"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"}
@@ -1174,6 +1174,38 @@ function resolveDirectionCollisions(skeleton) {
1174
1174
  */
1175
1175
  export function normalizeSkeleton(skeleton) {
1176
1176
  const notes = [];
1177
+ // WHO THE CLIENT IS, before anything counts them.
1178
+ //
1179
+ // NOT a missing guard -- an audit of the four names the model writes
1180
+ // about other lists (2026-08-28) found all four checked: heldBy,
1181
+ // winCondition.toNpc and .item fail in validateSkeleton below, and
1182
+ // `client` fails in validateClient. The gap is narrower and it is
1183
+ // about COST and ORDER.
1184
+ //
1185
+ // COST: validateClient REJECTS an unresolvable client, so a one-word
1186
+ // near miss -- "Mr. Osprey" against a roster saying "Osprey" -- burns
1187
+ // a full regeneration to fix a name the skeleton already contains.
1188
+ // Every other name reference gained a repair on 2026-08-27; this one
1189
+ // did not, so it is the only near miss still paying full price.
1190
+ //
1191
+ // ORDER: the opposition floor counts hostiles as
1192
+ // `npcs.filter(n => n.name !== skeleton.client)` in validateSkeleton,
1193
+ // which runs BEFORE validateClient ever sees the assembled scene. So
1194
+ // an unrepaired near miss counts the CLIENT as a hostile and skews
1195
+ // the difficulty floor by one -- silently, and in a check whose whole
1196
+ // job is to notice an under-populated cast.
1197
+ //
1198
+ // Repairing here fixes both: the count is right, and a resolvable
1199
+ // name never reaches the rejection. What genuinely matches nothing
1200
+ // still falls to validateClient, which owns that verdict and words
1201
+ // it better than a duplicate here would.
1202
+ if (skeleton.client) {
1203
+ const canonicalClient = resolveNamedRef(skeleton.client, (skeleton.npcs ?? []).map(n => n.name), 'the scene-level "client"');
1204
+ if (canonicalClient && canonicalClient !== skeleton.client) {
1205
+ notes.push(`client "${skeleton.client}" renamed to "${canonicalClient}" to match the roster.`);
1206
+ skeleton.client = canonicalClient;
1207
+ }
1208
+ }
1177
1209
  // Topology before anything that reasons about topology: freeStranded-
1178
1210
  // Rooms, clue placement and the curb all walk this graph, and they
1179
1211
  // should walk the one the ENGINE will actually build.