@maka/maka-cli 5.161.0 → 5.162.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.161.0",
3
+ "version": "5.162.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.",
@@ -392,5 +392,9 @@
392
392
  // aggressor and their target; the world event carries it; "initiative"
393
393
  // marks them. Hostiles are red on the room map by stance or by live
394
394
  // enmity, not by the retired exchange flag.
395
- export const ENGINE_VERSION = '1.44.0';
395
+ // 1.45.0 (2026-09-11): THE MAPS AS DATA. The condition beat carries the
396
+ // room's live grid, every body's engine-resolved square, and the room
397
+ // panel's own hostile test; the map event carries the fog-applied
398
+ // district layout (data.district). Envelope and beat shape both grew.
399
+ export const ENGINE_VERSION = '1.45.0';
396
400
  //# sourceMappingURL=engine-version.js.map
@@ -2401,7 +2401,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2401
2401
  if (!this._conditionSink && process.env.MAKA_NO_DDP === '1')
2402
2402
  return;
2403
2403
  void import('./utilities/condition-report.js')
2404
- .then(m => m.reportConditionSoon(this.player, this._conditionSink))
2404
+ .then(m => m.reportConditionSoon(this.player, this._conditionSink, {
2405
+ // The room panel's own red (updateExits below): stance OR live
2406
+ // enmity in the viewer's encounter. Same predicate, same map.
2407
+ isHostile: a => hostileByStance(a) || enemyInFight(this.scene, this.player, a),
2408
+ }))
2405
2409
  .catch(() => undefined);
2406
2410
  };
2407
2411
  // A ROOM CHANGE IS A SAVE BEAT (player request 2026-09-02): where you
@@ -17,7 +17,7 @@ import { Logger } from './utilities/logger.js';
17
17
  import { currentSession, runInSession } from './utilities/session-context.js';
18
18
  import { restorePlayer, serializePlayer } from './utilities/persistence.js';
19
19
  import { runWithAuthToken } from './utilities/cloud-saves.js';
20
- import { renderMapViewport, mapCaption } from './utilities/map-view.js';
20
+ import { renderMapViewport, mapCaption, serializeDistrictMap } from './utilities/map-view.js';
21
21
  import { hostileByStance, enemyInFight, renderRoomLayoutCompact, crewOfTable, roomCaption } from './utilities/room-view.js';
22
22
  import { serializeRoomGrid } from './utilities/room-grid.js';
23
23
  import { entrySpotFor, spotOf } from './utilities/spots.js';
