@maka/maka-cli 5.120.0 → 5.122.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.
Files changed (147) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +72 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +300 -61
  4. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  5. package/bundle/typescript/src/commands/game/sideQuest/aspects.js +11 -1
  6. package/bundle/typescript/src/commands/game/sideQuest/aspects.js.map +1 -1
  7. package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +33 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +85 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -1
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +2 -2
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +31 -2
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +8 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +10 -3
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +17 -4
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +1 -1
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.d.ts +0 -7
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js +39 -3
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/catalog.js.map +1 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +31 -5
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -1
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/fit.d.ts +29 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/fit.js +85 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/fit.js.map +1 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +16 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +273 -28
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +4 -4
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +16 -5
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.d.ts +19 -10
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +43 -227
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js.map +1 -1
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +31 -3
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +3 -1
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -1
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +34 -1
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +81 -12
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +5 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +5 -2
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +4 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -1
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +4 -6
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -1
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +10 -0
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +50 -2
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +27 -0
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js.map +1 -1
  58. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  59. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +45 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  61. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +3 -2
  62. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +34 -41
  63. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
  64. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js +4 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/factories/repro-scene.js.map +1 -1
  66. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +44 -1
  67. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  68. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +47 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/game.js +115 -11
  70. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  71. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +17 -0
  72. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +30 -2
  73. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
  74. package/bundle/typescript/src/commands/game/sideQuest/models/device.d.ts +86 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/models/device.js +103 -1
  76. package/bundle/typescript/src/commands/game/sideQuest/models/device.js.map +1 -1
  77. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +140 -45
  78. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +456 -104
  79. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  80. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +2 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +7 -5
  82. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  83. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +113 -4
  84. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +289 -47
  85. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  86. package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +4 -0
  87. package/bundle/typescript/src/commands/game/sideQuest/qualities.js +16 -0
  88. package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -1
  89. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +9 -9
  90. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +6 -6
  91. package/bundle/typescript/src/commands/game/sideQuest/types/repro.d.ts +8 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/types/repro.js.map +1 -1
  93. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +9 -2
  94. package/bundle/typescript/src/commands/game/sideQuest/types/seed/device-seed.d.ts +17 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/ui.js +23 -26
  96. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  97. package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js +1 -1
  98. package/bundle/typescript/src/commands/game/sideQuest/utilities/affordances.js.map +1 -1
  99. package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.d.ts +38 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.js +67 -0
  101. package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-combat.js.map +1 -0
  102. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.d.ts +279 -4
  103. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js +221 -212
  104. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js.map +1 -1
  105. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.d.ts +48 -5
  106. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +84 -17
  107. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js.map +1 -1
  108. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +9 -0
  109. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +8 -0
  110. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
  111. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +6 -0
  112. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +52 -12
  113. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  114. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +32 -0
  115. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +33 -0
  116. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -1
  117. package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +10 -1
  118. package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js.map +1 -1
  119. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +5 -0
  120. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +35 -1
  121. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -1
  122. package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js +3 -1
  123. package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js.map +1 -1
  124. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.d.ts +121 -0
  125. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js +141 -0
  126. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js.map +1 -0
  127. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +11 -3
  128. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
  129. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +1 -1
  130. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
  131. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.d.ts +93 -0
  132. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +201 -0
  133. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js.map +1 -0
  134. package/bundle/typescript/src/commands/game/sideQuest/utilities/range.d.ts +19 -13
  135. package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js +77 -34
  136. package/bundle/typescript/src/commands/game/sideQuest/utilities/range.js.map +1 -1
  137. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +91 -9
  138. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js.map +1 -1
  139. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +16 -0
  140. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +50 -3
  141. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  142. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.d.ts +9 -1
  143. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +28 -4
  144. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js.map +1 -1
  145. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +43 -3
  146. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  147. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { agentRatingFromName } from './utilities/programs.js';
2
+ import { MATRIX_STANCE_ACTION } from './models/player.js';
2
3
  import blessed from 'blessed';
3
4
  import { hostLabel } from './utilities/grid-names.js';
4
5
  import { marksPlaced, marksOnYou } from './utilities/marks.js';
@@ -54,6 +55,7 @@ import { GoCommand } from './commands/go.js';
54
55
  import { UseCommand } from './commands/use.js';
55
56
  import { EquipCommand } from './commands/equip.js';
56
57
  import { UnequipCommand } from './commands/unequip.js';
