@maka/maka-cli 5.3.17 → 5.5.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 (245) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +62 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +196 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +16 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +65 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +16 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +122 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +243 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +16 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +39 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.d.ts +21 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +82 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js.map +1 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +219 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +397 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +72 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +3 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +59 -2
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +50 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +13 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +147 -4
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +199 -0
  59. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
  60. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.d.ts +15 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +75 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +43 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
  67. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
  70. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
  71. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +47 -4
  73. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  74. package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.d.ts +18 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +100 -0
  82. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -0
  83. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +10 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +33 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
  86. package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
  87. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
  88. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
  90. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
  91. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
  94. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
  96. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
  97. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
  98. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +31 -2
  101. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
  102. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +71 -7
  103. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  104. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +58 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
  108. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
  109. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
  110. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.d.ts +15 -0
  111. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +35 -0
  112. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js.map +1 -0
  113. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
  114. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
  115. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
  116. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +86 -0
  117. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
  118. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
  119. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
  120. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
  121. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
  122. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
  123. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
  124. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
  125. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
  126. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
  127. package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
  128. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +138 -2
  129. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  130. package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +8 -0
  131. package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
  132. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +6 -3
  133. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js.map +1 -1
  134. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
  135. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  136. package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
  137. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +102 -1
  138. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  139. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
  140. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
  141. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
  142. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
  143. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
  144. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
  145. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +3 -0
  146. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
  147. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +175 -0
  148. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +673 -0
  149. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -0
  150. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
  151. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +139 -21
  152. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  153. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +174 -0
  154. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +647 -0
  155. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
  156. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +138 -9
  157. package/bundle/typescript/src/commands/game/sideQuest/game.js +757 -197
  158. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  159. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
  160. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
  161. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
  162. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
  163. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
  164. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
  165. package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
  166. package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
  167. package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
  168. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
  169. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
  170. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
  171. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
  172. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
  173. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  174. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +27 -0
  175. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +312 -50
  176. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  177. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +278 -0
  178. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +661 -9
  179. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  180. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
  181. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +199 -6
  182. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
  183. package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +24 -0
  184. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +53 -18
  185. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  186. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +73 -0
  187. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +208 -3
  188. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  189. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +319 -70
  190. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +661 -227
  191. package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
  192. package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
  193. package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
  194. package/bundle/typescript/src/commands/game/sideQuest/types/seed/npc-seed.d.ts +1 -0
  195. package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +42 -0
  196. package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
  197. package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +4 -0
  198. package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +8 -0
  199. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
  200. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
  201. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
  202. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
  203. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
  204. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
  205. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
  206. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
  207. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
  208. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
  209. package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -6
  210. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  211. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
  212. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
  213. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
  214. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +145 -0
  215. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +354 -0
  216. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
  217. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
  218. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
  219. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
  220. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
  221. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
  222. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
  223. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
  224. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
  225. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
  226. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +77 -0
  227. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +265 -0
  228. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
  229. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
  230. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
  231. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
  232. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +78 -7
  233. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  234. package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
  235. package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
  236. package/bundle/typescript/src/tools/ai/ai.class.d.ts +42 -0
  237. package/bundle/typescript/src/tools/ai/ai.class.js +117 -0
  238. package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
  239. package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
  240. package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
  241. package/bundle/typescript/src/tools/https/https.class.d.ts +3 -2
  242. package/bundle/typescript/src/tools/https/https.class.js +51 -2
  243. package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
  244. package/bundle/typescript/src/tools/https/https.interface.d.ts +10 -0
  245. package/package.json +1 -1