@@ -297,7 +297,18 @@ export async function createHeadlessSession(opts) {
297
297
  const mapLines = renderMapViewport(rooms, here, sealing, eyes);
298
298
  if (mapLines.length > 0)
299
299
  mapLines.push(mapCaption(here.name));
300
- logger.system(mapLines.join('\n'), { actor: p.name }, 'map');
300
+ // STRUCTURED DISTRICT MAP, ADDITIVE (2026-09-11): the same layout
301
+ // as data, on this same event, so the browser client draws it in
302
+ // its own style instead of reparsing the text (the `data.grid`
303
+ // pattern below). A decoration must never kill the push.
304
+ let district;
305
+ try {
306
+ district = serializeDistrictMap(rooms, here, sealing, eyes);
307
+ }
308
+ catch (err) {
309
+ logger.write(`District map serialize failed for ${p.name} in ${here.name}: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
310
+ }
311
+ logger.system(mapLines.join('\n'), { actor: p.name }, 'map', district ? { district } : undefined);
301
312
  // THE ROOM BOX'S TWIN (playtest 2026-08-27, reported twice:
302
313
  // "the room minimap isn't updating in the shared run, I only
303
314
  // see @open floor"). Game.updateExits early-returns on
@@ -1,6 +1,8 @@
1
1
  import { Logger } from './logger.js';
2
- import { visibleActorsIn } from './spots.js';
2
+ import { visibleActorsIn, seatingIn } from './spots.js';
3
3
  import { hostLabel } from './grid-names.js';
4
+ import { serializeRoomGrid } from './room-grid.js';
5
+ import { hostileByStance } from './room-view.js';
4
6
  /**
5
7
  * THE LIVE CONDITION EMITTER -- the CLI half of the realtime sheet lane
6
8
  * (contract agreed with maka-cli.com, 2026-09-01). On every condition
@@ -71,13 +73,56 @@ export function setMarksPlacedResolver(fn) {
71
73
  export function setActorRoleResolver(fn) {
72
74
  actorRole = fn;
73
75
  }
76
+ /**
77
+ * THE ROOM'S GEOMETRY, ON THE BEAT (2026-09-11, 8bZXzNXLgKwE5wxgh: "no
78
+ * icons being drawn on the character sheet in the active run"). The web
79
+ * sheet rebuilt its room from the SAVE's hub seed, and a generated run
80
+ * room is not in it -- so in a run the sheet had no furniture, no doors,
81
+ * and no cell to seat a spot-sitting NPC on. The beat already fires on
82
+ * every position change and is the one channel both the sheet and the
83
+ * play page read, so the live grid rides it: the SAME wire shape the
84
+ * room event carries for the browser client (headless.ts pushMap ->
85
+ * serializeRoomGrid). Physical planes only -- jacked in, the meat map is
86
+ * a lie the sheet already refuses to draw. A decoration never kills the
87
+ * beat: any throw here is an absent field, not a lost report.
88
+ */
89
+ function roomGridFor(player) {
90
+ const room = player.currentLocation;
91
+ if (!room || (player.plane !== 'meat' && player.plane !== 'drone'))
92
+ return undefined;
93
+ try {
94
+ const grid = room.ensureGrid();
95
+ return grid ? serializeRoomGrid(grid, room.name) : undefined;
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
74
101
  export function buildConditionReport(player, deps) {
75
- const { saveSlug, arSightUp } = deps;
102
+ const { saveSlug, arSightUp, isHostile } = deps;
76
103
  // BRICKED INCLUDED (web report 2026-09-02: "my deck got bricked, but its
77
104
  // status still says 4/12"): a forced dump clears activeDeck and
78
105
  // getCyberdeck() hides a bricked deck, so the beat read nothing and the
79
106
  // sheet froze on the last number. Status reads getAnyCyberdeck.
80
107
  const deck = player.activeDeck ?? player.getAnyCyberdeck();
108
+ // WHERE EVERYONE ACTUALLY STANDS (2026-09-11, user: "movement isn't
109
+ // snappy" on the web). The beat sent `atCell` alone, and `atCell` is
110
+ // the EXCEPTION -- set by a tactical step, cleared by "move to <spot>"
111
+ // and by most arrivals -- so after a plain move the beat carried no
112
+ // cell, the server kept the last one it had, and the dot on the web
113
+ // sat still at a square the runner had left. seatingIn is the engine's
114
+ // one answer to "which square" (the room panel, cover and ranges all
115
+ // read it), so the web now gets the same square the CLI draws. Read at
116
+ // fire time like everything else here; a throw means "no seat", never
117
+ // a lost beat.
118
+ const room = player.currentLocation;
119
+ let seats = new Map();
120
+ if (room && (player.plane === 'meat' || player.plane === 'drone')) {
121
+ try {
122
+ seats = seatingIn(room);
123
+ }
124
+ catch { /* keep the empty map */ }
125
+ }
81
126
  return {
82
127
  slug: saveSlug(player.name),
83
128
  damageTaken: player.damageTaken,
@@ -85,7 +130,8 @@ export function buildConditionReport(player, deps) {
85
130
  overflow: 0,
86
131
  room: player.currentLocation?.name,
87
132
  spot: player.atSpot,
88
- cell: player.atCell,
133
+ cell: player.atCell ?? seats.get(player.name),
134
+ grid: roomGridFor(player),
89
135
  plane: player.plane,
90
136
  // Where the meat sits while the persona rides the grid (web accepts
91
137
  // since 2026-09-02): "jacked in from Your Hideout". Absent in the meat.
@@ -196,11 +242,14 @@ export function buildConditionReport(player, deps) {
196
242
  .slice(0, 24)
197
243
  .map(a => ({
198
244
  name: a.name,
245
+ // THE SAME RED THE ROOM PANEL USES (see IConditionReportDeps
246
+ // .isHostile): stance flag OR live enmity, and never a
247
+ // surrendered one -- hostileByStance owns that clause.
199
248
  kind: a.allyOf ? 'ally'
200
- : a.hostile ? 'hostile' : 'neutral',
249
+ : (isHostile?.(a) ?? hostileByStance(a)) ? 'hostile' : 'neutral',
201
250
  role: actorRole(a),
202
251
  spot: a.atSpot,
203
- cell: a.atCell,
252
+ cell: a.atCell ?? seats.get(a.name),
204
253
  })),
205
254
  };
206
255
  }
@@ -227,7 +276,7 @@ function noteRoster(report) {
227
276
  const ADDITIVE_FIELDS = [
228
277
  'ar', 'actors', 'deckLine', 'deckDamage', 'astralPerceiving', 'bodyRoom', 'sustainingPenalty',
229
278
  'matrix', 'panMarks', 'hostMarksOnYou', 'marksPlaced', 'marksOnYou', 'deckStored', 'droneDamage', 'companions', 'pools', 'initiative',
230
- 'karma', 'nuyen', 'edge',
279
+ 'karma', 'nuyen', 'edge', 'grid',
231
280
  ];
232
281
  /** Refusal reasons already written to the session log -- one line each. */
233
282
  const announcedRefusals = new Map();
@@ -242,7 +291,10 @@ function noteRefusal(why) {
242
291
  catch { /* no session */ }
243
292
  }
244
293
  }
245
- export function reportConditionSoon(player, sink) {
294
+ export function reportConditionSoon(player, sink,
295
+ /** The Scene-side half of the beat (IConditionReportDeps.isHostile);
296
+ * the caller that owns a Scene supplies it, nobody else needs to. */
297
+ extra) {
246
298
  // Before the TIMER, not just before the call: a scheduled beat firing
247
299
  // after a Jest file finished lazy-imported into a torn-down
248
300
  // environment and failed CI with every test green (2026-09-01). Under
@@ -269,7 +321,7 @@ export function reportConditionSoon(player, sink) {
269
321
  // stance and brandish flow through their accessors' own reports;
270
322
  // sustained and deck programs are composed here and ride whatever
271
323
  // beat fires next. All read at FIRE time, like position.
272
- const report = buildConditionReport(player, { saveSlug, arSightUp });
324
+ const report = buildConditionReport(player, { saveSlug, arSightUp, isHostile: extra?.isHostile });
273
325
  noteRoster(report);
274
326
  if (sink) {
275
327
  // IN-PROCESS: no HubLink, no MAKA_NO_DDP gate, no version-skew
@@ -220,6 +220,39 @@ export function renderMapViewport(rooms, here, sealing, eyes, viewW = 21, viewH
220
220
  out[halfH] = out[halfH].slice(0, halfW) + `{inverse}${out[halfH][halfW] === ' ' ? '▣' : out[halfH][halfW]}{/inverse}` + out[halfH].slice(halfW + 1);
221
221
  return out;
222
222
  }
223
+ export function serializeDistrictMap(rooms, here, sealing, eyes) {
224
+ const floors = layoutFloors(rooms, here, sealing);
225
+ if (floors.length === 0)
226
+ return undefined;
227
+ const eye = fogEye(rooms, eyes?.visited);
228
+ const ar = eyes?.arEyes !== false;
229
+ const out = floors.map(floor => {
230
+ const linkSeen = (k) => {
231
+ const a = floor.roomAt.get(`${k.ax},${k.ay}`);
232
+ const b = floor.roomAt.get(`${k.bx},${k.by}`);
233
+ return !!a && !!b && eye.sees(a) && eye.sees(b) && (eye.knows(a) || eye.knows(b));
234
+ };
235
+ const seen = [...floor.posByRoom.entries()].filter(([r]) => eye.sees(r));
236
+ return {
237
+ rooms: seen.map(([r, p]) => {
238
+ const known = eye.knows(r);
239
+ return {
240
+ x: p.x, y: p.y,
241
+ ...(known ? { name: r.name } : {}),
242
+ known,
243
+ here: r === here,
244
+ node: known && ar && !!r.hasNode,
245
+ up: known && hasWay(r, 'up'),
246
+ down: known && hasWay(r, 'down'),
247
+ };
248
+ }),
249
+ links: floor.links.filter(linkSeen).map(k => ({ ...k })),
250
+ };
251
+ });
252
+ const current = Math.max(0, floors.findIndex(f => f.posByRoom.has(here)));
253
+ const notes = fogNotes(floors[current]?.notes ?? [], rooms, eye);
254
+ return { current, floors: out, notes };
255
+ }
223
256
  /**
224
257
  * The DETAILED view (the "map" text command): named cells, box-drawing
225
258
  * connections, and annotations -- the renderer shipped in 5.48.0, now
@@ -822,6 +822,14 @@ export function serializeRoomGrid(grid, roomName) {
822
822
  const [x, y, z] = cellKey.split(',').map(Number);
823
823
  return { kind: link.kind, x, y, z };
824
824
  });
825
+ const terrain = [...grid.terrain.values()].map(r => ({
826
+ id: r.id, label: r.label, cover: r.cover, blocking: r.blocking, cleared: r.cleared,
827
+ cells: r.cells.map(c => ({ x: c.x, y: c.y, z: c.z })),
828
+ }));
829
+ const levels = [];
830
+ for (let z = 1; z < grid.dims.z; z++) {
831
+ levels.push({ z, cells: levelFootprint(grid, z).map(c => ({ x: c.x, y: c.y })) });
832
+ }
825
833
  return {
826
834
  name: roomName,
827
835
  dims: { x: grid.dims.x, y: grid.dims.y, z: grid.dims.z },
@@ -829,6 +837,8 @@ export function serializeRoomGrid(grid, roomName) {
829
837
  doors,
830
838
  footprints,
831
839
  vertical,
840
+ terrain,
841
+ levels,
832
842
  };
833
843
  }
834
844
  function clamp(n, lo, hi) { return Math.max(lo, Math.min(hi, n)); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/maka-cli",
3
- "version": "5.161.0",
3
+ "version": "5.162.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.",