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