@maka/maka-cli 5.196.0 → 5.198.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/advance.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ar.js +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +8 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/download.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/edit-file.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js +5 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +62 -13
- package/bundle/typescript/src/commands/game/sideQuest/commands/hide.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/install.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +5 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/register.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +19 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/shout.js +69 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/snoop.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sprites.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +7 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +45 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +13 -5
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +80 -57
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/action-cost.js +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/earshot.js +116 -10
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.js +83 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +14 -14
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-style.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/overwatch.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/shared-run.js +8 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sameSpot, spotOf, spotsActive, OPEN_FLOOR } from './spots.js';
|
|
1
|
+
import { sameSpot, spotOf, spotsActive, OPEN_FLOOR, IMMEDIATE_VICINITY_M, actorDistanceMeters } from './spots.js';
|
|
2
2
|
/**
|
|
3
3
|
* WHO CAN OVERHEAR A MURMUR (playtest, Deditri: "a private place for
|
|
4
4
|
* calls"). A call is audible to the room, and that is correct and
|
|
@@ -17,17 +17,25 @@ import { sameSpot, spotOf, spotsActive, OPEN_FLOOR } from './spots.js';
|
|
|
17
17
|
* Market's "shadowed edge". Nothing new is invented here; the call path
|
|
18
18
|
* just respects the position everyone else already respects.
|
|
19
19
|
*
|
|
20
|
-
* THE RULE, and why it is not Room.say's rule
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* escalates
|
|
24
|
-
* murmur reaches your spot and stops there.
|
|
20
|
+
* THE RULE, and why it is STILL not Room.say's rule. A murmur into a
|
|
21
|
+
* commlink reaches your spot and stops there, full stop -- talking
|
|
22
|
+
* quietly into a phone is the whole point of a phone, and it never
|
|
23
|
+
* escalates however alone you are standing.
|
|
25
24
|
*
|
|
26
|
-
*
|
|
25
|
+
* Room speech used to share that shape and no longer does. `say` is now
|
|
26
|
+
* bounded by each LISTENER's hearing range in real metres
|
|
27
|
+
* (canHearSpeech below, en3p6eCpiRuqsTSKm) and never escalates on its
|
|
28
|
+
* own; shouting is a verb you type (commands/shout.ts). The two rules
|
|
29
|
+
* live in one file so they can be read against each other, which is the
|
|
30
|
+
* lesson of the drift that produced this module in the first place.
|
|
31
|
+
*
|
|
32
|
+
* WHAT DOES NOT BUY PRIVACY ON A CALL, deliberately:
|
|
27
33
|
* - CIRCULATING (no atSpot) is the compat state and means "anywhere in
|
|
28
|
-
* the room" --
|
|
29
|
-
*
|
|
30
|
-
*
|
|
34
|
+
* the room" -- your half of a CALL still reaches and is reached by
|
|
35
|
+
* everything, because there is no spot for it to stop at. Note this
|
|
36
|
+
* is the opposite of what circulating now means for ordinary speech,
|
|
37
|
+
* and the asymmetry is argued at canHearSpeech: a murmur has a place
|
|
38
|
+
* it comes from and a phone call does not.
|
|
31
39
|
* - THE OPEN FLOOR is a real position but it is bare ground in the
|
|
32
40
|
* middle of the room, no cover, always visible (see spots.ts). Being
|
|
33
41
|
* apart from people is not the same as being out of earshot, and a
|
|
@@ -40,6 +48,104 @@ import { sameSpot, spotOf, spotsActive, OPEN_FLOOR } from './spots.js';
|
|
|
40
48
|
* Nothing here caches, stores, or derives a second notion of where
|
|
41
49
|
* anybody is.
|
|
42
50
|
*/
|
|
51
|
+
/**
|
|
52
|
+
* HOW FAR ORDINARY SPEECH CARRIES, and how far one listener can hear it
|
|
53
|
+
* (en3p6eCpiRuqsTSKm: "'say' should only be perceived by anyone within
|
|
54
|
+
* 3-5m un-augmented. Really, it's the audible perception range of the
|
|
55
|
+
* listener").
|
|
56
|
+
*
|
|
57
|
+
* CANON IS SILENT ON THE DISTANCE, checked rather than assumed. SR5
|
|
58
|
+
* handles hearing as dice and limits, never as a range table: an audio
|
|
59
|
+
* enhancement "adds its Rating to your limit in audio Perception Tests"
|
|
60
|
+
* (p.445), cyberears give "hearing with normal ranges" (p.453-454), and
|
|
61
|
+
* the only metre figures anywhere near it belong to the directional and
|
|
62
|
+
* laser microphones (p.445, 100 m). There is no rule for how far a
|
|
63
|
+
* sentence travels, so THESE NUMBERS ARE THIS ENGINE'S, said out loud
|
|
64
|
+
* rather than dressed as the book's.
|
|
65
|
+
*
|
|
66
|
+
* What is defensible about them:
|
|
67
|
+
* - 5 m is already this engine's word for conversational range
|
|
68
|
+
* (spots.ts IMMEDIATE_VICINITY_M, and voiceBandFor calls anything
|
|
69
|
+
* inside it 'conversation'). Speech reaching exactly as far as the
|
|
70
|
+
* engine already says a conversation reaches is one number, not two.
|
|
71
|
+
* - The listener's perception dice extend it. Canon gives audio gear a
|
|
72
|
+
* DICE/LIMIT bonus and this engine models speech by DISTANCE, so the
|
|
73
|
+
* conversion is ours; a metre per die is the smallest unit that can
|
|
74
|
+
* express "you hear better than they do" in a model made of 2 m
|
|
75
|
+
* cells. Cyberears (+1) hear a metre further. That is an
|
|
76
|
+
* extrapolation of a canon bonus, not a canon rule.
|
|
77
|
+
*/
|
|
78
|
+
export const SPEECH_METERS = IMMEDIATE_VICINITY_M;
|
|
79
|
+
/**
|
|
80
|
+
* A SHOUT (commands/shout.ts) -- "anyone within a reasonable distance,
|
|
81
|
+
* 50m?" Also this engine's number and not the book's. In practice it is
|
|
82
|
+
* room-wide: the largest room this generator builds is tens of metres
|
|
83
|
+
* across, so the figure is a ceiling that says "the whole place hears
|
|
84
|
+
* you" rather than a boundary anyone will stand outside.
|
|
85
|
+
*/
|
|
86
|
+
export const SHOUT_METERS = 50;
|
|
87
|
+
/**
|
|
88
|
+
* How far THIS listener can hear, in metres. The report's own framing:
|
|
89
|
+
* range is a property of the ear, not of the mouth.
|
|
90
|
+
*
|
|
91
|
+
* Reads the same `perception` pool a Perception test reads, so a piece
|
|
92
|
+
* of chrome and a quality that both say "+1 perception" extend hearing
|
|
93
|
+
* identically and neither needs a line here. Never below the base -- a
|
|
94
|
+
* perception PENALTY should make you miss things you roll for, not
|
|
95
|
+
* shrink the room to nothing.
|
|
96
|
+
*/
|
|
97
|
+
export function hearingRangeMeters(listener) {
|
|
98
|
+
const bonus = listener.augBonus('perception') + listener.qualityBonus('perception');
|
|
99
|
+
return Math.max(SPEECH_METERS, SPEECH_METERS + bonus);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* CAN THIS LISTENER HEAR ORDINARY SPEECH FROM THERE -- the whole of the
|
|
103
|
+
* `say` rule, in one place so the verb, the NPC fan-out and any future
|
|
104
|
+
* reader cannot drift apart the way Room.say and murmurAudience once did.
|
|
105
|
+
*
|
|
106
|
+
* THE CIRCULATING ASYMMETRY, which is the actual defect this item was
|
|
107
|
+
* filed about. "Circulating" (no atSpot) is the compat state meaning
|
|
108
|
+
* "somewhere in this room, unfixed", and the old rule read it as "at
|
|
109
|
+
* every spot at once" -- so a circulating listener heard everything.
|
|
110
|
+
* That is how a patrol officer walking through the back room answered a
|
|
111
|
+
* question put to a Johnson at a table (CugfYsH8DWH8FcfgS, same
|
|
112
|
+
* session): Kessler was `moving through`, so he was nowhere, so he was
|
|
113
|
+
* everywhere.
|
|
114
|
+
*
|
|
115
|
+
* ASK spotOf, NEVER actorCell. A circulating body IS given a cell by
|
|
116
|
+
* seatingIn -- its "drift seat" -- and that cell is deliberately not a
|
|
117
|
+
* position: spots.ts says so in as many words ("a place to DRAW
|
|
118
|
+
* someone, never a position they hold"), and sameSpot and canReach both
|
|
119
|
+
* short-circuit on atSpot before any of it is consulted. A distance
|
|
120
|
+
* check written against the drift seat would have put Kessler a
|
|
121
|
+
* plausible two metres from the table and let him hear after all --
|
|
122
|
+
* correct-looking arithmetic over a number that means nothing.
|
|
123
|
+
*
|
|
124
|
+
* A LISTENER with no position is therefore NOT in earshot of a murmur:
|
|
125
|
+
* someone crossing the room is not standing at your table. A SPEAKER
|
|
126
|
+
* with no position still reaches the room, and that is deliberate rather
|
|
127
|
+
* than an oversight -- they are moving among everybody, there is no
|
|
128
|
+
* "here" for the words to fall short of, and refusing to let a
|
|
129
|
+
* circulating player be heard at all would make the game's main verb
|
|
130
|
+
* fail for the commonest position in it.
|
|
131
|
+
*
|
|
132
|
+
* A room that declares NO spots keeps the old room-wide behaviour
|
|
133
|
+
* outright. There is no geometry in such a room to be near or far in,
|
|
134
|
+
* and inventing one would refuse speech in the small abstract rooms
|
|
135
|
+
* where `say` is all there is.
|
|
136
|
+
*/
|
|
137
|
+
export function canHearSpeech(speaker, listener) {
|
|
138
|
+
const room = speaker.currentLocation;
|
|
139
|
+
if (!spotsActive(room))
|
|
140
|
+
return true;
|
|
141
|
+
// The speaker is unplaced: they are moving among everyone.
|
|
142
|
+
if (spotOf(speaker) === undefined)
|
|
143
|
+
return true;
|
|
144
|
+
// The listener is unplaced: they are passing through, not standing here.
|
|
145
|
+
if (spotOf(listener) === undefined)
|
|
146
|
+
return false;
|
|
147
|
+
return actorDistanceMeters(room, speaker, listener) <= hearingRangeMeters(listener);
|
|
148
|
+
}
|
|
43
149
|
/**
|
|
44
150
|
* Everyone in the speaker's room close enough to catch a murmur.
|
|
45
151
|
* From a named spot: whoever shares it, plus anyone circulating.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hostConcealedFrom } from './jackpoint.js';
|
|
2
|
-
import { matchesHostName, hostLabel } from './grid-names.js';
|
|
3
|
-
import { fuzzyPickName } from './fuzzy-match.js';
|
|
2
|
+
import { matchesHostName, hostLabel, hostAliases } from './grid-names.js';
|
|
3
|
+
import { fuzzyPickName, normalizeLoose } from './fuzzy-match.js';
|
|
4
|
+
import { MAX_MARKS } from './marks.js';
|
|
4
5
|
/**
|
|
5
6
|
* THE GRID HAS NO STREETS (user canon check 2026-09-02; SR5 pp.217-218,
|
|
6
7
|
* p.246). Jacked in and outside a host, a persona is on its grid: "a
|
|
@@ -88,6 +89,64 @@ export function resolveHostInReach(rooms, actor, query) {
|
|
|
88
89
|
const picked = fuzzyPickName(bare, hosts.map(r => r.name));
|
|
89
90
|
return picked ? hosts.find(r => r.name === picked) : undefined;
|
|
90
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* DOES THIS QUERY NAME A HOST OUTRIGHT -- no fuzz, no guessing.
|
|
94
|
+
*
|
|
95
|
+
* resolveHostInReach ends in a fuzzy fallback, which is right for it:
|
|
96
|
+
* once a player has committed to meaning a host, a near miss should
|
|
97
|
+
* still find one. This is the other question, and it exists because
|
|
98
|
+
* `hack`/`mark` resolve PANs FIRST (WTLfRHxJEnPmN2ht9: "this keeps
|
|
99
|
+
* matching on Torque's PAN even though I gave it exactly the host
|
|
100
|
+
* name"). The PAN pass runs fuzzyPickName over the names of every body
|
|
101
|
+
* in reach, and that matcher's tier 2 is substring containment in
|
|
102
|
+
* either direction -- so a runner standing near anyone whose name is
|
|
103
|
+
* contained in the host's could never reach the host by typing its
|
|
104
|
+
* name, because the PAN pass answered before the host pass was asked.
|
|
105
|
+
*
|
|
106
|
+
* So the caller asks this first: an EXACT alias hit (grid-names.ts's
|
|
107
|
+
* hostAliases, loosely normalized -- case, punctuation and a trailing
|
|
108
|
+
* "host"/"node" all forgiven) means the player named a host and the PAN
|
|
109
|
+
* pass must not get to reinterpret it. Anything less falls through and
|
|
110
|
+
* the old order stands, so a genuinely ambiguous word still reaches the
|
|
111
|
+
* PAN it always did.
|
|
112
|
+
*
|
|
113
|
+
* The bare aliases ("host", "the host", "node", "ice") resolve to the
|
|
114
|
+
* host over the vicinity, exactly as resolveHostInReach resolves them
|
|
115
|
+
* -- one query, one answer, whichever door asks.
|
|
116
|
+
*/
|
|
117
|
+
export function namesHostExactly(rooms, actor, query) {
|
|
118
|
+
const q = query.trim().toLowerCase();
|
|
119
|
+
if (q === '')
|
|
120
|
+
return undefined;
|
|
121
|
+
if (q === 'host' || q === 'the host')
|
|
122
|
+
return hostOver(gridVicinity(actor));
|
|
123
|
+
const near = hostOver(gridVicinity(actor));
|
|
124
|
+
if (near && hostNamed(near, q))
|
|
125
|
+
return near;
|
|
126
|
+
return hostsInReach(rooms, actor).find(r => hostNamed(r, q));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* THE APOSTROPHE, which a player does not type and the matcher insisted
|
|
130
|
+
* on. normalizeLoose SPLITS punctuation rather than dropping it -- right,
|
|
131
|
+
* and load-bearing, because "Mr.Krow" has to become "mr krow" and not
|
|
132
|
+
* "mrkrow". The side effect is that "Torque's Garage" normalizes to
|
|
133
|
+
* "torque s garage", so a player typing the name they READ, minus the
|
|
134
|
+
* apostrophe they did not bother with, missed every alias.
|
|
135
|
+
*
|
|
136
|
+
* So the comparison is tried twice: matchesHostName first (the shared
|
|
137
|
+
* rule, unchanged and still the authority), then again with whitespace
|
|
138
|
+
* squashed out of both sides, which forgives exactly the punctuation the
|
|
139
|
+
* player dropped and nothing else. "torques garage" reaches the host;
|
|
140
|
+
* "torque" -- a whole word shorter -- still does not, which is the line
|
|
141
|
+
* that keeps this from stealing the PAN pass.
|
|
142
|
+
*/
|
|
143
|
+
function hostNamed(room, q) {
|
|
144
|
+
if (matchesHostName(q, room))
|
|
145
|
+
return true;
|
|
146
|
+
const squash = (s) => normalizeLoose(s).replace(/\s+/g, '');
|
|
147
|
+
const squashed = squash(q);
|
|
148
|
+
return squashed.length > 0 && hostAliases(room).some(a => squash(a) === squashed);
|
|
149
|
+
}
|
|
91
150
|
/** Noise between the persona and a room's icons: 0 for the vicinity (and inside a host), -DISTRICT_NOISE dice elsewhere. */
|
|
92
151
|
export function noiseFor(actor, room) {
|
|
93
152
|
if (actor.insideHost)
|
|
@@ -113,9 +172,20 @@ export function noiseNote(noise) {
|
|
|
113
172
|
export function hostLine(room, actor) {
|
|
114
173
|
const inside = actor.insideHost === room;
|
|
115
174
|
const marks = room.hostMarksBy.get(actor.name) ?? 0;
|
|
175
|
+
// STANDING IS TWO FACTS, NOT ONE (spcebZTKN24GYRHXW: "it's saying
|
|
176
|
+
// I've already cracked open (hold 3 marks) but I don't").
|
|
177
|
+
//
|
|
178
|
+
// "cracked open" is the SITE's state -- somebody took this node to
|
|
179
|
+
// three marks and its vault has been hanging open ever since. The
|
|
180
|
+
// marks count is YOUR state, and canon wipes it on every reboot
|
|
181
|
+
// (p.236). The two used to be one string with the crack winning, so a
|
|
182
|
+
// rebooted persona holding nothing read a line that says the place is
|
|
183
|
+
// yours -- while every device on it refused them for want of marks.
|
|
184
|
+
// Print both whenever they disagree.
|
|
185
|
+
const held = `${marks}/${MAX_MARKS} marks`;
|
|
116
186
|
const standing = room.hostSanctioned ? 'haven'
|
|
117
|
-
: room.hostCracked ? 'cracked open'
|
|
118
|
-
: marks > 0 ?
|
|
187
|
+
: room.hostCracked ? (marks >= MAX_MARKS ? 'cracked open' : `cracked open, but you hold ${held}`)
|
|
188
|
+
: marks > 0 ? held
|
|
119
189
|
: 'sealed';
|
|
120
190
|
const alert = room.hostAlert && !room.hostSanctioned ? ', walls UP' : '';
|
|
121
191
|
const over = hostOver(gridVicinity(actor)) === room ? ', over you' : '';
|
|
@@ -133,9 +203,16 @@ export function hostLine(room, actor) {
|
|
|
133
203
|
export function hostDockLine(room, actor, width) {
|
|
134
204
|
const inside = actor.insideHost === room;
|
|
135
205
|
const marks = room.hostMarksBy.get(actor.name) ?? 0;
|
|
136
|
-
|
|
206
|
+
// Same split as hostLine, cut to the dock's width: "open" alone is the
|
|
207
|
+
// site's state and was reading as the persona's. A crack you no longer
|
|
208
|
+
// hold marks on shows the marks, because that is the number every
|
|
209
|
+
// device on this node is about to check (spcebZTKN24GYRHXW).
|
|
210
|
+
const tag = room.hostCracked && marks >= MAX_MARKS ? ' open'
|
|
211
|
+
: room.hostCracked ? ` open ${marks}/${MAX_MARKS}`
|
|
212
|
+
: marks > 0 ? ` ${marks}/${MAX_MARKS}`
|
|
213
|
+
: '';
|
|
137
214
|
const raw = `${inside ? '▣' : '●'} ${room.name} HR${room.hostRating}${tag}`;
|
|
138
215
|
const cut = [...raw].slice(0, Math.max(4, width)).join('');
|
|
139
|
-
return `{
|
|
216
|
+
return `{light-blue-fg}${cut}{/light-blue-fg}`;
|
|
140
217
|
}
|
|
141
218
|
//# sourceMappingURL=grid-reach.js.map
|
|
@@ -460,20 +460,20 @@ export function gridSculpt(actor, room) {
|
|
|
460
460
|
const purpose = room.host?.purpose;
|
|
461
461
|
const sculpt = room.host?.sculpt;
|
|
462
462
|
return [
|
|
463
|
-
`{
|
|
464
|
-
`{
|
|
463
|
+
`{light-blue-fg}INSIDE :: ${hostLabel(room, { capital: true }).toUpperCase()} [HR ${room.hostRating}]${purpose ? ` -- ${purpose}` : ''}{/light-blue-fg}`,
|
|
464
|
+
`{light-blue-fg}${sculpt ?? 'Architecture all the way around -- the outside grid is a wall away and might as well be weather.'}{/light-blue-fg}`,
|
|
465
465
|
].join('\n');
|
|
466
466
|
}
|
|
467
467
|
// ON THE GRID (SR5 pp.217-218): no street under the persona -- a black
|
|
468
468
|
// flatland, the body's room named only because that is where the
|
|
469
469
|
// signal comes from. A host directly overhead gets a line of its own.
|
|
470
470
|
const body = actor.bodyRoom ?? room;
|
|
471
|
-
const header = `{
|
|
471
|
+
const header = `{light-blue-fg}THE GRID :: jacked in from ${body.name.toUpperCase()}{/light-blue-fg}`;
|
|
472
472
|
const grid = actor.currentGrid;
|
|
473
|
-
const flat = `{
|
|
473
|
+
const flat = `{light-blue-fg}Black flatland under a black sky -- the icons are the only things out here.${grid ? ` Riding ${grid.name}${grid.userPenalty ? ' (-2 on everything you do, p.233)' : ''}.` : ''}{/light-blue-fg}`;
|
|
474
474
|
if (!room.hasNode)
|
|
475
475
|
return `${header}\n${flat}`;
|
|
476
|
-
return `${header}\n${flat}\n{
|
|
476
|
+
return `${header}\n${flat}\n{light-blue-fg}${hostLabel(room, { capital: true })} hangs directly overhead, sealed. What it holds is inside it.{/light-blue-fg}`;
|
|
477
477
|
}
|
|
478
478
|
/**
|
|
479
479
|
* The icons rendered as the standard cyan block, or the thin-grid line.
|
|
@@ -502,19 +502,19 @@ export function gridIconBlock(scene, actor, room, rooms) {
|
|
|
502
502
|
if (actor.insideHost || !rooms) {
|
|
503
503
|
const icons = gridIcons(scene, actor, room);
|
|
504
504
|
if (icons.length === 0) {
|
|
505
|
-
return `{
|
|
505
|
+
return `{light-blue-fg}Thin grid out here -- background noise, nothing worth cracking.{/light-blue-fg}`;
|
|
506
506
|
}
|
|
507
|
-
return `{
|
|
507
|
+
return `{light-blue-fg}ICONS IN REACH:{/light-blue-fg}\n${icons.map(i => ` {light-blue-fg}${i}{/light-blue-fg}`).join('\n')}`;
|
|
508
508
|
}
|
|
509
509
|
const out = [];
|
|
510
510
|
const hosts = hostsInReach(rooms, actor);
|
|
511
511
|
out.push(hosts.length > 0
|
|
512
|
-
? `{
|
|
513
|
-
: `{
|
|
512
|
+
? `{light-blue-fg}HOSTS OVERHEAD:{/light-blue-fg}\n${hosts.map(h => ` {light-blue-fg}${hostLine(h, actor)}{/light-blue-fg}`).join('\n')}`
|
|
513
|
+
: `{light-blue-fg}Nothing overhead -- no host stands over this district.{/light-blue-fg}`);
|
|
514
514
|
const near = gridIcons(scene, actor, gridVicinity(actor)).filter(i => !isHostLine(i));
|
|
515
515
|
out.push(near.length > 0
|
|
516
|
-
? `{
|
|
517
|
-
: `{
|
|
516
|
+
? `{light-blue-fg}ICONS IN REACH:{/light-blue-fg}\n${near.map(i => ` {light-blue-fg}${i}{/light-blue-fg}`).join('\n')}`
|
|
517
|
+
: `{light-blue-fg}Nothing near -- background noise around your signal.{/light-blue-fg}`);
|
|
518
518
|
// FARTHER OUT, WITHOUT AN ADDRESS (SR5 p.235, p.243): Matrix Perception
|
|
519
519
|
// never tells you where a device physically sits -- that is Trace Icon,
|
|
520
520
|
// two marks and an opposed test. So far icons are listed by the noise
|
|
@@ -539,9 +539,9 @@ export function gridIconBlock(scene, actor, room, rooms) {
|
|
|
539
539
|
byBand.set(noise, list);
|
|
540
540
|
}
|
|
541
541
|
const far = [...byBand.entries()].sort((a, b) => a[0] - b[0])
|
|
542
|
-
.map(([noise, names]) => ` {
|
|
542
|
+
.map(([noise, names]) => ` {light-blue-fg}· dim, noise -${noise}: ${names.join(', ')}{/light-blue-fg}`);
|
|
543
543
|
if (far.length > 0)
|
|
544
|
-
out.push(`{
|
|
544
|
+
out.push(`{light-blue-fg}FARTHER OUT -- dimmer with distance:{/light-blue-fg}\n${far.join('\n')}`);
|
|
545
545
|
// A blank line between the distance bands (user, 2026-09-02): three
|
|
546
546
|
// sections read as three when they do not touch.
|
|
547
547
|
return out.join('\n\n');
|
|
@@ -556,7 +556,7 @@ export function gridIconBlock(scene, actor, room, rooms) {
|
|
|
556
556
|
* the other personas, and the files. Cut to the dock's rows and columns.
|
|
557
557
|
*/
|
|
558
558
|
export function hostInteriorPanel(scene, actor, host, rows, cols) {
|
|
559
|
-
const cut = (raw) => `{
|
|
559
|
+
const cut = (raw) => `{light-blue-fg}${[...raw].slice(0, Math.max(4, cols)).join('')}{/light-blue-fg}`;
|
|
560
560
|
const lines = [];
|
|
561
561
|
lines.push(cut(`INSIDE ${host.name}`));
|
|
562
562
|
if (host.purpose)
|
|
@@ -58,10 +58,10 @@ export function accrueOverwatch(scene, actor, defenseHits, why) {
|
|
|
58
58
|
// The grid's built-in warning system (p.221): subtle ripples, once
|
|
59
59
|
// per band -- the only free hint the player ever gets.
|
|
60
60
|
if (before < 30 && actor.overwatchScore >= 30) {
|
|
61
|
-
return { lines: [`{
|
|
61
|
+
return { lines: [`{light-blue-fg}The grid RIPPLES -- hard, close, and looking. Something vast has nearly found the thread you're hanging by. ("reboot" is a clean slate.){/light-blue-fg}`], converged: false };
|
|
62
62
|
}
|
|
63
63
|
if (before < 20 && actor.overwatchScore >= 20) {
|
|
64
|
-
return { lines: [`{
|
|
64
|
+
return { lines: [`{light-blue-fg}A ripple crosses the grid -- subtle, wrong, deliberate. Somewhere above, an eye has started counting your fingerprints.{/light-blue-fg}`], converged: false };
|
|
65
65
|
}
|
|
66
66
|
return { lines: [], converged: false };
|
|
67
67
|
}
|
|
@@ -454,7 +454,7 @@ export function fileReach(actor, room) {
|
|
|
454
454
|
*/
|
|
455
455
|
export function planeTintItemName(item, viewerPlane) {
|
|
456
456
|
if (item.plane === 'matrix') {
|
|
457
|
-
return `{
|
|
457
|
+
return `{light-blue-fg}${item.name} [data]{/light-blue-fg}`;
|
|
458
458
|
}
|
|
459
459
|
if (item.plane === 'astral') {
|
|
460
460
|
return `{magenta-fg}${item.name} [astral]{/magenta-fg}`;
|
|
@@ -1253,6 +1253,14 @@ export class SharedRunSession {
|
|
|
1253
1253
|
catch { /* the name is a courtesy; the warning stands without it */ }
|
|
1254
1254
|
return `The dispatcher picks up -- and you hesitate. You never settled with ${who}: ${owed}¥ still on the table. ("call taxi" again to leave it and go.)`;
|
|
1255
1255
|
}
|
|
1256
|
+
// MID-FIGHT (hHvR2kxrqAKuuXwED). This shortcut skips the engine's
|
|
1257
|
+
// CallCommand exactly as the unclaimed-pay case above does, so
|
|
1258
|
+
// the server carries the refusal and this renders it in the
|
|
1259
|
+
// engine's own words. It arms NOTHING: unlike `unsettled`, no
|
|
1260
|
+
// second call gets you a cab.
|
|
1261
|
+
if (e.error === 'in-combat') {
|
|
1262
|
+
return `Mid-fight is no time to hail a cab. Finish this, then call.`;
|
|
1263
|
+
}
|
|
1256
1264
|
if (e.error === 'bad-status' || e.error === 'interrupted') {
|
|
1257
1265
|
await this.homecoming();
|
|
1258
1266
|
return '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.198.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|