@maka/maka-cli 5.5.0 → 5.7.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 (212) hide show
  1. package/bundle/typescript/package.json +2 -2
  2. package/bundle/typescript/src/commands/_index.d.ts +4 -0
  3. package/bundle/typescript/src/commands/_index.js +4 -0
  4. package/bundle/typescript/src/commands/_index.js.map +1 -1
  5. package/bundle/typescript/src/commands/debug.js +1 -7
  6. package/bundle/typescript/src/commands/debug.js.map +1 -1
  7. package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js +17 -1
  8. package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js.map +1 -1
  9. package/bundle/typescript/src/commands/env/_index.d.ts +3 -0
  10. package/bundle/typescript/src/commands/env/_index.js +4 -0
  11. package/bundle/typescript/src/commands/env/_index.js.map +1 -0
  12. package/bundle/typescript/src/commands/env/env.command.d.ts +3 -0
  13. package/bundle/typescript/src/commands/env/env.command.js +22 -0
  14. package/bundle/typescript/src/commands/env/env.command.js.map +1 -0
  15. package/bundle/typescript/src/commands/env/get.sub.cmd.d.ts +1 -0
  16. package/bundle/typescript/src/commands/env/get.sub.cmd.js +46 -0
  17. package/bundle/typescript/src/commands/env/get.sub.cmd.js.map +1 -0
  18. package/bundle/typescript/src/commands/env/prompt-for-env.d.ts +10 -0
  19. package/bundle/typescript/src/commands/env/prompt-for-env.js +27 -0
  20. package/bundle/typescript/src/commands/env/prompt-for-env.js.map +1 -0
  21. package/bundle/typescript/src/commands/env/set.sub.cmd.d.ts +1 -0
  22. package/bundle/typescript/src/commands/env/set.sub.cmd.js +59 -0
  23. package/bundle/typescript/src/commands/env/set.sub.cmd.js.map +1 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +16 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +79 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  27. package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +31 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +125 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -0
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +17 -7
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +113 -15
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -1
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.d.ts +37 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +193 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +3 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -1
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +10 -3
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +3 -2
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +12 -4
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +7 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js.map +1 -1
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.d.ts +16 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js +97 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/buy.js.map +1 -0
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +21 -10
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +41 -27
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -1
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +12 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +63 -7
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +35 -1
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +277 -44
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.d.ts +13 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +37 -0
  59. package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -0
  60. package/bundle/typescript/src/commands/game/sideQuest/commands/drink.d.ts +14 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js +15 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/commands/drink.js.map +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +1 -1
  64. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
  65. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +7 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
  67. package/bundle/typescript/src/commands/game/sideQuest/commands/eat.d.ts +14 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js +15 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/commands/eat.js.map +1 -0
  70. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +10 -0
  71. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +3 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
  74. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +101 -1
  75. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
  76. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +4 -1
  77. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +15 -11
  78. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  79. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +3 -3
  80. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  81. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +58 -2
  82. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
  83. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.d.ts +26 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +125 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js.map +1 -0
  86. package/bundle/typescript/src/commands/game/sideQuest/commands/install.d.ts +21 -0
  87. package/bundle/typescript/src/commands/game/sideQuest/commands/install.js +125 -0
  88. package/bundle/typescript/src/commands/game/sideQuest/commands/install.js.map +1 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/commands/jump.d.ts +16 -0
  90. package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js +62 -0
  91. package/bundle/typescript/src/commands/game/sideQuest/commands/jump.js.map +1 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.d.ts +16 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js +76 -0
  94. package/bundle/typescript/src/commands/game/sideQuest/commands/lifestyle.js.map +1 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js +4 -0
  96. package/bundle/typescript/src/commands/game/sideQuest/commands/lock.js.map +1 -1
  97. package/bundle/typescript/src/commands/game/sideQuest/commands/pay.d.ts +13 -0
  98. package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js +39 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/commands/pay.js.map +1 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +5 -1
  101. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -1
  102. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +5 -2
  103. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -1
  104. package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.d.ts +21 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js +121 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/commands/quality-trade.js.map +1 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +60 -12
  108. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  109. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +27 -10
  110. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
  111. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +6 -2
  112. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  113. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.d.ts +17 -0
  114. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js +125 -0
  115. package/bundle/typescript/src/commands/game/sideQuest/commands/sell.js.map +1 -0
  116. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +36 -3
  117. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
  118. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +9 -1
  119. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js.map +1 -1
  120. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +27 -4
  121. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -1
  122. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +1 -1
  123. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -1
  124. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +8 -0
  125. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  126. package/bundle/typescript/src/commands/game/sideQuest/commands/treat.d.ts +14 -0
  127. package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js +67 -0
  128. package/bundle/typescript/src/commands/game/sideQuest/commands/treat.js.map +1 -0
  129. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +7 -0
  130. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  131. package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +29 -0
  132. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +118 -11
  133. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  134. package/bundle/typescript/src/commands/game/sideQuest/commands/work.d.ts +15 -0
  135. package/bundle/typescript/src/commands/game/sideQuest/commands/work.js +68 -0
  136. package/bundle/typescript/src/commands/game/sideQuest/commands/work.js.map +1 -0
  137. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
  138. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
  139. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +1 -1
  140. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +131 -4
  141. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
  142. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +6 -2
  143. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +10 -6
  144. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -1
  145. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +115 -0
  146. package/bundle/typescript/src/commands/game/sideQuest/game.js +464 -25
  147. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  148. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +28 -0
  149. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +111 -0
  150. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  151. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +70 -7
  152. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  153. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +109 -4
  154. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +423 -39
  155. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  156. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +6 -6
  157. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +38 -14
  158. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
  159. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +9 -0
  160. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +41 -8
  161. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  162. package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +44 -0
  163. package/bundle/typescript/src/commands/game/sideQuest/qualities.js +155 -0
  164. package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -0
  165. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +274 -5
  166. package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
  167. package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +17 -0
  168. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +2 -0
  169. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +4 -0
  170. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
  171. package/bundle/typescript/src/commands/game/sideQuest/ui.js +50 -3
  172. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  173. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +13 -6
  174. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +25 -14
  175. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -1
  176. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +100 -10
  177. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  178. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.d.ts +56 -0
  179. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js +144 -0
  180. package/bundle/typescript/src/commands/game/sideQuest/utilities/commerce.js.map +1 -0
  181. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.d.ts +10 -0
  182. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js +16 -0
  183. package/bundle/typescript/src/commands/game/sideQuest/utilities/outdoors.js.map +1 -0
  184. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +21 -0
  185. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +84 -0
  186. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
  187. package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.d.ts +17 -0
  188. package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js +23 -0
  189. package/bundle/typescript/src/commands/game/sideQuest/utilities/terminal-reset.js.map +1 -0
  190. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +51 -15
  191. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  192. package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js +27 -1
  193. package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js.map +1 -1
  194. package/bundle/typescript/src/commands/run/run.command.js +1 -7
  195. package/bundle/typescript/src/commands/run/run.command.js.map +1 -1
  196. package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js +1 -6
  197. package/bundle/typescript/src/commands/testCmd/mocha.sub.cmd.js.map +1 -1
  198. package/bundle/typescript/src/generators/config.js +11 -10
  199. package/bundle/typescript/src/generators/config.js.map +1 -1
  200. package/bundle/typescript/src/templates/meteor/config/pm2.config.js +3 -3
  201. package/bundle/typescript/src/templates/meteor/project/gitignore +2 -1
  202. package/bundle/typescript/src/tool.d.ts +2 -0
  203. package/bundle/typescript/src/tool.js +3 -0
  204. package/bundle/typescript/src/tool.js.map +1 -1
  205. package/bundle/typescript/src/tools/aws/aws.class.js +6 -4
  206. package/bundle/typescript/src/tools/aws/aws.class.js.map +1 -1
  207. package/bundle/typescript/src/tools/dotenvx/dotenvx.class.d.ts +24 -0
  208. package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js +66 -0
  209. package/bundle/typescript/src/tools/dotenvx/dotenvx.class.js.map +1 -0
  210. package/bundle/typescript/src/tools/ssh/ssh.class.js +1 -1
  211. package/bundle/typescript/src/tools/ssh/ssh.class.js.map +1 -1
  212. package/package.json +2 -2
