@maka/maka-cli 5.3.17 → 5.4.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 (218) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +43 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +131 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +14 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +79 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +224 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +149 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +378 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +60 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +40 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +43 -0
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +11 -0
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +115 -4
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +176 -0
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +39 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
  58. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +35 -4
  59. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +9 -0
  67. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +18 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
  70. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
  71. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
  72. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
  73. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
  74. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
  82. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
  83. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +23 -2
  84. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
  85. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +20 -3
  86. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  87. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
  88. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +49 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
  90. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
  91. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
  94. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
  95. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
  96. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +65 -0
  97. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
  98. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
  101. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
  102. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
  103. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
  104. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
  108. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +80 -1
  109. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  110. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
  111. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  112. package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
  113. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +101 -1
  114. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  115. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
  116. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
  117. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
  118. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
  119. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
  120. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
  121. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +2 -0
  122. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
  123. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
  124. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +138 -21
  125. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  126. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +119 -0
  127. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +816 -0
  128. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
  129. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +70 -9
  130. package/bundle/typescript/src/commands/game/sideQuest/game.js +403 -164
  131. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  132. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
  133. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
  134. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
  135. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
  136. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
  137. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
  138. package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
  139. package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
  140. package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
  141. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
  142. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
  143. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
  144. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
  145. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
  146. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  147. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +25 -0
  148. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +220 -47
  149. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  150. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +213 -0
  151. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +525 -9
  152. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  153. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
  154. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +174 -6
  155. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
  156. package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +20 -0
  157. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +39 -18
  158. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  159. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +69 -0
  160. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +175 -2
  161. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  162. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +80 -1
  163. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +7 -2
  164. package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
  165. package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
  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/npc-seed.d.ts +1 -0
  168. package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +34 -0
  169. package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
  170. package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +3 -0
  171. package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +5 -0
  172. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
  173. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
  174. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
  175. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
  176. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
  177. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
  178. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
  179. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
  180. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
  181. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
  182. package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -5
  183. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  184. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
  185. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
  186. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
  187. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +143 -0
  188. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +328 -0
  189. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
  190. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
  191. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
  192. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
  193. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
  194. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
  195. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
  196. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
  197. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
  198. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
  199. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +44 -0
  200. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +193 -0
  201. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
  202. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
  203. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
  204. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
  205. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +54 -7
  206. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  207. package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
  208. package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
  209. package/bundle/typescript/src/tools/ai/ai.class.d.ts +35 -0
  210. package/bundle/typescript/src/tools/ai/ai.class.js +112 -0
  211. package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
  212. package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
  213. package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
  214. package/bundle/typescript/src/tools/https/https.class.d.ts +2 -1
  215. package/bundle/typescript/src/tools/https/https.class.js +43 -0
  216. package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
  217. package/bundle/typescript/src/tools/https/https.interface.d.ts +8 -0
  218. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  import blessed from 'blessed';
2
2
  import { capitalCase } from 'change-case';
3
- import { Door } from './models/_index.js';
3
+ import { Room } from './models/_index.js';
4
+ import { Direction } from './types/shared/direction-enum.js';
5
+ import { Logger } from './utilities/logger.js';
4
6
  import { LookCommand } from './commands/look.js';
5
7
  import { TakeCommand } from './commands/take.js';
6
8
  import { GoCommand } from './commands/go.js';
@@ -18,20 +20,72 @@ import { StoreCommand } from './commands/store.js';
18
20
  import { GiveCommand } from './commands/give.js';
19
21
  import { ReadCommand } from './commands/read.js';
20
22
  import { KillCommand } from './commands/kill.js';
23
+ import { AttackCommand } from './commands/attack.js';
24
+ import { SpellsCommand } from './commands/spells.js';
25
+ import { InventoryCommand } from './commands/inv.js';
26
+ import { EquipmentCommand } from './commands/equipment.js';
27
+ import { MapCommand } from './commands/map.js';
28
+ import { SilentCommand } from './commands/silent.js';
29
+ import { RestCommand } from './commands/rest.js';
30
+ import { ReloadCommand } from './commands/reload.js';
31
+ import { SubdueCommand } from './commands/subdue.js';
32
+ import { EdgeCommand } from './commands/edge.js';
33
+ import { SheetCommand } from './commands/sheet.js';
34
+ import { CallCommand } from './commands/call.js';
35
+ import { EndCallCommand } from './commands/end-call.js';
36
+ import { PickCommand } from './commands/pick.js';
37
+ import { HackCommand } from './commands/hack.js';
38
+ import { CastCommand } from './commands/cast.js';
39
+ import { StanceCommand } from './commands/stance.js';
40
+ import { SurrenderCommand } from './commands/surrender.js';
41
+ import { JackCommand } from './commands/jack.js';
42
+ import { ProjectCommand, ReturnCommand } from './commands/project.js';
43
+ import { AssenseCommand } from './commands/assense.js';
44
+ import { DroneCommand } from './commands/drone.js';
45
+ import { PanCommand } from './commands/pan.js';
46
+ import { RebootCommand } from './commands/reboot.js';
47
+ import { AutoFight } from './utilities/auto-fight.js';
21
48
  import { CommandFactory } from './factories/command-factory.js';
