@maka/maka-cli 5.169.0 → 5.171.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/factories/scene-chunks.js +53 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +8 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +34 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.171.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.",
|
|
@@ -157,6 +157,10 @@ Tailoring rules:
|
|
|
157
157
|
- Never gate FINISHING the run behind a capability they lack: no matrix-plane winCondition
|
|
158
158
|
item unless they can already jack in (deck or resonance) or a findable Cyberdeck is placed
|
|
159
159
|
in a reachable room; no ward/"spell"-bypass-only route for someone who can't cast.
|
|
160
|
+
- The mirror of that rule: if they CAN jack in and the winCondition item is a Datachip or
|
|
161
|
+
Document, it is PAYDATA -- "plane": "matrix", in a hasNode room, with an ice NPC stationed
|
|
162
|
+
there. A hacker's file is never lying on a desk in the meat world; the host IS the job.
|
|
163
|
+
(A physical objective -- a person, a prototype, a briefcase -- stays physical.)
|
|
160
164
|
- Don't hand them loot they can't use as a puzzle rewardItem or the winCondition rewardItem
|
|
161
165
|
(spellcasting gear for an adept, a second cyberdeck or drone for someone who already has
|
|
162
166
|
one). Useless-to-them gear may still exist as ordinary world dressing or valuables.
|
|
@@ -500,7 +504,9 @@ Hard structural rules (validated mechanically -- a violation is rejected):
|
|
|
500
504
|
skill can still take the shortcut -- pick the lock, hack the panel -- but the found way
|
|
501
505
|
must exist for a runner who has neither.
|
|
502
506
|
- The winCondition item must not be heldBy winCondition.toNpc. If a matrix-plane item is the
|
|
503
|
-
winCondition item, its room has hasNode true AND a "matrix"-plane NPC stationed there.
|
|
507
|
+
winCondition item, its room has hasNode true AND a "matrix"-plane NPC stationed there. If
|
|
508
|
+
the runner can jack in, a Datachip/Document winCondition item MUST be that matrix-plane
|
|
509
|
+
item -- data for a hacker lives on a host, never on a desk.
|
|
504
510
|
- ${isContinuation
|
|
505
511
|
? 'ZERO "starting"-role items -- the runner arrives with everything they own. 4-6'
|
|
506
512
|
: '3-5 "starting"-role items (basics the runner doesn\'t already own -- see the dossier), 4-6'}
|
|
@@ -763,6 +769,14 @@ export function partyCanDispel(player) {
|
|
|
763
769
|
export function partyCanDeck(player) {
|
|
764
770
|
return !!player && (player.isTechnomancer() || !!player.getCyberdeck());
|
|
765
771
|
}
|
|
772
|
+
/** The item categories that ARE data: the only shapes paydata takes
|
|
773
|
+
* (schema: "matrix" = paydata, category Datachip/Document). A win item
|
|
774
|
+
* in one of these, for a runner who can deck, belongs on a host --
|
|
775
|
+
* see the rule in validateSkeleton (jfjjTmJ7NYwDaFiyB). */
|
|
776
|
+
export const DATA_ITEM_CATEGORIES = ['datachip', 'document'];
|
|
777
|
+
export function isDataShaped(category) {
|
|
778
|
+
return !!category && DATA_ITEM_CATEGORIES.includes(category.trim().toLowerCase());
|
|
779
|
+
}
|
|
766
780
|
/**
|
|
767
781
|
* WHAT KIND OF JOB IS ON THE TABLE.
|
|
768
782
|
*
|
|
@@ -990,6 +1004,44 @@ export function validateSkeleton(skeleton, player, crew) {
|
|
|
990
1004
|
fail(`winCondition.item "${skeleton.winCondition.item}" is not in items.`);
|
|
991
1005
|
if (!npcNames.has(skeleton.winCondition.toNpc))
|
|
992
1006
|
fail(`winCondition.toNpc "${skeleton.winCondition.toNpc}" is not in npcs.`);
|
|
1007
|
+
// A HACKER'S DATA LIVES ON A HOST (jfjjTmJ7NYwDaFiyB, Ted: "the
|
|
1008
|
+
// encrypted data chip was just laying there in the meat world...
|
|
1009
|
+
// hardly a fun run for a decker that never needs to attack a host,
|
|
1010
|
+
// get the data, download it, and get out"). The data-run brief only
|
|
1011
|
+
// fires on the DATA_RUN_SHARE roll; the other seven jobs in ten are
|
|
1012
|
+
// standard runs, and a standard run was free to make its objective a
|
|
1013
|
+
// Datachip on a desk -- for a runner whose whole toolkit is the
|
|
1014
|
+
// Matrix. So: whenever the runner CAN jack in and the objective is
|
|
1015
|
+
// data-shaped, it is paydata, and paydata sits inside a host with ice
|
|
1016
|
+
// on it. A runner who cannot deck keeps the chip on the desk (the
|
|
1017
|
+
// "never gate finishing" rule outranks this one), and a physical
|
|
1018
|
+
// objective -- a person, a prototype, a briefcase -- is untouched.
|
|
1019
|
+
//
|
|
1020
|
+
// Checked HERE, at the skeleton, and not left to validateMatrixDefense
|
|
1021
|
+
// (scene-seed-generator.ts): that one runs after the detail passes and
|
|
1022
|
+
// its wording routes to the items chunk through classifyRepair, which
|
|
1023
|
+
// cannot add a hasNode flag or an ice NPC -- the skeleton owns both.
|
|
1024
|
+
// Worded to route to 'skeleton' (no "items[", no "Win condition item").
|
|
1025
|
+
const winItem = skeleton.items.find(i => i.name === skeleton.winCondition.item);
|
|
1026
|
+
if (winItem && isDataShaped(winItem.category) && winItem.plane !== 'matrix' && partyCanDeck(player)) {
|
|
1027
|
+
fail(`the winCondition item "${winItem.name}" is a ${winItem.category} on the meat plane, and this runner can jack in -- for a decker or technomancer, data is PAYDATA: give it "plane": "matrix", place it in a hasNode room (not heldBy anyone), and station a "matrix"-plane NPC (ice) there. A chip lying on a desk is no job for a hacker.`);
|
|
1028
|
+
}
|
|
1029
|
+
// The other half of the same rule, for ANY matrix-plane objective: the
|
|
1030
|
+
// schema says its room has hasNode and an ice NPC stationed there, and
|
|
1031
|
+
// until now nothing at the skeleton checked it.
|
|
1032
|
+
if (winItem?.plane === 'matrix') {
|
|
1033
|
+
if (!winItem.room) {
|
|
1034
|
+
fail(`the winCondition item "${winItem.name}" is matrix-plane paydata but has no "room" -- data lives on a host, never in a pocket. Place it in a hasNode room.`);
|
|
1035
|
+
}
|
|
1036
|
+
const vault = skeleton.rooms.find(r => r.name === winItem.room);
|
|
1037
|
+
if (!vault?.hasNode) {
|
|
1038
|
+
fail(`the winCondition item "${winItem.name}" is matrix-plane paydata in "${winItem.room}", which has no "hasNode": true -- a matrix file can only exist on a host. Mark that room hasNode.`);
|
|
1039
|
+
}
|
|
1040
|
+
const ice = skeleton.npcs.some(n => n.plane === 'matrix' && n.startLocation === winItem.room);
|
|
1041
|
+
if (!ice) {
|
|
1042
|
+
fail(`the winCondition item "${winItem.name}" is matrix-plane paydata in hasNode room "${winItem.room}" with no ice guarding it -- station a "matrix"-plane NPC there. An undefended vault is a free win.`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
993
1045
|
// Lock-and-key reachability. CRITICAL: edges are BIDIRECTIONAL -- the
|
|
994
1046
|
// engine mirrors every declared exit into a shared two-way Door
|
|
995
1047
|
// (room.addExit writes both sides), so a one-sided skeleton exit still
|
|
@@ -2394,6 +2394,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2394
2394
|
// never report; the anonymous/bench gate is evaluated at fire time
|
|
2395
2395
|
// because _anonymous is decided after construction.
|
|
2396
2396
|
this.player.onConditionChanged = () => {
|
|
2397
|
+
// THE TERMINAL'S ROOM PANE RIDES THE SAME SIGNAL (q6tBaFn5EDmGMoJFh):
|
|
2398
|
+
// the room now reports every occupant's move to the humans in it
|
|
2399
|
+
// (Room.noteOccupancyChanged), and this is the one hook a human's
|
|
2400
|
+
// report fires -- so a body stepping across the room while the
|
|
2401
|
+
// player watches repaints here at once, not on the next keystroke
|
|
2402
|
+
// or the 2-second map heartbeat. Same call a typed move makes;
|
|
2403
|
+
// updateExits is a no-op without a screen, and combat repaints
|
|
2404
|
+
// itself blow by blow (see mapHeartbeatTick).
|
|
2405
|
+
if (this._mapBox && !this.player.inExchange)
|
|
2406
|
+
this.updateExits(this.player.currentLocation);
|
|
2397
2407
|
if (this._anonymous || this._bench)
|
|
2398
2408
|
return;
|
|
2399
2409
|
// Checked HERE too, before the import -- a dynamic import after a
|
|
@@ -391,10 +391,14 @@ export async function createHeadlessSession(opts) {
|
|
|
391
391
|
// THE WEB MAP FOLLOWS THE ROOM, NOT JUST YOUR OWN BODY
|
|
392
392
|
// (oHhjxjqKuptDGMHLD: a looted body stayed on the map "until I
|
|
393
393
|
// move"; 9aEMA4APbB6rYtWnT: a killed NPC's dot never changed).
|
|
394
|
-
// The condition beat is what the browser draws actors from
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
394
|
+
// The condition beat is what the browser draws actors from. A
|
|
395
|
+
// kill or a loot mutates the room, not the killer, so this
|
|
396
|
+
// nudge -- one per command, coalesced -- still covers those.
|
|
397
|
+
// MOVEMENT NO LONGER WAITS FOR IT (q6tBaFn5EDmGMoJFh): the room
|
|
398
|
+
// publishes every occupant's position change to the humans in
|
|
399
|
+
// it the moment it happens (Room.noteOccupancyChanged), so an
|
|
400
|
+
// NPC stepping on its own reflect chain between commands
|
|
401
|
+
// reaches the browser without anyone typing.
|
|
398
402
|
game.player.noteConditionChanged();
|
|
399
403
|
return res;
|
|
400
404
|
})),
|
|
@@ -2258,7 +2258,10 @@ export class Player extends AbstractPlayer {
|
|
|
2258
2258
|
// gets no cover) -- the compat default for everything un-migrated.
|
|
2259
2259
|
// Accessor (live-sheet, 2026-09-01, user: "let's get that streaming
|
|
2260
2260
|
// in too"): in-room movement walks the sheet's dot map, so the write
|
|
2261
|
-
// fires the report hook on CHANGE. Source-compatible;
|
|
2261
|
+
// fires the report hook on CHANGE. Source-compatible; an NPC's own
|
|
2262
|
+
// hook is a no-op -- which is why the ROOM is told as well
|
|
2263
|
+
// (Room.noteOccupancyChanged, q6tBaFn5EDmGMoJFh): the humans standing
|
|
2264
|
+
// in it are the ones whose maps draw this body, and they report.
|
|
2262
2265
|
_atSpot;
|
|
2263
2266
|
get atSpot() { return this._atSpot; }
|
|
2264
2267
|
set atSpot(v) {
|
|
@@ -2266,6 +2269,7 @@ export class Player extends AbstractPlayer {
|
|
|
2266
2269
|
return;
|
|
2267
2270
|
this._atSpot = v;
|
|
2268
2271
|
this.noteConditionChanged();
|
|
2272
|
+
this._currentLocation?.noteOccupancyChanged(this);
|
|
2269
2273
|
}
|
|
2270
2274
|
/**
|
|
2271
2275
|
* WHICH PAPER YOU CARRY (player request 2026-08-25, twice: "I should
|
|
@@ -2310,6 +2314,8 @@ export class Player extends AbstractPlayer {
|
|
|
2310
2314
|
return;
|
|
2311
2315
|
this._atCell = v;
|
|
2312
2316
|
this.noteConditionChanged();
|
|
2317
|
+
// The witnesses' maps move too -- see the atSpot accessor above.
|
|
2318
|
+
this._currentLocation?.noteOccupancyChanged(this);
|
|
2313
2319
|
}
|
|
2314
2320
|
/** The spot this actor was SEEDED at -- i.e. the fixture they work
|
|
2315
2321
|
* (a bartender's bar, a shopkeeper's counter). Seating puts them on
|
|
@@ -619,6 +619,7 @@ Keep it concise and dramatic.`;
|
|
|
619
619
|
this.logger.write(`${other.name} will observe ${actor.name}`);
|
|
620
620
|
}
|
|
621
621
|
}
|
|
622
|
+
this.noteOccupancyChanged(actor);
|
|
622
623
|
}
|
|
623
624
|
}
|
|
624
625
|
removeActor(actor) {
|
|
@@ -633,6 +634,39 @@ Keep it concise and dramatic.`;
|
|
|
633
634
|
actor.off('action', other._onSeenAction);
|
|
634
635
|
}
|
|
635
636
|
}
|
|
637
|
+
this.noteOccupancyChanged(actor);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* THE ROOM PUBLISHES ITS OWN OCCUPANCY (q6tBaFn5EDmGMoJFh, 2026-09-11:
|
|
641
|
+
* "I still can't see the NPCs moving unless I move").
|
|
642
|
+
*
|
|
643
|
+
* The browser draws every dot on the room map from a human's condition
|
|
644
|
+
* report (condition-report.ts buildConditionReport reads THIS room's
|
|
645
|
+
* actors at fire time), and that report is fired by
|
|
646
|
+
* Player.noteConditionChanged -- which every position setter calls on
|
|
647
|
+
* the actor that MOVED. An NPC has no reporter of its own (game.ts sets
|
|
648
|
+
* the hook on the primary human only, on purpose), so an NPC stepping
|
|
649
|
+
* across the room on its own reflect chain, a companion answering the
|
|
650
|
+
* pursuit heartbeat, or a hostile taking its Action Phase changed the
|
|
651
|
+
* room's truth and told nobody. The only thing that ever refreshed the
|
|
652
|
+
* dots was the once-per-command nudge in headless.ts -- which is
|
|
653
|
+
* exactly "they move when I move".
|
|
654
|
+
*
|
|
655
|
+
* So the room tells everyone standing in it. NPCs and crew shells
|
|
656
|
+
* no-op (no hook); each human present fires its own report, and the
|
|
657
|
+
* report layer coalesces a burst (pendingByPlayer, 250 ms) so a mob of
|
|
658
|
+
* four stepping at once is one beat per watching human. DDP carries it
|
|
659
|
+
* from there -- no timer, no polling, no beat of its own (user ruling:
|
|
660
|
+
* "this is a pub/sub mechanic that doesn't need beats").
|
|
661
|
+
*
|
|
662
|
+
* `cause` is left out: the mover already reported itself through its
|
|
663
|
+
* own setter, and a second report for the same change is just noise.
|
|
664
|
+
*/
|
|
665
|
+
noteOccupancyChanged(cause) {
|
|
666
|
+
for (const other of this.actors) {
|
|
667
|
+
if (other !== cause)
|
|
668
|
+
other.noteConditionChanged();
|
|
669
|
+
}
|
|
636
670
|
}
|
|
637
671
|
getActors() {
|
|
638
672
|
return Array.from(this.actors);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.171.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.",
|