@maka/maka-cli 5.121.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 (96) 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/buy.js +8 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -1
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +4 -2
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -1
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +16 -1
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +273 -28
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.d.ts +19 -10
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +43 -227
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js.map +1 -1
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +31 -3
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +5 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +4 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -1
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +4 -6
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -1
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +10 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +50 -2
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +27 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js.map +1 -1
  33. package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
  34. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +17 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/engine-version.js.map +1 -1
  36. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +3 -2
  37. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +34 -41
  38. package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
  39. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +37 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  41. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +47 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/game.js +103 -4
  43. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.d.ts +17 -0
  45. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js +30 -2
  46. package/bundle/typescript/src/commands/game/sideQuest/headless-harness.js.map +1 -1
  47. package/bundle/typescript/src/commands/game/sideQuest/models/device.d.ts +14 -0
  48. package/bundle/typescript/src/commands/game/sideQuest/models/device.js +14 -0
  49. package/bundle/typescript/src/commands/game/sideQuest/models/device.js.map +1 -1
  50. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +6 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +8 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  53. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +82 -1
  54. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +118 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  56. package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +4 -0
  57. package/bundle/typescript/src/commands/game/sideQuest/qualities.js +16 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -1
  59. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +9 -9
  60. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +6 -6
  61. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +5 -1
  62. package/bundle/typescript/src/commands/game/sideQuest/ui.js +23 -26
  63. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  64. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.d.ts +24 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +34 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js.map +1 -1
  67. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +9 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +8 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
  70. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +25 -2
  71. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +32 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +33 -0
  74. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -1
  75. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +5 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +35 -1
  77. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -1
  78. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.d.ts +121 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js +141 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js.map +1 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +5 -1
  82. package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
  83. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.d.ts +93 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +201 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js.map +1 -0
  86. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js +91 -9
  87. package/bundle/typescript/src/commands/game/sideQuest/utilities/room-view.js.map +1 -1
  88. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.d.ts +16 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +50 -3
  90. package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
  91. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.d.ts +7 -1
  92. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +7 -2
  93. package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js.map +1 -1
  94. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +43 -3
  95. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  96. package/package.json +1 -1
@@ -2,35 +2,55 @@ import { Player } from './models/player.js';
2
2
  import { Item } from './models/item.js';
3
3
  import { Size, Category, Rating } from './types/shared/item-enum.js';
4
4
  import { getAug } from './augmentations.js';