22
49
  import { SceneSynthesizer } from './factories/scene-factory.js';
50
+ import { SceneSeedGenerator, FIXER_NAME } from './factories/scene-seed-generator.js';
51
+ import { CALL_ICON, CALL_COLOR } from './utilities/comm-style.js';
23
52
  export default class Game {
24
53
  initialized;
25
54
  static instance;
26
55
  sceneSeed;
27
56
  scene;
28
57
  player;
58
+ // The fight autopilot (Tab / "stance" command) -- created once, reads
59
+ // live game state at fire time so it survives scene transitions.
60
+ autoFight = new AutoFight(this);
61
+ // Just three surfaces: a full-width log, a HUD strip, and the input box.
62
+ // The old always-on Inventory/Equipment/Map sidebar boxes became the
63
+ // on-demand inv/equipment/map commands (see commands/inv.ts et al.) to
64
+ // give the log the whole screen.
29
65
  _screen;
30
- _exitsBox;
31
- _inventoryBox;
32
66
  _logBox;
67
+ _statusBox;
33
68
  _inputBox;
34
- _equipmentBox;
69
+ _useAi = false;
70
+ _logFilePath = '';
71
+ _tier = 1;
72
+ // Guards against two overlapping continueToNextScene() calls -- found via
73
+ // a real session where calling the fixer twice in a row (nothing stopped
74
+ // it, and a 20-40+ second draft invites a second, impatient call) fired
75
+ // two independent drafts at once. Both read the same starting _tier
76
+ // before either finished, both generated a "tier 2" job, and whichever
77
+ // one finished last silently overwrote the first one's scene, room, and
78
+ // player-location state out from under whatever the player was already
79
+ // doing in it.
80
+ _draftInProgress = false;
81
+ // A single fixed room, created once and never torn down across job
82
+ // transitions -- the player's own place, unlike every job's generated
83
+ // rooms which get discarded wholesale when the next job replaces them.
84
+ _homeRoom;
85
+ _homeRoomExitDirection;
86
+ static HOME_DIRECTION_PRIORITY = [
87
+ Direction.DOWN, Direction.UP, Direction.SOUTH, Direction.NORTH, Direction.EAST, Direction.WEST,
88
+ ];
35
89
  constructor(player, sceneSeed) {
36
90
  this.player = player;
37
91
  this.sceneSeed = sceneSeed;
@@ -55,10 +109,65 @@ export default class Game {
55
109
  CommandFactory.registerCommand('give', GiveCommand);
56
110
  CommandFactory.registerCommand('read', ReadCommand);
57
111
  CommandFactory.registerCommand('kill', KillCommand);
112
+ CommandFactory.registerCommand('attack', AttackCommand);
113
+ CommandFactory.registerCommand('spells', SpellsCommand);
114
+ CommandFactory.registerCommand('inv', InventoryCommand);
115
+ CommandFactory.registerCommand('inventory', InventoryCommand);
116
+ CommandFactory.registerCommand('equipment', EquipmentCommand);
117
+ CommandFactory.registerCommand('eq', EquipmentCommand);
118
+ CommandFactory.registerCommand('map', MapCommand);
119
+ CommandFactory.registerCommand('exits', MapCommand);
120
+ CommandFactory.registerCommand('silent', SilentCommand);
121
+ CommandFactory.registerCommand('rest', RestCommand);
122
+ CommandFactory.registerCommand('reload', ReloadCommand);
123
+ CommandFactory.registerCommand('subdue', SubdueCommand);
124
+ CommandFactory.registerCommand('edge', EdgeCommand);
125
+ CommandFactory.registerCommand('sheet', SheetCommand);
126
+ CommandFactory.registerCommand('stats', SheetCommand);
127
+ CommandFactory.registerCommand('call', CallCommand);
128
+ CommandFactory.registerCommand('end-call', EndCallCommand);
129
+ CommandFactory.registerCommand('pick', PickCommand);
130
+ CommandFactory.registerCommand('hack', HackCommand);
131
+ CommandFactory.registerCommand('cast', CastCommand);
132
+ CommandFactory.registerCommand('stance', StanceCommand);
133
+ CommandFactory.registerCommand('surrender', SurrenderCommand);
134
+ CommandFactory.registerCommand('jack', JackCommand);
135
+ CommandFactory.registerCommand('project', ProjectCommand);
136
+ CommandFactory.registerCommand('return', ReturnCommand);
137
+ CommandFactory.registerCommand('assense', AssenseCommand);
138
+ CommandFactory.registerCommand('drone', DroneCommand);
139
+ CommandFactory.registerCommand('pan', PanCommand);
140
+ CommandFactory.registerCommand('reboot', RebootCommand);
58
141
  }
59
142
  determineStartRoom() {
60
143
  return this.scene.determineStartRoom();
61
144
  }
145
+ /**
146
+ * Wires a fresh, always-unlocked path between the persistent home base
147
+ * and wherever the player is starting the current job from. Clears the
148
+ * previous job's connection first (if any) so the home base never keeps
149
+ * a stale door into an abandoned scene's now-orphaned rooms -- only ever
150
+ * one way out at a time, pointing at whichever job is actually current.
151
+ */
152
+ connectHomeRoom(jobStartRoom) {
153
+ if (this._homeRoomExitDirection) {
154
+ this._homeRoom.exits.delete(this._homeRoomExitDirection);
155
+ }
156
+ const direction = Game.HOME_DIRECTION_PRIORITY.find(d => !jobStartRoom.exits.has(d));
157
+ if (!direction) {
158
+ // Every direction is already spoken for on this job's start room --
159
+ // rare (most generated start rooms use 1-2 exits), but leave the
160
+ // player without a home-base link this job rather than clobbering
161
+ // one of the job's own exits.
162
+ this._homeRoomExitDirection = undefined;
163
+ return;
164
+ }
165
+ jobStartRoom.addExit(direction, this._homeRoom);
166
+ // Tracked from the HOME room's own side (the opposite of `direction`,
167
+ // which is the job room's side) -- that's the key actually stored on
168
+ // this._homeRoom.exits, and what gets cleared above next time around.
169
+ this._homeRoomExitDirection = jobStartRoom.getOppositeDirection(direction);
170
+ }
62
171
  get logBox() {
63
172
  return this._logBox;
64
173
  }
@@ -66,11 +175,11 @@ export default class Game {
66
175
  return this._inputBox;
67
176
  }
68
177
  async init(options, screen) {
69
- this.initMap(screen);
178
+ this._useAi = options.useAi;
179
+ this._logFilePath = options.logFilePath;
70
180
  this.initInputBox(screen);
71
181
  this.initLogBox(screen);
72
- this.initEquipmentBox(screen);
73
- this.initInventoryBox(screen);
182
+ this.initStatusBox(screen);
74
183
  // Create a loading box
75
184
  const loadingBox = blessed.box({
76
185
  parent: screen,
@@ -101,8 +210,139 @@ export default class Game {
101
210
  this.player.currentLocation = this.determineStartRoom();
102
211
  this.registerCommands();
103
212
  this._screen = screen;
213
+ this._homeRoom = new Room({
214
+ name: 'Your Hideout',
215
+ 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.`,
216
+ roomType: 'Residence',
217
+ });
218
+ this.scene.addRoom(this._homeRoom);
219
+ this.connectHomeRoom(this.player.currentLocation);
104
220
  this.initialized = true;
105
- return `Welcome, ${this.player.name}\n ${this.scene.story}`;
221
+ // Nudge the player toward "call" up front -- the fixer convention means
222
+ // the story deliberately doesn't narrate a completed briefing, so
223
+ // without this hint there'd be no in-game indication that "call" is how
224
+ // you actually get your orders.
225
+ const wc = this.scene.getWinCondition();
226
+ const callHint = (wc?.type === 'give' && wc.toNpc)
227
+ ? `\n\nYour commlink is quiet for now, but ${wc.toNpc} is expecting to hear from you -- try "call ${wc.toNpc}" when you're ready to check in.`
228
+ : '';
229
+ return `Welcome, ${this.player.name}\n ${this.scene.story}${callHint}`;
230
+ }
231
+ /**
232
+ * Synchronous readiness check for continueToNextScene() -- lets callers
233
+ * give instant feedback for the "not ready yet" cases (job not done, no
234
+ * AI configured) without waiting on anything async.
235
+ */
236
+ canDraftNextScene() {
237
+ if (this._draftInProgress) {
238
+ return { allowed: false, message: `Already drafting the next job -- give it a moment, he'll call back.` };
239
+ }
240
+ if (!this.scene.isCompleted()) {
241
+ return { allowed: false, message: `This job isn't finished yet.` };
242
+ }
243
+ if (!this._useAi) {
244
+ return { allowed: false, message: `Continuing needs an AI-generated job, which requires an AI provider to be configured (OPENAI_API_KEY or ANTHROPIC_API_KEY).` };
245
+ }
246
+ return { allowed: true };
247
+ }
248
+ /**
249
+ * Fires continueToNextScene() without waiting on it -- job generation can
250
+ * take 20-40+ seconds, and neither "continue" nor calling the fixer
251
+ * should freeze the rest of the session behind that. Self-announces the
252
+ * result once it resolves, styled the same as any other comms exchange,
253
+ * since by design nothing else is holding a reference to display it.
254
+ * Public: called directly by call.ts when the player calls the fixer
255
+ * after a job's done, not just from "continue" here in game.ts.
256
+ */
257
+ draftNextSceneInBackground() {
258
+ void this.continueToNextScene()
259
+ .then(nextJobText => {
260
+ if (nextJobText) {
261
+ Logger.getInstance().logWithColor(`${CALL_ICON} ${nextJobText}`, CALL_COLOR);
262
+ }
263
+ })
264
+ .catch(err => {
265
+ Logger.getInstance().error(`Failed to draft the next job: ${err}`);
266
+ });
267
+ }
268
+ /**
269
+ * Drafts and loads the next job once the current scene's win condition
270
+ * has been met. Reuses the same long-lived Player (and its inventory),
271
+ * so gear compounds across the session -- nothing is persisted to disk,
272
+ * it just carries forward for the life of this process. Re-checks
273
+ * canDraftNextScene() itself (callers are expected to check it first for
274
+ * instant feedback, but this stays safe to call directly too).
275
+ */
276
+ async continueToNextScene() {
277
+ const gate = this.canDraftNextScene();
278
+ if (!gate.allowed) {
279
+ return gate.message ?? `This job isn't finished yet.`;
280
+ }
281
+ this._draftInProgress = true;
282
+ try {
283
+ return await this.draftNextScene();
284
+ }
285
+ finally {
286
+ this._draftInProgress = false;
287
+ }
288
+ }
289
+ /**
290
+ * The actual drafting work, split out from continueToNextScene() purely
291
+ * so that method can wrap it in the try/finally that clears
292
+ * _draftInProgress -- every return path below (including the early
293
+ * "couldn't draft" one) needs that cleanup to run.
294
+ */
295
+ async draftNextScene() {
296
+ const nextTier = this._tier + 1;
297
+ // Carry the current fixer's identity forward -- without this, each
298
+ // "continue" generates a scene with zero knowledge of the last one, so
299
+ // the AI just invents a brand new job-giver every time. Looked up by
300
+ // identity (is there an actor actually named FIXER_NAME?), not via the
301
+ // win condition's toNpc -- that's often a one-off contact the fixer
302
+ // pointed the runner toward, not the fixer himself (see the "broker,
303
+ // not always the destination" convention).
304
+ const fixerStillInScene = this.scene.allActors.some(a => a.name === FIXER_NAME);
305
+ const previousFixerName = fixerStillInScene ? FIXER_NAME : undefined;
306
+ // Append, don't replace -- this used to be setContent(), which wiped
307
+ // the whole log box (including the JOB COMPLETE banner and everything
308
+ // before it) for the entire generation wait, which can run 20-40+
309
+ // seconds for a tiered scene with AI flavor enabled. Framed as the
310
+ // fixer's own line when he's actually in this scene, rather than a
311
+ // bare system message -- he's the one drumming up the next gig.
312
+ const waitingLine = fixerStillInScene
313
+ ? `\n${CALL_ICON} ${FIXER_NAME}: "I'll call you back when I've got something lined up." (drafting the next job...)`
314
+ : '\nDrafting the next job... please wait.';
315
+ this.logBox.pushLine(waitingLine);
316
+ this.logBox.scrollTo(this.logBox.getScrollHeight());
317
+ this._screen.render();
318
+ const nextSeed = await SceneSeedGenerator.generate(this._logFilePath, nextTier, previousFixerName);
319
+ if (!nextSeed) {
320
+ return `Couldn't draft a new job this time. Type "continue" to try again, or "quit" to stop here.`;
321
+ }
322
+ this._tier = nextTier;
323
+ this.sceneSeed = nextSeed;
324
+ // A call partner from the old scene wouldn't exist in the new one --
325
+ // hang up before moving on, rather than leave a dangling reference.
326
+ if (this.player.activeCallPartner) {
327
+ this.player.activeCallPartner = undefined;
328
+ }
329
+ // false: this is a continuation, not a fresh game -- the player already
330
+ // has gear from prior jobs, so don't dump another starter kit on top.
331
+ this.scene = await SceneSynthesizer.synthesizeFromJson(nextSeed, this.player, this._useAi, false);
332
+ this.scene.initialize(this);
333
+ this.player.currentLocation = this.determineStartRoom();
334
+ // New job, fresh luck (see commands/edge.ts).
335
+ this.player.refreshEdge();
336
+ // The home base is owned by Game, not any one Scene -- re-inject it and
337
+ // rewire its one exit to point at this job's start room instead of the
338
+ // just-abandoned one.
339
+ this.scene.addRoom(this._homeRoom);
340
+ this.connectHomeRoom(this.player.currentLocation);
341
+ this.updateEquipment(this.player.equipment);
342
+ this.updateInventory(this.player.inventory, capitalCase(this.scene.getCurrencyType()));
343
+ this.updateExits(this.player.currentLocation);
344
+ this.updateStatus();
345
+ return `\n=== JOB #${this._tier}: ${nextSeed.name} ===\n${this.scene.story}`;
106
346
  }
107
347
  async close() {
108
348
  this._inputBox.removeAllListeners(); // removes all event listeners
@@ -113,42 +353,13 @@ export default class Game {
113
353
  process.exit(2);
114
354
  });
115
355
  }
116
- initMap(screen) {
117
- // Map
118
- const exitsBox = blessed.box({
119
- top: '70%',
120
- left: '75%',
121
- width: '25%',
122
- height: '20%',
123
- border: {
124
- type: 'line'
125
- },
126
- label: ' Map ',
127
- content: 'Available exits will be shown here...',
128
- tags: true,
129
- scrollable: true,
130
- scrollbar: {
131
- ch: ' ',
132
- track: {
133
- bg: 'yellow'
134
- },
135
- style: {
136
- inverse: true
137
- }
138
- },
139
- keys: true,
140
- mouse: true,
141
- });
142
- screen.append(exitsBox);
143
- this._exitsBox = exitsBox;
144
- }
145
356
  initInputBox(screen) {
146
357
  // Create an input box for user commands
147
358
  const inputBox = blessed.textbox({
148
359
  bottom: 0,
149
360
  left: 0,
150
361
  width: '100%',
151
- height: '10%',
362
+ height: 3,
152
363
  tags: true,
153
364
  border: { type: 'line' },
154
365
  style: { fg: 'white', border: { fg: 'cyan' } },
@@ -161,8 +372,21 @@ export default class Game {
161
372
  screen.append(inputBox);
162
373
  // Key bindings
163
374
  screen.key(['escape', 'C-c'], () => process.exit(0));
164
- // Focus on the input box by default
165
- inputBox.focus();
375
+ // Focus on the input box by default -- but ONLY if it isn't already
376
+ // focused. screen.append() auto-focuses the first keyable element
377
+ // (Node.insert), and since the sidebar boxes were removed this textbox
378
+ // IS the first one, so it arrives here already focused with its input
379
+ // reader mid-setup. Blessed's _focus() has no same-element guard:
380
+ // focusing an already-focused textbox fires a self-blur that cancels
381
+ // that reader BEFORE its keypress listener finishes attaching (the
382
+ // attach rides a delayed tick), so the cancel removes nothing, a
383
+ // second reader starts, both listeners land -- and every keystroke
384
+ // doubles, from startup ("exit" arrived as "eexxiitt" in a real
385
+ // session). Previously the Map box was appended first and soaked up
386
+ // the auto-focus, which is the only reason this never fired before.
387
+ if (screen.focused !== inputBox) {
388
+ inputBox.focus();
389
+ }
166
390
  // Render the screen
167
391
  screen.render();
168
392
  }
@@ -172,8 +396,9 @@ export default class Game {
172
396
  id: 'logBox',
173
397
  top: '0%',
174
398
  left: '0%',
175
- width: '75%',
176
- height: '90%',
399
+ width: '100%',
400
+ // Fixed-row footer: 8 rows of HUD (status box) + 3 rows of input.
401
+ height: '100%-11',
177
402
  border: {
178
403
  type: 'line'
179
404
  },
@@ -197,139 +422,138 @@ export default class Game {
197
422
  logBox.removeAllListeners('mousemove');
198
423
  screen.append(logBox);
199
424
  }
200
- initEquipmentBox(screen) {
201
- const equipmentBox = blessed.box({
202
- top: '45%',
203
- left: '75%',
204
- width: '25%',
205
- height: '25%',
425
+ /**
426
+ * The HUD: a fixed strip between the log and the input box for the
427
+ * important real-time state -- condition tracks, armed/link status,
428
+ * sustained spells, active call. Anything that changes moment to moment
429
+ * and shouldn't need a command to check lives here; everything slower
430
+ * (full inventory, slot-by-slot equipment, the map) is an on-demand
431
+ * command instead. Future contexts (astral projection, VR hot-sim) get
432
+ * their own line here the same way the call line did: small explicit
433
+ * pieces, not a status framework built ahead of need.
434
+ */
435
+ initStatusBox(screen) {
436
+ const statusBox = blessed.box({
437
+ id: 'statusBox',
438
+ bottom: 3, // sits directly on the input box
439
+ left: '0%',
440
+ width: '100%',
441
+ height: 8, // 6 content rows inside the border: location, tracks,
442
+ // readiness, and up to two transient lines (sustained, call)
206
443
  border: {
207
444
  type: 'line'
208
445
  },
209
- label: ' Equipment ',
210
- content: 'Your equipped items will be listed here...',
211
446
  tags: true,
212
- scrollable: true,
213
- scrollbar: {
214
- ch: ' ',
215
- track: {
216
- bg: 'yellow'
217
- },
218
- style: {
219
- inverse: true
220
- }
221
- },
222
- keys: true,
223
- mouse: true,
224
- });
225
- this._equipmentBox = equipmentBox;
226
- // Append the equipment box to the screen
227
- screen.append(equipmentBox);
228
- }
229
- initInventoryBox(screen) {
230
- const inventoryBox = blessed.box({
231
- top: '0%',
232
- left: '75%',
233
- width: '25%',
234
- height: '45%',
235
- border: {
236
- type: 'line'
237
- },
238
- label: ' Inventory ',
239
- content: 'Your items will be listed here...',
240
- tags: true, // Enable formatting tags in content
241
- scrollable: true, // Enable scrolling if the content overflows
242
- scrollbar: {
243
- ch: ' ',
244
- track: {
245
- bg: 'yellow'
246
- },
247
- style: {
248
- inverse: true
249
- }
250
- },
251
- keys: true,
252
- mouse: true,
447
+ content: '',
253
448
  });
254
- // Append the inventory box to the screen
255
- screen.append(inventoryBox);
256
- this._inventoryBox = inventoryBox;
449
+ this._statusBox = statusBox;
450
+ screen.append(statusBox);
257
451
  }
258
- updateEquipment(playerEquipment) {
259
- const sections = [];
260
- const renderSlotGroup = (label, group) => {
261
- if (!group) {
262
- sections.push(`{bold}${label}:{/bold} None`);
263
- return;
264
- }
265
- const equipped = Object.entries(group)
266
- .filter(([_, item]) => item !== null)
267
- .map(([slot, item]) => ` ${slot}: ${item.name}`);
268
- if (equipped.length > 0) {
269
- sections.push(`{bold}${label}:{/bold}`);
270
- sections.push(...equipped);
271
- }
272
- else {
273
- sections.push(`{bold}${label}:{/bold} None`);
274
- }
275
- };
276
- renderSlotGroup("Head", playerEquipment.head);
277
- renderSlotGroup("Torso", playerEquipment.torso);
278
- renderSlotGroup("Legs", playerEquipment.legs);
279
- renderSlotGroup("Feet", playerEquipment.feet);
280
- renderSlotGroup("Arms", playerEquipment.arms);
281
- renderSlotGroup("Gloves", playerEquipment.gloves);
282
- renderSlotGroup("Left Hand", playerEquipment.leftHand);
283
- renderSlotGroup("Right Hand", playerEquipment.rightHand);
284
- this._equipmentBox.setContent(sections.join('\n'));
285
- this._screen.render();
286
- }
287
- ;
288
- updateExits(room) {
289
- let description = `Currently in: ${room.name}\n\n`;
290
- description += `----- Exits ------\n`;
291
- if (room.exits.size > 0) {
292
- room.exits.forEach((exit, key) => {
293
- if (exit instanceof Door) {
294
- const nextRoom = exit.getOtherSideToName(room);
295
- description += `${capitalCase(key)}: ${nextRoom}`;
296
- if (exit.checkIfLocked()) {
297
- description += " (Locked)";
298
- }
299
- else if (exit.checkIfLockable()) {
300
- description += ' (Unlocked)';
301
- }
302
- description += '\n';
303
- }
304
- else {
305
- description += `${capitalCase(key)}\n`; // Example: "NORTH"
306
- }
307
- });
452
+ /**
453
+ * Refreshes the status box from current player state. Called after any
454
+ * command that can change what belongs there (currently "call"/"end-call")
455
+ * and once on load/job transition.
456
+ */
457
+ updateStatus() {
458
+ // updateInventory/updateEquipment route here now, and those hooks can
459
+ // fire from synthesis paths before the blessed UI exists.
460
+ if (!this._statusBox)
461
+ return;
462
+ const player = this.player;
463
+ const lines = [];
464
+ // Line 1: where you are, and the fight autopilot mode. The stance
465
+ // lives HERE -- the shortest HUD line -- because appended to the long
466
+ // readiness line it wrapped out of the box on narrower terminals and
467
+ // effectively only ever showed in the Tab announcement. Always shown,
468
+ // even MANUAL: the HUD is how players discover Tab cycles it at all
469
+ // (see utilities/auto-fight.ts). MANUAL is white, not gray -- blessed's
470
+ // gray is ANSI "bright black", which on a stock dark terminal palette
471
+ // is illegible enough that the stance looked absent entirely.
472
+ const stanceColor = player.autoStance === 'aggressive' ? 'red' : player.autoStance === 'guarded' ? 'yellow' : 'white';
473
+ const stance = `{${stanceColor}-fg}⚔ ${player.autoStance.toUpperCase()}{/${stanceColor}-fg}`;
474
+ if (player.currentLocation) {
475
+ lines.push(`{bold}📍 ${player.currentLocation.name}{/bold} | ${stance}`);
308
476
  }
309
477
  else {
310
- description += '\nThere are no exits here.';
478
+ lines.push(stance);
311
479
  }
312
- this._exitsBox.setContent(description);
313
- this._screen.render();
314
- }
315
- // Function to update the inventory
316
- updateInventory(playerInventory, currencyName = 'currency') {
317
- const items = playerInventory.getAllItems();
318
- let currencyTotal = 0;
319
- const itemLines = [];
320
- for (const item of items) {
321
- let line = item.name;
322
- if (item.isCurrencyCarrier?.() && item.currencyAmount > 0) {
323
- line += ` [${item.currencyAmount}]`;
324
- currencyTotal += item.currencyAmount;
325
- }
326
- itemLines.push(line);
480
+ // Line 2: both condition monitors -- in a game where a fight can kill
481
+ // you and casting can knock you out, current damage on either track is
482
+ // never something a player should have to ask for. Colors shift as
483
+ // each track fills; the stacked wound modifier from both tracks shows
484
+ // once.
485
+ const trackColor = (taken, max) => taken === 0 ? 'green' : taken >= max - 3 ? 'red' : 'yellow';
486
+ const wound = player.woundModifier;
487
+ const woundNote = wound < 0 ? ` {red-fg}(${wound} dice){/red-fg}` : '';
488
+ const physColor = trackColor(player.damageTaken, player.maxConditionBoxes);
489
+ const stunColor = trackColor(player.stunTaken, player.maxStunBoxes);
490
+ lines.push(`{${physColor}-fg}Physical ${player.conditionSummary()}{/${physColor}-fg} ` +
491
+ `{${stunColor}-fg}Stun ${player.stunSummary()}{/${stunColor}-fg}${woundNote}`);
492
+ // Line 3: readiness -- armed state, armor, and whether the commlink is
493
+ // actually live (equipped), the recurring "why can't I call anyone"
494
+ // friction surfaced at a glance.
495
+ const weapon = player.getWeaponProfile();
496
+ const weaponItem = player.equipment.rightHand?.weapon ?? player.equipment.leftHand?.weapon;
497
+ const ammoNote = weaponItem?.isFirearm()
498
+ ? (weaponItem.jammed
499
+ ? ` {red-fg}[JAMMED -- reboot]{/red-fg}`
500
+ : weaponItem.ammo === 0 ? ` {red-fg}[DRY]{/red-fg}` : ` [${weaponItem.ammo} rds]`)
501
+ : '';
502
+ const armed = weapon.name === 'bare fists'
503
+ ? `{gray-fg}Unarmed{/gray-fg}`
504
+ : `Armed: ${weapon.name} (DV ${weapon.damage}${weapon.poolBonus > 0 ? `, +${weapon.poolBonus} dice` : ''})${ammoNote}`;
505
+ const armorNote = `Armor +${player.getArmorValue()}`;
506
+ const hasLink = player.equipment.head?.commlink != null;
507
+ const link = !hasLink
508
+ ? `{gray-fg}Link offline{/gray-fg}`
509
+ : player.runningSilent
510
+ ? `{magenta-fg}Link SILENT{/magenta-fg}`
511
+ : player.arOffline
512
+ ? `{red-fg}AR CUT -- reboot{/red-fg}`
513
+ : `{${CALL_COLOR}-fg}Link online{/${CALL_COLOR}-fg}`;
514
+ lines.push(`${armed} | ${armorNote} | ${link}`);
515
+ // Lines 4+: transient states, present only while active. Future
516
+ // contexts (astral projection, VR hot-sim) join here the same way.
517
+ if (player.sustainedArmor > 0) {
518
+ lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (-2 dice to everything else){/magenta-fg}`);
519
+ }
520
+ if (player.activeCallPartner) {
521
+ lines.push(`{${CALL_COLOR}-fg}${CALL_ICON} On a call with ${player.activeCallPartner.name}{/${CALL_COLOR}-fg}`);
522
+ }
523
+ // Off-plane context: which reality you're in, your persona's Matrix
524
+ // track (cold-sim damage lands there), and where your empty body lies.
525
+ if (player.plane === 'matrix') {
526
+ // Matrix damage is hardware damage: show the DECK's track. A
527
+ // technomancer has no deck -- their stun track (already on line 2)
528
+ // is what the ice chews on.
529
+ const track = player.activeDeck
530
+ ? ` Deck ${player.activeDeck.deckSummary()}${player.simMode === 'hot' ? ' +biofeedback' : ''}`
531
+ : ` (living persona -- damage is REAL stun)`;
532
+ lines.push(`{cyan-fg}▣ MATRIX ${(player.simMode ?? 'cold').toUpperCase()}-SIM${track} -- body at ${player.bodyRoom?.name ?? '???'}{/cyan-fg}`);
533
+ }
534
+ else if (player.plane === 'astral') {
535
+ lines.push(`{magenta-fg}✧ ASTRAL -- ${player.astralTicks} move${player.astralTicks === 1 ? '' : 's'} out -- body at ${player.bodyRoom?.name ?? '???'}{/magenta-fg}`);
327
536
  }
328
- itemLines.unshift(`{bold}Total ${currencyName}:{/bold} ${currencyTotal}\n`);
329
- this._inventoryBox.setContent(itemLines.join('\n'));
537
+ this._statusBox.setContent(lines.join('\n'));
330
538
  this._screen.render();
331
539
  }
332
- ;
540
+ // The three methods below were the render paths for the old always-on
541
+ // sidebar boxes. Those boxes are gone (the log owns the whole screen; see
542
+ // commands/inv.ts, equipment.ts, map.ts for the on-demand replacements),
543
+ // but the methods stay as no-ops: dozens of call sites across commands,
544
+ // scene proxies, and ui.ts still ping them after inventory/equipment/exit
545
+ // changes, and keeping the hooks alive means a future HUD element can tap
546
+ // those same signals without re-plumbing. Each refreshes the HUD, since
547
+ // several HUD fields (armed, armor, link) derive from equipment state.
548
+ updateEquipment(_playerEquipment) {
549
+ this.updateStatus();
550
+ }
551
+ updateExits(_room) {
552
+ // Nothing to render -- the "map" command reads live room state.
553
+ }
554
+ updateInventory(_playerInventory, _currencyName = 'currency') {
555
+ this.updateStatus();
556
+ }
333
557
  // Public method to get the instance of the Game class
334
558
  static async getInstance(player, sceneSeed) {
335
559
  if (!Game.instance) {
@@ -341,12 +565,24 @@ export default class Game {
341
565
  const parts = input.trim().split(' ');
342
566
  const verb = parts[0].toLowerCase();
343
567
  const args = parts.slice(1);
344
- if (verb === 'quit') {
568
+ if (verb === 'quit' || verb === 'exit') {
345
569
  return null;
346
570
  }
347
571
  if (verb === 'maka') {
348
572
  return 'You are still in the game, type "quit" to exit';
349
573
  }
574
+ if (verb === 'continue') {
575
+ // Instant feedback for "not ready yet" (job not done, no AI
576
+ // configured); otherwise kicks off drafting in the background rather
577
+ // than freezing this command (and everything after it) behind a
578
+ // 20-40+ second generation call -- same as calling the fixer does.
579
+ const gate = this.canDraftNextScene();
580
+ if (!gate.allowed) {
581
+ return gate.message ?? `This job isn't finished yet.`;
582
+ }
583
+ this.draftNextSceneInBackground();
584
+ return `Understood -- drafting the next job now. Feel free to keep exploring in the meantime.`;
585
+ }
350
586
  if (verb === 'help') {
351
587
  return this.getHelpMessage();
352
588
  }
@@ -357,6 +593,8 @@ export default class Game {
357
593
  actor: this.player,
358
594
  rooms: this.scene.getRooms(),
359
595
  scene: this.scene,
596
+ screen: this._screen,
597
+ game: this,
360
598
  };
361
599
  const command = CommandFactory.createCommand(verb, context);
362
600
  if (command) {
@@ -375,6 +613,7 @@ export default class Game {
375
613
  }
376
614
  }
377
615
  helpText += `- clear: Clear the screen.\n`;
616
+ helpText += `- continue: Take on a new, tougher job once you've completed the current one.\n`;
378
617
  helpText += `- quit: Exit the game.\n`;
379
618
  return helpText;
380
619
  }