@maka/maka-cli 5.237.0 → 5.239.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/enter-host.js +15 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +76 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +15 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/device.js +61 -37
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +12 -15
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ar.js +18 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.js +36 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +14 -4
- package/bundle/typescript/src/commands/game/sideQuest/utilities/silent-icons.js +25 -14
- package/bundle/typescript/src/commands/game/sideQuest/utilities/surveillance.js +32 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.239.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.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { movePersonaWith } from '../utilities/matrix-roster.js';
|
|
3
|
-
import { resolveHostInReach, hostsInReach, gridVicinity, hostOver } from '../utilities/grid-reach.js';
|
|
3
|
+
import { resolveHostInReach, hostsInReach, gridVicinity, hostOver, hostDoorOpenFor } from '../utilities/grid-reach.js';
|
|
4
4
|
import { hint } from '../utilities/hints.js';
|
|
5
5
|
import { hostLabel } from '../utilities/grid-names.js';
|
|
6
6
|
import { billAction } from '../utilities/action-cost.js';
|
|
@@ -61,11 +61,21 @@ export class EnterHostCommand extends Command {
|
|
|
61
61
|
// actions to get a mark has bought the door, and charging them a
|
|
62
62
|
// test here would be inventing a rule the book explicitly denies
|
|
63
63
|
// ("There is no test involved").
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
//
|
|
65
|
+
// AND IT IS THE ONLY GATE (DxfeZTBrP6Bu7MLF4). It used to read `||
|
|
66
|
+
// hostCracked`, which is the SITE's state and never expires -- see
|
|
67
|
+
// grid-reach.ts hostDoorOpenFor for why that was a door nobody could
|
|
68
|
+
// ever close again. A persona whose keys are gone gets its own line:
|
|
69
|
+
// the wreckage it can see from out here is real, and saying "doesn't
|
|
70
|
+
// know you" about a node it personally tore open would read as the
|
|
71
|
+
// engine forgetting the last ten minutes.
|
|
72
|
+
if (!hostDoorOpenFor(target, actor)) {
|
|
73
|
+
const wrecked = target.hostCracked;
|
|
66
74
|
return [
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
wrecked
|
|
76
|
+
? `{light-blue-fg}${hostLabel(target, { capital: true })} is still hanging open where you broke it -- and it does not know YOU. Your keys are out of the lattice; the architecture has nothing shaped like your icon to open for.{/light-blue-fg}`
|
|
77
|
+
: `{light-blue-fg}${hostLabel(target, { capital: true })} doesn't know you. Its architecture has no door shaped like your icon.{/light-blue-fg}`,
|
|
78
|
+
hint(`A host opens to anyone holding a MARK on it, and marks die with the session you placed them in (p.239, p.236) -- "hack ${target.name}" keys it quietly, "force ${target.name}" takes one loud.`),
|
|
69
79
|
].join('\n');
|
|
70
80
|
}
|
|
71
81
|
// A Complex Action (p.239) -- billed only inside a Combat Turn.
|
|
@@ -179,6 +179,22 @@ export class HackCommand extends BypassCommand {
|
|
|
179
179
|
const roster = names.length > 0
|
|
180
180
|
? ` In reach: ${names.slice(0, 8).join(', ')}${names.length > 8 ? `, and ${names.length - 8} more` : ''} -- "${this.verbName} <name>" reaches one from here (Tab completes it).`
|
|
181
181
|
: '';
|
|
182
|
+
// AND IF THEY NAMED SOMETHING THAT IS SIMPLY DARK, SAY SO
|
|
183
|
+
// (reporter's ruling 2026-09-17: slaved security gear comes up
|
|
184
|
+
// with its radio off). A player who types "hack keypad" has read
|
|
185
|
+
// the keypad off the room's own prose with their eyes; the grid
|
|
186
|
+
// genuinely has nothing by that name, and "no host, no PAN, no
|
|
187
|
+
// camera" is a true sentence that answers the wrong question and
|
|
188
|
+
// teaches nothing. This is the one moment the difference between
|
|
189
|
+
// HIDDEN and DARK is worth spelling out, and it is also the only
|
|
190
|
+
// place a decker on a fully hardwired site would otherwise never
|
|
191
|
+
// meet the verb that still works.
|
|
192
|
+
const dark = named
|
|
193
|
+
? (this.actor.bodyRoom ?? this.actor.currentLocation).devices.find(d => d.rating > 0 && !d.broadcastsAro() && d.name.toLowerCase().includes(named.toLowerCase()))
|
|
194
|
+
: undefined;
|
|
195
|
+
if (dark) {
|
|
196
|
+
return `${dark.name} has NO RADIO -- it is wired into this place, not broadcasting, and the grid cannot see it at all (p.421). Nothing you do out here will reach it.${hint(` (p.421 takes away WIRELESS hacking, not the port: "jack out", walk to it, and "tap ${dark.name}" -- a cable beats its master's ratings and a mark on a slave is a mark on the host.)`)}`;
|
|
197
|
+
}
|
|
182
198
|
return named
|
|
183
199
|
? `Nothing on the grid answers to "${named}" -- no host by that name, no PAN, no camera.${roster}`
|
|
184
200
|
: `Thin grid out here -- no host node hangs over ${gridVicinity(this.actor).name}.${roster}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { whereIsThat } from '../utilities/matrix-roster.js';
|
|
2
|
-
import { resolveHostInReach, roomsInReach, noiseNote, gridVicinity } from '../utilities/grid-reach.js';
|
|
2
|
+
import { resolveHostInReach, roomsInReach, noiseNote, gridVicinity, hostDoorOpenFor } from '../utilities/grid-reach.js';
|
|
3
3
|
import { perceptionTest } from '../utilities/perception.js';
|
|
4
4
|
import { matchesHostName, matchesCameraName, hostLabel } from '../utilities/grid-names.js';
|
|
5
5
|
import { Door } from '../models/door.js';
|
|
@@ -392,7 +392,7 @@ export class LookCommand extends Command {
|
|
|
392
392
|
return `That file is on the ${room.offlineServer} -- a box with no Matrix presence. Nothing out here reaches it; your body has to be in ${room.name} (p.233).`;
|
|
393
393
|
}
|
|
394
394
|
if (reach === 'enter') {
|
|
395
|
-
return `That file is INSIDE the host -- from out on the grid you have its shell and nothing through it (p.246). ${(room
|
|
395
|
+
return `That file is INSIDE the host -- from out on the grid you have its shell and nothing through it (p.246). ${hostDoorOpenFor(room, this.actor) ? `"enter" to go in.` : `A mark opens the door: "hack ${hostLabel(room)}" (quiet) or "force ${hostLabel(room)}" (loud), then "enter".`}`;
|
|
396
396
|
}
|
|
397
397
|
// The host's seal covers the HOST's archive. A file on an offline
|
|
398
398
|
// server is not in there -- cracking the building's host does
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from './command.js';
|
|
2
2
|
import { whereIsThat } from '../utilities/matrix-roster.js';
|
|
3
|
-
import { gridVicinity } from '../utilities/grid-reach.js';
|
|
3
|
+
import { gridVicinity, hostDoorOpenFor } from '../utilities/grid-reach.js';
|
|
4
4
|
import { fileReach } from '../utilities/planes.js';
|
|
5
5
|
import { canReach, ensureAtSpot, sealedRouteTo } from '../utilities/spots.js';
|
|
6
6
|
import { hint } from '../utilities/hints.js';
|
|
@@ -143,7 +143,7 @@ export class TakeCommand extends Command {
|
|
|
143
143
|
return `Those files are on the ${room.offlineServer} -- off the network entirely. No persona reaches that box; your BODY has to be in ${room.name} (p.233).`;
|
|
144
144
|
}
|
|
145
145
|
if (reach === 'enter') {
|
|
146
|
-
return `Those files are INSIDE the host, and you're out on the grid looking at its shell (p.246). ${room
|
|
146
|
+
return `Those files are INSIDE the host, and you're out on the grid looking at its shell (p.246). ${hostDoorOpenFor(room, this.actor) ? `"enter" first.` : `Get a mark on it and "enter" -- or "hack" your way in.`}`;
|
|
147
147
|
}
|
|
148
148
|
// Same split as look.ts: the host's seal does not reach a box that
|
|
149
149
|
// is not on the network. The air gap IS the server's security.
|
|
@@ -291,7 +291,7 @@ export class TakeCommand extends Command {
|
|
|
291
291
|
if (far.reach === 'offline')
|
|
292
292
|
return `${far.item.name} is on the ${far.room.offlineServer} in ${far.room.name} -- an offline server, off the network entirely. No persona reaches that box; your BODY has to be in ${far.room.name} (p.233).`;
|
|
293
293
|
if (far.reach === 'host')
|
|
294
|
-
return `${far.item.name} is INSIDE ${far.room.name}'s host, and you're out on the grid looking at its shell (p.246). ${far.room
|
|
294
|
+
return `${far.item.name} is INSIDE ${far.room.name}'s host, and you're out on the grid looking at its shell (p.246). ${hostDoorOpenFor(far.room, this.actor) ? `"enter ${far.room.name}" first.` : `Get a mark on it and "enter" -- or "hack" your way in.`}`;
|
|
295
295
|
if (far.reach === 'open')
|
|
296
296
|
return `${far.item.name} is loose on the grid -- a dim icon. ${hint(`"download ${far.item.name}" writes it to a chip from here.`)}`;
|
|
297
297
|
}
|
|
@@ -337,7 +337,7 @@ export class TakeCommand extends Command {
|
|
|
337
337
|
return `The ${item.name} sits on the ${room.offlineServer} -- a box with no Matrix presence at all. No persona reaches it from anywhere: get your BODY into ${room.name} and cable in (p.233).`;
|
|
338
338
|
}
|
|
339
339
|
if (reach === 'enter') {
|
|
340
|
-
return `The ${item.name} is INSIDE the host -- you're on the outside grid, reading its shell (p.246). ${(room
|
|
340
|
+
return `The ${item.name} is INSIDE the host -- you're on the outside grid, reading its shell (p.246). ${hostDoorOpenFor(room, this.actor) ? `"enter" first.` : `Get a mark on it and "enter" -- or "hack" your way in.`}`;
|
|
341
341
|
}
|
|
342
342
|
// A LOOSE END IS ERASED, NOT CARRIED (win-condition-seed.ts 'erase',
|
|
343
343
|
// 2026-09-14): pulling the footage down to a chip leaves the desk's
|
|
@@ -106,7 +106,11 @@ export class TapCommand extends Command {
|
|
|
106
106
|
return this.nothingNamed(room, named);
|
|
107
107
|
}
|
|
108
108
|
if (!isWatched(room)) {
|
|
109
|
-
|
|
109
|
+
// NOT "your overlay tags them", which stopped being true when
|
|
110
|
+
// slaved lenses went dark (reporter's ruling 2026-09-17): a
|
|
111
|
+
// hardwired cluster has no icon for the overlay to tag. The
|
|
112
|
+
// housings are a thing the room's prose names and your eyes find.
|
|
113
|
+
return `No cameras in here to cable into.${hint(` (Look for the housings -- a hardwired cluster shows no icon at all, and still takes a cable.)`)}`;
|
|
110
114
|
}
|
|
111
115
|
// The refusals TEACH (item 9 was a player asking whether the rules
|
|
112
116
|
// allowed this at all) -- each one names what a direct connection
|
|
@@ -393,6 +397,16 @@ export class TapCommand extends Command {
|
|
|
393
397
|
}
|
|
394
398
|
const now = Math.min(MAX_MARKS, held + 1);
|
|
395
399
|
device.marksBy.set(actor.name, now);
|
|
400
|
+
// YOU CANNOT HOLD A MARK ON AN ICON YOU CANNOT PICK OUT. Every other
|
|
401
|
+
// route to a mark goes through seeing the thing first -- from the
|
|
402
|
+
// grid you have to target an icon, and targeting a silent one means
|
|
403
|
+
// beating it in an opposed Matrix Perception. The cable is the one
|
|
404
|
+
// route that does not, because it is your hand on the housing rather
|
|
405
|
+
// than a search. Without this the new verb left a state nothing else
|
|
406
|
+
// in the game can reach: a device you own and cannot see, invisible
|
|
407
|
+
// on the grid you just keyed it from. Banked OUTSIDE (p.246), since
|
|
408
|
+
// what you learned you learned off the object itself.
|
|
409
|
+
device.markSpotted(actor.name);
|
|
396
410
|
const lines = [
|
|
397
411
|
`{light-blue-fg}The filament finds the connector and ${device.name} stops being furniture -- it's a device, and it's talking to you.{/light-blue-fg}`,
|
|
398
412
|
` {light-blue-fg}It never gets to hide behind the network: a cable doesn't ask permission, so it has to defend itself on DR ${device.rating} alone (p.233).{/light-blue-fg}`,
|
|
@@ -1566,5 +1566,80 @@
|
|
|
1566
1566
|
// hostInteriorIcons listed personas, files and ice and never the WAN,
|
|
1567
1567
|
// so a device found in there first appeared when you left. It is
|
|
1568
1568
|
// listed inside now, where p.233 puts it.
|
|
1569
|
-
|
|
1569
|
+
// 1.87.0 (2026-09-17): SLAVED SECURITY GEAR IS DARK, NOT HIDING
|
|
1570
|
+
// (eJANPpm7qCaCAJBJw, reporter's ruling: "If a device isn't running
|
|
1571
|
+
// wireless, it cannot be seen. I assume that security devices, such as
|
|
1572
|
+
// maglocks, cameras, sensors, etc., do not run wireless ... Let's give
|
|
1573
|
+
// devices a 'wireless' setting. Default security devices to have this
|
|
1574
|
+
// attribute of 'off'.")
|
|
1575
|
+
// TWO DIFFERENT ABSENCES, and the whole change is keeping them apart.
|
|
1576
|
+
// RUNNING SILENT (p.235-236) is an icon hiding: a sweep can beat it,
|
|
1577
|
+
// and a lost sweep still reports that something is there. DARK (p.421,
|
|
1578
|
+
// "can no longer be wirelessly hacked") is no icon at all: nothing to
|
|
1579
|
+
// contest, and no hint anything was ever there. Security furniture was
|
|
1580
|
+
// the first; it is the second now.
|
|
1581
|
+
// NOT A BLANKET, per the reporter's own correction: "not ALL cameras
|
|
1582
|
+
// need to be slaved to a host -- a mom and pop store can have a
|
|
1583
|
+
// wireless camera." The MASTER decides. A lock, panel or camera
|
|
1584
|
+
// cluster in a room with a host over it is wired into that site's
|
|
1585
|
+
// network and comes up dark; one with no host over it keeps its radio,
|
|
1586
|
+
// because the radio is the only way it reaches anyone. Seeds override
|
|
1587
|
+
// per device (IDeviceSeedJson.wireless) and per site
|
|
1588
|
+
// (Room.camerasWireless).
|
|
1589
|
+
// AND IT IS NOT A WALL -- the reporter's second correction, which was
|
|
1590
|
+
// also right: "physical contact CAN get access to the host through its
|
|
1591
|
+
// slaved device. Physical is NOT wireless." p.421 takes away WIRELESS
|
|
1592
|
+
// hacking and a filament is not wireless, so 1.86.0's `tap <device>`
|
|
1593
|
+
// still cables a dark maglock, still beats its own DR instead of its
|
|
1594
|
+
// master's (p.233), and the mark it buys is still a mark on the host.
|
|
1595
|
+
// From inside that host you are directly connected to the whole WAN,
|
|
1596
|
+
// radios or not. A dark site is not un-deckable; it is a site you have
|
|
1597
|
+
// to put a body into ONCE.
|
|
1598
|
+
// THE LEAK 1.86.0 PAPERED OVER IS NOW GONE AT THE ROOT. That version
|
|
1599
|
+
// answered "when I leave the host, all slaved devices are
|
|
1600
|
+
// automatically visible" with a second spotted-record per vantage. The
|
|
1601
|
+
// real answer is that there is no sweep inside a host to win: p.233
|
|
1602
|
+
// makes a persona in a WAN "directly connected to all devices in the
|
|
1603
|
+
// WAN", and a direct connection is a LINK, not a search -- the same
|
|
1604
|
+
// reason `tap` reaches a device nobody has ever spotted. The same
|
|
1605
|
+
// relationship cannot mean "you hold a link to it" for the cable and
|
|
1606
|
+
// "now go find it" for the host. So slaved devices are simply present
|
|
1607
|
+
// from inside, nothing is rolled in there, and nothing can be carried
|
|
1608
|
+
// back out. Device.spottedInsideBy and Room.camerasSpottedInsideBy are
|
|
1609
|
+
// retired.
|
|
1610
|
+
// WHERE A DARK DEVICE IS TAUGHT: naming one from the grid used to get
|
|
1611
|
+
// "no host by that name, no PAN, no camera" -- a true sentence
|
|
1612
|
+
// answering the wrong question, since the player read the keypad off
|
|
1613
|
+
// the room with their eyes. It now says the icon does not exist and
|
|
1614
|
+
// names the cable. The AR overlay deliberately says NOTHING: that
|
|
1615
|
+
// surface annotates broadcasts, which is its stated contract, and the
|
|
1616
|
+
// first cut of this violated it.
|
|
1617
|
+
// 1.88.0 (2026-09-17): THE DOOR INTO A HOST IS A MARK AND NOTHING ELSE
|
|
1618
|
+
// (DxfeZTBrP6Bu7MLF4, Maka: "I shouldn't be able to re-enter the host
|
|
1619
|
+
// if I have no marks on it"). `enter` accepted Room.hostCracked as a
|
|
1620
|
+
// substitute for the key. That flag is ROOM-GLOBAL and permanent for
|
|
1621
|
+
// the save -- once anybody took a node to three marks its door never
|
|
1622
|
+
// shut again, for anybody. The reporter's own transcript walks it:
|
|
1623
|
+
// three marks forced into the Approach host, cracked, JACK OUT (canon
|
|
1624
|
+
// deletes every mark they placed, p.242), jack back in reading MARKS
|
|
1625
|
+
// 0 held -- and straight back inside with the Patrol locking on.
|
|
1626
|
+
// Canon is one sentence (p.239, RAG-checked 2026-09-17): "a host
|
|
1627
|
+
// allows anyone to enter if they've got a mark", marks "only last a
|
|
1628
|
+
// single Matrix session" (p.236), three of them buy nothing that
|
|
1629
|
+
// outlives them, and a host's owner cannot be changed (p.237). This
|
|
1630
|
+
// is 1.56.0's ruling (spcebZTKN24GYRHXW, a cracked host is not a host
|
|
1631
|
+
// you hold marks on) finally applied to the one verb the flag was
|
|
1632
|
+
// actually a door for. The crack's WORLD consequences are untouched:
|
|
1633
|
+
// the vault still hangs open, the archive still reads unsealed, the
|
|
1634
|
+
// maglocks it released stay released -- those are facts about the
|
|
1635
|
+
// site, and the key is a fact about you.
|
|
1636
|
+
// ONE GATE, ONE ANSWER: grid-reach.ts hostDoorOpenFor is now the only
|
|
1637
|
+
// place that decides, and every surface that says "enter" asks it --
|
|
1638
|
+
// the verb, look/take's file refusals, and the grid icon, which used
|
|
1639
|
+
// to print `("enter" walks in)` under a crack and would otherwise have
|
|
1640
|
+
// become an invitation the verb refuses.
|
|
1641
|
+
// THE HAVEN IS THE ONE EXEMPTION and is labelled as an engine ruling,
|
|
1642
|
+
// not canon: a sanctioned host (Room.hostSanctioned, JackPoint) is a
|
|
1643
|
+
// members' place, and membership is the mark.
|
|
1644
|
+
export const ENGINE_VERSION = '1.88.0';
|
|
1570
1645
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -11,7 +11,7 @@ export const OBJECTIVE_MAX_WEIGHT_KG = 15;
|
|
|
11
11
|
import { Category } from '../types/shared/item-enum.js';
|
|
12
12
|
import { Logger } from '../utilities/logger.js';
|
|
13
13
|
import { migrateLegacySeed } from './seed-migration.js';
|
|
14
|
-
import { Device } from '../models/device.js';
|
|
14
|
+
import { Device, WIRELESS_OFF_BY_DEFAULT } from '../models/device.js';
|
|
15
15
|
import { fuzzyPickName } from '../utilities/fuzzy-match.js';
|
|
16
16
|
import { adoptSeededIce } from '../utilities/ic-actors.js';
|
|
17
17
|
import { NPC_EQUIP_CATEGORIES, outfitNpcBySlug } from '../npc-kits.js';
|
|
@@ -439,6 +439,20 @@ Key Locations: ${json.rooms.map(r => r.name).join(', ')}
|
|
|
439
439
|
structure: d.structure,
|
|
440
440
|
armor: d.armor,
|
|
441
441
|
footprint: d.footprint,
|
|
442
|
+
// THE RADIO, AND WHAT DECIDES IT (reporter's ruling
|
|
443
|
+
// 2026-09-17, eJANPpm7qCaCAJBJw). A security device wired into
|
|
444
|
+
// a site's network is DARK -- no icon on the grid at all, not
|
|
445
|
+
// a hidden one. A standalone one keeps its radio: "not ALL
|
|
446
|
+
// cameras need to be slaved to a host -- a mom and pop store
|
|
447
|
+
// can have a wireless camera", and the same is true of the
|
|
448
|
+
// maglock on a corner shop's back door.
|
|
449
|
+
//
|
|
450
|
+
// SLAVED means a host stands over its own room, which is the
|
|
451
|
+
// same master tap.ts cables through to and the same WAN
|
|
452
|
+
// hack.ts reaches from inside. One definition, three readers.
|
|
453
|
+
// The seed still overrides either way.
|
|
454
|
+
wireless: d.wireless ?? !(roomJson.hasNode === true && WIRELESS_OFF_BY_DEFAULT.has(kind)),
|
|
455
|
+
silent: d.silent,
|
|
442
456
|
});
|
|
443
457
|
room.devices.push(device);
|
|
444
458
|
// A seed may declare a device ALREADY OPEN. Applied before the
|
|
@@ -25,6 +25,33 @@ export const DEFAULT_DEVICE_RATING = {
|
|
|
25
25
|
* has no wireless mode to hide in.
|
|
26
26
|
*/
|
|
27
27
|
export const SILENT_BY_DEFAULT = new Set(['lock', 'panel']);
|
|
28
|
+
/**
|
|
29
|
+
* WHICH KINDS ARE BORN WITH THE RADIO OFF (reporter's ruling 2026-09-17,
|
|
30
|
+
* eJANPpm7qCaCAJBJw: "If a device isn't running wireless, it cannot be
|
|
31
|
+
* seen. I assume that security devices, such as maglocks, cameras,
|
|
32
|
+
* sensors, etc., do not run wireless ... Let's give devices a 'wireless'
|
|
33
|
+
* setting. Default security devices to have this attribute of 'off'").
|
|
34
|
+
*
|
|
35
|
+
* The setting already existed and every seed left it on. This makes the
|
|
36
|
+
* security furniture dark by default instead, which is a stronger claim
|
|
37
|
+
* than running silent and a different one: silent is an icon HIDING
|
|
38
|
+
* (p.235-236, beatable by Matrix Perception), dark is no icon at all
|
|
39
|
+
* (p.421, "can no longer be wirelessly hacked" -- there is nothing to
|
|
40
|
+
* roll against).
|
|
41
|
+
*
|
|
42
|
+
* IT IS NOT A WALL, and that is the whole reason the ruling is
|
|
43
|
+
* affordable. p.421 says WIRELESSLY. A cable is not wireless: `tap
|
|
44
|
+
* <device>` still cables into a dark maglock in the flesh, still beats
|
|
45
|
+
* its own DR rather than its master's (p.233), and the mark it buys is
|
|
46
|
+
* still a mark on the host (p.233, Data Trails p.87-88). From inside
|
|
47
|
+
* that host you are then directly connected to the whole WAN, radios or
|
|
48
|
+
* not. So a dark site is not un-deckable -- it is a site you have to
|
|
49
|
+
* put a body into once, after which everything works.
|
|
50
|
+
*
|
|
51
|
+
* `silent` stays set on these kinds too, for the seed that turns a
|
|
52
|
+
* radio back ON: a device broadcasting again should still be hiding.
|
|
53
|
+
*/
|
|
54
|
+
export const WIRELESS_OFF_BY_DEFAULT = new Set(['lock', 'panel']);
|
|
28
55
|
/**
|
|
29
56
|
* THE BOOK'S BARRIER TIERS (p.197 Barrier Ratings, RAG-checked 2026-09-03):
|
|
30
57
|
* Structure / Armor. Fragile is standard glass; Reinforced is a security
|
|
@@ -165,53 +192,50 @@ export class Device {
|
|
|
165
192
|
*/
|
|
166
193
|
spottedBy = new Set();
|
|
167
194
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
195
|
+
* IS THIS ICON THERE FOR THIS VIEWER, from where they are standing?
|
|
196
|
+
*
|
|
197
|
+
* `inside` means the viewer's persona is inside the host this device
|
|
198
|
+
* is slaved to, and it is the whole of the difference (Maka, 2026-09-17:
|
|
199
|
+
* "physical contact CAN get access to the host through its slaved
|
|
200
|
+
* device. Physical is NOT wireless").
|
|
172
201
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
202
|
+
* INSIDE THE WAN THERE IS NOTHING TO FIND. p.233: "if you are in a
|
|
203
|
+
* host that has a WAN, you are considered directly connected to all
|
|
204
|
+
* devices in the WAN." A direct connection is a LINK, not a search --
|
|
205
|
+
* which is exactly why `tap` reaches a device nobody has ever spotted.
|
|
206
|
+
* The same relationship cannot mean "you hold a link to it" for the
|
|
207
|
+
* cable and "now go find it" for the host. So from in here a slaved
|
|
208
|
+
* device is simply present, radio or no radio: the WAN link is the
|
|
209
|
+
* WIRE, and the airwaves have nothing to do with it.
|
|
181
210
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
211
|
+
* OUTSIDE IT IS THE AIRWAVES OR NOTHING. No wireless, no icon (p.421:
|
|
212
|
+
* a wireless-off device "can no longer be wirelessly hacked"). With a
|
|
213
|
+
* radio it still has to be picked out of the noise if it is running
|
|
214
|
+
* silent (p.235-236).
|
|
185
215
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
216
|
+
* THIS IS ALSO WHAT KILLS THE LEAK the reporter found -- "when I leave
|
|
217
|
+
* the host, all slaved devices are automatically visible" -- and kills
|
|
218
|
+
* it by construction rather than by bookkeeping. 1.86.0 fixed it with
|
|
219
|
+
* a second `spottedInsideBy` record so a cheap sweep inside could not
|
|
220
|
+
* satisfy the dear gate outside. There is no sweep inside any more, so
|
|
221
|
+
* there is nothing to bank and no second record to keep honest.
|
|
189
222
|
*/
|
|
190
|
-
spottedInsideBy = new Set();
|
|
191
|
-
/** Is this icon on the grid for this viewer? Wireless and rated (the
|
|
192
|
-
* old broadcastsAro question), and either loud or already spotted
|
|
193
|
-
* FROM WHERE THEY ARE STANDING -- `inside` means inside the host this
|
|
194
|
-
* device is slaved to, where p.233 makes them directly connected. */
|
|
195
223
|
hasIconFor(viewerName, inside = false) {
|
|
224
|
+
if (inside)
|
|
225
|
+
return this.rating > 0;
|
|
196
226
|
if (!this.broadcastsAro())
|
|
197
227
|
return false;
|
|
198
|
-
|
|
199
|
-
return true;
|
|
200
|
-
if (this.spottedBy.has(viewerName))
|
|
201
|
-
return true;
|
|
202
|
-
return inside && this.spottedInsideBy.has(viewerName);
|
|
228
|
+
return !this.silent || this.spottedBy.has(viewerName);
|
|
203
229
|
}
|
|
204
|
-
/** Hidden from this viewer, and therefore worth rolling against.
|
|
230
|
+
/** Hidden from this viewer, and therefore worth rolling against. Never
|
|
231
|
+
* from inside its own host: see hasIconFor. */
|
|
205
232
|
isHiddenFrom(viewerName, inside = false) {
|
|
206
|
-
return this.broadcastsAro() && this.silent && !this.
|
|
233
|
+
return !inside && this.broadcastsAro() && this.silent && !this.spottedBy.has(viewerName);
|
|
207
234
|
}
|
|
208
|
-
/** Record a sweep this viewer won
|
|
209
|
-
*
|
|
210
|
-
markSpotted(viewerName
|
|
211
|
-
|
|
212
|
-
this.spottedInsideBy.add(viewerName);
|
|
213
|
-
else
|
|
214
|
-
this.spottedBy.add(viewerName);
|
|
235
|
+
/** Record a sweep this viewer won -- or a cable they put into it, which
|
|
236
|
+
* teaches the same fact the harder way. */
|
|
237
|
+
markSpotted(viewerName) {
|
|
238
|
+
this.spottedBy.add(viewerName);
|
|
215
239
|
}
|
|
216
240
|
/**
|
|
217
241
|
* WHAT IT IS MADE OF (p.197-198): Structure is the condition monitor
|
|
@@ -82,23 +82,20 @@ export class Room extends AbstractRoom {
|
|
|
82
82
|
*/
|
|
83
83
|
camerasSpottedBy = new Set();
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* slaved to
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* came back out. p.246: the two spaces are separate.
|
|
92
|
-
*
|
|
93
|
-
* Outward-only, like the device's: an outside spot counts everywhere,
|
|
94
|
-
* an inside one stays inside.
|
|
85
|
+
* DOES THIS CLUSTER RUN A RADIO AT ALL (reporter's ruling 2026-09-17,
|
|
86
|
+
* eJANPpm7qCaCAJBJw)? Unset takes the derived answer in
|
|
87
|
+
* surveillance.ts camerasBroadcast: slaved to a host means DARK,
|
|
88
|
+
* standalone means broadcasting -- "not ALL cameras need to be slaved
|
|
89
|
+
* to a host, a mom and pop store can have a wireless camera". Set it
|
|
90
|
+
* to force either way per site.
|
|
95
91
|
*/
|
|
96
|
-
|
|
97
|
-
/** Has this viewer pinned the cluster's icon
|
|
92
|
+
camerasWireless;
|
|
93
|
+
/** Has this viewer pinned the cluster's icon? Inside the host these
|
|
94
|
+
* lenses are slaved to, there is nothing to pin: p.233 makes a
|
|
95
|
+
* persona in the WAN directly connected to everything on it, which is
|
|
96
|
+
* a LINK and not a search. Mirrors Device.hasIconFor. */
|
|
98
97
|
camerasSpottedFor(viewerName, inside = false) {
|
|
99
|
-
|
|
100
|
-
return true;
|
|
101
|
-
return inside && this.camerasSpottedInsideBy.has(viewerName);
|
|
98
|
+
return inside || this.camerasSpottedBy.has(viewerName);
|
|
102
99
|
}
|
|
103
100
|
/**
|
|
104
101
|
* WHAT THE LENSES RATE (eJANPpm7qCaCAJBJw, third rejection: "the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isLinked, CALL_COLOR } from './comm-style.js';
|
|
2
|
-
import { isWatched, camerasLive, canSnoopFeeds } from './surveillance.js';
|
|
2
|
+
import { isWatched, camerasLive, canSnoopFeeds, camerasBroadcast } from './surveillance.js';
|
|
3
3
|
import { Category } from '../types/shared/item-enum.js';
|
|
4
4
|
import { hint } from './hints.js';
|
|
5
5
|
import { cameraDrTag, drTagFor } from './grid-view.js';
|
|
@@ -143,7 +143,12 @@ export function aroTags(scene, actor, room) {
|
|
|
143
143
|
// the host, so it shows only to whoever owns the feeds.
|
|
144
144
|
// Same gate as the grid: this overlay draws ICONS, and a camera icon
|
|
145
145
|
// is the first thing p.217 lists as deliberately hidden until spotted.
|
|
146
|
-
|
|
146
|
+
// A DARK CLUSTER IS CORRECTLY ABSENT, for the same reason a dark
|
|
147
|
+
// device is (reporter's ruling 2026-09-17): lenses wired into a site's
|
|
148
|
+
// host broadcast nothing, and this overlay annotates broadcasts. The
|
|
149
|
+
// housings are still there to be seen and still take a cable -- that
|
|
150
|
+
// is the meat layer's business, not this one's.
|
|
151
|
+
if (isWatched(room) && camerasBroadcast(scene.getRooms(), room) && room.camerasSpottedBy.has(actor.name)) {
|
|
147
152
|
const rooms = scene.getRooms();
|
|
148
153
|
// The verb rides the tag: standing in the room IS the requirement
|
|
149
154
|
// for a direct connection (commands/tap.ts), so this is the one
|
|
@@ -174,6 +179,17 @@ export function aroTags(scene, actor, room) {
|
|
|
174
179
|
// (p.217, p.235-236). The OBJECT is untouched: the maglock is still
|
|
175
180
|
// screwed to the door, still described by a meat-world `look`, and
|
|
176
181
|
// still picked, breached, shot and keyed exactly as before.
|
|
182
|
+
// A DARK DEVICE IS CORRECTLY ABSENT FROM THIS LIST (reporter's
|
|
183
|
+
// ruling 2026-09-17: slaved security gear comes up with its radio
|
|
184
|
+
// off). This overlay annotates BROADCASTS -- see the contract at the
|
|
185
|
+
// top of the file -- and a device with no radio has nothing to
|
|
186
|
+
// annotate, exactly like a silent PAN or an offline server.
|
|
187
|
+
//
|
|
188
|
+
// The first cut of that ruling printed a "NO RADIO" line here on the
|
|
189
|
+
// grounds that a body has eyes. It does, but this is not the eyes:
|
|
190
|
+
// the object is named by the room's own prose and worked by the meat
|
|
191
|
+
// verbs, and commands/bypass-command.ts is where a body is told what
|
|
192
|
+
// its hands and its cable can reach. Two surfaces, one contract each.
|
|
177
193
|
if (!device.hasIconFor(actor.name))
|
|
178
194
|
continue;
|
|
179
195
|
// THE CABLE IS THE OTHER VERB (eJANPpm7qCaCAJBJw). The overlay
|
|
@@ -41,6 +41,42 @@ export function hostsInReach(rooms, actor) {
|
|
|
41
41
|
export function hostOver(room) {
|
|
42
42
|
return room?.hasNode ? room : undefined;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* IS THE DOOR OPEN TO THIS PERSONA -- the one answer, so that the verb
|
|
46
|
+
* that crosses and every hint that says "enter" cannot disagree
|
|
47
|
+
* (DxfeZTBrP6Bu7MLF4, Maka: "I shouldn't be able to re-enter the host if
|
|
48
|
+
* I have no marks on it").
|
|
49
|
+
*
|
|
50
|
+
* p.239 is one sentence long: "Marks required 1 ... a host allows anyone
|
|
51
|
+
* to enter if they've got a mark." RAG-checked 2026-09-17, which also
|
|
52
|
+
* settles the other half: marks "only last a single Matrix session and
|
|
53
|
+
* are deleted when you reboot" (p.236), reaching three of them buys
|
|
54
|
+
* nothing that outlives them, and a host's owner cannot be changed
|
|
55
|
+
* (p.237) -- so there is no state in canon that stands in for the key.
|
|
56
|
+
*
|
|
57
|
+
* WHAT USED TO STAND IN FOR IT. `enter` accepted Room.hostCracked, which
|
|
58
|
+
* is ROOM-GLOBAL and permanent for the save: once anybody took the node
|
|
59
|
+
* to three marks, that door never closed again -- not for the decker who
|
|
60
|
+
* jacked out (canon wipes their marks, p.242) and not for a team-mate
|
|
61
|
+
* who had never touched it. That is the same confusion 1.56.0 named for
|
|
62
|
+
* `hack`/`mark` (spcebZTKN24GYRHXW: a cracked host is not a host you
|
|
63
|
+
* hold marks on) with the one verb the flag was actually a door for left
|
|
64
|
+
* out. The crack's WORLD consequences are untouched -- a vault hanging
|
|
65
|
+
* open stays open, its archive stays listed -- because those are facts
|
|
66
|
+
* about the site and this is a fact about your keys.
|
|
67
|
+
*
|
|
68
|
+
* THE HAVEN IS THE ONE EXEMPTION, and it is an engine ruling, not canon:
|
|
69
|
+
* a sanctioned host (utilities/jackpoint.ts, Room.hostSanctioned) is a
|
|
70
|
+
* members' place you belong to, and membership is the mark. Labelled
|
|
71
|
+
* here rather than hidden in a boolean.
|
|
72
|
+
*/
|
|
73
|
+
export function hostDoorOpenFor(room, actor) {
|
|
74
|
+
if (!room.hasNode)
|
|
75
|
+
return false;
|
|
76
|
+
if (room.hostSanctioned)
|
|
77
|
+
return true;
|
|
78
|
+
return (room.hostMarksBy.get(actor.name) ?? 0) >= 1;
|
|
79
|
+
}
|
|
44
80
|
/**
|
|
45
81
|
* A host by any name the game shows for it (grid-names.ts), from
|
|
46
82
|
* anywhere on the grid. Bare "host" (or nothing) means the one over the
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { sameHostSide } from '../models/player.js';
|
|
2
2
|
import { personasInside } from './matrix-roster.js';
|
|
3
|
-
import { hostsInReach, hostLine, roomsInReach, gridVicinity } from './grid-reach.js';
|
|
3
|
+
import { hostsInReach, hostLine, roomsInReach, gridVicinity, hostDoorOpenFor } from './grid-reach.js';
|
|
4
4
|
import { NPC } from '../models/npc.js';
|
|
5
5
|
import { hostLabel, hostPurposeFor } from './grid-names.js';
|
|
6
6
|
import { isLinked } from './comm-style.js';
|
|
7
|
-
import { isWatched, camerasLive, canSnoopFeeds, cameraMasterHost } from './surveillance.js';
|
|
7
|
+
import { isWatched, camerasLive, canSnoopFeeds, cameraMasterHost, camerasBroadcast } from './surveillance.js';
|
|
8
8
|
/**
|
|
9
9
|
* The grid's icon inventory for one room -- everything a persona can
|
|
10
10
|
* ACT on, each with its verb (player rulings 2026-08-23/24: "it's hard
|
|
@@ -142,7 +142,17 @@ export function gridIcons(scene, actor, room) {
|
|
|
142
142
|
icons.push(`● ${hostLabel(room, { capital: true })} -- three spheres turning over rooms that were never sold to anyone. You're on the list; nothing here is hunting.`);
|
|
143
143
|
}
|
|
144
144
|
else if (room.hostCracked) {
|
|
145
|
-
|
|
145
|
+
// THE WRECKAGE IS THE SITE'S; THE DOOR IS STILL YOURS TO HOLD
|
|
146
|
+
// (DxfeZTBrP6Bu7MLF4). This line offered `"enter" walks in` on the
|
|
147
|
+
// room-global crack alone, so a persona whose marks a jack-out had
|
|
148
|
+
// just wiped (p.242) read an invitation the verb then refused --
|
|
149
|
+
// or, before the verb was fixed, accepted, which is the report.
|
|
150
|
+
// Both halves stay true now: the vault hangs open, and the key is
|
|
151
|
+
// per-persona and dies with the session (p.236).
|
|
152
|
+
const way = hostDoorOpenFor(room, actor)
|
|
153
|
+
? `("enter" walks in on your mark)`
|
|
154
|
+
: `("hack ${hostLabel(room)}" or "force ${hostLabel(room)}" for a mark -- yours are gone, and a host still asks for one, p.239)`;
|
|
155
|
+
icons.push(`● ${hostLabel(room, { capital: true })}${hr}${purpose} -- cracked open, security in shreds. Whatever it guarded is unsealed, inside. ${way}`);
|
|
146
156
|
}
|
|
147
157
|
else {
|
|
148
158
|
const myMarks = room.hostMarksBy.get(actor.name) ?? 0;
|
|
@@ -337,7 +347,7 @@ export function gridIcons(scene, actor, room) {
|
|
|
337
347
|
// still described in the meat, and a body still "tap"s one in the
|
|
338
348
|
// flesh -- it is the ICON that takes a Matrix Perception to pick out
|
|
339
349
|
// of the noise (utilities/silent-icons.ts).
|
|
340
|
-
if (isWatched(room) && room.camerasSpottedBy.has(actor.name)) {
|
|
350
|
+
if (isWatched(room) && camerasBroadcast(scene.getRooms(), room) && room.camerasSpottedBy.has(actor.name)) {
|
|
341
351
|
const rooms = scene.getRooms();
|
|
342
352
|
if (!camerasLive(rooms, room)) {
|
|
343
353
|
icons.push(`◎ Camera cluster ${cameraDr(room, actor.name)}-- looped on empty hallways. Security sees NOTHING here.${canSnoopFeeds(rooms, room, actor) ? ` ("snoop" rides the feeds you own)` : ''}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { rollPool, formatRoll } from './dice.js';
|
|
2
2
|
import { deviceHidePool, wanHidePool } from './matrix-intrusion.js';
|
|
3
|
-
import { isWatched, cameraMasterHost } from './surveillance.js';
|
|
3
|
+
import { isWatched, cameraMasterHost, camerasBroadcast } from './surveillance.js';
|
|
4
4
|
import { gridActionModifier, gridNote } from './grids.js';
|
|
5
|
-
function hiddenDevice(device, master
|
|
5
|
+
function hiddenDevice(device, master) {
|
|
6
6
|
// p.235-236: the hider rolls LOGIC + SLEAZE. Not Firewall -- that
|
|
7
7
|
// resists an intrusion (p.238/p.240) and has nothing to do with being
|
|
8
8
|
// seen. This used to call deviceDefensePool, the INTRUSION pool, so
|
|
@@ -13,11 +13,7 @@ function hiddenDevice(device, master, inside) {
|
|
|
13
13
|
return {
|
|
14
14
|
name: device.name,
|
|
15
15
|
hide: { pool: hide.pool, label: hide.label },
|
|
16
|
-
|
|
17
|
-
// slave's bare rating; the grid outside asks for the master's
|
|
18
|
-
// Rating + Sleaze. Writing both from one roll is what made
|
|
19
|
-
// "enter, glance, exit" reveal a street's worth of silent icons.
|
|
20
|
-
reveal: (viewer) => { device.markSpotted(viewer, inside); },
|
|
16
|
+
reveal: (viewer) => { device.markSpotted(viewer); },
|
|
21
17
|
};
|
|
22
18
|
}
|
|
23
19
|
function hiddenPersona(actor) {
|
|
@@ -38,14 +34,23 @@ function hiddenPersona(actor) {
|
|
|
38
34
|
* body does.
|
|
39
35
|
*/
|
|
40
36
|
export function hiddenIconsIn(scene, room, viewer) {
|
|
41
|
-
// A device in a room with a host is slaved to it (p.233)
|
|
42
|
-
// the
|
|
43
|
-
//
|
|
37
|
+
// A device in a room with a host is slaved to it (p.233) and borrows
|
|
38
|
+
// the master's ratings to stay hidden.
|
|
39
|
+
//
|
|
40
|
+
// NOTHING IS HIDDEN FROM INSIDE THE HOST. p.233: a persona in a WAN is
|
|
41
|
+
// "considered directly connected to all devices in the WAN", and a
|
|
42
|
+
// direct connection is a LINK, not a search -- the same reason `tap`
|
|
43
|
+
// reaches a device nobody has ever spotted. isHiddenFrom answers false
|
|
44
|
+
// for every slave in here, so this list comes back empty of devices
|
|
45
|
+
// and the sweep in there is only ever about PEOPLE.
|
|
46
|
+
//
|
|
47
|
+
// That is also what retired the `spottedInsideBy` record 1.86.0 added:
|
|
48
|
+
// a sweep that cannot happen has no result to keep on the right side
|
|
49
|
+
// of the wall.
|
|
44
50
|
const inside = !!viewer.hostInside;
|
|
45
|
-
const deviceMaster = inside ? undefined : room.host;
|
|
46
51
|
const out = room.devices
|
|
47
52
|
.filter(d => d.isHiddenFrom(viewer.name, inside))
|
|
48
|
-
.map(d => hiddenDevice(d,
|
|
53
|
+
.map(d => hiddenDevice(d, room.host));
|
|
49
54
|
// THE CAMERAS, which p.217 names FIRST. They are not a Device -- the
|
|
50
55
|
// cluster is derived from the room (surveillance.ts isWatched) -- so
|
|
51
56
|
// the rating rides the ROOM (Room.cameraRating), seeded per site.
|
|
@@ -63,7 +68,13 @@ export function hiddenIconsIn(scene, room, viewer) {
|
|
|
63
68
|
// could not have reconciled them because two of the three were never
|
|
64
69
|
// printed. One seeded number feeds all three now, so what the player
|
|
65
70
|
// reads is what it rolls (reporter's ruling: per-site, not global).
|
|
66
|
-
|
|
71
|
+
// A DARK CLUSTER IS NOT HIDING, IT IS ABSENT (reporter's ruling
|
|
72
|
+
// 2026-09-17). Lenses wired into a site's host broadcast nothing at
|
|
73
|
+
// all, so there is no icon to contest and not even a "something is
|
|
74
|
+
// running silent here" -- p.421 leaves a wireless-off device with
|
|
75
|
+
// nothing to find. camerasBroadcast is the one predicate; the grid
|
|
76
|
+
// view reads the same one.
|
|
77
|
+
if (isWatched(room) && camerasBroadcast(scene?.getRooms() ?? {}, room) && !room.camerasSpottedFor(viewer.name, inside)) {
|
|
67
78
|
// THE THIRD CUT, and the reporter found it by doing the arithmetic
|
|
68
79
|
// out loud: "Device Rating + Firewall on the Camera is 2d6? so, the
|
|
69
80
|
// firewall of the host is a 1?" No -- Firewall was never the right
|
|
@@ -83,7 +94,7 @@ export function hiddenIconsIn(scene, room, viewer) {
|
|
|
83
94
|
hide: { pool: hide.pool, label: hide.label },
|
|
84
95
|
// Banked on the side of the wall it was won (p.246), like the
|
|
85
96
|
// devices above.
|
|
86
|
-
reveal: (v) => {
|
|
97
|
+
reveal: (v) => { room.camerasSpottedBy.add(v); },
|
|
87
98
|
});
|
|
88
99
|
}
|
|
89
100
|
for (const a of scene?.allActors ?? []) {
|
|
@@ -46,6 +46,38 @@ export function cameraMasterHost(rooms, room) {
|
|
|
46
46
|
// site network -- whichever host you own, you own these feeds.
|
|
47
47
|
return Object.values(rooms).find(r => r.hasNode);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* DO THESE LENSES BROADCAST AT ALL (reporter's ruling 2026-09-17,
|
|
51
|
+
* eJANPpm7qCaCAJBJw)?
|
|
52
|
+
*
|
|
53
|
+
* The ruling was "default security devices to have this attribute of
|
|
54
|
+
* 'off'", and then the correction that keeps it from being a blanket:
|
|
55
|
+
* "not ALL cameras need to be slaved to a host -- a mom and pop store
|
|
56
|
+
* can have a wireless camera."
|
|
57
|
+
*
|
|
58
|
+
* So the master decides, and it is the same test cameraMasterHost
|
|
59
|
+
* already answers. A cluster wired into a site's network is part of the
|
|
60
|
+
* security plant and comes up DARK -- no icon on the grid, nothing to
|
|
61
|
+
* sweep for, not even a "something is running silent here". A cluster
|
|
62
|
+
* with no host anywhere is the corner shop's lens on a cheap radio, and
|
|
63
|
+
* it keeps broadcasting, because the radio is the only way it reaches
|
|
64
|
+
* the owner's commlink at all.
|
|
65
|
+
*
|
|
66
|
+
* `Room.camerasWireless` overrides in either direction, for the corp
|
|
67
|
+
* floor that wants a visible decoy or the shop with a hardwired DVR.
|
|
68
|
+
*
|
|
69
|
+
* NONE OF THIS TOUCHES THE LENS. It is still bolted to the wall, still
|
|
70
|
+
* seen by eyes, still shot, still looped once you own the host, and
|
|
71
|
+
* still cabled into by `tap` in the flesh -- p.421 takes away WIRELESS
|
|
72
|
+
* hacking and a filament is not wireless.
|
|
73
|
+
*/
|
|
74
|
+
export function camerasBroadcast(rooms, room) {
|
|
75
|
+
if (!isWatched(room))
|
|
76
|
+
return false;
|
|
77
|
+
if (room.camerasWireless !== undefined)
|
|
78
|
+
return room.camerasWireless;
|
|
79
|
+
return !cameraMasterHost(rooms, room);
|
|
80
|
+
}
|
|
49
81
|
/**
|
|
50
82
|
* CAN THIS ACTOR MAKE A DIRECT CONNECTION to the cameras in the room
|
|
51
83
|
* they are standing in (canon p.232 "Direct Connections")? A direct
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.239.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.",
|