@maka/maka-cli 5.388.0 → 5.390.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.388.0",
3
+ "version": "5.390.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.",
@@ -43,6 +43,18 @@ const DRIFT_WAYS = new Set([
43
43
  Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST,
44
44
  Direction.UP, Direction.DOWN,
45
45
  ]);
46
+ /**
47
+ * IS THIS DOOR LOCKED TO SOMEONE LEAVING `from`. A tenant lock
48
+ * (Game.lockForeignResidences) is a flat's deadbolt: it keeps the street
49
+ * out and opens from inside, so nobody standing in the flat -- a save
50
+ * made there before the lock existed, a runner who breached in -- is
51
+ * shut in by it.
52
+ */
53
+ function lockedFrom(door, from) {
54
+ if (!door.checkIfLocked())
55
+ return false;
56
+ return !(door.tenantLocked && from.residenceTier !== undefined);
57
+ }
46
58
  export class GoCommand extends Command {
47
59
  /**
48
60
  * WHICH PLANE A COMPANION CAN HEEL ON, one rule per kind.
@@ -738,7 +750,7 @@ export class GoCommand extends Command {
738
750
  const way = wayOut(currentRoom, direction, here);
739
751
  const plane = way?.kind === 'seam' ? this.scene?.ensurePlane?.() : undefined;
740
752
  const at = plane?.placements.get(roomKey(currentRoom.name));
741
- const gated = exit instanceof Door && exit.checkIfLocked();
753
+ const gated = exit instanceof Door && lockedFrom(exit, currentRoom);
742
754
  if (way?.kind === 'seam' && at && here && !gated) {
743
755
  const world = { x: at.origin.x + way.door.x, y: at.origin.y + way.door.y, z: at.origin.z + way.door.z };
744
756
  const there = plane.placements.get(roomKey(way.target.name));
@@ -772,7 +784,7 @@ export class GoCommand extends Command {
772
784
  // crossExit). Asked BEFORE the walk: the bolt is visible from
773
785
  // across the room, and a runner told about it here is not made to
774
786
  // cross the floor to hear it.
775
- if (exit instanceof Door && exit.checkIfLocked() && this.actor.plane === 'meat') {
787
+ if (exit instanceof Door && lockedFrom(exit, currentRoom) && this.actor.plane === 'meat') {
776
788
  this.actor.performAction('is prevented by locked door', currentRoom.name);
777
789
  // NAME THE ROUTE (blocker 2026-08-27). This is FIRST CONTACT --
778
790
  // what a player types before they know a barrier is even there
@@ -815,7 +827,7 @@ export class GoCommand extends Command {
815
827
  */
816
828
  async crossExit(exit, direction, currentRoom, crossNote) {
817
829
  if (exit instanceof Door) {
818
- if (exit.checkIfLocked() && this.actor.plane === 'meat') {
830
+ if (lockedFrom(exit, currentRoom) && this.actor.plane === 'meat') {
819
831
  this.actor.performAction('is prevented by locked door', currentRoom.name);
820
832
  return `${crossNote}The door in the direction "${direction}" is locked.${lockedRouteHint(currentRoom, direction, exit)}`;
821
833
  }
@@ -109,7 +109,16 @@ export class RestCommand extends Command {
109
109
  // lifestyleChoices residence against the rooms the seed provides.
110
110
  // It answers "may someone of this tier live in this scene", never
111
111
  // "is this particular room your bedroom".
112
- const isResidence = room.roomType?.toLowerCase() === 'residence';
112
+ //
113
+ // AND A BED IS YOURS OR IT IS NOT A BED (kL23W3ufgsF5MZ2bt). "The
114
+ // roomType of _homeRoom and of nothing else" stopped being true when
115
+ // every district above Tacoma authored its own residence: a Street
116
+ // runner visiting Everett slept in Mill Court Flats on somebody
117
+ // else's lease. A residence a district authors FOR A TIER is the
118
+ // tenant's and nobody else's; any other Residence room (the runtime
119
+ // hole, a bench's `home` fixture) keeps the room-type answer.
120
+ const foreignFlat = room.residenceTier !== undefined && room !== this.game?.homeRoom;
121
+ const isResidence = room.roomType?.toLowerCase() === 'residence' && !foreignFlat;
113
122
  if (actor.stunTaken === 0 && actor.damageTaken === 0 && damagedDevices.length === 0 && damagedDrones.length === 0 && damagedImplants.length === 0) {
114
123
  // Nothing to heal -- but sleeping is its own point now (needs
115
124
  // system: edge refills only fed-and-slept, see Game.
@@ -83,6 +83,23 @@ export class TravelCommand extends Command {
83
83
  return `The driver takes the ${charge.paid} you have and doesn't ask twice.`;
84
84
  return `The fare takes its ${fare}.`;
85
85
  }
86
+ /**
87
+ * A CAB TAKES BODIES (6pdp3ELhsT8ihgtwH: "when using travel while
88
+ * projecting, my body is moved to the district, and am no longer
89
+ * projecting -- and when I return there is a duplicate body"). The
90
+ * ride moved the whole runner while the form was out, and bodyRoom
91
+ * kept a flesh in the room it had just left. call.ts's taxi has always
92
+ * refused off the meat plane; the ride across the sprawl is the same
93
+ * cab, so it asks the same question.
94
+ */
95
+ offPlaneRefusal() {
96
+ switch (this.actor.plane) {
97
+ case 'astral': return `Cabs take bodies, not ghosts -- "return" first.`;
98
+ case 'matrix': return `Cabs take bodies, not personas -- "jack out" first.`;
99
+ case 'drone': return `Your body is slumped where you left it and the frame can't hail a cab -- "jump out" first.`;
100
+ default: return undefined;
101
+ }
102
+ }
86
103
  async execute(args = []) {
87
104
  const game = this.game;
88
105
  // "travel to the safehouse" is "travel safehouse" (player 2026-09-25:
@@ -113,6 +130,9 @@ export class TravelCommand extends Command {
113
130
  if (!goingHome && destination === undefined) {
114
131
  return `Nowhere called "${wanted}" that a cab will take you.${hint(' ("travel" on its own lists the districts; "travel home" leaves.)')}`;
115
132
  }
133
+ const offPlane = this.offPlaneRefusal();
134
+ if (offPlane)
135
+ return offPlane;
116
136
  if (!game.onLeaveSpaceRequested)
117
137
  return `The door sticks -- the way home is not answering. Try again in a moment.`;
118
138
  if (inLiveCombat(this.scene, this.actor))
@@ -173,6 +193,9 @@ export class TravelCommand extends Command {
173
193
  return `No crew, no table -- seat a table or take a seat first.${hint(' ("crew invite <runner>", or accept an invite.)')}`;
174
194
  if (!this.scene.isHumanControlled(this.actor))
175
195
  return `The crew's table is for the crew, not the help.`;
196
+ const offPlane = this.offPlaneRefusal();
197
+ if (offPlane)
198
+ return offPlane;
176
199
  if (game.hasOpenRun)
177
200
  return `Not with a job left open -- finish it or walk out first.`;
178
201
  if (inLiveCombat(this.scene, this.actor))
@@ -251,6 +274,9 @@ export class TravelCommand extends Command {
251
274
  ? `You are home.`
252
275
  : `You are already in ${districts.find(d => d.id === targetId)?.label ?? targetId}.`;
253
276
  }
277
+ const offPlane = this.offPlaneRefusal();
278
+ if (offPlane)
279
+ return offPlane;
254
280
  // THE FARE IS READ OFF BOTH ENDS, so it has to be computed before the move
255
281
  // -- afterwards `districtId` is the destination and the origin is gone.
256
282
  const hereRating = game.districtPressure?.neighborhood;
@@ -3389,5 +3389,15 @@
3389
3389
  // or ranged attack whose line crosses a standing face is at -1, the
3390
3390
  // Light Fog row of the Environmental Modifiers table (p.175; confirmed
3391
3391
  // against the rules index and SR5 references online). Melee is exempt.
3392
- export const ENGINE_VERSION = '1.200.0';
3392
+ // 1.201.0 (2026-10-05): A CAB TAKES BODIES (6pdp3ELhsT8ihgtwH): `travel`
3393
+ // refuses a runner who is projecting, jacked in or jumped in, as `call
3394
+ // taxi` always has -- the ride moved the form and left a body behind.
3395
+ // SOMEBODY ELSE'S FLAT IS LOCKED (kL23W3ufgsF5MZ2bt): an authored
3396
+ // residence that is not the runner's home has its street door locked
3397
+ // (Game.lockForeignResidences; opens from inside), and `rest` counts
3398
+ // only the runner's own home as a bed.
3399
+ // 1.202.0 (2026-10-06): NO "other" ON THE HELP SCREEN. The nine sprite
3400
+ // powers (camouflage, cookie, ... watermark) shelve under resonance;
3401
+ // registered in a loop, they had slipped past the taxonomy test.
3402
+ export const ENGINE_VERSION = '1.202.0';
3393
3403
  //# sourceMappingURL=engine-version.js.map
@@ -3522,6 +3522,7 @@ export default class Game {
3522
3522
  // cosmetics below, ask whether this district authors a building for
3523
3523
  // the tier now being paid for, and move house if it does.
3524
3524
  this.rebindHomeForTier();
3525
+ this.lockForeignResidences();
3525
3526
  /**
3526
3527
  * AN AUTHORED RESIDENCE IS THE SEED'S, NOT THE TIER TABLE'S.
3527
3528
  *
@@ -3634,6 +3635,50 @@ export default class Game {
3634
3635
  return Object.values(this.scene?.getRooms?.() ?? {})
3635
3636
  .find(r => r.residenceTier === this.lifestyle.name);
3636
3637
  }
3638
+ /**
3639
+ * SOMEBODY ELSE'S FLAT IS LOCKED (kL23W3ufgsF5MZ2bt: "I'm able to enter
3640
+ * and use an apartment for a lifestyle I don't pay for").
3641
+ *
3642
+ * Every district above Tacoma authors one residence, for its own tier,
3643
+ * and that room used to stand open to anyone on the street: a Street
3644
+ * runner visiting Everett walked into Mill Court Flats and slept in its
3645
+ * bed. The address is the lease's, so the door is locked to everyone
3646
+ * but its tenant -- and unlocked again the day the lease is theirs.
3647
+ * Only the locks this put on are ever lifted: a door a seed or a device
3648
+ * locked keeps its own reasons.
3649
+ *
3650
+ * Breaking in is still a way in (breach); this is a lock, not a wall.
3651
+ */
3652
+ lockForeignResidences() {
3653
+ const rooms = Object.values(this.scene?.getRooms?.() ?? {});
3654
+ for (const room of rooms) {
3655
+ if (room.residenceTier === undefined)
3656
+ continue;
3657
+ const ours = room === this._homeRoom;
3658
+ const doors = new Set();
3659
+ for (const r of rooms) {
3660
+ for (const ex of r.exits.values()) {
3661
+ if (ex instanceof Door && (ex.roomA === room || ex.roomB === room))
3662
+ doors.add(ex);
3663
+ }
3664
+ }
3665
+ for (const door of doors) {
3666
+ // YOUR OWN DOOR: lifted whether or not this session put the lock
3667
+ // on -- a hub overlay restores isLocked but not the flag, so a
3668
+ // flat locked on a visit and moved into later would otherwise
3669
+ // come back shut on its tenant. Keyed and device-held locks are
3670
+ // the seed's, and stay.
3671
+ if (ours && door.isLocked && !door.heldBy && (door.tenantLocked || !door.unlockItemOrSolution)) {
3672
+ door.isLocked = false;
3673
+ door.tenantLocked = false;
3674
+ }
3675
+ else if (!ours && !door.isLocked && !door.heldBy) {
3676
+ door.isLocked = true;
3677
+ door.tenantLocked = true;
3678
+ }
3679
+ }
3680
+ }
3681
+ }
3637
3682
  rebindHomeForTier() {
3638
3683
  const current = this._homeRoom;
3639
3684
  if (current === undefined)
@@ -5750,6 +5795,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5750
5795
  this.gangedRooms.clear();
5751
5796
  this.peopledRooms.clear();
5752
5797
  this.patrolledRooms.clear();
5798
+ // Arrived in a district with somebody else's flat on it (or your own).
5799
+ this.lockForeignResidences();
5753
5800
  const arrivalLines = this.unfoldCompanionsOnArrival();
5754
5801
  this.repaintForScene();
5755
5802
  this.refreshLogLabel();
@@ -6450,6 +6497,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
6450
6497
  // everything stored since.
6451
6498
  const world = elsewhere ? this._hubArchive[here] : save.hub;
6452
6499
  applyHubOverlay(this.scene, world ? { ...world, home: save.hub.home } : save.hub, this.player, this._homeRoom, here);
6500
+ // The overlay restores door locks; whose flat is whose is decided now.
6501
+ this.lockForeignResidences();
6453
6502
  this._homeDistrictId = save.game.homeDistrictId;
6454
6503
  this._birthDistrictId = save.game.birthDistrictId;
6455
6504
  this._tier = save.game.tier;
@@ -8,6 +8,10 @@ export class Door extends AbstractDoor {
8
8
  unlockItemOrSolution;
9
9
  /** Set when a Device holds this door shut -- see IDoorHolder. */
10
10
  heldBy;
11
+ /** Locked because it is the street door of a residence that is not the
12
+ * runner's own (Game.lockForeignResidences) -- the lock that lifts the
13
+ * day the lease is theirs. */
14
+ tenantLocked;
11
15
  logger;
12
16
  isLockable;
13
17
  constructor(config) {
@@ -1643,6 +1643,10 @@ export function lockSeenFromOutside(building, side, door) {
1643
1643
  return ` ${device.name} holds it, and it is worked from inside.`;
1644
1644
  if (door.heldBy)
1645
1645
  return ` It's barred from ${door.heldBy.room} -- ${door.heldBy.device} is in there.`;
1646
+ // SOMEBODY'S FLAT (kL23W3ufgsF5MZ2bt): a residence that is not yours
1647
+ // is locked to you; Game.lockForeignResidences put the lock on.
1648
+ if (door.tenantLocked)
1649
+ return ` Somebody's flat -- the lease on it isn't yours, and neither is the key.`;
1646
1650
  if (door.unlockItemOrSolution)
1647
1651
  return ` It's keyed -- it takes ${door.unlockItemOrSolution}, from inside.`;
1648
1652
  return ` Bolted from the inside -- nothing on this side of it to work.`;
@@ -1,4 +1,5 @@
1
1
  import { isPlaytester } from './playtester.js';
2
+ import { SPRITE_POWERS } from '../sprite-powers.js';
2
3
  /**
3
4
  * THE VERBS A PLAYER MAY TYPE, in one place.
4
5
  *
@@ -35,7 +36,12 @@ export const HELP_CATEGORIES = [
35
36
  // `sustain` shelves here and not under magic: the command is
36
37
  // technomancer-gated (a sprite task, SR5 p.256) and refuses anyone
37
38
  // else, so the Emerged shelf is where its audience looks.
38
- { title: 'resonance', entries: [['compile'], ['decompile'], ['thread'], ['unravel'], ['register'], ['standby'], ['sustain'], ['sprites']] },
39
+ // The SPRITE POWERS (p.256-257) shelve here too, read off the same
40
+ // table game.ts registers them from. They were the last tenants of
41
+ // "other": registered in a loop, so the taxonomy test's
42
+ // registerCommand('x') sweep never saw them. A player runs them as
43
+ // "order <sprite> <power>"; typed bare they say so.
44
+ { title: 'resonance', entries: [['compile'], ['decompile'], ['thread'], ['unravel'], ['register'], ['standby'], ['sustain'], ['sprites'], ...SPRITE_POWERS.map(p => [p.key])] },
39
45
  { title: 'social', entries: [['say'], ['shout'], ['emote', 'me'], ['tell', 'to', 'talk-to'], ['call'], ['message'], ['messages'], ['end-call', 'end', 'hangup'], ['accept'], ['decline', 'deny', 'refuse'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['bring', 'escort', 'extract'], ['ask'], ['contacts']] },
40
46
  { title: 'street', entries: [['abandon'], ['browse'], ['buy'], ['sell'], ['catalog'], ['source', 'procure'], ['work', 'gig'], ['jobs'], ['journal'], ['perform'], ['palm'], ['docwagon'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['garage'], ['travel'], ['pay']] },
41
47
  { title: 'barriers', entries: [['pick'], ['breach'], ['dispel', 'dispell'], ['bluff'], ['unlock'], ['lock']] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.388.0",
3
+ "version": "5.390.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.",