@maka/maka-cli 5.122.0 → 5.123.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 (119) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js +12 -8
  3. package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js.map +1 -1
  4. package/bundle/typescript/src/commands/game/sideQuest/adept-powers.d.ts +15 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/adept-powers.js +12 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/adept-powers.js.map +1 -1
  7. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +10 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +40 -14
  9. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  10. package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +33 -10
  11. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +155 -72
  12. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -1
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +15 -6
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +1 -1
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +1 -1
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +33 -33
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +1 -1
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +1 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +5 -1
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js.map +1 -1
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +6 -6
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +4 -4
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +1 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js.map +1 -1
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.d.ts +20 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +9 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js.map +1 -1
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js +7 -3
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js.map +1 -1
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +3 -3
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +1 -1
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -1
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +3 -3
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +10 -10
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +1 -1
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js.map +1 -1
  48. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +24 -0
  49. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +95 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
  51. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +45 -1
  52. package/bundle/typescript/src/commands/game/sideQuest/game.js +119 -8
  53. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  54. package/bundle/typescript/src/commands/game/sideQuest/metatypes.d.ts +53 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/metatypes.js +86 -0
  56. package/bundle/typescript/src/commands/game/sideQuest/metatypes.js.map +1 -0
  57. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +8 -2
  58. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  59. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +215 -18
  60. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +548 -117
  61. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  62. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +2 -1
  63. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +3 -2
  64. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  65. package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +96 -4
  66. package/bundle/typescript/src/commands/game/sideQuest/qualities.js +290 -11
  67. package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -1
  68. package/bundle/typescript/src/commands/game/sideQuest/skill-groups.d.ts +11 -1
  69. package/bundle/typescript/src/commands/game/sideQuest/skill-groups.js +13 -1
  70. package/bundle/typescript/src/commands/game/sideQuest/skill-groups.js.map +1 -1
  71. package/bundle/typescript/src/commands/game/sideQuest/skills.d.ts +17 -0
  72. package/bundle/typescript/src/commands/game/sideQuest/skills.js +23 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/skills.js.map +1 -0
  74. package/bundle/typescript/src/commands/game/sideQuest/spells.d.ts +76 -3
  75. package/bundle/typescript/src/commands/game/sideQuest/spells.js +48 -21
  76. package/bundle/typescript/src/commands/game/sideQuest/spells.js.map +1 -1
  77. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +8 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.d.ts +90 -82
  79. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js +277 -10
  80. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js.map +1 -1
  81. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.d.ts +121 -2
  82. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +342 -18
  83. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js.map +1 -1
  84. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +65 -2
  85. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +74 -7
  86. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
  87. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +1 -1
  88. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  89. package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.d.ts +248 -0
  90. package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.js +38 -0
  91. package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.js.map +1 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js +1 -2
  93. package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js.map +1 -1
  94. package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js +1 -2
  95. package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js.map +1 -1
  96. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +5 -0
  97. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
  98. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +5 -1
  99. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
  100. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +22 -12
  101. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js.map +1 -1
  102. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +1 -2
  103. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  104. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.d.ts +1 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +20 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js.map +1 -1
  107. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +31 -0
  108. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  109. package/bundle/typescript/src/commands/login.js +19 -1
  110. package/bundle/typescript/src/commands/login.js.map +1 -1
  111. package/bundle/typescript/src/commands/logout.js +16 -1
  112. package/bundle/typescript/src/commands/logout.js.map +1 -1
  113. package/bundle/typescript/src/tools/guarded-file/guarded-file.class.d.ts +59 -0
  114. package/bundle/typescript/src/tools/guarded-file/guarded-file.class.js +120 -0
  115. package/bundle/typescript/src/tools/guarded-file/guarded-file.class.js.map +1 -0
  116. package/bundle/typescript/src/tools/mpi/mpi.class.d.ts +16 -0
  117. package/bundle/typescript/src/tools/mpi/mpi.class.js +25 -7
  118. package/bundle/typescript/src/tools/mpi/mpi.class.js.map +1 -1
  119. package/package.json +1 -1
@@ -20,10 +20,11 @@
20
20
  * gear -- these strings are what the player reads, and what an NPC's
21
21
  * swag briefing quotes.
22
22
  */