@@ -0,0 +1,673 @@
1
+ import { SKILL_NAMES } from '../models/player.js';
2
+ /**
3
+ * CHUNKED scene generation (see SceneSeedGenerator.generate for the
4
+ * pipeline that drives this module). One monolithic whole-scene completion
5
+ * kept hitting the response token ceiling as scenes grew -- a real session
6
+ * lost four of six tier-2 attempts to JSON truncated mid-string -- and a
7
+ * single validation miss threw the entire scene away. So generation now
8
+ * runs in passes:
9
+ *
10
+ * 1. SKELETON: the whole scene's STRUCTURE -- rooms with exits/locks/
11
+ * nodes, the NPC roster with locations, puzzle placements, every
12
+ * item's name/role/location, and the win condition. Compact (one-line
13
+ * concepts instead of prose), so every cross-reference the validators
14
+ * check lives in one small completion.
15
+ * 2. DETAIL CHUNKS, each its own small completion against the fixed
16
+ * skeleton: room prose; NPC voices/stats; puzzle riddles and
17
+ * passphrases; item descriptions (clue items get their puzzle's exact
18
+ * phrase baked into "details" -- which is why items runs AFTER
19
+ * puzzles).
20
+ * 3. Assembly + the same validators as ever; a failure regenerates ONLY
21
+ * the chunk responsible.
22
+ *
23
+ * No single completion carries the whole scene, so scene scale is no
24
+ * longer bounded by the provider's output budget.
25
+ */
26
+ // The fixer's name is a fixed Shadowrun-lore convention, not something
27
+ // generation ever varies (in-universe slang for an anonymous employer).
28
+ export const FIXER_NAME = 'Mr. Johnson';
29
+ // =========================== Player dossier ==============================
30
+ /**
31
+ * A compact character sheet injected into the skeleton prompt so a job is
32
+ * built FOR the runner actually playing it (archetype, stats, toolkit).
33
+ * Returns '' when no player is available.
34
+ */
35
+ export function buildPlayerDossier(player) {
36
+ if (!player)
37
+ return '';
38
+ const a = player;
39
+ const attrs = `body ${a.body}, agility ${a.agility}, reaction ${a.reaction}, strength ${a.strength}, ` +
40
+ `willpower ${a.willpower}, logic ${a.logic}, intuition ${a.intuition}, charisma ${a.charisma}` +
41
+ (a.magic > 0 ? `, magic ${a.magic}${a.adept ? ' (adept)' : ''}` : '') +
42
+ (a.resonance > 0 ? `, resonance ${a.resonance}` : '');
43
+ const topSkills = SKILL_NAMES
44
+ .filter(s => a.skillRating(s) > 0)
45
+ .sort((x, y) => a.skillRating(y) - a.skillRating(x))
46
+ .slice(0, 6)
47
+ .map(s => `${s} ${a.skillRating(s)}`)
48
+ .join(', ');
49
+ const carried = a.getCarriedWeapon();
50
+ const holsteredBonus = carried && !a.weaponDrawn ? carried.qualityBonus : 0;
51
+ const attack = a.getAttackPool() + holsteredBonus;
52
+ const defense = a.getDefensePool();
53
+ const deck = a.getAnyCyberdeck();
54
+ const caps = [];
55
+ if (a.adept) {
56
+ caps.push('Physical ADEPT: magic fuels their meat combat; NO spellcasting, NO astral projection -- spell formulas, grimoires, and foci are all useless to them');
57
+ }
58
+ else if (a.isAwakened()) {
59
+ caps.push('Awakened MAGE: casts manabolt/heal/armor innately, can astrally project ("project") and assense');
60
+ }
61
+ if (a.isTechnomancer()) {
62
+ caps.push('TECHNOMANCER: jacks into the Matrix with no deck (living persona)');
63
+ }
64
+ if (deck) {
65
+ caps.push(`Carries a cyberdeck (${deck.name}${deck.isBricked ? ', currently BRICKED' : ''}): can jack in, hack nodes, fight ice, lift matrix paydata, and crack PANs`);
66
+ }
67
+ if (a.inventory.getAllItems().some(i => String(i.category).toLowerCase() === 'drone')) {
68
+ caps.push('Has a surveillance drone: can scout all adjacent rooms at will');
69
+ }
70
+ if (a.charisma >= 5) {
71
+ caps.push(`A talker (charisma ${a.charisma}): social leverage and negotiation are their strength`);
72
+ }
73
+ if (a.logic >= 5 && !deck && !a.isTechnomancer()) {
74
+ caps.push(`Sharp technical mind (logic ${a.logic})`);
75
+ }
76
+ if (caps.length === 0) {
77
+ caps.push('No magic, no resonance, no deck, no drone -- a mundane who solves problems with gear, grit, and gunfire');
78
+ }
79
+ const gearBits = [];
80
+ if (carried)
81
+ gearBits.push(`weapon: ${carried.name}`);
82
+ const armor = a.getArmorValue();
83
+ if (armor > 0)
84
+ gearBits.push(`armor +${armor} soak`);
85
+ return `
86
+ THE RUNNER (build this job FOR this specific character):
87
+ - Name: ${a.name}${a.archetypeName ? ` -- archetype: ${a.archetypeName}` : ''}
88
+ - Attributes: ${attrs}
89
+ ${topSkills ? `- Top skills: ${topSkills}` : ''}
90
+ - Combat calibration: attack pool ~${attack}d6, defense pool ~${defense}d6${gearBits.length > 0 ? ` (${gearBits.join(', ')})` : ''}
91
+ ${caps.map(c => `- ${c}`).join('\n')}
92
+
93
+ Tailoring rules:
94
+ - Include at least one route, subplot, or payoff that rewards THIS runner's toolkit (matrix
95
+ content and hasNode rooms for a decker/technomancer; astral angles or Awakened opposition
96
+ for a mage; melee-shaped trouble for an adept; social leverage and "con"-bypass barriers
97
+ for a talker; recon value for a drone owner). Pick what fits this character and this
98
+ story -- don't bolt on every plane at once.
99
+ - Never gate FINISHING the run behind a capability they lack: no matrix-plane winCondition
100
+ item unless they can already jack in (deck or resonance) or a findable Cyberdeck is placed
101
+ in a reachable room; no ward/"spell"-bypass-only route for someone who can't cast.
102
+ - Don't hand them loot they can't use as a puzzle rewardItem or the winCondition rewardItem
103
+ (spellcasting gear for an adept, a second cyberdeck or drone for someone who already has
104
+ one). Useless-to-them gear may still exist as ordinary world dressing or valuables.
105
+ - The runner ALREADY OWNS everything the dossier lists -- never duplicate any of it (their
106
+ deck, drone, weapon, focus, commlink) in the "starting"-role items. Round those out with
107
+ complementary basics instead: ammunition, a medkit, armor, tools -- or fewer items.
108
+ - Scale opposition to the calibration pools above: rank-and-file threats should roll a few
109
+ dice below the runner's pools, and a centerpiece enforcer can match or slightly exceed
110
+ them. Don't send soft civilians at a combat monster, or elite security at a runner built
111
+ for talking their way through.
112
+ `.trim();
113
+ }
114
+ // ============================ Prompt builders ============================
115
+ const JSON_ONLY = `Output ONLY raw JSON -- no markdown fences, no commentary, no text before or after it.`;
116
+ function repairNote(previousError) {
117
+ return previousError
118
+ ? `\n\nYour previous attempt failed with this error -- fix it specifically and try again: ${previousError}`
119
+ : '';
120
+ }
121
+ export function buildSkeletonPrompt(tier, previousFixerName, player, previousError) {
122
+ // Tier 1 is the only fresh start (the launch menu's "Generate a new
123
+ // Run"); every Johnson-commissioned draft is tier 2+ and the runner
124
+ // arrives fully equipped -- authoring "starting" items there was pure
125
+ // waste, since synthesis skips granting them on continuations anyway.
126
+ const isContinuation = tier > 1;
127
+ // NPC counts EXCLUDE the fixer: he's overhead (unreachable, no gameplay).
128
+ const scope = tier <= 1
129
+ ? '3 to 4 rooms, exactly 1 NPC besides the fixer, and exactly 1 puzzle'
130
+ : tier === 2
131
+ ? '4 to 6 rooms, 2 NPCs besides the fixer, and 1 to 2 puzzles'
132
+ : '6 to 8 rooms, 3 to 4 NPCs besides the fixer, and 2 to 3 puzzles';
133
+ const continuity = previousFixerName
134
+ ? `\nThis is a CONTINUATION of an ongoing relationship with the same fixer, "${previousFixerName}" --
135
+ who commissioned the runner's last job and is lining up this one too. Reuse "${previousFixerName}"
136
+ as this scene's fixer (same voice/personality); do not reintroduce them as a stranger.`
137
+ : '';
138
+ const dossier = buildPlayerDossier(player);
139
+ return `
140
+ You are generating the STRUCTURE (skeleton) of a self-contained Shadowrun-flavored text
141
+ adventure "run". This is job #${tier} in an ongoing campaign -- scale stakes accordingly.
142
+ Prose comes later in separate passes: here, every "concept" is ONE tight sentence of intent,
143
+ and everything else is structure. Get the STRUCTURE exactly right -- every name is a
144
+ cross-reference other passes and the game engine will rely on verbatim.
145
+ ${continuity}
146
+ ${dossier ? `\n${dossier}\n` : ''}
147
+ ${JSON_ONLY}
148
+
149
+ {
150
+ "name": string, // the run's title
151
+ "story": string, // 1-2 SHORT teaser sentences shown before play -- name the shape of the
152
+ // trouble, never the objective, target names, locations, or passphrases
153
+ // (those are learned by calling the fixer).
154
+ "completionMessage": string, // 1-2 sentences shown when the job completes
155
+ "rooms": [{
156
+ "name": string, "concept": string, "roomType"?: string,
157
+ "isStartRoom"?: boolean, // EXACTLY ONE room
158
+ "reachable"?: boolean, // false ONLY for the fixer's remote room -- see FIXER below
159
+ "owner"?: string, // an NPC's name, if it's their room
160
+ "hasNode"?: boolean, // a live Matrix host: hackable from inside the Matrix; releases the
161
+ // room's maglocks AND unseals its matrix-plane files (the host is
162
+ // the vault). 1-2 rooms in corp/tech locations only. Ice NPCs and
163
+ // matrix-plane items belong ONLY in hasNode rooms, and any hasNode
164
+ // room holding matrix items MUST have an ice NPC stationed in it.
165
+ "nodeAccess"?: "wireless"|"wired", // "wired" = AIR-GAPPED: only hackable by jacking in
166
+ // physically inside the room. Use for at most one, most-sensitive
167
+ // host; never make its meat-side route impossible.
168
+ "warded"?: boolean, // astral barrier: projecting mages can't enter. Use sparingly.
169
+ "exits": [{ "direction": "north"|"east"|"south"|"west"|"up"|"down", "targetRoom": string,
170
+ "keyRequired"?: string, // an items[].name with role "key"
171
+ "puzzle"?: string }] // a puzzles[].name guarding this door. A door puzzle
172
+ // lives ON THE DOOR ONLY: that puzzle must have NO
173
+ // "room" field of its own (setting both is the most
174
+ // common structural mistake -- don't).
175
+ // Doors are TWO-WAY automatically: declare each connection from ONE
176
+ // side only (with its keyRequired/puzzle on that one declaration) --
177
+ // never declare the reverse direction too.
178
+ }],
179
+ "npcs": [{
180
+ "name": string, "concept": string, "startLocation": string,
181
+ "plane"?: "meat"|"matrix"|"astral" // "matrix" = ICE guarding a hasNode room;
182
+ // "astral" = a spirit. Default meat.
183
+ }],
184
+ "puzzles": [{
185
+ "name": string, "concept": string, // what the barrier physically IS (a maglock, a
186
+ // keypad, a scanner, a doorman, a ward)
187
+ "room"?: string, // room-anchored placement -- OMIT when an exits[].puzzle references it
188
+ "bypassType"?: "lockpick"|"hack"|"spell"|"con", // the matching risky route: literal
189
+ // lock -> "lockpick"; terminal/scanner -> "hack"; ward -> "spell";
190
+ // a PERSON/voice-gate that could be talked past -> "con". Omit if
191
+ // no such route. EXACTLY these lowercase strings.
192
+ "requiresPuzzle"?: string, // another puzzle solved FIRST (no cycles)
193
+ "rewardItem"?: string // an items[].name with role "reward" -- REQUIRED for room-anchored
194
+ // puzzles (solving it is how the player gets that item); omit for
195
+ // door puzzles (the open door is the reward)
196
+ }],
197
+ "items": [{
198
+ "name": string,
199
+ "role": "winCondition"|"reward"|"key"|"clue"|"loot"|"starting",
200
+ "category": string, // one of: Weapon, MeleeWeapon, RangedWeapon, Armor, BodyArmor,
201
+ // ArmorClothing, Helmet, Hat, LegArmor, ArmArmor, Gloves, Commlink,
202
+ // Cyberdeck, Drone, Credstick, CurrencyContainer, Key, Trinket,
203
+ // Document, Datachip, Vice, Book, Tool, Clothing, Glasses, Boots,
204
+ // Medical, StimPatch, SpellFormula, Grimoire, Focus, Ammunition
205
+ "room"?: string, "heldBy"?: string, // exactly one of these for roles winCondition/key/
206
+ // clue/loot; NEITHER for roles reward/starting
207
+ "plane"?: "meat"|"matrix", // "matrix" = paydata: only in a hasNode room, category
208
+ // Datachip/Document. A matrix-plane winCondition item makes
209
+ // the job a datarun (its room MUST have ice).
210
+ "forPuzzle"?: string // role "clue" only: which puzzle's passphrase this clue reveals.
211
+ // EVERY puzzle needs exactly one clue item, category "Book",
212
+ // placed in a DIFFERENT room than that puzzle (door puzzles: any
213
+ // room). The clue's text is written in a later pass.
214
+ // ${isContinuation ? 'Role "starting" is FORBIDDEN in this scene -- the runner arrives fully equipped (see dossier).' : 'Role "starting" items are the fresh runner\'s basic kit.'}
215
+ }],
216
+ "winCondition": {
217
+ "item": string, // the items[].name with role "winCondition" -- MUST have room or heldBy
218
+ // set (never heldBy "toNpc" -- the player has to find it), and it is
219
+ // the central plot object of "story"
220
+ "toNpc": string, // an npcs[].name -- who wants it delivered
221
+ "payout": number, // the job's pay in nuyen; the engine actually transfers this and the
222
+ // fixer quotes it. ~2000-6000 for job #1, roughly half again more per
223
+ // job after that
224
+ "rewardItem"?: string // optional bonus: an items[].name with role "reward"
225
+ }
226
+ }
227
+
228
+ FIXER: the job-giver is named EXACTLY "${FIXER_NAME}" (in-universe slang for an anonymous
229
+ employer -- never invent another name for the role). Give him his own room marked
230
+ "reachable": false with NO exits to reachable rooms; "call" is the only way to reach him.
231
+ He MUST hold a Commlink-category item (role "loot", heldBy him), and the player must have a
232
+ commlink available (${isContinuation ? 'the runner already carries their own -- see the dossier' : '"starting" role, or findable in a reachable room'}). He does NOT have to
233
+ be winCondition.toNpc -- a different on-the-ground contact is often the better story; any
234
+ such contact in an unreachable room needs a commlink of their own too.
235
+
236
+ Hard structural rules (validated mechanically -- a violation is rejected):
237
+ - Every exits[].targetRoom, npcs[].startLocation, items[].room name a real rooms[].name;
238
+ every items[].heldBy names a real npcs[].name; every exits[].puzzle names a real
239
+ puzzles[].name; every keyRequired names a real items[].name with role "key".
240
+ - Exactly one room has isStartRoom true. Never lock the start room's only exits behind keys
241
+ placed beyond them: every key must be reachable WITHOUT passing through the door it opens.
242
+ - A "reachable": false room never connects to a reachable one, and nothing the player must
243
+ retrieve (winCondition item, keys, clues) is ever placed in one.
244
+ - Each puzzle is placed EXACTLY one way: its own "room", or exactly one exits[].puzzle
245
+ reference. Each puzzle has exactly one "clue"-role Book item (forPuzzle set) in a
246
+ DIFFERENT room -- EXCEPT when the puzzle's bypassType matches a skill the runner's
247
+ dossier shows real strength in (a lockpick barrier for a master locksmith, a con
248
+ barrier for a silver-tongued face): their skill IS the way past, and the clue may be
249
+ omitted. When in doubt, include the clue. requiresPuzzle chains never cycle.
250
+ - The winCondition item must not be heldBy winCondition.toNpc. If a matrix-plane item is the
251
+ winCondition item, its room has hasNode true AND a "matrix"-plane NPC stationed there.
252
+ - ${isContinuation
253
+ ? 'ZERO "starting"-role items -- the runner arrives with everything they own. 4-6'
254
+ : '3-5 "starting"-role items (basics the runner doesn\'t already own -- see the dossier), 4-6'}
255
+ "loot"-role world items, one "clue" per puzzle.
256
+
257
+ Scope: ${scope}. Gritty noir Shadowrun tone: a fixer, a job, a debt, a betrayal -- and a
258
+ fresh premise, not a generic "corp blackmail vault" rerun.${repairNote(previousError)}
259
+ `.trim();
260
+ }
261
+ /** Shared context header for the detail passes: the fixed skeleton. */
262
+ function skeletonContext(skeleton) {
263
+ return `The scene's FIXED skeleton (all names are final -- reference them EXACTLY, never invent new ones):
264
+ ${JSON.stringify(skeleton, null, 1)}`;
265
+ }
266
+ export function buildRoomsPrompt(skeleton, previousError) {
267
+ return `
268
+ You are writing the room descriptions for a Shadowrun text adventure.
269
+
270
+ ${skeletonContext(skeleton)}
271
+
272
+ For EVERY room in the skeleton, write evocative 2-3 sentence prose expanding its "concept" --
273
+ gritty cyberpunk noir, concrete sensory detail, no mechanics talk and no spoilers (never
274
+ reveal passphrases, hidden items, or plot twists in a room description).
275
+
276
+ ${JSON_ONLY}
277
+ [ { "name": string, "description": string, "roomType": string } ]
278
+ One entry per skeleton room, "name" copied exactly.${repairNote(previousError)}
279
+ `.trim();
280
+ }
281
+ export function buildNpcsPrompt(skeleton, previousError) {
282
+ return `
283
+ You are writing the characters for a Shadowrun text adventure.
284
+
285
+ ${skeletonContext(skeleton)}
286
+
287
+ For EVERY npc in the skeleton, expand its "concept":
288
+ - "description": private GM notes on personality/motives (never shown verbatim to the player).
289
+ - "dialogue": 2-4 short in-character lines. For "${FIXER_NAME}" (the fixer) this is the REAL
290
+ briefing -- the objective (the winCondition item), where to start looking, the stakes, and
291
+ the exact payout figure from the skeleton (the engine pays exactly that number). When
292
+ referring to places, use EXACT room names from the skeleton -- never invent locations.
293
+ - "combat": OPTIONAL attribute block -- omit for anyone who'd never fight. Each attribute
294
+ 1-8 (soft civilian 1-2, street-level 3-4, hardened enforcer 5-6, elite 7-8):
295
+ { "body", "agility", "reaction", "combatSkill", "strength"?, "willpower"?, "logic"?,
296
+ "intuition"?, "charisma"?, "magic"?, "resonance"?, "adept"?,
297
+ "skills"?: { "firearms"?, "blades"?, "unarmed"?, "sneaking"?, "perception"?, "hacking"?,
298
+ "locksmith"?, "negotiation"?, "con"?, "intimidation"?, "assensing"? } }
299
+ "magic" 3-6 ONLY for a genuinely Awakened concept; "plane":"matrix" ice fights with
300
+ logic 4-7; give combat blocks to anyone the story expects in a firefight. "skills" is
301
+ optional flavor for defined concepts (a sharpshooter's "firearms": 6).
302
+
303
+ ${JSON_ONLY}
304
+ [ { "name": string, "description": string, "dialogue": string[], "combat"?: { ... } } ]
305
+ One entry per skeleton npc, "name" copied exactly.${repairNote(previousError)}
306
+ `.trim();
307
+ }
308
+ export function buildPuzzlesPrompt(skeleton, previousError) {
309
+ if (skeleton.puzzles.length === 0)
310
+ return '';
311
+ return `
312
+ You are writing the barriers (puzzles) for a Shadowrun text adventure.
313
+
314
+ ${skeletonContext(skeleton)}
315
+
316
+ For EVERY puzzle in the skeleton, expand its "concept" (what the barrier physically is --
317
+ that's fixed, as is its bypassType):
318
+ - "riddle": what the player sees on approaching it, shown verbatim -- PLAIN and concrete,
319
+ never a cryptic riddle-poem. Start with "You see" (or similar), name the barrier, then
320
+ what it demands (a code, a phrase, a name), the way an NPC would describe it. Never state
321
+ the credential itself.
322
+ - "requiredPhrase": the exact credential -- ALL LOWERCASE, 2-5 plain words, no punctuation.
323
+ A concrete literal thing (a code, a passphrase, a name), never wordplay.
324
+ - "successMessage" / "failureMessage": one sentence each, in-fiction.
325
+ - "lockedMessage": only for puzzles with "requiresPuzzle" -- what the player sees trying it
326
+ too early, hinting at the prerequisite without naming mechanics.
327
+
328
+ ${JSON_ONLY}
329
+ [ { "name": string, "riddle": string, "requiredPhrase": string, "successMessage": string,
330
+ "failureMessage": string, "lockedMessage"?: string } ]
331
+ One entry per skeleton puzzle, "name" copied exactly.${repairNote(previousError)}
332
+ `.trim();
333
+ }
334
+ export function buildItemsPrompt(skeleton, puzzles, previousError) {
335
+ const clueSpecs = skeleton.items
336
+ .filter(i => i.role === 'clue' && i.forPuzzle)
337
+ .map(i => {
338
+ const phrase = puzzles.find(p => p.name === i.forPuzzle)?.requiredPhrase;
339
+ return `- "${i.name}" is the clue for puzzle "${i.forPuzzle}": its "details" text MUST contain the exact phrase "${phrase ?? 'UNKNOWN'}" verbatim (a diary entry, scrawled note, or manual that gives it away once read).`;
340
+ })
341
+ .join('\n');
342
+ return `
343
+ You are writing the items for a Shadowrun text adventure.
344
+
345
+ ${skeletonContext(skeleton)}
346
+
347
+ For EVERY item in the skeleton, expand it into a full item ("name" and "category" are fixed):
348
+ - "description": one sentence of always-visible flavor.
349
+ - "details": deeper text shown on close examination or reading. CLUE ITEMS (below) put
350
+ their puzzle's credential HERE -- never in "description".
351
+ - "size": "Tiny"|"Small"|"Medium"|"Large"|"VeryLarge"; "rating": "Common"|"Standard"|"Rare"|
352
+ "Legendary"|"MilitaryGrade" (rating scales combat/heal/deck power -- reserve Rare+ for
353
+ earned rewards); "shape"/"color"/"texture": short literals; "weight": realistic kilograms
354
+ (0.05 chip, ~2 pistol, ~4 armor); "currencyAmount" only for Credstick/CurrencyContainer.
355
+
356
+ Clue requirements (mechanically validated -- the exact phrase must appear in "details"):
357
+ ${clueSpecs || '- (no clue items in this scene)'}
358
+
359
+ ${JSON_ONLY}
360
+ [ { "name": string, "description": string, "size": string, "category": string,
361
+ "shape": string, "color": string, "texture": string, "rating": string, "weight": number,
362
+ "currencyAmount"?: number, "details"?: string, "transferable"?: boolean } ]
363
+ One entry per skeleton item, "name" and "category" copied exactly.${repairNote(previousError)}
364
+ `.trim();
365
+ }
366
+ // ========================= Parse / validate / assemble ====================
367
+ /**
368
+ * Whether THIS runner can reliably work a barrier of the given bypassType
369
+ * with their own hands -- no written-down credential required. This is
370
+ * what loosens the "every puzzle needs a discoverable clue" rule when the
371
+ * scene is generated against a dossier: a lockpick maglock in a scene
372
+ * built for a master locksmith doesn't need its code lying in a book --
373
+ * the pick IS their route. Deliberately conservative: EXPLICIT skill
374
+ * ratings only (the combatSkill fallback would let any bruiser "qualify"
375
+ * as a hacker), threshold 3 (trained professional), and with no dossier
376
+ * at all the strict clue rule stays.
377
+ */
378
+ export function canBypassUnaided(player, bypassType) {
379
+ if (!player || !bypassType)
380
+ return false;
381
+ const rated = (skill) => player.skills[skill] ?? 0;
382
+ switch (bypassType) {
383
+ case 'lockpick': return rated('locksmith') >= 3;
384
+ case 'hack': return rated('hacking') >= 3 || player.isTechnomancer() || !!player.getAnyCyberdeck();
385
+ case 'spell': return player.isAwakened() && !player.adept;
386
+ case 'con': return rated('con') >= 3 || player.charisma >= 5;
387
+ default: return false;
388
+ }
389
+ }
390
+ /** Strips accidental markdown fences and parses; throws with a useful message. */
391
+ export function parseJsonReply(reply) {
392
+ const cleaned = reply.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
393
+ return JSON.parse(cleaned);
394
+ }
395
+ /**
396
+ * Fast structural checks on the skeleton BEFORE any detail pass spends
397
+ * tokens building on it. Deeper semantics (reachability, clue coverage,
398
+ * matrix defense...) are still enforced by the assembled-scene validators.
399
+ */
400
+ export function validateSkeleton(skeleton, player) {
401
+ const fail = (msg) => { throw new Error(`Skeleton invalid: ${msg}`); };
402
+ if (!Array.isArray(skeleton?.rooms) || skeleton.rooms.length === 0)
403
+ fail('no rooms.');
404
+ if (!Array.isArray(skeleton?.npcs) || skeleton.npcs.length === 0)
405
+ fail('no npcs.');
406
+ if (!Array.isArray(skeleton?.items))
407
+ fail('no items array.');
408
+ if (!skeleton.winCondition?.item || !skeleton.winCondition?.toNpc)
409
+ fail('winCondition needs "item" and "toNpc".');
410
+ if (typeof skeleton.winCondition.payout !== 'number' || skeleton.winCondition.payout <= 0)
411
+ fail('winCondition.payout must be a positive number.');
412
+ const roomNames = new Set(skeleton.rooms.map(r => r.name));
413
+ const npcNames = new Set(skeleton.npcs.map(n => n.name));
414
+ const itemNames = new Set(skeleton.items.map(i => i.name));
415
+ const puzzleNames = new Set((skeleton.puzzles ?? []).map(p => p.name));
416
+ const startRooms = skeleton.rooms.filter(r => r.isStartRoom);
417
+ if (startRooms.length !== 1)
418
+ fail(`exactly one room must have "isStartRoom": true (found ${startRooms.length}).`);
419
+ const doorRefs = new Map();
420
+ for (const room of skeleton.rooms) {
421
+ for (const exit of room.exits ?? []) {
422
+ if (!roomNames.has(exit.targetRoom))
423
+ fail(`exit from "${room.name}" targets unknown room "${exit.targetRoom}".`);
424
+ if (exit.puzzle) {
425
+ if (!puzzleNames.has(exit.puzzle))
426
+ fail(`exit from "${room.name}" references unknown puzzle "${exit.puzzle}".`);
427
+ doorRefs.set(exit.puzzle, (doorRefs.get(exit.puzzle) ?? 0) + 1);
428
+ }
429
+ if (exit.keyRequired && !itemNames.has(exit.keyRequired))
430
+ fail(`exit from "${room.name}" requires unknown key item "${exit.keyRequired}".`);
431
+ }
432
+ }
433
+ for (const npc of skeleton.npcs) {
434
+ if (!roomNames.has(npc.startLocation))
435
+ fail(`npc "${npc.name}" starts in unknown room "${npc.startLocation}".`);
436
+ }
437
+ for (const item of skeleton.items) {
438
+ if (item.room && !roomNames.has(item.room))
439
+ fail(`item "${item.name}" placed in unknown room "${item.room}".`);
440
+ if (item.heldBy && !npcNames.has(item.heldBy))
441
+ fail(`item "${item.name}" heldBy unknown npc "${item.heldBy}".`);
442
+ if (item.forPuzzle && !puzzleNames.has(item.forPuzzle))
443
+ fail(`clue item "${item.name}" references unknown puzzle "${item.forPuzzle}".`);
444
+ }
445
+ // The three structural faults real generations actually shipped -- an
446
+ // unplaced-or-double-placed puzzle, a puzzle with no clue item at all
447
+ // (unfixable by ANY items pass), and unreachable rooms -- must die HERE,
448
+ // before four detail passes build on bad bones.
449
+ for (const puzzle of skeleton.puzzles ?? []) {
450
+ if (puzzle.room && !roomNames.has(puzzle.room))
451
+ fail(`puzzle "${puzzle.name}" placed in unknown room "${puzzle.room}".`);
452
+ if (puzzle.rewardItem && !itemNames.has(puzzle.rewardItem))
453
+ fail(`puzzle "${puzzle.name}" rewards unknown item "${puzzle.rewardItem}".`);
454
+ const refs = doorRefs.get(puzzle.name) ?? 0;
455
+ if (puzzle.room && refs > 0)
456
+ fail(`puzzle "${puzzle.name}" is placed both in room "${puzzle.room}" AND on a door (exits[].puzzle) -- exactly one or the other.`);
457
+ if (!puzzle.room && refs === 0)
458
+ fail(`puzzle "${puzzle.name}" isn't placed anywhere -- set its "room", or reference it from one exits[].puzzle.`);
459
+ if (refs > 1)
460
+ fail(`puzzle "${puzzle.name}" is referenced by ${refs} doors -- a puzzle can only guard one.`);
461
+ const clue = skeleton.items.find(i => i.role === 'clue' && i.forPuzzle === puzzle.name);
462
+ if (!clue) {
463
+ // Loosened when the dossier says the runner can work this barrier
464
+ // unaided -- their bypass skill IS the way past (player note from a
465
+ // real failed generation: "it just needs a way past, and if the
466
+ // player has the means, it should be allowed").
467
+ if (!canBypassUnaided(player, puzzle.bypassType)) {
468
+ fail(`puzzle "${puzzle.name}" has no "clue"-role item with "forPuzzle": "${puzzle.name}" and its bypassType (${puzzle.bypassType ?? 'none'}) is not something this runner reliably handles -- add a "clue"-role Book item in a different room, or give the barrier a bypassType matching the runner's dossier strengths.`);
469
+ }
470
+ }
471
+ else {
472
+ if (!clue.room)
473
+ fail(`clue item "${clue.name}" needs a "room" (a different one than its puzzle's).`);
474
+ if (puzzle.room && clue.room === puzzle.room)
475
+ fail(`clue item "${clue.name}" sits in the SAME room as its puzzle "${puzzle.name}" -- place the clue in a different room.`);
476
+ }
477
+ }
478
+ if (!itemNames.has(skeleton.winCondition.item))
479
+ fail(`winCondition.item "${skeleton.winCondition.item}" is not in items.`);
480
+ if (!npcNames.has(skeleton.winCondition.toNpc))
481
+ fail(`winCondition.toNpc "${skeleton.winCondition.toNpc}" is not in npcs.`);
482
+ // Lock-and-key reachability. CRITICAL: edges are BIDIRECTIONAL -- the
483
+ // engine mirrors every declared exit into a shared two-way Door
484
+ // (room.addExit writes both sides), so a one-sided skeleton exit still
485
+ // connects both rooms in play. The first version of this walk was
486
+ // directed-only and falsely stranded rooms reachable via the mirrored
487
+ // direction, burning entire generations on a phantom fault.
488
+ const npcRoom = new Map(skeleton.npcs.map(n => [n.name, n.startLocation]));
489
+ const itemRoom = new Map();
490
+ for (const item of skeleton.items) {
491
+ if (item.room)
492
+ itemRoom.set(item.name, item.room);
493
+ else if (item.heldBy) {
494
+ const loc = npcRoom.get(item.heldBy);
495
+ if (loc)
496
+ itemRoom.set(item.name, loc);
497
+ }
498
+ }
499
+ const edges = [];
500
+ for (const room of skeleton.rooms) {
501
+ for (const exit of room.exits ?? []) {
502
+ edges.push({ a: room.name, b: exit.targetRoom, keyRequired: exit.keyRequired, puzzle: exit.puzzle });
503
+ }
504
+ }
505
+ const reachable = new Set([startRooms[0].name]);
506
+ let grew = true;
507
+ while (grew) {
508
+ grew = false;
509
+ for (const edge of edges) {
510
+ const from = reachable.has(edge.a) ? edge.a : reachable.has(edge.b) ? edge.b : undefined;
511
+ const to = from === edge.a ? edge.b : edge.a;
512
+ if (!from || reachable.has(to))
513
+ continue;
514
+ const keyLoc = edge.keyRequired ? itemRoom.get(edge.keyRequired) : undefined;
515
+ const passable = !!edge.puzzle || !edge.keyRequired || (keyLoc !== undefined && reachable.has(keyLoc));
516
+ if (passable) {
517
+ reachable.add(to);
518
+ grew = true;
519
+ }
520
+ }
521
+ }
522
+ const stranded = skeleton.rooms.filter(r => r.reachable !== false && !reachable.has(r.name));
523
+ if (stranded.length > 0) {
524
+ fail(`these rooms are unreachable from the start room given the locked doors and key placements: ${stranded.map(r => r.name).join(', ')}. Every key must be findable WITHOUT passing through the door it opens, and every normal room needs a route in (or mark it "reachable": false).`);
525
+ }
526
+ }
527
+ /**
528
+ * Auto-fixes skeleton faults that have exactly one sane resolution,
529
+ * instead of burning a whole retry on them. Today: a puzzle both placed
530
+ * in a room AND referenced by exactly one door keeps the DOOR placement
531
+ * (the model near-always meant "the door in that room" -- this exact
532
+ * fault killed two consecutive real generations). Returns notes for the
533
+ * log.
534
+ */
535
+ export function normalizeSkeleton(skeleton) {
536
+ const notes = [];
537
+ const doorRefs = new Map();
538
+ for (const room of skeleton.rooms ?? []) {
539
+ for (const exit of room.exits ?? []) {
540
+ if (exit.puzzle)
541
+ doorRefs.set(exit.puzzle, (doorRefs.get(exit.puzzle) ?? 0) + 1);
542
+ }
543
+ }
544
+ for (const puzzle of skeleton.puzzles ?? []) {
545
+ if (puzzle.room && (doorRefs.get(puzzle.name) ?? 0) === 1) {
546
+ notes.push(`normalized: puzzle "${puzzle.name}" was double-placed (room + door); kept the door placement.`);
547
+ delete puzzle.room;
548
+ }
549
+ }
550
+ return notes;
551
+ }
552
+ /** Merges the skeleton's structure with the detail passes into a full seed. */
553
+ export function assembleScene(skeleton, details, playerName) {
554
+ const roomDetail = new Map(details.rooms.map(r => [r.name, r]));
555
+ const npcDetail = new Map(details.npcs.map(n => [n.name, n]));
556
+ const puzzleDetail = new Map(details.puzzles.map(p => [p.name, p]));
557
+ const itemDetail = new Map(details.items.map(i => [i.name, i]));
558
+ const rooms = skeleton.rooms.map(room => ({
559
+ name: room.name,
560
+ description: roomDetail.get(room.name)?.description ?? room.concept,
561
+ roomType: roomDetail.get(room.name)?.roomType ?? room.roomType ?? 'Generic',
562
+ isStartRoom: room.isStartRoom,
563
+ reachable: room.reachable,
564
+ hasNode: room.hasNode,
565
+ nodeAccess: room.nodeAccess,
566
+ warded: room.warded,
567
+ owner: room.owner,
568
+ exits: (room.exits ?? []).map(e => ({
569
+ direction: e.direction,
570
+ targetRoom: e.targetRoom,
571
+ keyRequired: e.keyRequired,
572
+ puzzle: e.puzzle,
573
+ })),
574
+ }));
575
+ const npcs = skeleton.npcs.map(npc => {
576
+ const detail = npcDetail.get(npc.name);
577
+ return {
578
+ plane: npc.plane,
579
+ description: detail?.description ?? npc.concept,
580
+ dialogue: detail?.dialogue ?? [],
581
+ player: {
582
+ name: npc.name,
583
+ startLocation: npc.startLocation,
584
+ lifestyleChoices: [],
585
+ combat: detail?.combat,
586
+ },
587
+ };
588
+ });
589
+ const toItemJson = (skel, placed) => {
590
+ const detail = itemDetail.get(skel.name);
591
+ return {
592
+ name: skel.name,
593
+ description: detail?.description ?? skel.name,
594
+ size: detail?.size ?? 'Small',
595
+ category: skel.category,
596
+ shape: detail?.shape ?? 'Nondescript',
597
+ color: detail?.color ?? 'Gray',
598
+ texture: detail?.texture ?? 'Worn',
599
+ rating: detail?.rating ?? 'Common',
600
+ weight: typeof detail?.weight === 'number' ? detail.weight : 0.5,
601
+ currencyAmount: detail?.currencyAmount,
602
+ details: detail?.details,
603
+ transferable: detail?.transferable,
604
+ plane: skel.plane,
605
+ room: placed ? skel.room : undefined,
606
+ heldBy: placed ? skel.heldBy : undefined,
607
+ };
608
+ };
609
+ const startingSkels = skeleton.items.filter(i => i.role === 'starting');
610
+ const worldSkels = skeleton.items.filter(i => i.role !== 'starting');
611
+ const items = worldSkels.map(skel => toItemJson(skel, skel.role !== 'reward'));
612
+ const startingItems = startingSkels.map(skel => toItemJson(skel, false));
613
+ const puzzles = (skeleton.puzzles ?? []).map(puzzle => {
614
+ const detail = puzzleDetail.get(puzzle.name);
615
+ return {
616
+ name: puzzle.name,
617
+ room: puzzle.room,
618
+ riddle: detail?.riddle ?? puzzle.concept,
619
+ requiredPhrase: detail?.requiredPhrase ?? '',
620
+ successMessage: detail?.successMessage ?? 'It opens.',
621
+ failureMessage: detail?.failureMessage ?? 'Nothing happens.',
622
+ lockedMessage: detail?.lockedMessage,
623
+ requiresPuzzle: puzzle.requiresPuzzle,
624
+ rewardItem: puzzle.rewardItem,
625
+ bypassType: puzzle.bypassType,
626
+ };
627
+ });
628
+ const startRoom = skeleton.rooms.find(r => r.isStartRoom)?.name ?? skeleton.rooms[0]?.name ?? '';
629
+ return {
630
+ name: skeleton.name,
631
+ story: skeleton.story,
632
+ currency: { type: 'nuyen', authorityRoomTags: ['Bank'] },
633
+ residenceTiers: ['Street', 'Squatter', 'Low', 'Middle', 'High', 'Luxury'],
634
+ rooms,
635
+ npcs,
636
+ items,
637
+ puzzles,
638
+ player: {
639
+ name: playerName,
640
+ startLocation: startRoom,
641
+ lifestyleChoices: [{ type: 'residence', metadata: { tier: 'Street', location: startRoom } }],
642
+ startingItems,
643
+ },
644
+ winCondition: {
645
+ type: 'give',
646
+ item: skeleton.winCondition.item,
647
+ toNpc: skeleton.winCondition.toNpc,
648
+ rewardItem: skeleton.winCondition.rewardItem,
649
+ payout: skeleton.winCondition.payout,
650
+ completionMessage: skeleton.completionMessage,
651
+ },
652
+ };
653
+ }
654
+ /**
655
+ * Maps a validation failure to the chunk that owns the broken content, so
656
+ * a repair regenerates ONE pass instead of the whole scene. Anything
657
+ * structural (topology, placement, rosters) is skeleton-owned and restarts
658
+ * the pipeline with the error as feedback.
659
+ */
660
+ export function classifyRepair(message) {
661
+ if (/no discoverable clue/i.test(message))
662
+ return 'items';
663
+ if (/startingItems|Win condition (item|rewardItem)|items\[/i.test(message))
664
+ return 'items';
665
+ if (/requiredPhrase|riddle|lockedMessage/i.test(message))
666
+ return 'puzzles';
667
+ if (/npcs\[|dialogue/i.test(message))
668
+ return 'npcs';
669
+ if (/rooms\[\d+\][^]*name/i.test(message))
670
+ return 'skeleton';
671
+ return 'skeleton';
672
+ }
673
+ //# sourceMappingURL=scene-chunks.js.map