58
+ import { FitCommand, UnfitCommand } from './commands/fit.js';
57
59
  import { DropCommand } from './commands/drop.js';
58
60
  import { UnlockCommand } from './commands/unlock.js';
59
61
  import { LockCommand } from './commands/lock.js';
@@ -218,6 +220,26 @@ export default class Game {
218
220
  /** Its twin, docked to its right: the current room's own layout
219
221
  * (spatial-viz follow-up 2026-08-25). See utilities/room-view.ts. */
220
222
  _roomMapBox;
223
+ /** The last string actually painted into each dock -- compared so a
224
+ * repaint (and its scheduleRender cost) only happens when the
225
+ * content genuinely changed, same reasoning as _clockFace above.
226
+ * Read by setMapOverlay/setRoomOverlay; written there too. */
227
+ _lastMapPaint;
228
+ _lastRoomPaint;
229
+ /** The docked panels' HEARTBEAT (2FrYWFdLnh6uqFLcp, player: "I'd like
230
+ * the area map to update on a heartbeat, not only when I issue a
231
+ * command. That way I can tell where things are in 'real time'").
232
+ *
233
+ * Every other panel refresh rides a player-typed command
234
+ * (updateExits already fires on move/look/lock/unlock -- see its own
235
+ * header) or a server push in a shared run. Nothing repainted it
236
+ * while the player sat still watching an NPC close in. This ticker
237
+ * is that missing beat for solo play, built on the clock's own
238
+ * lesson one field up: call the SAME updateExits a command would,
239
+ * and let the _lastMapPaint/_lastRoomPaint diff (not a second
240
+ * change-detector) decide whether anything actually redraws. */
241
+ _mapHeartbeatTimer;
242
+ static MAP_HEARTBEAT_MS = 2_000;
221
243
  _useAi = false;
222
244
  _aiRefusalAnnounced = false;
223
245
  // ACCOUNT BINDING (ruling 2026-08-23): who this runner belongs to,
@@ -1317,7 +1339,7 @@ export default class Game {
1317
1339
  for (const c of this.companions) {
1318
1340
  const dev = c.boundDevice;
1319
1341
  const services = typeof c.services === 'number' ? `, ${c.services} service${c.services === 1 ? '' : 's'}` : '';
1320
- const line = c.kind === 'drone' && dev ? `Pilot ${2 + dev.qualityBonus}, hull ${dev.droneSummary()}`
1342
+ const line = c.kind === 'drone' && dev ? `Pilot ${dev.pilotRating}, hull ${dev.droneSummary()}`
1321
1343
  : c.kind === 'agent' && dev ? `rating ${c.force} on ${dev.name} ${dev.deckSummary()}`
1322
1344
  : c.kind === 'sprite' ? `${c.spriteType ?? 'sprite'} level ${c.force}${services}${c.registered ? ', registered' : ''}`
1323
1345
  : c.kind === 'spirit' ? `Force ${c.force}${services}`
@@ -1338,7 +1360,7 @@ export default class Game {
1338
1360
  for (const d of player.inventory.getAllItems().filter(i => i.isDrone())) {
1339
1361
  if (out.some(o => o.name === d.name))
1340
1362
  continue;
1341
- out.push({ name: d.name, kind: 'drone', deployed: false, line: `Pilot ${2 + d.qualityBonus}, hull ${d.droneSummary()}${d.isWrecked ? ' -- WRECKED' : ''}`, damage: { taken: d.droneDamage, max: d.maxDroneBoxes } });
1363
+ out.push({ name: d.name, kind: 'drone', deployed: false, line: `Pilot ${d.pilotRating}, hull ${d.droneSummary()}${d.isWrecked ? ' -- WRECKED' : ''}`, damage: { taken: d.droneDamage, max: d.maxDroneBoxes } });
1342
1364
  }
1343
1365
  const deck = player.getAnyCyberdeck();
1344
1366
  if (deck && !this.companions.some(c => c.kind === 'agent')) {
@@ -1384,13 +1406,15 @@ export default class Game {
1384
1406
  tiny: 0.5, small: 2, medium: 8, large: 20, huge: 40,
1385
1407
  };
1386
1408
  deployDroneShell(item, room) {
1387
- const pilot = 2 + item.qualityBonus;
1388
- const armed = /armed|combat|gun|roto|assault|sentry|weapon/i.test(`${item.name} ${item.description ?? ''}`);
1409
+ // The drone's own Pilot, Body and mount (catalog v13 vehicle block; the
1410
+ // price-tier and name fallbacks live in Item for generated frames).
1411
+ const pilot = item.pilotRating;
1412
+ const armed = item.hasWeaponMount;
1389
1413
  const shell = this.spawnCompanionShell({
1390
1414
  name: `${item.name} (autopilot)`,
1391
1415
  description: `${this.player.name}'s drone running its dog-brain autopilot: it follows them, watches, and ${armed ? 'shoots what threatens them' : 'has cameras where guns would go -- it never attacks'}.`,
1392
1416
  kind: 'drone',
1393
- combat: { body: 2 + item.qualityBonus, agility: pilot, reaction: pilot, logic: pilot, intuition: pilot, willpower: pilot, strength: 1, charisma: 1, skills: { perception: pilot, ...(armed ? { firearms: pilot } : {}) } },
1417
+ combat: { body: item.droneBody, agility: pilot, reaction: pilot, logic: pilot, intuition: pilot, willpower: pilot, strength: 1, charisma: 1, skills: { perception: pilot, ...(armed ? { firearms: pilot } : {}) } },
1394
1418
  room, plane: 'meat', force: pilot, boundDevice: item,
1395
1419
  });
1396
1420
  // Take-command weight checks (Player.addInventory) enforce this cap
@@ -2359,6 +2383,8 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2359
2383
  CommandFactory.registerCommand('exit-host', ExitHostCommand);
2360
2384
  CommandFactory.registerCommand('rest', RestCommand);
2361
2385
  CommandFactory.registerCommand('reload', ReloadCommand);
2386
+ CommandFactory.registerCommand('fit', FitCommand);
2387
+ CommandFactory.registerCommand('unfit', UnfitCommand);
2362
2388
  CommandFactory.registerCommand('subdue', SubdueCommand);
2363
2389
  // SUBDUING p.195 (see commands/grapple.ts): restrain without damage.
2364
2390
  // Canon skill hooks (see SkillName in models/player.ts).
@@ -2846,6 +2872,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2846
2872
  this.warmNpcAppearancesInBackground();
2847
2873
  this.startPursuitHeartbeat();
2848
2874
  this.startClockTicker();
2875
+ this.startMapHeartbeat();
2849
2876
  // The banner learns where we are only after the hub/scene bookkeeping
2850
2877
  // above settles.
2851
2878
  this.refreshLogLabel();
@@ -4329,6 +4356,50 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4329
4356
  this._clockTimer = undefined;
4330
4357
  }
4331
4358
  }
4359
+ /**
4360
+ * THE PANELS' MISSING BEAT (2FrYWFdLnh6uqFLcp). updateExits already
4361
+ * fires on everything the PLAYER does; nothing fired it for what
4362
+ * everyone else in the room does while the player sits still and
4363
+ * watches. Same lifecycle as startClockTicker one field up --
4364
+ * headless-guarded, idempotent, unref'd -- because this is the same
4365
+ * shape of thing: a UI-only convenience with nothing behind it for a
4366
+ * session with no screen to paint.
4367
+ */
4368
+ startMapHeartbeat() {
4369
+ if (!this._mapBox || this._mapHeartbeatTimer)
4370
+ return;
4371
+ this._mapHeartbeatTimer = setInterval(() => this.mapHeartbeatTick(), Game.MAP_HEARTBEAT_MS);
4372
+ this._mapHeartbeatTimer.unref?.();
4373
+ }
4374
+ stopMapHeartbeat() {
4375
+ if (this._mapHeartbeatTimer) {
4376
+ clearInterval(this._mapHeartbeatTimer);
4377
+ this._mapHeartbeatTimer = undefined;
4378
+ }
4379
+ }
4380
+ /**
4381
+ * ONE TICK, and it does nothing clever: call the exact function a
4382
+ * player's own move/look already calls. Whether anything actually
4383
+ * repaints is _lastMapPaint/_lastRoomPaint's call, not this one's --
4384
+ * see setMapOverlay/setRoomOverlay. That is deliberate: a second,
4385
+ * bespoke "did the room change" detector here would be a second
4386
+ * place for that answer to drift from what the panels actually paint.
4387
+ *
4388
+ * SKIPPED MID-EXCHANGE. Combat already repaints itself blow-by-blow
4389
+ * (CombatExchange narrates and this.scene.updateStatus()/updateExits
4390
+ * follow each strike) -- a wall-clock beat competing with that is
4391
+ * noise at best. It also stays the RIGHT default once combat moves to
4392
+ * a turn-based cadence (player's stated plan): pacing there is the
4393
+ * turn structure's job, not a 2-second ticker's.
4394
+ */
4395
+ mapHeartbeatTick() {
4396
+ if (this.player.inExchange)
4397
+ return;
4398
+ const here = this.player.currentLocation;
4399
+ if (!here)
4400
+ return;
4401
+ this.updateExits(here);
4402
+ }
4332
4403
  stopPresenceHeartbeat() {
4333
4404
  if (this._presenceTimer) {
4334
4405
  clearInterval(this._presenceTimer);
@@ -4345,6 +4416,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4345
4416
  // A stray interval outliving a closed shared session would be a
4346
4417
  // nasty thing to find later, so it retires with the rest of them.
4347
4418
  this.stopClockTicker();
4419
+ this.stopMapHeartbeat();
4348
4420
  if (this._presenceEquipTimer)
4349
4421
  clearTimeout(this._presenceEquipTimer);
4350
4422
  this.stopPursuitHeartbeat();
@@ -4736,9 +4808,18 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4736
4808
  this.hideMapOverlay();
4737
4809
  return;
4738
4810
  }
4739
- this._mapBox.setContent(lines.join('\n'));
4740
4811
  this._mapBox.show();
4741
4812
  this.syncMapDock();
4813
+ // SAME GUARD THE CLOCK USES ONE FIELD UP: paint only when the
4814
+ // content actually differs. Load-bearing now that updateExits can
4815
+ // fire off the map heartbeat as well as a typed command -- an idle
4816
+ // scene must cost a string compare, not a repaint, or "live" would
4817
+ // mean "flickering".
4818
+ const joined = lines.join('\n');
4819
+ if (joined === this._lastMapPaint)
4820
+ return;
4821
+ this._lastMapPaint = joined;
4822
+ this._mapBox.setContent(joined);
4742
4823
  // Coalesced -- see scheduleRender. A shared-run burst repaints once,
4743
4824
  // not once per event.
4744
4825
  this.scheduleRender();
@@ -4748,6 +4829,10 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4748
4829
  return;
4749
4830
  this._mapBox.hide();
4750
4831
  this.syncMapDock();
4832
+ // A hide is a real visible change even when the content string
4833
+ // would still match what's cached -- the NEXT show of that same
4834
+ // content must repaint, not be skipped as "unchanged".
4835
+ this._lastMapPaint = undefined;
4751
4836
  // Coalesced -- see scheduleRender. A shared-run burst repaints once,
4752
4837
  // not once per event.
4753
4838
  this.scheduleRender();
@@ -4766,9 +4851,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4766
4851
  this.hideRoomOverlay();
4767
4852
  return;
4768
4853
  }
4769
- this._roomMapBox.setContent(lines.join('\n'));
4770
4854
  this._roomMapBox.show();
4771
4855
  this.syncMapDock();
4856
+ // Same guard as setMapOverlay -- see its comment.
4857
+ const joined = lines.join('\n');
4858
+ if (joined === this._lastRoomPaint)
4859
+ return;
4860
+ this._lastRoomPaint = joined;
4861
+ this._roomMapBox.setContent(joined);
4772
4862
  // Coalesced -- see scheduleRender. A shared-run burst repaints once,
4773
4863
  // not once per event.
4774
4864
  this.scheduleRender();
@@ -4778,6 +4868,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4778
4868
  return;
4779
4869
  this._roomMapBox.hide();
4780
4870
  this.syncMapDock();
4871
+ // See hideMapOverlay: a hide invalidates the cache so the next
4872
+ // show of matching content still repaints.
4873
+ this._lastRoomPaint = undefined;
4781
4874
  // Coalesced -- see scheduleRender. A shared-run burst repaints once,
4782
4875
  // not once per event.
4783
4876
  this.scheduleRender();
@@ -4929,14 +5022,25 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
4929
5022
  // each under the picture it belongs to, so the HUD stops repeating
4930
5023
  // what two panels already show. The breadcrumb went with it -- the
4931
5024
  // district map draws the way you came far better than a text trail.
4932
- const stanceColor = player.autoStance === 'lethal' ? 'red' : player.autoStance === 'non-lethal' ? 'yellow' : 'white';
5025
+ // THE STANCE THE HUD SHOWS IS THE ONE IN FORCE (uMQAhaAysgaKpWFkn,
5026
+ // 2026-09-03). Jacked in, the fight autopilot is not what Tab
5027
+ // moves and not what your next action obeys -- the Matrix stance
5028
+ // is (Brute Force or Hack on the Fly). Showing the meat stance to
5029
+ // a persona would be the same class of bug as the 2026-08-27
5030
+ // report that the HUD did not follow Tab at all: a readout naming
5031
+ // a setting the player cannot currently change.
5032
+ const onMatrix = player.plane === 'matrix';
5033
+ const stanceWord = onMatrix ? MATRIX_STANCE_ACTION[player.matrixStance] : player.autoStance;
5034
+ const stanceColor = onMatrix
5035
+ ? (player.matrixStance === 'attack' ? 'red' : 'yellow')
5036
+ : player.autoStance === 'lethal' ? 'red' : player.autoStance === 'non-lethal' ? 'yellow' : 'white';
4933
5037
  // WHAT'S IN YOUR HANDS rides alongside the stance (player ruling
4934
5038
  // 2026-08-25). The two answer the same question -- how ready are you
4935
5039
  // -- and the stance line was left short and lonely once the location
4936
5040
  // moved out to the map captions, while the readiness line below ran
4937
5041
  // long. armedLine is built further down with the rest of the gear
4938
5042
  // read; only its placement moved.
4939
- lines.push(`{${stanceColor}-fg}⚔ ${player.autoStance.toUpperCase()}{/${stanceColor}-fg} ${this.armedLine(player)}`);
5043
+ lines.push(`{${stanceColor}-fg}${onMatrix ? '◈' : '⚔'} ${stanceWord.toUpperCase()}{/${stanceColor}-fg} ${this.armedLine(player)}`);
4940
5044
  // Line 2: both condition monitors -- in a game where a fight can kill
4941
5045
  // you and casting can knock you out, current damage on either track is
4942
5046
  // never something a player should have to ask for. Colors shift as
@@ -5227,7 +5331,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5227
5331
  const mapInner = mapWidth ?? 15;
5228
5332
  const mapView = Math.max(7, mapInner % 2 === 0 ? mapInner - 1 : mapInner);
5229
5333
  const mapLines = renderMapViewport(this.scene.getRooms(), here, (r, d) => this.scene.barrierSealing(r, d), {
5230
- visited: this.player.hasSoft('mapsoft') ? undefined /* Mapsoft (p.442): the district drawn and named */ : this.player.visitedRooms,
5334
+ visited: this.player.hasCapability('no-fog') ? undefined /* Mapsoft (p.442): the district drawn and named -- its row's capability (catalog v13) */ : this.player.visitedRooms,
5231
5335
  arEyes: this.player.hasMatrixEyes(),
5232
5336
  }, mapView, tallestRoomRows());
5233
5337
  // The captions the HUD used to carry: where you are under the
@@ -5640,7 +5744,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
5640
5744
  */
5641
5745
  static HELP_CATEGORIES = [
5642
5746
  { title: 'moving', entries: [['look'], ['go'], ['move', 'walk', 'approach'], ['run'], ['sprint'], ['climb', 'mantle', 'scale', 'clamber'], ['descend'], ['sit', 'kneel'], ['lie', 'prone'], ['stand'], ['follow'], ['unfollow'], ['map', 'exits'], ['search'], ['sneak']] },
5643
- { title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['loot'], ['drop'], ['give'], ['store'], ['open'], ['close'], ['put'], ['equip'], ['unequip'], ['brandish', 'draw'], ['holster', 'stow'], ['reload'], ['use'], ['read']] },
5747
+ { title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['loot'], ['drop'], ['give'], ['store'], ['open'], ['close'], ['put'], ['equip'], ['unequip'], ['fit'], ['unfit'], ['brandish', 'draw'], ['holster', 'stow'], ['reload'], ['use'], ['read']] },
5644
5748
  { title: 'combat', entries: [['attack'], ['kill'], ['subdue'], ['grapple', 'restrain', 'clinch'], ['struggle'], ['release'], ['cover'], ['stance'], ['surrender'], ['edge'], ['rest'], ['heal', 'firstaid', 'bandage', 'patch']] },
5645
5749
  // The party layer: everyone who walks (or flies, or manifests) at
5646
5750
  // your side answers to these.