23
- import type { SkillName } from '../models/player.js';
24
- import type { AugBonusKind } from '../augmentations.js';
25
- export declare const CATALOG_VERSION = 13;
26
- export type CatalogKind = 'gear' | 'quality' | 'spell' | 'augmentation' | 'metatype';
23
+ import type { IQuality } from '../qualities.js';
24
+ import type { ICatalogChoice, ICatalogRestrictions } from './effects.js';
25
+ import type { ICatalogEffect } from './effects.js';
26
+ export declare const CATALOG_VERSION = 14;
27
+ export type CatalogKind = 'gear' | 'quality' | 'spell' | 'augmentation' | 'metatype' | 'adept-power' | 'metamagic';
27
28
  /** The skill a weapon is fired or swung with. The last four have no engine
28
29
  * skill yet (SkillName); the engine falls back per models/player.ts. */
29
30
  export type WeaponSkill = 'pistols' | 'automatics' | 'longarms' | 'archery' | 'blades' | 'clubs' | 'unarmed' | 'heavy-weapons' | 'throwing-weapons' | 'exotic-melee' | 'exotic-ranged';
@@ -136,80 +137,7 @@ export interface ICatalogMod {
136
137
  /** Weapon classes it refuses: a silencer fits no revolver or shotgun (p.432). */
137
138
  excludes?: ('shotgun' | 'revolver')[];
138
139
  }
139
- export type EffectPool = AugBonusKind | 'pan-defense' | 'matrix-soak' | 'biofeedback' | 'toxin' | 'climbing';
140
- export type EffectLimit = 'physical' | 'mental' | 'social' | 'accuracy';
141
- export type EffectAttribute = 'body' | 'agility' | 'reaction' | 'strength' | 'willpower' | 'logic' | 'intuition' | 'charisma' | 'edge' | 'magic' | 'resonance' | 'reach' | 'armor-bonus';
142
- export type EffectMatrixAttribute = 'attack' | 'sleaze' | 'dataProcessing' | 'firewall' | 'damage';
143
- /** A capability is a flag the engine asks for by key (Player.hasCapability). */
144
- export type EffectCapability = 'dni' | 'smartlink' | 'smartgun' | 'low-light' | 'thermographic' | 'no-fog' | 'silenced' | 'full-body' | 'autopicker' | 'maglock-opposed';
145
- /**
146
- * A modifier the row grants while it applies. `value` is a NUMBER, never a
147
- * string: every reader adds it, and a string would need parsing at each
148
- * one and would drift in the web's seed. `scaleBy` multiplies it by the
149
- * row's `ratingValue` (rating), the cast Force (force) or the hits of a
150
- * sustained spell (hits). `when` defaults by kind: a wearable's effects
151
- * apply worn, a weapon's held, a program's running, a data soft's stored,
152
- * a mod's fitted, an augmentation's installed, a quality's held.
153
- */
154
- export type ICatalogEffect = {
155
- type: 'dice';
156
- category: 'skills';
157
- property: SkillName;
158
- value: number;
159
- scaleBy?: EffectScale;
160
- when?: EffectWhen;
161
- only?: string;
162
- } | {
163
- type: 'dice';
164
- category: 'pools';
165
- property: EffectPool;
166
- value: number;
167
- scaleBy?: EffectScale;
168
- when?: EffectWhen;
169
- only?: string;
170
- } | {
171
- type: 'limit';
172
- category: 'limits';
173
- property: EffectLimit;
174
- value: number;
175
- scaleBy?: EffectScale;
176
- when?: EffectWhen;
177
- only?: string;
178
- } | {
179
- type: 'attribute';
180
- category: 'attributes';
181
- property: EffectAttribute;
182
- value: number;
183
- scaleBy?: EffectScale;
184
- when?: EffectWhen;
185
- only?: string;
186
- } | {
187
- type: 'attribute';
188
- category: 'matrix';
189
- property: EffectMatrixAttribute;
190
- value: number;
191
- scaleBy?: EffectScale;
192
- when?: EffectWhen;
193
- only?: string;
194
- } | {
195
- type: 'initiative';
196
- category: 'pools';
197
- property: 'dice' | 'score';
198
- value: number;
199
- scaleBy?: EffectScale;
200
- when?: EffectWhen;
201
- only?: string;
202
- } | {
203
- type: 'flag';
204
- category: 'capability';
205
- property: EffectCapability;
206
- value: number;
207
- scaleBy?: EffectScale;
208
- when?: EffectWhen;
209
- only?: string;
210
- };
211
- export type EffectScale = 'rating' | 'force' | 'hits';
212
- export type EffectWhen = 'worn' | 'held' | 'carried' | 'running' | 'stored' | 'fitted' | 'installed' | 'held-quality' | 'sustained';
140
+ export type { EffectPool, EffectLimit, EffectAttribute, EffectMatrixAttribute, EffectCapability, ICatalogEffect, EffectScale, EffectWhen, } from './effects.js';
213
141
  /** Every kind shares this. */