@@ -1,6 +1,10 @@
1
1
  import blessed from 'blessed';
2
2
  import { capitalCase } from 'change-case';
3
+ import { Category } from './types/shared/item-enum.js';
4
+ import { Item } from './models/item.js';
3
5
  import { Room } from './models/_index.js';
6
+ import { isFence, LIFESTYLE_TIERS } from './utilities/commerce.js';
7
+ import { rollPool, formatRoll } from './utilities/dice.js';
4
8
  import { Direction } from './types/shared/direction-enum.js';
5
9
  import { Logger } from './utilities/logger.js';
6
10
  import { LookCommand } from './commands/look.js';
@@ -47,6 +51,20 @@ import { PanCommand } from './commands/pan.js';
47
51
  import { RebootCommand } from './commands/reboot.js';
48
52
  import { BrandishCommand, HolsterCommand } from './commands/brandish.js';
49
53
  import { AdvanceCommand } from './commands/advance.js';
54
+ import { QualityTradeCommand } from './commands/quality-trade.js';
55
+ import { AskCommand } from './commands/ask.js';
56
+ import { ContactsCommand } from './commands/contacts.js';
57
+ import { JumpCommand } from './commands/jump.js';
58
+ import { InitiateCommand } from './commands/initiate.js';
59
+ import { InstallCommand } from './commands/install.js';
60
+ import { BuyCommand } from './commands/buy.js';
61
+ import { SellCommand } from './commands/sell.js';
62
+ import { PayCommand } from './commands/pay.js';
63
+ import { LifestyleCommand } from './commands/lifestyle.js';
64
+ import { TreatCommand } from './commands/treat.js';
65
+ import { WorkCommand } from './commands/work.js';
66
+ import { EatCommand } from './commands/eat.js';
67
+ import { DrinkCommand } from './commands/drink.js';
50
68
  import { equipIntoEmptySlots } from './archetypes.js';
51
69
  import { NPC } from './models/npc.js';
52
70
  import { HaggleCommand } from './commands/haggle.js';
