@maka/maka-cli 5.124.0 → 5.125.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 (41) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js +13 -5
  3. package/bundle/typescript/src/commands/game/sideQuest/commands/companions.js.map +1 -1
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +5 -2
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js +10 -4
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js.map +1 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.d.ts +7 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js +58 -9
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js.map +1 -1
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +7 -1
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +7 -2
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +22 -1
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +11 -1
  19. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  20. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +39 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +99 -23
  22. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +42 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/game.js +112 -33
  25. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  26. package/bundle/typescript/src/commands/game/sideQuest/models/home-containers.d.ts +36 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/models/home-containers.js +62 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/models/home-containers.js.map +1 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +15 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +8 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.d.ts +32 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js +45 -5
  34. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/utilities/owned-drones.d.ts +32 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/utilities/owned-drones.js +24 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/utilities/owned-drones.js.map +1 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.d.ts +34 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +63 -27
  40. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
  41. package/package.json +1 -1
@@ -18,12 +18,14 @@ import { Item } from './models/item.js';
18
18
  import { Room, Container } from './models/_index.js';
19
19
  import { Door } from './models/door.js';
20
20
  import { SAVE_VERSION } from './types/save-file.js';
21
- import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, spillPathFor, } from './utilities/persistence.js';
21
+ import { serializePlayer, restorePlayer, captureHubOverlay, applyHubOverlay, applyHomeBlock, writeSaveAtomic, deleteSave, restoreItem, readSave, serializeItem, saveSlug, spillPathFor, } from './utilities/persistence.js';
22
22
  import { heartbeatPresence, heartbeatPresenceNow } from './utilities/social.js';
23
23
  import { presenceBeatAllowed } from './utilities/presence-gate.js';
24
24
  import { pushSave, burnCloudSaveFor, enqueueBoundPush, flushPushQueue, writeSpill, takeLastMemorial, authToken, clearSpill, hasPendingPush, } from './utilities/cloud-saves.js';
25
25
  import { DOCWAGON_TIERS, RESUSC_FEE, DEATH_COMP } from './utilities/docwagon.js';
26
- import { isFence, LIFESTYLE_TIERS, canonicalLifestyleTier } from './utilities/commerce.js';
26
+ import { isFence, LIFESTYLE_TIERS, LIFESTYLE_OPTIONS, canonicalLifestyleTier, canonicalLifestyleOption, lifestyleOptionAllowed } from './utilities/commerce.js';
27
+ import { reconcileGarage } from './models/home-containers.js';
28
+ import { ownedDrones } from './utilities/owned-drones.js';
27
29
  import { attachBrandMark, attachBrandMarkBottomRight } from './utilities/brand-mark.js';
28
30
  import { logoPanelLines } from './utilities/logo-mark.js';
29
31
  import { bestFakeSin, wandRead, processArrest } from './utilities/sin.js';
@@ -1362,10 +1364,17 @@ export default class Game {
1362
1364
  // roster the runner OWNS and has not sent out -- drones in the bag, the
1363
1365
  // deck's agent, registered sprites waiting in the Resonance. Spirits
1364
1366
  // exist only while summoned, so none is ever stowed.
1365
- for (const d of player.inventory.getAllItems().filter(i => i.isDrone())) {
1367
+ // ...and the frames parked in the Garage (2026-09-05): owned, not
1368
+ // deployed, and tagged so the sheet says where.
1369
+ for (const { item: d, where } of ownedDrones(player, this._homeRoom, { reach: 'owned' })) {
1366
1370
  if (out.some(o => o.name === d.name))
1367
1371
  continue;
1368
- 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 } });
1372
+ out.push({
1373
+ name: d.name, kind: 'drone', deployed: false,
1374
+ ...(where === 'garage' ? { where: 'garage' } : {}),
1375
+ line: `Pilot ${d.pilotRating}, hull ${d.droneSummary()}${d.isWrecked ? ' -- WRECKED' : ''}`,
1376
+ damage: { taken: d.droneDamage, max: d.maxDroneBoxes },
1377
+ });
1369
1378
  }
1370
1379
  const deck = player.getAnyCyberdeck();
