@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.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/_index.d.ts +1 -0
- package/bundle/typescript/src/commands/game/_index.js +1 -0
- package/bundle/typescript/src/commands/game/_index.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.d.ts +103 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js +423 -25
- package/bundle/typescript/src/commands/game/sideQuest/commands/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +61 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.d.ts +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.js +48 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/review.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.d.ts +87 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +320 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +32 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +8 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +48 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +91 -7
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +26 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +55 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +106 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.d.ts +17 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js +250 -36
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-dump.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.d.ts +30 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-review.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.d.ts +85 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js +100 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog-vocab.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.d.ts +45 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js +45 -3
- package/bundle/typescript/src/commands/game/sideQuest/utilities/backlog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js +22 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/commit-ref.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.d.ts +42 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js +57 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/handoff.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.d.ts +68 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js +147 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/repro-launch.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +39 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js +46 -0
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.d.ts +3 -1
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +269 -42
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/tools/log/log.class.d.ts +17 -0
- package/bundle/typescript/src/tools/log/log.class.js +30 -1
- package/bundle/typescript/src/tools/log/log.class.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
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"}
|
|
@@ -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
|
-
|
|
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
|