214
142
  export interface ICatalogBase {
215
143
  /** Absent = 'gear'. */
@@ -222,6 +150,34 @@ export interface ICatalogBase {
222
150
  page?: string;
223
151
  approx?: boolean;
224
152
  effects?: ICatalogEffect[];
153
+ /**
154
+ * THE SPELL A FORMULA TEACHES, by spells.ts key.
155
+ *
156
+ * A spell formula is written for ONE spell (p.461, p.299: "the spell
157
+ * you want"), and its PRICE is set by that spell's category. The
158
+ * five rows here used to be the categories themselves -- "Combat
159
+ * Spell Formula" -- and Player.knowsSpell matched them by looking
160
+ * for the spell's name inside the item's name. No category name
161
+ * ever contains a spell name, so the match could never fire: every
162
+ * one of them was 500 to 2,000 nuyen for a scroll that taught
163
+ * nothing.
164
+ */
165
+ teaches?: string;
166
+ /**
167
+ * A pick this row cannot work without (ICatalogChoice). ON THE BASE,
168
+ * so a quality, an augmentation, a spell or an adept power all declare
169
+ * one the same way -- a Hardwire naming a skill is the same question
170
+ * Aptitude asks, and asking it twice in two shapes is how the picker
171
+ * ends up rebuilt per kind.
172
+ */
173
+ choice?: ICatalogChoice;
174
+ /**
175
+ * What this row refuses to be held with (ICatalogRestrictions), in the
176
+ * same one shape for every kind. Assembled from each source's own
177
+ * per-kind fields, so one enforcement pass covers all 69 rather than
178
+ * one hand-written gate covering one.
179
+ */
180
+ restrictions?: ICatalogRestrictions;
225
181
  }
226
182
  export interface ICatalogItem extends ICatalogBase {
227
183
  kind?: 'gear';
@@ -266,11 +222,41 @@ export interface ICatalogQuality extends ICatalogBase {
266
222
  requires?: 'awakened' | 'mundane' | 'caster-or-technomancer';
267
223
  requiresQuality?: string;
268
224
  requiresMetatype?: Metatype[];
225
+ /**
226
+ * THE FOUR FIELDS qualityRows() writes and this interface did not
227
+ * declare (found by maka-cli-com-c3 reading the commit, 2026-09-04).
228
+ *
229
+ * They were invisible to the compiler because the object literal
230
+ * carrying them contains conditional spreads, and a literal with a
231
+ * spread is no longer FRESH -- excess-property checking does not
232
+ * apply to it. So the row shipped four fields the published .d.ts
233
+ * denied existed, and the web's mirror would have needed a cast to
234
+ * read `row.quality.choice` -- the sort of cast that later gets
235
+ * tidied away by someone who trusts the type.
236
+ *
237
+ * DERIVED FROM IQuality, not retyped. `family`'s union and the shape
238
+ * of a choice live in qualities.ts, and indexing them here means the
239
+ * catalog cannot fall behind the source the way it just did. It also
240
+ * answers the question c3 asked -- "tell me if the names or the
241
+ * family union move" -- with "they cannot, if you index them too".
242
+ */
243
+ family: IQuality['family'];
244
+ inert?: string;
245
+ choice?: ICatalogChoice;
246
+ excludesQuality?: string;
269
247
  };
270
248
  }
271
249
  export interface ICatalogSpell extends ICatalogBase {
272
250
  kind: 'spell';
273
251
  spell: {
252
+ /**
253
+ * WHICH ROUTINE CASTS IT. The one field a row could not supply,
254
+ * and so the one thing stopping a spell from being sourced from
255
+ * the catalog rather than compiled in -- which is where the
256
+ * rules are headed (mjmcee, 2026-09-04: "the engine rule comes
257
+ * from the catalog. We shouldn't be hard coding the rules").
258
+ */
259
+ cast: 'combat' | 'heal' | 'armor' | 'sustained-utility' | 'barrier';
274
260
  category: 'Combat' | 'Detection' | 'Health' | 'Illusion' | 'Manipulation';
275
261
  type: 'mana' | 'physical';
276
262
  range: 'LOS' | 'LOS(A)' | 'touch';
@@ -296,6 +282,31 @@ export interface ICatalogAugmentation extends ICatalogBase {
296
282
  canonEssence?: number;
297
283
  };
298
284
  }
285
+ /**
286
+ * AN ADEPT POWER (p.308-310). Bought with Power Points rather than nuyen
287
+ * or karma, which is why it carries no price: `levelCosts` is the PP to
288
+ * HOLD it at each level, and a magic-capped power extends linearly to the
289
+ * character's Magic.
290
+ */
291
+ export interface ICatalogAdeptPower extends ICatalogBase {
292
+ kind: 'adept-power';
293
+ adeptPower: {
294
+ levelCosts: number[];
295
+ magicCapped: boolean;
296
+ costPerLevel?: number;
297
+ };
298
+ }
299
+ /**
300
+ * A METAMAGIC (p.325-326) or a technomancer's echo (p.258). Bought with an
301
+ * initiation or submersion grade, so it has no price either; `path` is who
302
+ * may take it.
303
+ */
304
+ export interface ICatalogMetamagic extends ICatalogBase {
305
+ kind: 'metamagic';
306
+ metamagic: {
307
+ path: 'caster' | 'adept' | 'techno';
308
+ };
309
+ }
299
310
  export interface ICatalogMetatype extends ICatalogBase {
300
311
  kind: 'metatype';
301
312
  metatype: {
@@ -307,11 +318,8 @@ export interface ICatalogMetatype extends ICatalogBase {
307
318
  lifestyleCostMult?: number;
308
319
  };
309
320
  }
310
- export type ICatalogRow = ICatalogItem | ICatalogQuality | ICatalogSpell | ICatalogAugmentation | ICatalogMetatype;
321
+ export type ICatalogRow = ICatalogItem | ICatalogQuality | ICatalogSpell | ICatalogAugmentation | ICatalogMetatype | ICatalogAdeptPower | ICatalogMetamagic;
311
322
  /** Gear or not -- the discriminator every consumer of the gear list uses. */
312
323
  export declare function isGearRow(r: ICatalogRow): r is ICatalogItem;
313
324
  export declare const CATALOG_ITEMS: ICatalogItem[];
314
- /** THE WHOLE SEED, every kind. Gear today; qualities, spells, augmentations
315
- * and metatypes join it in v14. The loader (utilities/catalog.ts) weighs
316
- * THIS against the server and the cache. */
317
325
  export declare const CATALOG_ROWS: ICatalogRow[];
@@ -20,7 +20,13 @@
20
20
  * gear -- these strings are what the player reads, and what an NPC's
21
21
  * swag briefing quotes.
22
22
  */
23
- export const CATALOG_VERSION = 13;
23
+ import { AUGMENTATIONS } from '../augmentations.js';
24
+ import { QUALITIES } from '../qualities.js';
25
+ import { ADEPT_POWER_CATALOG } from '../adept-powers.js';
26
+ import { SPELL_CATALOG } from '../spells.js';
27
+ import { METATYPES } from '../metatypes.js';
28
+ import { METAMAGICS } from '../commands/initiate.js';
29
+ export const CATALOG_VERSION = 14;
24
30
  /** Gear or not -- the discriminator every consumer of the gear list uses. */
25
31
  export function isGearRow(r) {
26
32
  return r.kind === undefined || r.kind === 'gear';
@@ -279,11 +285,12 @@ export const CATALOG_ITEMS = [
279
285
  row('spirit-focus-1', 'Spirit Focus (Force 1)', 'Focus', 4000, 3, 'Where a summoner keeps the leash.', { page: 'p.461', shape: 'talisman', size: 'Tiny', weight: 0.1, rating: 'Standard', legality: 'R', stats: 'Force 1: force x 4,000 nuyen, avail (force x 3)R', ratingValue: 1, effects: [{ type: 'dice', category: 'skills', property: 'summoning', value: 1, when: 'carried', scaleBy: 'rating' }] }),
280
286
  row('weapon-focus-1', 'Weapon Focus (Force 1)', 'Focus', 7000, 4, 'A blade that cuts things that have never had a body.', { page: 'p.461', shape: 'blade', size: 'Medium', weight: 1.2, rating: 'Rare', legality: 'R', stats: 'Force 1: force x 7,000 nuyen, avail (force x 4)R', ratingValue: 1 }),
281
287
  row('power-focus-1', 'Power Focus (Force 1)', 'Focus', 18000, 4, 'A bound anchor for living mana. The real investment.', { page: 'p.461', shape: 'amulet', size: 'Tiny', weight: 0.2, rating: 'Rare', legality: 'R', stats: 'Force 1: force x 18,000 nuyen, avail (force x 4)R', ratingValue: 1, effects: [{ type: 'dice', category: 'skills', property: 'spellcasting', value: 1, when: 'carried', scaleBy: 'rating' }] }),
282
- row('detection-spell-formula', 'Detection Spell Formula', 'SpellFormula', 500, 4, 'The notation for seeing what is not on offer. Bring talent.', { page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, legality: 'R' }),
283
- row('health-spell-formula', 'Health Spell Formula', 'SpellFormula', 500, 4, 'Mending, written down. Cheaper than a street doc, harder to learn.', { page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, legality: 'R' }),
284
- row('illusion-spell-formula', 'Illusion Spell Formula', 'SpellFormula', 1000, 8, 'How to lie to eyes, ears, and sensors at the same time.', { page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, rating: 'Standard', legality: 'R' }),
285
- row('manipulation-spell-formula', 'Manipulation Spell Formula', 'SpellFormula', 1500, 8, 'Moving the world with the mind, notated in someone else\'s hand.', { page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, rating: 'Standard', legality: 'R' }),
286
- row('combat-spell-formula', 'Combat Spell Formula', 'SpellFormula', 2000, 8, 'A combat spell, notated for study. Bring talent.', { page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, rating: 'Standard', legality: 'R' }),
288
+ // SPELL FORMULAE ARE GENERATED, one per spell, by formulaRows() below
289
+ // -- a formula is written for ONE spell and priced by that spell's
290
+ // category (p.461). The five category-named rows that used to sit
291
+ // here could never teach anything: knowsSpell looks for the spell
292
+ // name inside the item name, and "Combat Spell Formula" contains no
293
+ // spell's name.
287
294
  // ---- Drones (p.466) ----
288
295
  // ANTHROPOMORPHIC DRONES (Rigger 5.0 p.145-148) -- the FIRST rows in
289
296
  // this catalog cited to a book other than the core rulebook, so the
@@ -393,8 +400,268 @@ export const CATALOG_ITEMS = [
393
400
  row('credstick-platinum', 'Certified Credstick (Platinum)', 'Credstick', 500, 10, 'Half a million, certified. People have died for the empty ones.', { page: 'p.443', shape: 'stick', size: 'Tiny', weight: 0.1, color: 'platinum', rating: 'Standard', stats: 'Max 500,000 nuyen' }),
394
401
  row('credstick-ebony', 'Certified Credstick (Ebony)', 'Credstick', 1000, 20, 'A million nuyen and a very short list of people who own one.', { page: 'p.443', shape: 'stick', size: 'Tiny', weight: 0.1, color: 'ebony', rating: 'Rare', stats: 'Max 1,000,000 nuyen' }),
395
402
  ];
396
- /** THE WHOLE SEED, every kind. Gear today; qualities, spells, augmentations
397
- * and metatypes join it in v14. The loader (utilities/catalog.ts) weighs
398
- * THIS against the server and the cache. */
399
- export const CATALOG_ROWS = CATALOG_ITEMS;
403
+ /**
404
+ * THE NON-GEAR ROWS (catalog v14, user 2026-09-04: "I don't see any
405
+ * spells, or qualities there. The catalog should cover ALL things that
406
+ * can be obtained in the game, backed by canon").
407
+ *
408
+ * DERIVED, NEVER RETYPED. qualities.ts and augmentations.ts are the
409
+ * engine's own sources of truth -- the clinic charges from them, the
410
+ * creation ledger prices from them, the sheet prints from them -- so the
411
+ * catalog reads THOSE rather than carrying a second copy. A hand-copied
412
+ * list of something that already exists cannot stay right: that is
413
+ * exactly how the Conjurer shipped broken this morning, when a duplicate
414
+ * of MAGICAL_GROUP_SKILLS went stale the day the original moved.
415
+ *
416
+ * Every field here already existed on the source rows. Nothing is
417
+ * invented to fill the shape: `requiresMetatype` stays absent because
418
+ * this engine models no metatype and a gate it cannot check is a rule it
419
+ * cannot keep (qualities.ts says so in its own words), and `legality` /
420
+ * `canonEssence` stay absent on augmentations for the same reason --
421
+ * the source does not carry them.
422
+ */
423
+ /**
424
+ * The restrictions a row declares, assembled from whichever per-kind
425
+ * fields its source happens to use. One shape out, so the server can
426
+ * enforce every kind in one pass; the sources keep the field names that
427
+ * read well where they live.
428
+ *
429
+ * Returns undefined rather than an empty object when a row restricts
430
+ * nothing, so "has restrictions" stays a truthiness check and 400 rows
431
+ * do not each grow an empty object.
432
+ */
433
+ function restrictionsOf(src) {
434
+ const excludes = [
435
+ ...(src.excludes ?? []),
436
+ ...(src.excludesQuality !== undefined ? [src.excludesQuality] : []),
437
+ ];
438
+ const out = {
439
+ ...(excludes.length > 0 ? { excludes } : {}),
440
+ ...(src.requiresQuality !== undefined ? { needs: [src.requiresQuality] } : {}),
441
+ ...(src.requires !== undefined ? { path: src.requires } : {}),
442
+ };
443
+ return Object.keys(out).length > 0 ? out : undefined;
444
+ }
445
+ function qualityRows() {
446
+ return QUALITIES.map(q => ({
447
+ kind: 'quality',
448
+ slug: q.key,
449
+ name: q.name,
450
+ description: q.effect,
451
+ page: q.page,
452
+ // The mechanics, in the same vocabulary a weapon states its DV in,
453
+ // so a reader of the catalog gets a quality's NUMBERS and not just
454
+ // its sentence. Absent where SR5's effect is not a flat modifier
455
+ // (or where the engine implements it as its own rule) -- see the
456
+ // `effects` comment in qualities.ts, which says why that is the
457
+ // honest answer rather than a gap to be filled in.
458
+ ...(q.effects !== undefined ? { effects: q.effects } : {}),
459
+ // On the ROW, not in the quality block -- ICatalogBase carries both,
460
+ // so chrome and spells declare them in the same place.
461
+ ...(q.choice !== undefined ? { choice: q.choice } : {}),
462
+ ...(restrictionsOf(q) !== undefined ? { restrictions: restrictionsOf(q) } : {}),
463
+ quality: {
464
+ polarity: q.kind,
465
+ karma: q.karma,
466
+ ...(q.maxRating !== undefined ? { maxRating: q.maxRating } : {}),
467
+ ...(q.retired !== undefined ? { retired: q.retired } : {}),
468
+ ...(q.requires !== undefined ? { requires: q.requires } : {}),
469
+ ...(q.requiresQuality !== undefined ? { requiresQuality: q.requiresQuality } : {}),
470
+ // WHAT KIND OF RULE this is, and whether the engine runs it yet.
471
+ // `family` is always present (qualities.ts requires it); `inert`
472
+ // is present only when the quality currently delivers nothing,
473
+ // and names the missing seat -- so a reader of the catalog can
474
+ // tell a finished rule from one that is still only priced.
475
+ family: q.family,
476
+ ...(q.inert !== undefined ? { inert: q.inert } : {}),
477
+ // The pick chargen has to collect before the quality can work,
478
+ // and the quality it may not be held alongside (p.65-66).
479
+ ...(q.excludesQuality !== undefined ? { excludesQuality: q.excludesQuality } : {}),
480
+ },
481
+ }));
482
+ }
483
+ function augmentationRows() {
484
+ return AUGMENTATIONS.map(a => ({
485
+ kind: 'augmentation',
486
+ slug: a.key,
487
+ name: a.name,
488
+ description: a.effect,
489
+ page: a.page,
490
+ // The dice, in the same vocabulary a weapon states its DV in. Chrome
491
+ // declared a `bonuses` map of its own until 2026-09-04, which is why
492
+ // this column read empty in the catalog while every piece of 'ware in
493
+ // the game was moving real dice.
494
+ ...(a.effects !== undefined ? { effects: a.effects } : {}),
495
+ // 62 of the 69 restrictions in the catalog are these: the exclusions
496
+ // that stop Wired Reflexes I riding alongside a synaptic booster, or
497
+ // three vision enhancements stacking. Declared for a long time,
498
+ // enforced by nothing.
499
+ ...(restrictionsOf(a) !== undefined ? { restrictions: restrictionsOf(a) } : {}),
500
+ augmentation: {
501
+ ware: a.kind,
502
+ essence: a.essence,
503
+ cost: a.cost,
504
+ wakeLine: a.wakeLine,
505
+ ...(a.excludes !== undefined ? { excludes: a.excludes } : {}),
506
+ ...(a.avail !== undefined ? { avail: a.avail } : {}),
507
+ ...(a.canonCost !== undefined ? { canonCost: a.canonCost } : {}),
508
+ },
509
+ }));
510
+ }
511
+ /** THE WHOLE SEED, every kind: gear, qualities and augmentations. Spells
512
+ * and metatypes are still outstanding -- see the note above qualityRows.
513
+ * The loader (utilities/catalog.ts) weighs THIS against the server and
514
+ * the cache. */
515
+ /**
516
+ * THE SPELLS (SR5 p.283-295). Held out of the catalog until now because
517
+ * ICatalogSpell wants Type, Range and Duration and the engine stored none
518
+ * of them -- recorded at the time as a deliberate omission rather than an
519
+ * oversight. spells.ts carries all three since 2026-09-04, each read off
520
+ * the printed stat block, so the shape is satisfied by canon rather than
521
+ * by inference and the rows can ship.
522
+ *
523
+ * A spell has no PRICE: it is learned, not bought, which is why the row
524
+ * carries no availability or nuyen and the spell block carries the drain
525
+ * instead. What a formula costs on the street is a GEAR row, and there
526
+ * are five of those already (Category 'SpellFormula').
527
+ */
528
+ /**
529
+ * A spell's line, composed from its own stat block.
530
+ *
531
+ * spells.ts carries no prose -- it never needed any, because cast.ts
532
+ * names the spell and rolls it -- and inventing twenty-one descriptions
533
+ * to fill a `description` field would be putting words in the book's
534
+ * mouth. So the line is DERIVED: the printed Type, Range, Duration and
535
+ * Drain, in a sentence, plus what resists it. Every word of it is data
536
+ * the row already carries.
537
+ */
538
+ function spellLine(sp) {
539
+ const range = sp.range === 'touch' ? 'Touch' : sp.range === 'LOS(A)' ? 'line of sight, area' : 'line of sight';
540
+ const duration = sp.duration === 'I' ? 'instantaneous' : sp.duration === 'S' ? 'sustained' : 'permanent';
541
+ const drain = `Drain F${sp.drainOffset === 0 ? '' : sp.drainOffset > 0 ? ` + ${sp.drainOffset}` : ` - ${-sp.drainOffset}`}`;
542
+ const resist = sp.soak === 'willpower' ? '; resisted with Willpower'
543
+ : sp.soak === 'body' ? '; resisted with Body'
544
+ : sp.soak === 'armor' ? '; soaked in full, less AP' : '';
545
+ return `${sp.category} spell, ${sp.type}, ${range}, ${duration}. ${drain}${resist}.`;
546
+ }
547
+ /**
548
+ * WHAT A FORMULA COSTS, by the category of the spell it teaches (p.461,
549
+ * priced from the Magical Goods table). The formula is for ONE spell;
550
+ * the CATEGORY is only what sets the price, which is why these rows are
551
+ * generated per spell rather than per category.
552
+ */
553
+ const FORMULA_PRICE = {
554
+ Detection: { price: 500, avail: 4 },
555
+ Health: { price: 500, avail: 4 },
556
+ Illusion: { price: 1000, avail: 8 },
557
+ Manipulation: { price: 1500, avail: 8 },
558
+ Combat: { price: 2000, avail: 8 },
559
+ };
560
+ /**
561
+ * ONE FORMULA PER SPELL, derived from SPELL_CATALOG.
562
+ *
563
+ * These are GEAR rows -- a scroll you buy from a talismonger and carry --
564
+ * and they are the first gear the engine reads for something other than
565
+ * a dice modifier: `teaches` names the spell, and Player.knowsSpell reads
566
+ * it. Generated rather than authored so a spell added tomorrow is
567
+ * purchasable the same day, with no second edit to forget.
568
+ *
569
+ * The Detection price sits in the table above and produces no rows,
570
+ * because this engine has no Detection spell yet. That is deliberate: a
571
+ * "Detection Spell Formula" WAS on sale here for 500 nuyen, for a
572
+ * category with nothing in it.
573
+ */
574
+ function formulaRows() {
575
+ return SPELL_CATALOG.map(sp => {
576
+ const { price, avail } = FORMULA_PRICE[sp.category];
577
+ return row(`spell-formula-${sp.key}`, `Spell Formula (${sp.name})`, 'SpellFormula', price, avail, `${sp.name}, notated for study in somebody else's hand. Bring talent.`, {
578
+ page: 'p.461', shape: 'scroll', size: 'Tiny', weight: 0.1, legality: 'R',
579
+ teaches: sp.key,
580
+ stats: `${sp.category} formula: ${price} nuyen, avail ${avail}R`,
581
+ });
582
+ });
583
+ }
584
+ function spellRows() {
585
+ return SPELL_CATALOG.map(sp => ({
586
+ kind: 'spell',
587
+ slug: sp.key,
588
+ name: sp.name,
589
+ description: spellLine(sp),
590
+ page: sp.page,
591
+ // What it does to whoever it is held on, in the shared vocabulary.
592
+ // Absent on every Instantaneous spell -- a combat spell resolves at
593
+ // cast time -- and on the two barriers, whose effect is a wall in a
594
+ // room rather than a modifier on anybody.
595
+ ...(sp.effects !== undefined ? { effects: sp.effects } : {}),
596
+ spell: {
597
+ cast: sp.cast,
598
+ category: sp.category,
599
+ type: sp.type,
600
+ range: sp.range,
601
+ duration: sp.duration,
602
+ drainOffset: sp.drainOffset,
603
+ ...(sp.soak !== undefined ? { soak: sp.soak } : {}),
604
+ ...(sp.stun !== undefined ? { stun: sp.stun } : {}),
605
+ ...(sp.area !== undefined ? { area: sp.area } : {}),
606
+ ...(sp.touch !== undefined ? { touch: sp.touch } : {}),
607
+ },
608
+ }));
609
+ }
610
+ function adeptPowerRows() {
611
+ return ADEPT_POWER_CATALOG.map(a => ({
612
+ kind: 'adept-power',
613
+ slug: a.key,
614
+ name: a.name,
615
+ description: a.effect,
616
+ page: a.page,
617
+ ...(a.effects !== undefined ? { effects: a.effects } : {}),
618
+ adeptPower: {
619
+ levelCosts: a.levelCosts,
620
+ magicCapped: a.magicCapped,
621
+ ...(a.costPerLevel !== undefined ? { costPerLevel: a.costPerLevel } : {}),
622
+ },
623
+ }));
624
+ }
625
+ function metamagicRows() {
626
+ return METAMAGICS.map(m => ({
627
+ kind: 'metamagic',
628
+ slug: m.key,
629
+ name: m.name,
630
+ description: m.effect,
631
+ page: m.page,
632
+ ...(m.effects !== undefined ? { effects: m.effects } : {}),
633
+ metamagic: { path: m.path },
634
+ }));
635
+ }
636
+ /**
637
+ * THE METATYPES (p.66). Rows only: this engine models no metatype on a
638
+ * character and this pass does not change that -- what it does is put the
639
+ * table where it can be read, cited and priced, so the shape stops being
640
+ * an empty promise in the schema.
641
+ */
642
+ function metatypeRows() {
643
+ return METATYPES.map(m => ({
644
+ kind: 'metatype',
645
+ slug: m.key,
646
+ name: m.name,
647
+ description: m.description,
648
+ page: m.page,
649
+ ...(m.effects !== undefined ? { effects: m.effects } : {}),
650
+ metatype: {
651
+ attributes: m.attributes,
652
+ sprintMetersPerHit: m.sprintMetersPerHit,
653
+ ...(m.lifestyleCostMult !== undefined ? { lifestyleCostMult: m.lifestyleCostMult } : {}),
654
+ },
655
+ }));
656
+ }
657
+ export const CATALOG_ROWS = [
658
+ ...CATALOG_ITEMS,
659
+ ...formulaRows(),
660
+ ...qualityRows(),
661
+ ...augmentationRows(),
662
+ ...adeptPowerRows(),
663
+ ...metamagicRows(),
664
+ ...spellRows(),
665
+ ...metatypeRows(),
666
+ ];
400
667
  //# sourceMappingURL=catalog-data.js.map