5
- import { creationSplit } from './qualities.js';
6
- const gearDef = (partial) => ({
7
- size: Size.Small,
8
- weight: 1,
9
- shape: 'Gear',
10
- color: 'Black',
11
- texture: 'Matte',
12
- ...partial,
13
- });
5
+ import { creationSplit, getQuality, CREATION_KARMA, KARMA_CARRYOVER_MAX, KARMA_TO_NUYEN_MAX, KARMA_NUYEN_RATE, HELD_NUYEN_MAX } from './qualities.js';
6
+ import { catalogGearDef, catalogRowFor } from './utilities/catalog.js';
14
7
  export const ARCHETYPES = [
15
8
  {
16
9
  // No qualities at all IS the identity: nothing bought, nothing owed --
17
10
  // the untouched 25 ledger splits per RAW into 7 karma + 20,000 nuyen.
18
11
  name: 'Runner',
12
+ priorities: { metatype: 'C', attributes: 'A', magic: 'E', skills: 'B', resources: 'D' },
19
13
  description: 'The balanced default: decent at everything, spectacular at nothing.',
20
- combat: { body: 4, agility: 4, reaction: 4, combatSkill: 3, strength: 3, willpower: 3, logic: 3, intuition: 3, charisma: 3, edge: 2, magic: 0,
21
- skills: { automatics: 3, pistols: 3, longarms: 3, blades: 2, unarmed: 3, sneaking: 2, perception: 3, gymnastics: 2, running: 2, swimming: 2, hacking: 1, locksmith: 1, negotiation: 2, con: 1, etiquette: 1, 'first-aid': 2 } },
14
+ combat: { body: 4, agility: 4, reaction: 4, combatSkill: 3, strength: 4, willpower: 4, logic: 4, intuition: 4, charisma: 4, edge: 3, magic: 0,
15
+ skills: { automatics: 3, pistols: 3, longarms: 3, blades: 2, unarmed: 3, sneaking: 3, perception: 4, gymnastics: 2, running: 2, swimming: 2, hacking: 1, locksmith: 1, negotiation: 3, con: 1, etiquette: 1, 'first-aid': 2 } },
16
+ // Nothing owed and nothing exotic -- the generalist spends the ledger
17
+ // on noticing things and shrugging off wounds.
18
+ qualities: ['perceptive', 'quick-healer'],
19
+ augs: [
20
+ 'datajack', 'smartlink', 'reaction-enhancers-1', 'synaptic-booster'
21
+ ],
22
+ gearSlugs: [
23
+ 'ares-predator-v', 'quick-draw-holster', 'spare-clip', 'regular-ammo',
24
+ 'gel-rounds', 'defiance-t-250', 'armor-jacket', 'combat-boots',
25
+ 'erika-elite', 'fake-sin-4', 'fake-license-4', 'medkit-3', 'trauma-patch',
26
+ 'stim-patch', 'ear-buds', 'glasses', 'microtransceiver', 'lockpick-set',
27
+ 'tool-kit', 'climbing-gear', 'stealth-rope', 'survival-kit',
28
+ 'respirator-4', 'smoke-grenade', 'flash-bang', 'tag-eraser',
29
+ 'white-noise-generator', 'mapsoft', 'credstick-silver'
30
+ ],
22
31
  },
23
32
  {
24
33
  name: 'Street Samurai',
34
+ priorities: { metatype: 'C', attributes: 'B', magic: 'E', skills: 'A', resources: 'D' },
25
35
  description: 'Chrome and trigger discipline. Hits hard, soaks harder, thinks later.',
26
36
  combat: { body: 5, agility: 5, reaction: 4, combatSkill: 5, strength: 4, willpower: 3, logic: 2, intuition: 3, charisma: 2, edge: 2, magic: 0,
27
- skills: { automatics: 6, pistols: 6, longarms: 6, blades: 4, unarmed: 5, gymnastics: 4, running: 4, swimming: 4, perception: 3, intimidation: 4, sneaking: 2, 'first-aid': 1 } },
28
- augs: ['wired-reflexes-1', 'smartlink'],
37
+ skills: { automatics: 6, pistols: 6, longarms: 6, blades: 4, unarmed: 5, gymnastics: 4, running: 3, swimming: 3, perception: 3, intimidation: 4, sneaking: 2 } },
29
38
  // Built to take a beating; the record says the beatings went both
30
39
  // ways. Ledger 25 - 16 + 10 = 19 -> 7 karma + 20,000 nuyen.
31
- qualities: ['toughness', 'high-pain-tolerance', 'sinner-criminal'],
32
- gear: [
33
- gearDef({ name: 'Ares Predator V', description: 'The iconic heavy pistol of the shadows -- smartgun-linked, and it never jams when it matters.', category: Category.RangedWeapon, rating: Rating.Rare, shape: 'Pistol', weight: 2 }),
40
+ qualities: ['toughness', 'high-pain-tolerance', 'quick-healer', 'sinner-criminal'],
41
+ // The book's own sam (p.112): the Predator with its smartgun, and a
42
+ // lined coat -- Armor 9 under the street jacket.
43
+ augs: [
44
+ 'wired-reflexes-1', 'smartlink', 'bone-lacing', 'dermal-plating-2',
45
+ 'platelet-factories', 'cybereyes', 'vision-enhancement-2'
46
+ ],
47
+ gearSlugs: [
48
+ 'ares-predator-v', 'quick-draw-holster', 'spare-clip', 'regular-ammo',
49
+ 'apds', 'ares-alpha', 'combat-knife', 'lined-coat', 'armored-pants',
50
+ 'combat-boots', 'helmet', 'hermes-ikon', 'fake-sin-4', 'fake-license-4',
51
+ 'medkit-6', 'trauma-patch', 'stim-patch', 'ear-buds', 'glasses',
52
+ 'microtransceiver', 'white-noise-generator', 'survival-kit',
53
+ 'credstick-silver'
34
54
  ],
35
55
  },
36
56
  {
@@ -38,6 +58,7 @@ export const ARCHETYPES = [
38
58
  // nothing and cannot project -- half their magic rides every meat
39
59
  // attack and defense pool instead (see Player.adept).
40
60
  name: 'Adept',
61
+ priorities: { metatype: 'D', attributes: 'B', magic: 'C', skills: 'A', resources: 'E' },
41
62
  description: 'Magic as muscle: faster than chrome, no spells, no projection -- the body IS the focus.',
42
63
  // CHOSEN POWERS (regen 2026-09-01): Magic 5 = 5 free Power Points
43
64
  // (p.69), spent to the last half-point -- Improved Reflexes 2 (2.5) +
@@ -45,14 +66,19 @@ export const ARCHETYPES = [
45
66
  // Sense 1 (0.5) + Mystic Armor 1 (0.5). Richer than the compressed
46
67
  // floor(5/2) bonus this replaces: real initiative, physical fists,
47
68
  // armor that was never worn.
48
- combat: { body: 4, agility: 4, reaction: 4, combatSkill: 4, strength: 4, willpower: 4, logic: 2, intuition: 3, charisma: 2, edge: 2, magic: 5, adept: true,
69
+ combat: { body: 4, agility: 4, reaction: 4, combatSkill: 4, strength: 4, willpower: 4, logic: 2, intuition: 3, charisma: 3, edge: 2, magic: 5, adept: true,
49
70
  adeptPowers: { 'improved-reflexes': 2, 'improved-ability-combat': 2, 'killing-hands': 1, 'combat-sense': 1, 'mystic-armor': 1 },
50
71
  skills: { blades: 6, unarmed: 5, gymnastics: 5, running: 5, swimming: 5, sneaking: 4, perception: 3, assensing: 3, intimidation: 2, automatics: 2, pistols: 2, longarms: 2 } },
51
72
  // The body IS the focus -- and it rejects the sim the way it rejects
52
73
  // chrome. Ledger 25 - 14 + 5 = 16 -> 7 karma + 18,000 nuyen.
53
74
  qualities: ['catlike', 'natural-athlete', 'simsense-vertigo'],
54
- gear: [
55
- gearDef({ name: 'Weapon Focus Katana', description: 'A blade that drinks its wielder\'s magic -- the edge sings at frequencies steel shouldn\'t reach.', category: Category.MeleeWeapon, rating: Rating.Rare, size: Size.Medium, shape: 'Sword', color: 'Silver', texture: 'Folded Steel', weight: 1.5 }),
75
+ // The katana is the p.423 row (Acc 7, STR+3, AP -3); the bonded weapon
76
+ // focus rides beside it as the Force 1 row.
77
+ gearSlugs: [
78
+ 'katana', 'weapon-focus-1', 'qi-focus-1', 'combat-knife', 'armor-jacket',
79
+ 'combat-boots', 'fake-sin-2', 'meta-link', 'medkit-3', 'trauma-patch',
80
+ 'stim-patch', 'ear-buds', 'glasses', 'climbing-gear', 'stealth-rope',
81
+ 'gecko-tape-gloves', 'survival-kit', 'credstick-silver'
56
82
  ],
57
83
  },
58
84
  {
@@ -63,6 +89,7 @@ export const ARCHETYPES = [
63
89
  // body, and NEVER astrally projects -- the one place the path sides
64
90
  // with the adept.
65
91
  name: 'Mystic Adept',
92
+ priorities: { metatype: 'D', attributes: 'C', magic: 'A', skills: 'B', resources: 'E' },
66
93
  description: 'Spell in one hand, killing hands on the other -- both worlds, and the astral door stays shut.',
67
94
  // Ledger: 25 - 10 (2 Power Points) + 10 (Astral Beacon) = 25 -> 7
68
95
  // karma + 20,000 nuyen. The beacon is the price of burning on both
@@ -70,34 +97,48 @@ export const ARCHETYPES = [
70
97
  // Powers: Improved Reflexes 1 (1.5) + Killing Hands (0.5) = 2.0 --
71
98
  // the bought points, spent whole. Spells: Magic 6 allows 12; eight
72
99
  // chosen, breadth over artillery.
73
- combat: { body: 3, agility: 3, reaction: 3, combatSkill: 3, strength: 3, willpower: 4, logic: 3, intuition: 4, charisma: 3, edge: 2, magic: 6, adept: false, powerPoints: 2,
100
+ combat: { body: 3, agility: 3, reaction: 3, combatSkill: 3, strength: 2, willpower: 4, logic: 3, intuition: 4, charisma: 2, edge: 2, magic: 6, adept: false, powerPoints: 2,
74
101
  knownSpells: ['manabolt', 'stunbolt', 'fireball', 'heal', 'armor', 'invisibility', 'reflexes', 'barrier'],
75
102
  adeptPowers: { 'improved-reflexes': 1, 'killing-hands': 1 },
76
103
  skills: { spellcasting: 5, counterspelling: 3, assensing: 3, unarmed: 4, blades: 3, gymnastics: 3, running: 3, swimming: 2, perception: 3, sneaking: 3, pistols: 2 } },
77
- qualities: ['astral-beacon'],
78
- gear: [
79
- gearDef({ name: 'Twin-Path Amulet', description: 'A focus worn where the two disciplines meet -- half orichalcum lens, half knuckle charm.', category: Category.Focus, rating: Rating.Standard, size: Size.Tiny, shape: 'Amulet', color: 'Copper', texture: 'Braided Metal', weight: 0.1 }),
104
+ qualities: ['focused-concentration', 'mentor-spirit', 'astral-beacon'],
105
+ gearSlugs: [
106
+ 'power-focus-1', 'reagents', 'armor-jacket', 'combat-boots', 'meta-link',
107
+ 'medkit-3', 'trauma-patch', 'stim-patch', 'ear-buds', 'glasses',
108
+ 'survival-kit', 'credstick-silver'
80
109
  ],
81
110
  },
82
111
  {
83
112
  name: 'Covert Ops',
113
+ priorities: { metatype: 'C', attributes: 'B', magic: 'E', skills: 'A', resources: 'D' },
84
114
  description: 'In through the vents, out before the alarm. Locks are suggestions.',
85
- combat: { body: 3, agility: 5, reaction: 4, combatSkill: 3, strength: 3, willpower: 3, logic: 3, intuition: 5, charisma: 3, edge: 3, magic: 0,
115
+ combat: { body: 3, agility: 5, reaction: 4, combatSkill: 3, strength: 3, willpower: 2, logic: 3, intuition: 5, charisma: 3, edge: 3, magic: 0,
86
116
  skills: { sneaking: 6, locksmith: 5, perception: 4, gymnastics: 4, running: 4, swimming: 4, automatics: 3, pistols: 3, longarms: 3, unarmed: 3, con: 3, hacking: 2 } },
87
- augs: ['cybereyes'],
88
117
  // Quiet, charmed, and never quite off duty -- the paranoia that keeps
89
118
  // you alive doesn't sleep either. Ledger 25 - 19 + 15 = 21 -> 7 karma
90
119
  // + 20,000 nuyen.
91
- qualities: ['catlike', 'lucky', 'sinner-national', 'insomnia'],
92
- gear: [
93
- gearDef({ name: 'Lockpick Set', description: 'Precision picks, tension bars, and a maglock passkey of arguable legality.', category: Category.Tool, rating: Rating.Standard, size: Size.Tiny, weight: 0.3 }),
94
- gearDef({ name: 'Chameleon Suit', description: 'Ruthenium-coated infiltration wear that drinks the light around it.', category: Category.ArmorClothing, rating: Rating.Standard, size: Size.Medium, shape: 'Suit', color: 'Shifting Gray', texture: 'Ruthenium Weave', weight: 2 }),
120
+ qualities: ['catlike', 'lucky', 'innocuous', 'sinner-national', 'insomnia'],
121
+ // The picks, and a Sequencer that now rolls its rating against the
122
+ // lock's (pick.ts). "Locks are suggestions" has teeth.
123
+ augs: [
124
+ 'cybereyes', 'vision-enhancement-2', 'datajack', 'muscle-toner-1'
125
+ ],
126
+ gearSlugs: [
127
+ 'chameleon-suit', 'lockpick-set', 'sequencer-4', 'autopicker-3',
128
+ 'keycard-copier-4', 'grapple-gun', 'stealth-rope', 'climbing-gear',
129
+ 'rappelling-gloves', 'gecko-tape-gloves', 'tag-eraser', 'stealth-tags',
130
+ 'micro-camera', 'data-tap', 'white-noise-generator', 'erika-elite',
131
+ 'fake-sin-4', 'fake-license-4', 'ares-light-fire-70', 'silencer',
132
+ 'spare-clip', 'regular-ammo', 'medkit-3', 'trauma-patch', 'stim-patch',
133
+ 'ear-buds', 'glasses', 'respirator-4', 'chisel-crowbar', 'survival-kit',
134
+ 'combat-boots', 'credstick-silver'
95
135
  ],
96
136
  },
97
137
  {
98
138
  name: 'Face',
139
+ priorities: { metatype: 'C', attributes: 'B', magic: 'E', skills: 'A', resources: 'D' },
99
140
  description: 'Talks the job open before anyone draws. Charm, luck, and just enough gun.',
100
- combat: { body: 3, agility: 3, reaction: 3, combatSkill: 3, strength: 3, willpower: 4, logic: 3, intuition: 4, charisma: 6, edge: 3, magic: 0,
141
+ combat: { body: 3, agility: 3, reaction: 3, combatSkill: 3, strength: 2, willpower: 4, logic: 3, intuition: 4, charisma: 6, edge: 3, magic: 0,
101
142
  // Etiquette in for sneaking: the Face blends SOCIALLY (RAW p.138:
102
143
  // "a social version of Sneak") -- vents are someone else's job.
103
144
  // Leadership in for intimidation (same Cha-social slot, ledger
@@ -107,44 +148,69 @@ export const ARCHETYPES = [
107
148
  // opens doors is exactly what witnesses describe to Knight Errant.
108
149
  // Ledger 25 - 23 + 10 = 12 -> 7 karma + 10,000 nuyen.
109
150
  qualities: ['first-impression', 'lucky', 'distinctive-style', 'sinner-national'],
110
- gear: [
111
- gearDef({ name: 'Transys Avalon', description: 'A top-shelf commlink -- the AR overlay alone is worth the felony it took to afford it.', category: Category.Commlink, rating: Rating.Rare, size: Size.Tiny, weight: 0.2 }),
112
- gearDef({ name: 'Berwick Suit', description: 'Tailored armor-weave that reads boardroom, not battlefield.', category: Category.ArmorClothing, rating: Rating.Standard, size: Size.Medium, shape: 'Suit', color: 'Charcoal', texture: 'Armored Silk', weight: 2 }),
151
+ // The book's Face (p.119): the Avalon, Actioneer Business Clothes
152
+ // (Armor 8), and a Shopsoft on the link for the haggle.
153
+ augs: [
154
+ 'tailored-pheromones-2', 'cybereyes', 'vision-enhancement-1', 'datajack'
155
+ ],
156
+ gearSlugs: [
157
+ 'transys-avalon', 'actioneer-business-clothes', 'shopsoft',
158
+ 'electrochromic-clothing', 'fake-sin-4', 'fake-license-4',
159
+ 'ares-light-fire-70', 'quick-draw-holster', 'spare-clip', 'regular-ammo',
160
+ 'micro-camera', 'data-tap', 'tag-eraser', 'white-noise-generator',
161
+ 'microtransceiver', 'ear-buds', 'glasses', 'medkit-3', 'stim-patch',
162
+ 'mapsoft', 'combat-boots', 'credstick-gold'
113
163
  ],
114
164
  },
115
165
  {
116
166
  name: 'Street Doc',
167
+ priorities: { metatype: 'B', attributes: 'A', magic: 'E', skills: 'C', resources: 'D' },
117
168
  description: 'Keeps the team breathing: brains, steady hands, and a bag full of trauma kits.',
118
- combat: { body: 3, agility: 3, reaction: 3, combatSkill: 2, strength: 3, willpower: 4, logic: 6, intuition: 4, charisma: 3, edge: 2, magic: 0,
169
+ combat: { body: 4, agility: 4, reaction: 4, combatSkill: 2, strength: 3, willpower: 4, logic: 6, intuition: 4, charisma: 3, edge: 4, magic: 0,
119
170
  skills: { 'first-aid': 6, perception: 4, negotiation: 3, automatics: 2, pistols: 2, longarms: 2, unarmed: 2, hacking: 2, con: 2 } },
120
171
  // Brilliant hands that freeze when the shooting starts -- which is
121
172
  // why they became the one who patches, not the one who charges.
122
173
  // Ledger 25 - 8 + 12 = 29 -> 7 karma + 20,000 nuyen.
123
- qualities: ['quick-healer', 'analytical-mind', 'combat-paralysis'],
124
- gear: [
125
- gearDef({ name: 'Trauma Kit', description: 'A doc-grade field kit: sprays, staples, and things that hiss.', category: Category.Medical, rating: Rating.Rare, weight: 1.5 }),
126
- gearDef({ name: 'Kamikaze Stim Patch', description: 'A combat stim slap-patch. The crash is tomorrow\'s problem.', category: Category.StimPatch, rating: Rating.Standard, size: Size.Tiny, weight: 0.1 }),
174
+ qualities: ['quick-healer', 'analytical-mind', 'photographic-memory', 'resistance-pathogens-and-toxins', 'combat-paralysis'],
175
+ // The Rating 6 medkit is the kit that thinks (first-aid.ts).
176
+ augs: [
177
+ 'cybereyes', 'vision-enhancement-1', 'datajack', 'cerebral-booster-1',
178
+ 'symbiotes-1'
179
+ ],
180
+ gearSlugs: [
181
+ 'medkit-6', 'medkit-supplies', 'disposable-syringe', 'antidote-patch-4',
182
+ 'tranq-patch-6', 'biomonitor', 'trauma-patch', 'stim-patch',
183
+ 'armor-jacket', 'combat-boots', 'renraku-sensei', 'fake-sin-4',
184
+ 'fake-license-4', 'ares-light-fire-70', 'quick-draw-holster', 'spare-clip',
185
+ 'regular-ammo', 'ear-buds', 'glasses', 'tool-kit', 'gas-mask',
186
+ 'respirator-4', 'survival-kit', 'credstick-silver'
127
187
  ],
128
188
  },
129
189
  {
130
190
  name: 'Combat Mage',
191
+ priorities: { metatype: 'D', attributes: 'B', magic: 'A', skills: 'C', resources: 'E' },
131
192
  description: 'Awakened artillery: manabolts through milspec armor, if the drain doesn\'t eat you first.',
132
193
  // CHOSEN SPELLBOOK (regen 2026-09-01): Magic 6 knows up to 12 (p.300)
133
194
  // and the artillery build takes all 12 -- every combat spell in the
134
195
  // catalog plus the sustains that keep the caster standing.
135
- combat: { body: 3, agility: 3, reaction: 3, combatSkill: 2, strength: 2, willpower: 5, logic: 4, intuition: 4, charisma: 3, edge: 2, magic: 6,
196
+ combat: { body: 4, agility: 3, reaction: 3, combatSkill: 2, strength: 2, willpower: 5, logic: 4, intuition: 4, charisma: 3, edge: 2, magic: 6,
136
197
  knownSpells: ['manabolt', 'stunbolt', 'powerbolt', 'fireball', 'lightning-bolt', 'ball-lightning', 'manaball', 'stunball', 'armor', 'heal', 'reflexes', 'barrier'],
137
198
  skills: { spellcasting: 6, counterspelling: 4, assensing: 4, conjuring: 3, perception: 3, unarmed: 2, automatics: 2, pistols: 2, longarms: 2 } },
138
199
  // Drain taught them what pain is worth ignoring; the astral bleeds
139
200
  // into their dreams and calls it rent. Ledger 25 - 7 + 10 = 28 -> 7
140
201
  // karma + 20,000 nuyen.
141
- qualities: ['high-pain-tolerance', 'insomnia'],
142
- gear: [
143
- gearDef({ name: 'Spellcasting Focus', description: 'An orichalcum-threaded ring that gathers mana the way a lens gathers light.', category: Category.Focus, rating: Rating.Standard, size: Size.Tiny, shape: 'Ring', color: 'Gold', texture: 'Orichalcum', weight: 0.1 }),
202
+ qualities: ['high-pain-tolerance', 'focused-concentration', 'mentor-spirit', 'quick-healer', 'insomnia'],
203
+ gearSlugs: [
204
+ 'spell-focus-1', 'combat-spell-formula', 'reagents', 'lodge-materials-3',
205
+ 'armor-jacket', 'combat-boots', 'fake-sin-2', 'fake-license-4',
206
+ 'meta-link', 'ares-light-fire-70', 'spare-clip', 'regular-ammo',
207
+ 'medkit-3', 'trauma-patch', 'stim-patch', 'ear-buds', 'glasses',
208
+ 'credstick-silver'
144
209
  ],
145
210
  },
146
211
  {
147
212
  name: 'Street Shaman',
213
+ priorities: { metatype: 'D', attributes: 'B', magic: 'C', skills: 'A', resources: 'E' },
148
214
  description: 'Awakened the hard way: iron will, sharp instincts, spirits on speed dial.',
149
215
  // CHOSEN SPELLBOOK (regen 2026-09-01): Magic 5 knows up to 10 (p.300)
150
216
  // -- the mender's list: mercy settings, wards, and the heal that is
@@ -156,8 +222,71 @@ export const ARCHETYPES = [
156
222
  // but nobody forgets the one draped in bones and braided wire.
157
223
  // Ledger 25 - 15 + 5 = 15 -> 7 karma + 16,000 nuyen.
158
224
  qualities: ['lucky', 'quick-healer', 'distinctive-style'],
159
- gear: [
160
- gearDef({ name: 'Spirit Talisman', description: 'Feathers, bone, and braided wire -- a focus that hums when the other world leans close.', category: Category.Focus, rating: Rating.Standard, size: Size.Tiny, shape: 'Talisman', color: 'Bone White', texture: 'Feather and Wire', weight: 0.2 }),
225
+ gearSlugs: [
226
+ 'spirit-focus-1', 'lodge-materials-3', 'reagents', 'armor-jacket',
227
+ 'combat-boots', 'fake-sin-2', 'meta-link', 'ares-light-fire-70',
228
+ 'spare-clip', 'regular-ammo', 'medkit-3', 'trauma-patch', 'stim-patch',
229
+ 'ear-buds', 'glasses', 'survival-kit', 'credstick-silver'
230
+ ],
231
+ },
232
+ {
233
+ // THE ASPECTED MAGICIAN, FINALLY PLAYABLE FROM THE MENU (user ask,
234
+ // 2026-09-03: "how about the aspected magician?"). aspects.ts, the
235
+ // Player.aspect field, the chargen path and the verb gates in
236
+ // summon.ts / cast.ts / dispel.ts have all been here since
237
+ // 2026-09-02 -- there was simply no pregen carrying `aspect`, so the
238
+ // fifth magical path existed everywhere except where a player picks
239
+ // a character. Two of them, because the engine honours exactly two
240
+ // aspects: Enchanter waits on alchemy (UNAVAILABLE_ASPECTS).
241
+ //
242
+ // Magic B on the Aspected row is Magic 5 (p.65, ASPECTED_PRIORITY) --
243
+ // the same rating the full magician gets at B, which is the trade:
244
+ // half the art, none of the discount.
245
+ name: 'Sorcerer',
246
+ priorities: { metatype: 'C', attributes: 'A', magic: 'B', skills: 'E', resources: 'D' },
247
+ description: 'Spells and nothing else: no spirits will ever answer you, and the astral is a window you cannot climb through.',
248
+ combat: { body: 3, agility: 3, reaction: 3, combatSkill: 2, strength: 2, willpower: 6, logic: 5, intuition: 5, charisma: 5, edge: 3, magic: 5, aspect: 'sorcerer',
249
+ // Magic x 2 at creation (p.69), the same cap a full magician's
250
+ // spellbook uses -- an aspected caster is not a smaller caster.
251
+ knownSpells: ['manabolt', 'stunbolt', 'powerbolt', 'manaball', 'stunball', 'lightning-bolt', 'armor', 'heal', 'reflexes', 'barrier'],
252
+ // Sorcery only. `conjuring` is absent on purpose and enforced twice
253
+ // over: the chargen payload check (forbiddenSkillsFor) and
254
+ // summon.ts's aspect gate.
255
+ skills: { spellcasting: 4, counterspelling: 4, 'ritual-spellcasting': 4, assensing: 4, perception: 4, negotiation: 3, unarmed: 3, gymnastics: 2, sneaking: 2 } },
256
+ // One art, held deeper than a generalist holds two -- and a mentor
257
+ // who never let them look at the other half.
258
+ // Ledger 25 - 9 = 16 -> 7 karma + 18,000 nuyen.
259
+ qualities: ['focused-concentration', 'mentor-spirit'],
260
+ gearSlugs: [
261
+ 'spell-focus-1', 'power-focus-1', 'combat-spell-formula', 'reagents',
262
+ 'lodge-materials-3', 'armor-jacket', 'combat-boots', 'fake-sin-4',
263
+ 'fake-license-4', 'renraku-sensei', 'mage-sight-goggles', 'medkit-3',
264
+ 'trauma-patch', 'stim-patch', 'ear-buds', 'glasses', 'microtransceiver',
265
+ 'white-noise-generator', 'survival-kit', 'ares-light-fire-70',
266
+ 'spare-clip', 'regular-ammo', 'credstick-silver'
267
+ ],
268
+ },
269
+ {
270
+ // The other half of the same coin. Charisma is the maxed attribute
271
+ // here rather than Willpower: summoning is a negotiation with
272
+ // something that does not want to be there.
273
+ name: 'Conjurer',
274
+ priorities: { metatype: 'C', attributes: 'A', magic: 'B', skills: 'E', resources: 'D' },
275
+ description: 'Spirits answer you and nothing else does -- no spells, no counterspelling, and the astral stays a window.',
276
+ combat: { body: 3, agility: 3, reaction: 3, combatSkill: 2, strength: 2, willpower: 5, logic: 5, intuition: 5, charisma: 6, edge: 3, magic: 5, aspect: 'conjurer',
277
+ // No spellbook at all: "they cannot perform any of the magical
278
+ // skills associated with the two groups they did not select"
279
+ // (p.69), and cast.ts refuses them by aspect, not by empty list.
280
+ skills: { conjuring: 6, summoning: 4, binding: 4, banishing: 4, assensing: 4, perception: 4, negotiation: 2, unarmed: 2 } },
281
+ // The spirits like them, which is not the same as the spirits being
282
+ // safe. Ledger 25 - 11 = 14 -> 7 karma + 14,000 nuyen.
283
+ qualities: ['spirit-affinity', 'focused-concentration'],
284
+ gearSlugs: [
285
+ 'spirit-focus-1', 'power-focus-1', 'lodge-materials-3', 'reagents',
286
+ 'armor-jacket', 'combat-boots', 'fake-sin-4', 'fake-license-4',
287
+ 'renraku-sensei', 'medkit-3', 'trauma-patch', 'stim-patch', 'ear-buds',
288
+ 'glasses', 'microtransceiver', 'survival-kit', 'ares-light-fire-70',
289
+ 'spare-clip', 'regular-ammo', 'credstick-silver'
161
290
  ],
162
291
  },
163
292
  {
@@ -166,24 +295,40 @@ export const ARCHETYPES = [
166
295
  // deck is the requisite: its quality feeds program damage and
167
296
  // firewall, and its hardware track is what the ice chews on.
168
297
  name: 'Decker',
298
+ priorities: { metatype: 'D', attributes: 'B', magic: 'E', skills: 'C', resources: 'A' },
169
299
  description: 'A ghost in the grid: hosts crack, ice burns, maglocks open. Just don\'t get caught in meatspace.',
170
- combat: { body: 3, agility: 2, reaction: 3, combatSkill: 2, strength: 2, willpower: 4, logic: 7, intuition: 5, charisma: 2, edge: 3, magic: 0,
300
+ combat: { body: 4, agility: 2, reaction: 3, combatSkill: 2, strength: 2, willpower: 4, logic: 6, intuition: 5, charisma: 2, edge: 3, magic: 0,
171
301
  skills: { hacking: 6, perception: 4, locksmith: 3, sneaking: 3, automatics: 2, pistols: 2, longarms: 2, unarmed: 2, con: 2 } },
172
- augs: ['datajack'],
173
302
  // Hosts are puzzles and hardware is clay -- meatspace gunfire is
174
303
  // neither, and the freeze proves it. Ledger 25 - 15 + 12 = 22 -> 7
175
304
  // karma + 20,000 nuyen.
176
- qualities: ['analytical-mind', 'juryrigger', 'combat-paralysis'],
177
- gear: [
178
- gearDef({ name: 'Novatech Navigator', description: 'A workhorse cyberdeck -- scuffed case, immaculate boards. Your ticket into the Matrix.', category: Category.Cyberdeck, rating: Rating.Standard, weight: 2 }),
305
+ qualities: ['analytical-mind', 'juryrigger', 'codeslinger-hack-on-the-fly', 'combat-paralysis'],
306
+ // The Navigator (DR 3, three programs running) and the book's own
307
+ // decker kit (p.121) trimmed to what the deck can run: the chips file
308
+ // into its storage on pickup (Player.addInventory).
309
+ augs: [
310
+ 'datajack', 'cerebral-booster-1', 'headware-cyberdeck', 'cybereyes',
311
+ 'vision-enhancement-1', 'sleep-regulator', 'reaction-enhancers-1'
312
+ ],
313
+ gearSlugs: [
314
+ 'novatech-navigator', 'exploit-program', 'armor-program',
315
+ 'biofeedback-filter-program', 'encryption-program', 'toolbox-program',
316
+ 'signal-scrub-program', 'browse-program', 'sneak-program',
317
+ 'hammer-program', 'decryption-program', 'agent-rating-4', 'trodes',
318
+ 'sim-module', 'hermes-ikon', 'armor-jacket', 'combat-boots', 'fake-sin-4',
319
+ 'fake-license-4', 'lockpick-set', 'sequencer-4', 'data-tap', 'tag-eraser',
320
+ 'white-noise-generator', 'microtransceiver', 'medkit-6', 'trauma-patch',
321
+ 'stim-patch', 'ear-buds', 'glasses', 'ares-light-fire-70', 'spare-clip',
322
+ 'regular-ammo', 'mapsoft', 'shopsoft', 'credstick-silver'
179
323
  ],
180
324
  },
181
325
  {
182
326
  // The living persona (see ICombatAttributes.resonance): no deck, no
183
327
  // commlink -- and no hardware between their stun track and the ice.
184
328
  name: 'Technomancer',
329
+ priorities: { metatype: 'D', attributes: 'C', magic: 'A', skills: 'B', resources: 'E' },
185
330
  description: 'The Matrix answers when they think at it. No deck to brick -- only a mind to burn.',
186
- combat: { body: 2, agility: 2, reaction: 3, combatSkill: 2, strength: 2, willpower: 5, logic: 5, intuition: 4, charisma: 3, edge: 3, magic: 0, resonance: 6,
331
+ combat: { body: 2, agility: 2, reaction: 3, combatSkill: 2, strength: 2, willpower: 5, logic: 5, intuition: 3, charisma: 2, edge: 3, magic: 0, resonance: 6,
187
332
  // The Resonance trio are three skills and the books test each by
188
333
  // name (SR5 p.250). A Technomancer who could compile and register
189
334
  // at 4 but decompile at the unskilled -1 would be an odd graduate;
@@ -194,26 +339,87 @@ export const ARCHETYPES = [
194
339
  // its 20% cut of everything) followed them into the shadows, and the
195
340
  // taxman never stops collecting. Ledger 25 - 5 + 15 = 35 -> 7 karma
196
341
  // + 20,000 nuyen (the rest went into that resonance).
197
- qualities: ['analytical-mind', 'sinner-corp'],
342
+ qualities: ['analytical-mind', 'natural-hardening', 'prime-datahaven-membership', 'focused-concentration', 'sinner-corp'],
343
+ gearSlugs: [
344
+ 'armor-jacket', 'combat-boots', 'fake-sin-4', 'meta-link',
345
+ 'microtransceiver', 'data-tap', 'micro-camera', 'stealth-tags',
346
+ 'tag-eraser', 'white-noise-generator', 'medkit-3', 'trauma-patch',
347
+ 'stim-patch', 'ear-buds', 'glasses', 'tool-kit', 'survival-kit', 'mapsoft',
348
+ 'credstick-silver'
349
+ ],
198
350
  },
199
351
  {
200
352
  // The drone is the mechanic: "drone" launches the Fly-Spy for live
201
353
  // recon of every adjacent room (see commands/drone.ts). Jumped-in
202
354
  // drone rigging can build on this later.
203
355
  name: 'Rigger',
356
+ priorities: { metatype: 'C', attributes: 'B', magic: 'E', skills: 'A', resources: 'D' },
204
357
  description: 'Eyes everywhere: the Fly-Spy scouts the next room so your body never has to.',
205
358
  combat: { body: 3, agility: 3, reaction: 5, combatSkill: 3, strength: 3, willpower: 3, logic: 5, intuition: 4, charisma: 2, edge: 2, magic: 0,
206
359
  skills: { piloting: 6, hacking: 4, perception: 4, automatics: 3, pistols: 3, longarms: 3, locksmith: 3, unarmed: 2, 'first-aid': 2 } },
207
- augs: ['control-rig'],
208
360
  // Anything with a motor runs better under their hands; the drone
209
361
  // feeds run all night, and so do they. Ledger 25 - 10 + 10 = 25 -> 7
210
362
  // karma + 20,000 nuyen.
211
- qualities: ['juryrigger', 'insomnia'],
212
- gear: [
213
- gearDef({ name: 'Fly-Spy Drone', description: 'A palm-sized surveillance drone, quiet as a moth and twice as nosy.', category: Category.Drone, rating: Rating.Standard, size: Size.Tiny, shape: 'Insectoid', color: 'Gunmetal', texture: 'Micro-rotor', weight: 0.5 }),
363
+ qualities: ['juryrigger', 'gearhead', 'insomnia'],
364
+ // The p.466 row: Pilot 3, Body 1, a real hull.
365
+ augs: [
366
+ 'control-rig', 'datajack', 'reaction-enhancers-1', 'cybereyes',
367
+ 'vision-enhancement-1'
368
+ ],
369
+ gearSlugs: [
370
+ 'fly-spy', 'mct-nissan-roto-drone', 'ferret-rpd-5x', 'autosoft-3',
371
+ 'tool-kit', 'miniwelder', 'erika-elite', 'fake-sin-4', 'fake-license-4',
372
+ 'armor-jacket', 'combat-boots', 'ares-light-fire-70', 'quick-draw-holster',
373
+ 'spare-clip', 'regular-ammo', 'medkit-3', 'trauma-patch', 'stim-patch',
374
+ 'ear-buds', 'glasses', 'microtransceiver', 'mapsoft', 'credstick-silver'
214
375
  ],
215
376
  },
216
377
  ];
378
+ /** The p.65 Resources column, lowest to highest. */
379
+ export const RESOURCES_PRIORITY = { E: 6_000, D: 50_000, C: 140_000, B: 275_000, A: 450_000 };
380
+ /**
381
+ * THE CREATION LEDGER, THE WAY THE CHARGEN VALIDATOR RUNS IT (user,
382
+ * 2026-09-03: "ensure the starting nuyen is appropriate for the archetype
383
+ * -- use the chargen api to validate"). maka-cli.com's validateWorksheet
384
+ * prices a build as: budget = the Resources column + converted karma x
385
+ * 2,000 (at most 10 karma, after 7 carry over, p.98); spent = gear at
386
+ * catalog price + 'ware at its cost x grade + the first month's
387
+ * lifestyle; and the factory caps what a runner may HOLD out of the
388
+ * remainder at 5,000 (p.95) -- the rest is gone into the build. Same
389
+ * constants, same prices, same rows; mirrored here because a pregen has
390
+ * no worksheet to POST (its skill spread is a spread, not a priority
391
+ * column, and the validator would refuse it on that before it priced
392
+ * it). The column is the archetype's own (IArchetype.priorities): the book's
393
+ * pregens are priority builds, and now so are these.
394
+ *
395
+ * Before this, outfitArchetype handed the converted karma over as CASH
396
+ * (up to 20,000): a pregen started with four times what a web-built
397
+ * runner may hold. Street lifestyle (the scenes' tier) costs nothing.
398
+ */
399
+ export function archetypeLedger(archetype) {
400
+ const gearNuyen = (archetype.gearSlugs ?? []).reduce((n, slug) => {
401
+ const row = catalogRowFor(slug);
402
+ if (!row)
403
+ throw new Error(`Archetype "${archetype.name}" names catalog slug "${slug}", which has no row.`);
404
+ return n + row.price;
405
+ }, 0);
406
+ const augNuyen = (archetype.augs ?? []).reduce((n, key) => n + (getAug(key)?.cost ?? 0), 0);
407
+ const spent = (archetype.qualities ?? []).reduce((n, key) => {
408
+ const q = getQuality(key);
409
+ return n + (q ? (q.kind === 'positive' ? q.karma : -q.karma) : 0);
410
+ }, 0) + (archetype.combat.powerPoints ?? 0) * 5;
411
+ const karmaRemainder = CREATION_KARMA - spent;
412
+ const karmaConverted = Math.min(Math.max(0, karmaRemainder - KARMA_CARRYOVER_MAX), KARMA_TO_NUYEN_MAX);
413
+ const karmaCarry = Math.min(Math.max(0, karmaRemainder), KARMA_CARRYOVER_MAX);
414
+ const kit = gearNuyen + augNuyen;
415
+ // The DECLARED column (IArchetype.priorities) -- the build's own row, the
416
+ // one the wizard validates against. archetype-priorities.test.ts proves
417
+ // it affords the kit.
418
+ const resources = archetype.priorities.resources;
419
+ const nuyenBudget = RESOURCES_PRIORITY[resources] + karmaConverted * KARMA_NUYEN_RATE;
420
+ const nuyenRemaining = nuyenBudget - kit;
421
+ return { gearNuyen, augNuyen, karmaRemainder, karmaCarry, karmaConverted, resources, nuyenBudget, nuyenRemaining, held: Math.max(0, Math.min(nuyenRemaining, HELD_NUYEN_MAX)) };
422
+ }
217
423
  export function getArchetype(name) {
218
424
  return ARCHETYPES.find(a => a.name === name) ?? ARCHETYPES[0];
219
425
  }
@@ -223,7 +429,24 @@ export function getArchetype(name) {
223
429
  * ones -- a samurai starts with the Predator IN HAND, not in a duffel.
224
430
  */
225
431
  export function createArchetypeGear(archetype) {
226
- return instantiateGear(archetype.gear ?? []);
432
+ return instantiateGear(archetypeGearDefs(archetype));
433
+ }
434
+ /**
435
+ * Every piece an archetype starts with, as constructor configs: the
436
+ * catalog rows first (by slug, converted once in utilities/catalog.ts),
437
+ * then any custom pieces. The harness serializes these into a save; the
438
+ * game instantiates them. A slug with no row throws -- at build time, in
439
+ * the tests that walk every archetype -- rather than shipping a runner
440
+ * without her gun.
441
+ */
442
+ export function archetypeGearDefs(archetype) {
443
+ const fromRows = (archetype.gearSlugs ?? []).map(slug => {
444
+ const def = catalogGearDef(slug);
445
+ if (!def)
446
+ throw new Error(`Archetype "${archetype.name}" names catalog slug "${slug}", which has no row.`);
447
+ return def;
448
+ });
449
+ return [...fromRows, ...(archetype.gear ?? [])];
227
450
  }
228
451
  function instantiateGear(defs) {
229
452
  return defs.map(def => new Item({
@@ -285,18 +508,23 @@ export function outfitArchetype(player, archetype) {
285
508
  powerPoints: archetype.combat.powerPoints ?? 0,
286
509
  });
287
510
  player.karma += split.karma;
288
- // The converted karma arrives as a CREDSTICK, not a floating number --
511
+ // WHAT IS HELD arrives as a CREDSTICK, not a floating number --
289
512
  // currency only counts when a carrier in the pack holds it (see
290
513
  // Player.currency), same pattern as the job-payment stick (scene.ts).
291
- if (split.nuyen > 0) {
514
+ // The amount is the chargen ledger's (archetypeLedger): what remains of
515
+ // the Resources column after the kit, capped at the 5,000 a runner may
516
+ // hold (p.95) -- not the converted karma as cash, which was four times
517
+ // what a web-built runner walks in with.
518
+ const ledger = archetypeLedger(archetype);
519
+ if (ledger.held > 0) {
292
520
  const gearStick = new Item({
293
521
  name: 'Certified Credstick (gear money)',
294
- description: 'Whatever was left when the outfitting was done -- certified, anonymous, and spending fast.',
522
+ description: `What was left when the outfitting was done -- certified, anonymous, and spending fast. (Resources ${ledger.resources}: ${ledger.nuyenRemaining.toLocaleString()} left after the kit, ${HELD_NUYEN_MAX.toLocaleString()} the most anyone walks out holding.)`,
295
523
  size: Size.Tiny, category: Category.Credstick,
296
524
  shape: 'Rectangle', color: 'Gunmetal', texture: 'Smooth',
297
525
  rating: Rating.Standard, weight: 0.05, transferable: true,
298
526
  });
299
- gearStick.addCurrency(split.nuyen);
527
+ gearStick.addCurrency(ledger.held);
300
528
  player.addInventory(gearStick);
301
529
  gearStick.owner = player.name;
302
530
  }
@@ -313,10 +541,21 @@ export function outfitArchetype(player, archetype) {
313
541
  player.addInventory(item);
314
542
  item.owner = player.name;
315
543
  if (EQUIP_ON_START.has(item.category)) {
544
+ // THE FIRST WEAPON LISTED IS THE SIGNATURE ONE, and a later one
545
+ // does not take its place (2026-09-03, found the moment loadouts
546
+ // grew past a single gun: a Street Samurai walked out holding a
547
+ // combat knife because it came after the Predator and the Alpha).
548
+ // The catch below already SAID "a second weapon just stays
549
+ // holstered" -- equip swaps, so it never did. Armor still layers
550
+ // over the baseline wardrobe on purpose, so only weapons are
551
+ // guarded here.
552
+ const isWeapon = item.category === Category.Weapon || item.category === Category.MeleeWeapon || item.category === Category.RangedWeapon;
553
+ if (isWeapon && player.getCarriedWeapon())
554
+ continue;
316
555
  try {
317
556
  player.equip(item);
318
557
  }
319
- catch { /* a second weapon just stays holstered */ }
558
+ catch { /* nothing to slot it into */ }
320
559
  }
321
560
  }
322
561
  }