@@ -99,6 +117,255 @@ export default class Game {
99
117
  get hasHub() {
100
118
  return this._hubScene !== undefined;
101
119
  }
120
+ /** The active job's name (seed name) -- notoriety incidents cite it. */
121
+ get currentJobName() {
122
+ return this.sceneSeed?.name;
123
+ }
124
+ // ==================== CONTACTS (SR5e p.386-389) ====================
125
+ // Earned, never bought: a hub NPC becomes a contact through a real
126
+ // transaction (a sale, a bowl served, stitches, a fenced score), and
127
+ // the relationship deepens through gigs (to Loyalty 6) or patronage
128
+ // (money alone caps at Loyalty 3 -- cred buys familiarity, not
129
+ // friendship). CONNECTION is the contact's reach (role heuristic,
130
+ // same duck-typing school as commerce.isFence); LOYALTY is yours to
131
+ // grow. "ask" (commands/ask.ts) is what the roster is FOR: legwork
132
+ // against the real pending-run seed. One pull per contact per
133
+ // homecoming (asked resets in returnToHub).
134
+ contacts = new Map();
135
+ /** How much juice the name carries, read off the role. */
136
+ static connectionFor(npc) {
137
+ const hay = `${npc.description ?? ''} ${npc.name}`.toLowerCase();
138
+ if (npc.name === FIXER_NAME || /fixer|johnson/.test(hay))
139
+ return 5;
140
+ if (/fence|pawn|launder|smuggl|no questions/.test(hay))
141
+ return 4;
142
+ if (/talismonger|street doc|clinic|surgeon|decker|information/.test(hay))
143
+ return 3;
144
+ if (/bartender|barkeep|vendor|noodle|shop|stall|merchant|dealer|market/.test(hay))
145
+ return 2;
146
+ return 1;
147
+ }
148
+ /**
149
+ * HOME TURF: the hub is where the runner LIVES -- everyone in the
150
+ * district already knows them (player request: "all NPCs there should
151
+ * already know them"). Called at hub capture: the whole meat-plane
152
+ * cast opens at Loyalty 1 ("knows your name"), so legwork works from
153
+ * day one and only run-scene faces are ever strangers. Depth is still
154
+ * earned -- trade, gigs, patronage -- exactly as before. Ice and
155
+ * spirits don't do noodles: non-meat actors stay off the roster.
156
+ */
157
+ seedHomeTurfContacts() {
158
+ for (const actor of this._hubScene?.allActors ?? []) {
159
+ if (actor === this.player || actor.plane !== 'meat')
160
+ continue;
161
+ if (this.contacts.has(actor.name))
162
+ continue;
163
+ this.contacts.set(actor.name, { connection: Game.connectionFor(actor), loyalty: 1, spent: 0, gigs: 0, asked: false });
164
+ }
165
+ Logger.getInstance().write(`Home turf: ${this.contacts.size} contacts seeded from the hub cast.`);
166
+ }
167
+ /**
168
+ * Records a real transaction with a hub NPC: first contact opens the
169
+ * relationship at Loyalty 1 (a safeguard -- the hub cast is pre-seeded
170
+ * by seedHomeTurfContacts); gigs raise it a point each (cap 6);
171
+ * cumulative spend raises it at each full 1,000 nuyen (cap 3).
172
+ * Returns a player-facing line when something changed, else null.
173
+ */
174
+ touchContact(name, kind, spent = 0) {
175
+ if (!this.hasHub)
176
+ return null;
177
+ const npc = this._hubScene?.allActors?.find(a => a.name === name && a !== this.player);
178
+ if (!npc)
179
+ return null;
180
+ let c = this.contacts.get(name);
181
+ if (!c) {
182
+ c = { connection: Game.connectionFor(npc), loyalty: 1, spent: 0, gigs: 0, asked: false };
183
+ this.contacts.set(name, c);
184
+ if (kind === 'trade')
185
+ c.spent += spent;
186
+ if (kind === 'gig')
187
+ c.gigs += 1;
188
+ Logger.getInstance().write(`Contact opened: ${name} (connection ${c.connection}).`);
189
+ return `${name} knows your name now -- a CONTACT (Connection ${c.connection}, Loyalty 1). ("contacts" for the roster, "ask ${name} about ..." for legwork.)`;
190
+ }
191
+ if (kind === 'gig') {
192
+ c.gigs += 1;
193
+ if (c.loyalty < 6) {
194
+ c.loyalty += 1;
195
+ return `${name} remembers who made that hand-off happen. (Loyalty ${c.loyalty}.)`;
196
+ }
197
+ return null;
198
+ }
199
+ const thousandsBefore = Math.floor(c.spent / 1000);
200
+ c.spent += spent;
201
+ if (Math.floor(c.spent / 1000) > thousandsBefore && c.loyalty < 3) {
202
+ c.loyalty += 1;
203
+ return `${name} has started keeping your usual ready. (Loyalty ${c.loyalty} -- patronage tops out at 3; run their errands to go deeper.)`;
204
+ }
205
+ return null;
206
+ }
207
+ // ==================== Downtime economy (hub life) ====================
208
+ // A fence's standing quote (see commands/sell.ts / haggle.ts): one at a
209
+ // time, replaced by any new quote, dead the moment the fence isn't at
210
+ // the counter. Lives here because sell and haggle are separate commands
211
+ // negotiating the same number.
212
+ pendingSale;
213
+ // LIFESTYLE (SR5e p.369, monthly costs scaled to per-job-cycle): the
214
+ // hideout has a tier, upkeep is charged on every homecoming
215
+ // (returnToHub), and the tier buys real mechanics -- rest quality at
216
+ // home (rest.ts) and, higher up, a bonus edge every job. Can't cover
217
+ // the upkeep? You slide DOWN a tier and the shortfall becomes debt --
218
+ // and while the tab stands, rest at the hideout recovers half (rest.ts;
219
+ // "pay" squares it, pay.ts; "lifestyle" views and moves tiers).
220
+ lifestyleTier = 'Squatter';
221
+ rentDebt = 0;
222
+ /** The active lifestyle tier's row (falls back to Squatter). */
223
+ get lifestyle() {
224
+ return LIFESTYLE_TIERS.find(t => t.name === this.lifestyleTier) ?? LIFESTYLE_TIERS[1];
225
+ }
226
+ /**
227
+ * Makes the hideout LOOK like what the rent buys: the room's description
228
+ * follows the active tier (player request -- an upgraded lifestyle read
229
+ * as the same windowless squat). Called wherever the tier moves: the
230
+ * lifestyle command, and the shortfall slide in returnToHub.
231
+ */
232
+ syncHomeToLifestyle() {
233
+ if (this._homeRoom)
234
+ this._homeRoom.description = this.lifestyle.homeDescription;
235
+ }
236
+ // RESTOCK (see returnToHub): consumable shop stock -- food, drink,
237
+ // ammo, patches -- snapshotted at hub capture and restored to any shelf
238
+ // found empty on each homecoming. Durable one-offs are deliberately NOT
239
+ // in the category set: the pawnshop's lockpicks, once sold, stay sold.
240
+ static RESTOCK_CATEGORIES = new Set([
241
+ Category.Food, Category.Drink, Category.Ammunition, Category.StimPatch, Category.Medical, Category.SlapPatch,
242
+ ]);
243
+ _restockSnapshot = [];
244
+ /** Captures every restockable shelf item's blueprint (hub capture time). */
245
+ snapshotVendorStock() {
246
+ this._restockSnapshot = [];
247
+ for (const room of Object.values(this.scene.getRooms())) {
248
+ for (const item of room.inventory.getAllItems()) {
249
+ if (!item.owner || !Game.RESTOCK_CATEGORIES.has(item.category))
250
+ continue;
251
+ this._restockSnapshot.push({
252
+ room: room.name,
253
+ owner: item.owner,
254
+ config: {
255
+ name: item.name, description: item.description, size: item.size,
256
+ category: item.category, shape: item.shape, color: item.color,
257
+ texture: item.texture, rating: item.rating, weight: item.weight,
258
+ details: item.details, price: item.price,
259
+ },
260
+ });
261
+ }
262
+ }
263
+ }
264
+ /** Refills empty shelves from the snapshot; returns how many came back. */
265
+ restockVendorShelves() {
266
+ let restocked = 0;
267
+ const rooms = this.scene.getRooms();
268
+ for (const entry of this._restockSnapshot) {
269
+ const room = Object.values(rooms).find(r => r.name === entry.room);
270
+ if (!room || room.inventory.hasItem(entry.config.name))
271
+ continue;
272
+ const fresh = new Item(entry.config);
273
+ fresh.owner = entry.owner;
274
+ room.inventory.addItem(fresh);
275
+ restocked += 1;
276
+ }
277
+ return restocked;
278
+ }
279
+ /** The fence moves merchandise: anything sold to a fence is sold ON. */
280
+ fenceSellsOn() {
281
+ for (const actor of this.scene.allActors) {
282
+ if (actor === this.player || !isFence(actor, actor.currentLocation))
283
+ continue;
284
+ for (const item of [...actor.inventory.getAllItems()]) {
285
+ if (item.transferable && item.category !== Category.Commlink) {
286
+ actor.inventory.removeItem(item.name);
287
+ }
288
+ }
289
+ }
290
+ }
291
+ // SIDE GIGS (see commands/work.ts / give.ts): one small favor on offer
292
+ // at a time, rotating each homecoming like the rumor. Take it with
293
+ // "work" in front of the giver; deliver the package with "give"; the
294
+ // pay lands on delivery.
295
+ gigAvailable;
296
+ activeGig;
297
+ _gigPool = [];
298
+ _gigIndex = 0;
299
+ /** Rotates the gig on offer (skipped while one is already in hand). */
300
+ advanceGig() {
301
+ if (this._gigPool.length === 0 || this.activeGig)
302
+ return;
303
+ this.gigAvailable = this._gigPool[this._gigIndex % this._gigPool.length];
304
+ this._gigIndex += 1;
305
+ }
306
+ /**
307
+ * WORK FROM A RELATIONSHIP (see commands/ask.ts): a contact puts THEIR
308
+ * errand on the table. Their seeded gigs get first claim -- spliced
309
+ * out of the rotating pool, so work handed to you personally never
310
+ * reappears on the ambient board -- and when the pool holds nothing of
311
+ * theirs, the relationship synthesizes one: a sealed parcel to another
312
+ * hub local, priced off Connection (their reach IS the stakes) with a
313
+ * Loyalty kicker. Loyalty 5+ pays the family rate (+25%). The offer
314
+ * lands as gigAvailable: the existing "work" pickup and "give"
315
+ * delivery handle the rest unchanged.
316
+ */
317
+ offerGigFrom(contactName) {
318
+ const contact = this.contacts.get(contactName);
319
+ if (!contact || !this.hasHub || this.activeGig)
320
+ return null;
321
+ let gig = undefined;
322
+ const seeded = this._gigPool.findIndex(g => g.from === contactName);
323
+ if (seeded >= 0) {
324
+ gig = this._gigPool.splice(seeded, 1)[0];
325
+ }
326
+ else {
327
+ const locals = (this._hubScene?.allActors ?? [])
328
+ .filter(a => a !== this.player && a.name !== contactName && a.plane === 'meat' && !a.isIncapacitated())
329
+ .map(a => a.name);
330
+ if (locals.length === 0)
331
+ return null;
332
+ const to = locals[Math.floor(Math.random() * locals.length)];
333
+ const pay = 60 + contact.connection * 40 + contact.loyalty * 10;
334
+ gig = {
335
+ from: contactName,
336
+ to,
337
+ itemName: `${contactName}'s Sealed Parcel`,
338
+ itemDescription: `A tight-wrapped parcel, taped past curiosity. Whatever it is, ${contactName} trusts you to not ask.`,
339
+ pay,
340
+ brief: `Quiet job, no questions asked: this needs to reach ${to}, today, unopened. ${pay} nuyen on delivery.`,
341
+ };
342
+ }
343
+ const familyRate = contact.loyalty >= 5;
344
+ if (familyRate)
345
+ gig = { ...gig, pay: Math.round(gig.pay * 1.25) };
346
+ this.gigAvailable = gig;
347
+ Logger.getInstance().write(`Contact gig offered by ${contactName}: "${gig.itemName}" -> ${gig.to}, ${gig.pay}${familyRate ? ' (family rate)' : ''}.`);
348
+ return { ...gig, familyRate };
349
+ }
350
+ // FIRST-OF-ITS-KIND bounty (see commands/sell.ts): the fence pays a
351
+ // 50% premium on the first item of each category to cross her counter.
352
+ fencedCategories = new Set();
353
+ // Street rumors: the hub seed ships a pool (scene2.json "rumors"); one
354
+ // is ACTIVE at a time, rotating each time a run wraps. The active rumor
355
+ // is planted in the world-event mesh (every hub NPC's prompt reads it,
356
+ // so Mama Wu can actually gossip) and handed to the next job's skeleton
357
+ // prompt as its backdrop -- the street foreshadows the work.
358
+ _rumorPool = [];
359
+ _rumorIndex = 0;
360
+ activeRumor;
361
+ /** Rotates to the next rumor and plants it where NPCs will hear it. */
362
+ advanceRumor() {
363
+ if (this._rumorPool.length === 0)
364
+ return;
365
+ this.activeRumor = this._rumorPool[this._rumorIndex % this._rumorPool.length];
366
+ this._rumorIndex += 1;
367
+ this._hubScene?.addWorldEvent(`Word around the district: ${this.activeRumor}`);
368
+ }
102
369
  /** The persistent hideout room -- go.ts's walk-home trigger checks it. */
103
370
  get homeRoom() {
104
371
  return this._homeRoom;
@@ -242,6 +509,26 @@ export default class Game {
242
509
  CommandFactory.registerCommand('hangup', EndCallCommand);
243
510
  CommandFactory.registerCommand('advance', AdvanceCommand);
244
511
  CommandFactory.registerCommand('train', AdvanceCommand);
512
+ CommandFactory.registerCommand('qualities', QualityTradeCommand);
513
+ CommandFactory.registerCommand('quality', QualityTradeCommand);
514
+ CommandFactory.registerCommand('ask', AskCommand);
515
+ CommandFactory.registerCommand('contacts', ContactsCommand);
516
+ CommandFactory.registerCommand('jump', JumpCommand);
517
+ CommandFactory.registerCommand('rig', JumpCommand);
518
+ CommandFactory.registerCommand('initiate', InitiateCommand);
519
+ CommandFactory.registerCommand('submerge', InitiateCommand);
520
+ CommandFactory.registerCommand('install', InstallCommand);
521
+ CommandFactory.registerCommand('chrome', InstallCommand);
522
+ CommandFactory.registerCommand('buy', BuyCommand);
523
+ CommandFactory.registerCommand('browse', BuyCommand);
524
+ CommandFactory.registerCommand('sell', SellCommand);
525
+ CommandFactory.registerCommand('pay', PayCommand);
526
+ CommandFactory.registerCommand('eat', EatCommand);
527
+ CommandFactory.registerCommand('drink', DrinkCommand);
528
+ CommandFactory.registerCommand('lifestyle', LifestyleCommand);
529
+ CommandFactory.registerCommand('treat', TreatCommand);
530
+ CommandFactory.registerCommand('work', WorkCommand);
531
+ CommandFactory.registerCommand('gig', WorkCommand);
245
532
  CommandFactory.registerCommand('haggle', HaggleCommand);
246
533
  CommandFactory.registerCommand('negotiate', HaggleCommand);
247
534
  CommandFactory.registerCommand('sneak', SneakCommand);
@@ -307,9 +594,12 @@ export default class Game {
307
594
  this.player.currentLocation = this.determineStartRoom();
308
595
  this.registerCommands();
309
596
  this._screen = screen;
597
+ // The description follows the rent from day one (see
598
+ // syncHomeToLifestyle) -- a fresh session at the default Squatter tier
599
+ // reads exactly as the hideout always has.
310
600
  this._homeRoom = new Room({
311
601
  name: 'Your Hideout',
312
- description: `A cramped, windowless room you can actually call your own -- a cot, a locker, and a door that locks from the inside. Whatever you leave here stays here, no matter where the next job takes you.`,
602
+ description: this.lifestyle.homeDescription,
313
603
  roomType: 'Residence',
314
604
  });
315
605
  this.scene.addRoom(this._homeRoom);
@@ -325,6 +615,17 @@ export default class Game {
325
615
  if (this.scene.isHub) {
326
616
  this._hubScene = this.scene;
327
617
  this._hubName = this.sceneSeed?.name ?? 'home';
618
+ // The street starts talking from minute one.
619
+ this._rumorPool = this.sceneSeed?.rumors ?? [];
620
+ this.advanceRumor();
621
+ // The economy wakes up with it: shelf blueprints for restocking,
622
+ // the first odd job on offer, and the lifestyle's edge perk.
623
+ this.snapshotVendorStock();
624
+ this._gigPool = this.sceneSeed?.gigs ?? [];
625
+ this.advanceGig();
626
+ this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
627
+ // Home turf knows its own (see seedHomeTurfContacts).
628
+ this.seedHomeTurfContacts();
328
629
  }
329
630
  // Gear up automatically: wearables (and the commlink) slot into any
330
631
  // EMPTY slots at load -- no "equip all" ritual to start a session
@@ -394,11 +695,13 @@ export default class Game {
394
695
  * instant feedback, but this stays safe to call directly too).
395
696
  */
396
697
  async continueToNextScene() {
397
- // Hub sessions: a finished RUN doesn't chain into the next draft --
398
- // "continue" takes you home instead, and the next run is commissioned
399
- // from there (call the fixer). Classic sessions skip this and keep
400
- // the old continue->draft loop.
401
- if (this._hubScene && this.scene !== this._hubScene && this.scene.isCompleted()) {
698
+ // Hub sessions: a RUN doesn't chain into the next draft -- going
699
+ // home is going home, and the next run is commissioned from there
700
+ // (call the fixer). Deliberately NOT gated on completion: the taxi
701
+ // will carry a runner away from a job gone sideways (see call.ts) --
702
+ // walking away just means the payout never landed. Classic sessions
703
+ // skip this and keep the old continue->draft loop.
704
+ if (this._hubScene && this.scene !== this._hubScene) {
402
705
  return this.returnToHub();
403
706
  }
404
707
  const gate = this.canDraftNextScene();
@@ -425,6 +728,9 @@ export default class Game {
425
728
  const hub = this._hubScene;
426
729
  if (!hub)
427
730
  return `There's no home to go back to.`;
731
+ // Read the run's fate BEFORE the scene swap below discards it -- a
732
+ // walked-away job gets a different homecoming line than a wrapped one.
733
+ const wrappedRun = this.scene !== hub && this.scene.isCompleted();
428
734
  // Settle cross-plane and combat state that must not leak into the hub
429
735
  // (the run's rooms -- including a bodyRoom the player left behind --
430
736
  // are about to be orphaned wholesale).
@@ -433,6 +739,7 @@ export default class Game {
433
739
  this.player.simMode = undefined;
434
740
  this.player.astralTicks = 0;
435
741
  this.player.activeDeck = undefined;
742
+ this.player.riggedDrone = undefined;
436
743
  this.player.astralPerceiving = false;
437
744
  this.player.sneaking = false;
438
745
  this.player.inExchange = false;
@@ -444,12 +751,60 @@ export default class Game {
444
751
  this.connectHomeRoom(hub.determineStartRoom());
445
752
  this.player.currentLocation = this._homeRoom;
446
753
  this.player.cameFrom = undefined;
754
+ this.pendingSale = undefined;
755
+ // Contacts have had time to work their networks again -- one legwork
756
+ // pull each per homecoming (see commands/ask.ts).
757
+ for (const contact of this.contacts.values())
758
+ contact.asked = false;
759
+ // LIFESTYLE upkeep comes due with the homecoming: this tier's share
760
+ // plus any standing debt, paid from carried cred as far as it goes.
761
+ // Coming up short SLIDES YOU DOWN a tier (the district notices) and
762
+ // the shortfall becomes the tab -- rest.ts halves recovery while you
763
+ // owe; "pay" squares it; "lifestyle" climbs back up.
764
+ const tier = this.lifestyle;
765
+ const due = tier.upkeep + this.rentDebt;
766
+ const paid = Math.min(this.player.currency, due);
767
+ if (paid > 0)
768
+ this.player.adjustCurrency(-paid);
769
+ this.rentDebt = due - paid;
770
+ let rentLine;
771
+ if (this.rentDebt > 0) {
772
+ const tierIndex = LIFESTYLE_TIERS.findIndex(t => t.name === tier.name);
773
+ if (tierIndex > 0) {
774
+ this.lifestyleTier = LIFESTYLE_TIERS[tierIndex - 1].name;
775
+ // The downgrade is visible the moment you look around.
776
+ this.syncHomeToLifestyle();
777
+ 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. ("pay" the tab, "lifestyle" to climb back.)`;
778
+ }
779
+ else {
780
+ rentLine = `Even the doorway costs something: ${this.rentDebt} nuyen on the tab. ("pay" when you're flush.)`;
781
+ }
782
+ }
783
+ else {
784
+ rentLine = `The landlord's drone takes its ${paid} nuyen (${tier.name} upkeep) at the door and buzzes off satisfied.`;
785
+ }
786
+ this.player.lifestyleEdgeBonus = this.lifestyle.edgeBonus;
787
+ // A High lifestyle erases the run: hot shower, real bed, full reset.
788
+ let comfortLine = '';
789
+ if (this.lifestyle.clearStunOnReturn && this.player.stunTaken > 0) {
790
+ const cleared = this.player.healStun(this.player.stunTaken);
791
+ comfortLine = `\nThe shower runs hot until the run is a rumor -- ${cleared} stun gone before your head hits the pillow.`;
792
+ }
793
+ // Fresh streets, fresh gossip, restocked shelves, new work.
794
+ this.advanceRumor();
795
+ this.fenceSellsOn();
796
+ const restocked = this.restockVendorShelves();
797
+ const restockLine = restocked > 0 ? `\nThe district has restocked around you -- the wok steams, the bar's shelf is full, the market stalls are loud again.` : '';
798
+ this.advanceGig();
447
799
  this.updateEquipment(this.player.equipment);
448
800
  this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
449
801
  this.updateExits(this.player.currentLocation);
450
802
  this.updateStatus();
451
- Logger.getInstance().write(`Returned to hub "${this._hubName}".`);
452
- return `\n=== ${this._hubName.toUpperCase()} ===\nThe run's wrapped and the street swallows you back up. You're home -- your hideout, one door off ${hub.determineStartRoom().name}. Call ${FIXER_NAME} when you're hungry for the next job.`;
803
+ Logger.getInstance().write(`Returned to hub "${this._hubName}". Lifestyle ${this.lifestyleTier}: ${paid} paid, ${this.rentDebt} owed. Restocked ${restocked}. Rumor: ${this.activeRumor ?? '(none)'}`);
804
+ const fateLine = wrappedRun
805
+ ? `The run's wrapped and the street swallows you back up.`
806
+ : `The run's behind you -- unfinished, unpaid, already someone else's problem. The street doesn't ask.`;
807
+ return `\n=== ${this._hubName.toUpperCase()} ===\n${fateLine} You're home -- your hideout, one door off ${hub.determineStartRoom().name}. Call ${FIXER_NAME} when you're hungry for the next job.\n${rentLine}${comfortLine}${restockLine}`;
453
808
  }
454
809
  /**
455
810
  * The actual drafting work, split out from continueToNextScene() purely
@@ -481,7 +836,7 @@ export default class Game {
481
836
  //
482
837
  // The player object rides along so the job is drafted FOR this runner
483
838
  // -- their archetype, stats, and toolkit (see buildPlayerDossier).
484
- const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName, this.player);
839
+ const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName, this.player, this.activeRumor);
485
840
  if (!nextSeed) {
486
841
  return `Incoming call -- ${FIXER_NAME}: "Job fell through, chummer. Client got cold feet before the ink dried. Give me another call in a bit -- I'll shake something loose."`;
487
842
  }
@@ -519,14 +874,21 @@ export default class Game {
519
874
  this.scene = await SceneSynthesizer.synthesizeFromJson(seed, this.player, false, false);
520
875
  this.scene.initialize(this);
521
876
  this.player.currentLocation = this.determineStartRoom();
522
- // New job, fresh luck (see commands/edge.ts).
523
- this.player.refreshEdge();
524
- // The home base is owned by Game, not any one Scene -- re-inject it and
525
- // rewire its one exit to point at this job's start room.
526
- this.scene.addRoom(this._homeRoom);
527
- this.connectHomeRoom(this.player.currentLocation);
528
- if (opts.startInHideout) {
529
- this.player.currentLocation = this._homeRoom;
877
+ // New job -- and the needs come due at the door (see
878
+ // applyDepartureNeeds): edge only refills fed-and-slept, and ignored
879
+ // needs land as locked fatigue. Lines ride the JOB banner below.
880
+ const needsLines = this.applyDepartureNeeds();
881
+ // The home base is owned by Game, not any one Scene. HUB sessions
882
+ // leave it OUT of run scenes (player request): your cot is across
883
+ // town, not stapled to the jobsite -- the taxi ("call taxi", fare on
884
+ // the tab if you're broke) is the road home. Classic sessions keep
885
+ // the hideout adjacent as always: it's the only home they have.
886
+ if (!this.hasHub) {
887
+ this.scene.addRoom(this._homeRoom);
888
+ this.connectHomeRoom(this.player.currentLocation);
889
+ if (opts.startInHideout) {
890
+ this.player.currentLocation = this._homeRoom;
891
+ }
530
892
  }
531
893
  this.updateEquipment(this.player.equipment);
532
894
  this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
@@ -535,7 +897,67 @@ export default class Game {
535
897
  // Fresh streets, no trail yet (see Player.cameFrom / the HUD crumb).
536
898
  this.player.cameFrom = undefined;
537
899
  this.warmNpcAppearancesInBackground();
538
- return `\n=== JOB #${this._tier}: ${seed.name} ===\n${this.scene.story}`;
900
+ return `\n=== JOB #${this._tier}: ${seed.name} ===\n${this.scene.story}${needsLines.length > 0 ? `\n${needsLines.join('\n')}` : ''}`;
901
+ }
902
+ /**
903
+ * The needs check at every job departure (SR5e p.56 / p.172, scaled to
904
+ * job-cycle "days"). Fed AND slept since the last departure: edge
905
+ * refills and you head out sharp. Skip either: edge stays at whatever's
906
+ * left. Keep skipping: escalating fatigue (1S, 2S, 3S... per cycle),
907
+ * resisted Body + Willpower, landing as LOCKED stun no recovery touches
908
+ * until the need is met (Player.healStun / markFed / markSlept). A
909
+ * Sleep Regulator (augmentations.ts) halves the effective sleep debt.
910
+ * Fatigue is capped one box short of a knockout -- RAW would drop you,
911
+ * but collapsing INSIDE the departure transition would strand the
912
+ * session between scenes; "running on fumes" carries the spirit.
913
+ */
914
+ applyDepartureNeeds() {
915
+ const p = this.player;
916
+ const fed = p.ateThisCycle;
917
+ const slept = p.sleptThisCycle;
918
+ const lines = [];
919
+ p.hungerStreak = fed ? 0 : p.hungerStreak + 1;
920
+ p.sleepStreak = slept ? 0 : p.sleepStreak + 1;
921
+ p.refreshEdge(fed && slept);
922
+ if (fed && slept) {
923
+ lines.push(`Fed and slept -- you head out SHARP. (Edge ${p.edgeRemaining}/${p.edge + p.lifestyleEdgeBonus}.)`);
924
+ }
925
+ else {
926
+ const missing = !fed && !slept ? 'meal and no sleep' : !fed ? 'meal' : 'sleep';
927
+ lines.push(`No ${missing} since the last job -- your edge doesn't come back (${p.edgeRemaining} left). A hot meal and a night in your own bed reset it.`);
928
+ }
929
+ // Escalating, resisted, LOCKED fatigue per ignored need.
930
+ const events = [];
931
+ if (p.hungerStreak > 0) {
932
+ events.push({ kind: 'hunger', label: 'Hunger', dmg: p.hungerStreak });
933
+ }
934
+ const effectiveSleepDebt = p.hasAug('sleep-regulator') ? Math.floor(p.sleepStreak / 2) : p.sleepStreak;
935
+ if (effectiveSleepDebt > 0) {
936
+ events.push({ kind: 'sleep', label: 'Sleep deprivation', dmg: effectiveSleepDebt });
937
+ }
938
+ for (const event of events) {
939
+ const resist = rollPool(Math.max(1, p.body + p.willpower));
940
+ const taken = Math.max(0, event.dmg - resist.hits);
941
+ const headroom = Math.max(0, p.maxStunBoxes - p.stunTaken - 1);
942
+ const applied = Math.min(taken, headroom);
943
+ if (applied > 0) {
944
+ p.takeStun(applied);
945
+ p.addLockedFatigue(event.kind, applied);
946
+ lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- ${applied} stun, and it will NOT heal until you ${event.kind === 'hunger' ? 'eat' : 'sleep'}: ${p.stunSummary()}`);
947
+ }
948
+ else if (taken > 0) {
949
+ lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- you're running on fumes; there's barely anything left to take.`);
950
+ }
951
+ else {
952
+ lines.push(`${event.label} (${event.dmg}S): resist ${formatRoll(resist)} -- you shake it off. For now.`);
953
+ }
954
+ Logger.getInstance().write(`Departure needs: ${event.label} ${event.dmg}S, resisted ${resist.hits}, applied ${applied} (locked).`);
955
+ }
956
+ // The new cycle starts unfed and unslept -- Wu's counter and your own
957
+ // cot are how these flip back (use.ts eat / rest.ts at a Residence).
958
+ p.ateThisCycle = false;
959
+ p.sleptThisCycle = false;
960
+ return lines;
539
961
  }
540
962
  /**
541
963
  * "travel": takes the arranged transport to the pending job (see
@@ -588,8 +1010,13 @@ export default class Game {
588
1010
  border: { type: 'line' },
589
1011
  style: { fg: 'white', border: { fg: 'cyan' } },
590
1012
  keys: true, // Enable keyboard input
591
- mouse: true, // Enable mouse input
592
- inputOnFocus: true, // Focus the text input when clicked,
1013
+ // NO mouse -- deliberately. Any widget with mouse:true makes
1014
+ // blessed arm xterm mouse tracking (?1000h/?1002h/?1003h/?1005h),
1015
+ // and three real sessions ended with the whole process tree dying
1016
+ // hard (no JS cleanup possible) and the shell flooded by raw mouse
1017
+ // reports on every cursor move. The input box is auto-focused and
1018
+ // keyboard-driven; click-to-focus bought nothing worth that risk.
1019
+ inputOnFocus: true,
593
1020
  vi: true
594
1021
  });
595
1022
  this._inputBox = inputBox;
@@ -639,11 +1066,13 @@ export default class Game {
639
1066
  }
640
1067
  },
641
1068
  keys: true, // Enable keyboard input
642
- mouse: true, // Enable mouse input
1069
+ // NO mouse (see initInputBox): never arm mouse tracking. The log
1070
+ // auto-scrolls (alwaysScroll) and keys/vi still scroll it when
1071
+ // focused -- wheel-scroll wasn't worth shells full of coordinate
1072
+ // bytes every time a session died hard.
643
1073
  tags: true // Intercept tags
644
1074
  });
645
1075
  this._logBox = logBox;
646
- logBox.removeAllListeners('mousemove');
647
1076
  screen.append(logBox);
648
1077
  }
649
1078
  /**
@@ -776,6 +1205,15 @@ export default class Game {
776
1205
  else if (player.plane === 'astral') {
777
1206
  lines.push(`{magenta-fg}✧ ASTRAL -- ${player.astralTicks} move${player.astralTicks === 1 ? '' : 's'} out -- body at ${player.bodyRoom?.name ?? '???'}{/magenta-fg}`);
778
1207
  }
1208
+ else if (player.plane === 'drone') {
1209
+ // Jumped-in: the HULL is the track that matters (attacks land there,
1210
+ // half bleeding through the link), so it lives on the HUD alongside
1211
+ // where the empty body was left.
1212
+ const drone = player.riggedDrone;
1213
+ const hull = drone ? ` Hull ${drone.droneSummary()}` : '';
1214
+ const mount = player.droneArmed ? ' ⌖ armed' : ' (recon -- no mounts)';
1215
+ lines.push(`{yellow-fg}✈ JUMPED IN: ${drone?.name ?? 'drone'} ${(player.simMode ?? 'cold').toUpperCase()}-SIM${mount}${hull} -- body at ${player.bodyRoom?.name ?? '???'}{/yellow-fg}`);
1216
+ }
779
1217
  this._statusBox.setContent(lines.join('\n'));
780
1218
  this._screen.render();
781
1219
  }
@@ -878,10 +1316,11 @@ export default class Game {
878
1316
  { title: 'gear', entries: [['inv', 'inventory'], ['equipment', 'eq'], ['take'], ['drop'], ['give'], ['store'], ['equip'], ['unequip'], ['brandish', 'draw'], ['holster', 'stow'], ['reload'], ['use'], ['read']] },
879
1317
  { title: 'combat', entries: [['attack', 'kill'], ['subdue'], ['stance'], ['surrender'], ['edge'], ['rest']] },
880
1318
  { title: 'magic', entries: [['spells'], ['cast'], ['project'], ['return'], ['assense']] },
881
- { title: 'matrix', entries: [['jack'], ['hack'], ['pan'], ['silent'], ['reboot'], ['drone']] },
882
- { title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['haggle', 'negotiate'], ['persuade', 'convince']] },
1319
+ { title: 'matrix', entries: [['jack'], ['hack'], ['pan'], ['silent'], ['reboot'], ['drone'], ['jump', 'rig']] },
1320
+ { title: 'social', entries: [['say'], ['talk-to'], ['call'], ['end-call', 'end', 'hangup'], ['haggle', 'negotiate'], ['persuade', 'convince'], ['ask'], ['contacts']] },
1321
+ { title: 'street', entries: [['browse', 'buy'], ['sell'], ['work', 'gig'], ['treat'], ['eat'], ['drink'], ['lifestyle'], ['pay']] },
883
1322
  { title: 'barriers', entries: [['solve'], ['pick'], ['bluff'], ['unlock'], ['lock']] },
884
- { title: 'character', entries: [['sheet', 'stats'], ['advance', 'train']] },
1323
+ { title: 'character', entries: [['sheet', 'stats'], ['advance', 'train'], ['qualities', 'quality'], ['initiate', 'submerge'], ['install', 'chrome']] },
885
1324
  { title: 'session', entries: [['help'], ['clear'], ['quit', 'exit']] },
886
1325
  ];
887
1326
  /** Descriptions for the verbs handled inline in handleInput() rather than registered. */