1371
1380
  if (deck && !this.companions.some(c => c.kind === 'agent')) {
@@ -1747,6 +1756,13 @@ export default class Game {
1747
1756
  // and while the tab stands, rest at the hideout recovers half (rest.ts;
1748
1757
  // "pay" squares it, pay.ts; "lifestyle" views and moves tiers).
1749
1758
  lifestyleTier = 'Squatter';
1759
+ /**
1760
+ * LIFESTYLE OPTIONS (SR5 p.370, 2026-09-05): riders on the tier, by
1761
+ * canonical key -- today only 'garage', the Special Work Area that
1762
+ * parks a rigger's frames (utilities/commerce.ts LIFESTYLE_OPTIONS).
1763
+ * Barred on Street; a slide to Street drops them (returnToHub).
1764
+ */
1765
+ lifestyleOptions = [];
1750
1766
  /** The Big Ten grid a High lifestyle rides (SR5 p.222). A placeholder default until chargen offers the pick (grids.ts). */
1751
1767
  gridProvider = DEFAULT_GRID_PROVIDER;
1752
1768
  rentDebt = 0;
@@ -1754,15 +1770,41 @@ export default class Game {
1754
1770
  get lifestyle() {
1755
1771
  return LIFESTYLE_TIERS.find(t => t.name === this.lifestyleTier) ?? LIFESTYLE_TIERS[1];
1756
1772
  }
1773
+ hasLifestyleOption(key) {
1774
+ return this.lifestyleOptions.includes(key);
1775
+ }
1776
+ /** What the homecoming collects: the tier's share plus every option's. */
1777
+ get lifestyleUpkeep() {
1778
+ return this.lifestyle.upkeep
1779
+ + LIFESTYLE_OPTIONS.filter(o => this.lifestyleOptions.includes(o.key)).reduce((n, o) => n + o.upkeep, 0);
1780
+ }
1781
+ /**
1782
+ * Drop every option the current tier bars (p.370: none on Street).
1783
+ * Returns the keys dropped, so the caller can say the lease lapsed.
1784
+ */
1785
+ dropBarredLifestyleOptions() {
1786
+ const dropped = this.lifestyleOptions.filter(o => !lifestyleOptionAllowed(o, this.lifestyleTier));
1787
+ if (dropped.length > 0)
1788
+ this.lifestyleOptions = this.lifestyleOptions.filter(o => !dropped.includes(o));
1789
+ return dropped;
1790
+ }
1757
1791
  /**
1758
1792
  * Makes the hideout LOOK like what the rent buys: the room's description
1759
1793
  * follows the active tier (player request -- an upgraded lifestyle read
1760
1794
  * as the same windowless squat). Called wherever the tier moves: the
1761
1795
  * lifestyle command, and the shortfall slide in returnToHub.
1796
+ *
1797
+ * THE GARAGE FOLLOWS THE OPTION (2026-09-05): the bay exists exactly
1798
+ * while the lifestyle carries 'garage'. Reconciling here, on the same
1799
+ * call every tier move already makes, is what keeps the furniture and
1800
+ * the lease from disagreeing. Returns the names of any frames a lapsed
1801
+ * lease spilled onto the floor, for the caller to mention.
1762
1802
  */
1763
1803
  syncHomeToLifestyle() {
1764
- if (this._homeRoom)
1765
- this._homeRoom.description = this.lifestyle.homeDescription;
1804
+ if (!this._homeRoom)
1805
+ return [];
1806
+ this._homeRoom.description = this.lifestyle.homeDescription;
1807
+ return reconcileGarage(this._homeRoom, this.player.name, this.hasLifestyleOption('garage'), this.lifestyle.garageDescription ?? 'A bay with a roll-down door. Your frames park here.');
1766
1808
  }
1767
1809
  // RESTOCK (see returnToHub): consumable shop stock -- food, drink,
1768
1810
  // ammo, patches -- snapshotted at hub capture and restored to any shelf
@@ -2993,35 +3035,61 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
2993
3035
  * matters less than its POSITION in init(): after seedHomeTurfContacts
2994
3036
  * and snapshotVendorStock (both deliberately pre-overlay).
2995
3037
  */
2996
- applyResumeState(save) {
2997
- restorePlayer(this.player, save.player);
2998
- // A BIRTH CERTIFICATE IS NOT A RESUME. A `reason: 'creation'` save
2999
- // (the web chargen factory) carries a player and a hub seed and
3000
- // NOTHING ELSE -- no world has happened to overlay. Applying its
3001
- // empty game/hub as if a session had produced them crashed the very
3002
- // first web-born runner at boot (`saved.deadActors is not iterable`,
3003
- // 2026-09-01) and would have zeroed the gig pool besides. The world
3004
- // boots fresh from the embedded seed, exactly as a CLI-born
3005
- // character's does; the first real requestSave writes the full state
3006
- // and every later resume takes the branch below.
3007
- if (save.reason === 'creation')
3008
- return;
3009
- applyHubOverlay(this.scene, save.hub, this.player, this._homeRoom);
3010
- // CANONICAL ON RESTORE (2026-09-01): the web factory once wrote a
3011
- // lowercase 'street' that matched no LIFESTYLE_TIERS name, and this
3012
- // line handed it to a getter that fell through to Squatter -- then
3013
- // the next save SERIALIZED that Squatter, local and cloud, burying
3014
- // any server-side repair. Canonicalizing here closes the fall-through
3015
- // for every save that still carries the old spelling: 'street' means
3016
- // Street, the factory's intent. (It cannot restore a PAID tier the
3017
- // local file never knew -- that is the cloud repair's job, and the
3018
- // sync protocol's, see the repair-visibility ruling in cloud-saves.)
3038
+ /**
3039
+ * THE LIFESTYLE THE SAVE PAID FOR, applied -- for EVERY save, a birth
3040
+ * certificate included (2026-09-05). This used to sit below the
3041
+ * `creation` early return in applyResumeState, so a web-born runner who
3042
+ * had paid a first month of High booted at the Squatter default, and
3043
+ * the first hub-arrival save then serialised that Squatter and buried
3044
+ * the tier for good. The lifestyle is Game-owned, not seed-owned; there
3045
+ * is no "world" it waits on.
3046
+ *
3047
+ * CANONICAL ON RESTORE (2026-09-01): the web factory once wrote a
3048
+ * lowercase 'street' that matched no LIFESTYLE_TIERS name, and this
3049
+ * line handed it to a getter that fell through to Squatter -- then
3050
+ * the next save SERIALIZED that Squatter, local and cloud, burying
3051
+ * any server-side repair. Canonicalizing here closes the fall-through
3052
+ * for every save that still carries the old spelling: 'street' means
3053
+ * Street, the factory's intent. (It cannot restore a PAID tier the
3054
+ * local file never knew -- that is the cloud repair's job, and the
3055
+ * sync protocol's, see the repair-visibility ruling in cloud-saves.)
3056
+ */
3057
+ applyLifestyleFromSave(save) {
3019
3058
  this.lifestyleTier = canonicalLifestyleTier(save.game.lifestyleTier) ?? 'Street';
3059
+ // Options: canonical, known, and allowed on the tier -- a 'garage' a
3060
+ // Street save somehow carries is dropped here rather than built.
3061
+ this.lifestyleOptions = (save.game.lifestyleOptions ?? [])
3062
+ .map(canonicalLifestyleOption)
3063
+ .filter((o) => o !== undefined && lifestyleOptionAllowed(o, this.lifestyleTier));
3020
3064
  if (typeof save.game.gridProvider === 'string' && save.game.gridProvider.trim())
3021
3065
  this.gridProvider = save.game.gridProvider;
3022
3066
  this.rentDebt = save.game.rentDebt;
3023
3067
  this.syncHomeToLifestyle();
3024
3068
  this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
3069
+ }
3070
+ applyResumeState(save) {
3071
+ restorePlayer(this.player, save.player);
3072
+ this.applyLifestyleFromSave(save);
3073
+ // A BIRTH CERTIFICATE IS NOT A RESUME. A `reason: 'creation'` save
3074
+ // (the web chargen factory) carries a player and a hub seed and NO
3075
+ // WORLD -- nothing has happened to overlay. Applying its empty hub as
3076
+ // if a session had produced it crashed the very first web-born runner
3077
+ // at boot (`saved.deadActors is not iterable`, 2026-09-01) and would
3078
+ // have zeroed the gig pool besides. The world boots fresh from the
3079
+ // embedded seed, exactly as a CLI-born character's does; the first
3080
+ // real requestSave writes the full state and every later resume
3081
+ // takes the branch below.
3082
+ //
3083
+ // WHAT A BIRTH CERTIFICATE DOES CARRY (2026-09-05): its lifestyle
3084
+ // (applied above) and its HOME block -- the Game-owned hideout's
3085
+ // furniture, which the factory fills when a rigger is born with a
3086
+ // garage full of frames. Both are the runner's, not the world's, so
3087
+ // both are applied; the seed-derived overlay stays skipped.
3088
+ if (save.reason === 'creation') {
3089
+ applyHomeBlock(this._homeRoom, save.hub?.home, this.player);
3090
+ return;
3091
+ }
3092
+ applyHubOverlay(this.scene, save.hub, this.player, this._homeRoom);
3025
3093
  this._tier = save.game.tier;
3026
3094
  this.contacts = new Map(save.game.contacts);
3027
3095
  // Roles are DERIVED, not earned -- re-derive them all on every
@@ -3287,6 +3355,7 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3287
3355
  location: this.hubLocationSnapshot(),
3288
3356
  gridProvider: this.gridProvider,
3289
3357
  lifestyleTier: this.lifestyleTier,
3358
+ lifestyleOptions: [...this.lifestyleOptions],
3290
3359
  rentDebt: this.rentDebt,
3291
3360
  tier: this._tier,
3292
3361
  contacts: [...this.contacts.entries()],
@@ -3685,7 +3754,9 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3685
3754
  // the shortfall becomes the tab -- rest.ts halves recovery while you
3686
3755
  // owe; "pay" squares it; "lifestyle" climbs back up.
3687
3756
  const tier = this.lifestyle;
3688
- const due = tier.upkeep + this.rentDebt;
3757
+ // The tier's share plus every option's (the garage, p.370).
3758
+ const upkeep = this.lifestyleUpkeep;
3759
+ const due = upkeep + this.rentDebt;
3689
3760
  const paid = Math.min(this.player.currency, due);
3690
3761
  if (paid > 0)
3691
3762
  this.player.adjustCurrency(-paid);
@@ -3695,16 +3766,24 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
3695
3766
  const tierIndex = LIFESTYLE_TIERS.findIndex(t => t.name === tier.name);
3696
3767
  if (tierIndex > 0) {
3697
3768
  this.lifestyleTier = LIFESTYLE_TIERS[tierIndex - 1].name;
3769
+ // A lease the new tier cannot carry lapses with it (Street bars
3770
+ // every option), and the garage's frames land on the floor --
3771
+ // still yours, still here, just not parked.
3772
+ const dropped = this.dropBarredLifestyleOptions();
3698
3773
  // The downgrade is visible the moment you look around.
3699
- this.syncHomeToLifestyle();
3700
- rentLine = `The landlord's drone pings before the door even shuts: ${paid > 0 ? `${paid} paid, ` : ''}${this.rentDebt} nuyen still owed. Your ${tier.name} arrangement lapses -- you're living ${this.lifestyleTier} now.${hint(` ("pay" the tab, "lifestyle" to climb back.)`)}`;
3774
+ const spilled = this.syncHomeToLifestyle();
3775
+ const lapse = dropped.length > 0
3776
+ ? ` The garage lease goes with it${spilled.length > 0 ? `; ${spilled.join(', ')} sit${spilled.length === 1 ? 's' : ''} in the alley now` : ''}.`
3777
+ : '';
3778
+ rentLine = `The landlord's drone pings before the door even shuts: ${paid > 0 ? `${paid} paid, ` : ''}${this.rentDebt} nuyen still owed. Your ${tier.name} arrangement lapses -- you're living ${this.lifestyleTier} now.${lapse}${hint(` ("pay" the tab, "lifestyle" to climb back.)`)}`;
3701
3779
  }
3702
3780
  else {
3703
3781
  rentLine = `Even the doorway costs something: ${this.rentDebt} nuyen on the tab.${hint(` ("pay" when you're flush.)`)}`;
3704
3782
  }
3705
3783
  }
3706
3784
  else {
3707
- rentLine = `The landlord's drone takes its ${paid} nuyen (${tier.name} upkeep) at the door and buzzes off satisfied.`;
3785
+ const share = upkeep > tier.upkeep ? ` (${tier.name} upkeep plus the garage)` : ` (${tier.name} upkeep)`;
3786
+ rentLine = `The landlord's drone takes its ${paid} nuyen${share} at the door and buzzes off satisfied.`;
3708
3787
  }
3709
3788
  this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
3710
3789
  // A High lifestyle erases the run: hot shower, real bed, full reset.