@anthusai/papyrus 0.0.0 → 1.0.0-next.1

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/amplify/auth/resource.js +61 -0
  3. package/amplify/auth/resource.ts +65 -0
  4. package/amplify/data/resource.js +2037 -0
  5. package/amplify/data/resource.ts +2148 -0
  6. package/amplify/data/schema.js +1850 -0
  7. package/amplify/data/schema.ts +1953 -0
  8. package/amplify/functions/assignment-action/handler.ts +1416 -0
  9. package/amplify/functions/assignment-action/resource.js +8 -0
  10. package/amplify/functions/assignment-action/resource.ts +9 -0
  11. package/amplify/functions/category-action/handler.ts +2259 -0
  12. package/amplify/functions/category-action/resource.js +8 -0
  13. package/amplify/functions/category-action/resource.ts +9 -0
  14. package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
  15. package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
  16. package/amplify/functions/console-chat-responder/Dockerfile +19 -0
  17. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
  18. package/amplify/functions/console-chat-responder/resource.js +153 -0
  19. package/amplify/functions/console-chat-responder/resource.ts +179 -0
  20. package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
  21. package/amplify/functions/email-submission-processor/handler.py +46 -0
  22. package/amplify/functions/email-submission-processor/requirements.txt +2 -0
  23. package/amplify/functions/email-submission-processor/resource.js +56 -0
  24. package/amplify/functions/email-submission-processor/resource.ts +69 -0
  25. package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
  26. package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
  27. package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
  28. package/amplify/functions/knowledge-query/handler.py +1 -0
  29. package/amplify/functions/knowledge-query/requirements.txt +3 -0
  30. package/amplify/functions/knowledge-query/resource.js +50 -0
  31. package/amplify/functions/knowledge-query/resource.ts +61 -0
  32. package/amplify/functions/manage-user-role/handler.ts +799 -0
  33. package/amplify/functions/manage-user-role/resource.js +8 -0
  34. package/amplify/functions/manage-user-role/resource.ts +9 -0
  35. package/amplify/functions/model-attachment-upload/handler.ts +387 -0
  36. package/amplify/functions/model-attachment-upload/resource.js +8 -0
  37. package/amplify/functions/model-attachment-upload/resource.ts +9 -0
  38. package/amplify/functions/newsroom-summary/handler.ts +321 -0
  39. package/amplify/functions/newsroom-summary/resource.js +8 -0
  40. package/amplify/functions/newsroom-summary/resource.ts +9 -0
  41. package/amplify/functions/procedure-action/handler.ts +621 -0
  42. package/amplify/functions/procedure-action/resource.js +8 -0
  43. package/amplify/functions/procedure-action/resource.ts +9 -0
  44. package/amplify/functions/reader-settings/handler.ts +322 -0
  45. package/amplify/functions/reader-settings/resource.js +8 -0
  46. package/amplify/functions/reader-settings/resource.ts +9 -0
  47. package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
  48. package/amplify/functions/ses-inbound-receive/resource.js +8 -0
  49. package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
  50. package/amplify/functions/shared/amplify-secrets.ts +65 -0
  51. package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
  52. package/amplify/functions/shared/email-mime-intake.ts +196 -0
  53. package/amplify/functions/shared/email-submission.ts +415 -0
  54. package/amplify/functions/shared/inbound-email-intake.ts +37 -0
  55. package/amplify/functions/shared/lambda-data-client.ts +21 -0
  56. package/amplify/functions/shared/model-payloads.ts +186 -0
  57. package/amplify/functions/shared/python-bundle.js +115 -0
  58. package/amplify/functions/shared/python-bundle.ts +141 -0
  59. package/amplify/functions/shared/slack-events.ts +111 -0
  60. package/amplify/functions/slack-delivery/handler.py +112 -0
  61. package/amplify/functions/slack-delivery/requirements.txt +2 -0
  62. package/amplify/functions/slack-delivery/resource.js +38 -0
  63. package/amplify/functions/slack-delivery/resource.ts +51 -0
  64. package/amplify/functions/slack-events/handler.ts +155 -0
  65. package/amplify/functions/slack-events/resource.js +11 -0
  66. package/amplify/functions/slack-events/resource.ts +12 -0
  67. package/amplify/inbound-email/stack.js +46 -0
  68. package/amplify/inbound-email/stack.ts +61 -0
  69. package/amplify/package.json +3 -0
  70. package/amplify/site-backend-config.js +1 -0
  71. package/amplify/site-backend-config.ts +30 -0
  72. package/amplify/site-backend.js +587 -0
  73. package/amplify/site-backend.ts +704 -0
  74. package/amplify/storage/resource.js +18 -0
  75. package/amplify/storage/resource.ts +19 -0
  76. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
  77. package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
  78. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
  79. package/app/[year]/[month]/[day]/page.tsx +22 -0
  80. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
  81. package/app/api/archive/editions/route.ts +18 -0
  82. package/app/api/media/[...path]/route.ts +35 -0
  83. package/app/api/revalidate/route.ts +94 -0
  84. package/app/archive/page.tsx +24 -0
  85. package/app/articles/[slug]/page.tsx +35 -0
  86. package/app/globals.css +10329 -0
  87. package/app/layout.tsx +146 -0
  88. package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
  89. package/app/newsroom/[section]/page.tsx +47 -0
  90. package/app/newsroom/forum/[threadId]/page.tsx +35 -0
  91. package/app/newsroom/insights/[threadId]/page.tsx +22 -0
  92. package/app/newsroom/insights/page.tsx +14 -0
  93. package/app/newsroom/layout.tsx +12 -0
  94. package/app/newsroom/page.tsx +31 -0
  95. package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
  96. package/app/page.tsx +272 -0
  97. package/app/settings/page.tsx +14 -0
  98. package/app/tailwind.css +65 -0
  99. package/bin/papyrus-app.mjs +10 -0
  100. package/bin/papyrus-infra.mjs +39 -0
  101. package/components/ai-elements/conversation.tsx +168 -0
  102. package/components/ai-elements/message.tsx +360 -0
  103. package/components/ai-elements/model-selector.tsx +30 -0
  104. package/components/ai-elements/prompt-input.tsx +1461 -0
  105. package/components/ai-elements/shimmer.tsx +76 -0
  106. package/components/ai-elements/suggestion.tsx +54 -0
  107. package/components/ai-elements/tool.tsx +173 -0
  108. package/components/amplify-client-provider.tsx +140 -0
  109. package/components/archive-grid.tsx +89 -0
  110. package/components/archive-shell.tsx +41 -0
  111. package/components/article-video.tsx +47 -0
  112. package/components/blog-page-background.tsx +20 -0
  113. package/components/dev-sandbox-editor-sign-in.tsx +60 -0
  114. package/components/news-desk-client-provider.tsx +211 -0
  115. package/components/news-desk-page.tsx +119 -0
  116. package/components/news-desk-taxonomy-client.ts +3684 -0
  117. package/components/newspaper.tsx +1929 -0
  118. package/components/newsroom-assignments-view.tsx +664 -0
  119. package/components/newsroom-ops-overview.tsx +226 -0
  120. package/components/newsroom-ops-shell.tsx +319 -0
  121. package/components/newsroom-references-view.tsx +598 -0
  122. package/components/newsroom-topics-view.tsx +425 -0
  123. package/components/papyrus-console-shell.tsx +1912 -0
  124. package/components/pictogram-figure.tsx +65 -0
  125. package/components/presentation-footer.tsx +146 -0
  126. package/components/reader-auth-control.tsx +162 -0
  127. package/components/reader-auth-state.ts +122 -0
  128. package/components/reader-settings.ts +332 -0
  129. package/components/reader-theme-control.tsx +85 -0
  130. package/components/reference-source-preview.tsx +163 -0
  131. package/components/settings-page.tsx +239 -0
  132. package/components/topic-steering-workspace.tsx +18063 -0
  133. package/components/ui/badge.tsx +30 -0
  134. package/components/ui/button-group.tsx +87 -0
  135. package/components/ui/button.tsx +58 -0
  136. package/components/ui/card.tsx +32 -0
  137. package/components/ui/command.tsx +194 -0
  138. package/components/ui/dialog.tsx +158 -0
  139. package/components/ui/dropdown-menu.tsx +266 -0
  140. package/components/ui/hover-card.tsx +51 -0
  141. package/components/ui/input-group.tsx +158 -0
  142. package/components/ui/input.tsx +20 -0
  143. package/components/ui/scroll-area.tsx +16 -0
  144. package/components/ui/select.tsx +199 -0
  145. package/components/ui/separator.tsx +23 -0
  146. package/components/ui/sheet.tsx +92 -0
  147. package/components/ui/spinner.tsx +10 -0
  148. package/components/ui/tabs.tsx +113 -0
  149. package/components/ui/textarea.tsx +18 -0
  150. package/components/ui/tooltip.tsx +66 -0
  151. package/components/use-resolved-papyrus-theme.ts +47 -0
  152. package/infra/amplify-app-shell.js +109 -0
  153. package/infra/amplify-app-shell.ts +155 -0
  154. package/infra/index.js +12 -0
  155. package/lib/amplify-outputs-guard.ts +24 -0
  156. package/lib/amplify-outputs-path.ts +16 -0
  157. package/lib/amplify-server-runtime.ts +63 -0
  158. package/lib/archive-data.ts +30 -0
  159. package/lib/archive-types.ts +15 -0
  160. package/lib/articles.ts +320 -0
  161. package/lib/cached-content-repository.ts +89 -0
  162. package/lib/category-dashboard.ts +37 -0
  163. package/lib/category-repository.ts +1907 -0
  164. package/lib/console-chat-client.ts +1316 -0
  165. package/lib/console-web-context.ts +118 -0
  166. package/lib/content-load-timing.ts +23 -0
  167. package/lib/content-repository.ts +40 -0
  168. package/lib/content-types.ts +104 -0
  169. package/lib/define-site.js +3 -0
  170. package/lib/define-site.ts +21 -0
  171. package/lib/demo-amplify-outputs.ts +15 -0
  172. package/lib/dev-sandbox-editor-auth.ts +18 -0
  173. package/lib/doctrine.ts +128 -0
  174. package/lib/edition-routes.ts +155 -0
  175. package/lib/edition-sections.ts +111 -0
  176. package/lib/empty-edition-layout-plan.ts +109 -0
  177. package/lib/excerpts.ts +8 -0
  178. package/lib/graphql-content-repository.ts +986 -0
  179. package/lib/image-url.ts +10 -0
  180. package/lib/layout-plan.ts +981 -0
  181. package/lib/layout-scenarios.ts +564 -0
  182. package/lib/markus-body.ts +168 -0
  183. package/lib/markus-ir.ts +235 -0
  184. package/lib/markus-projection.ts +243 -0
  185. package/lib/markus-to-article.ts +93 -0
  186. package/lib/news-desk-session.ts +160 -0
  187. package/lib/newspaper-layout.ts +3459 -0
  188. package/lib/newsroom-assignments.ts +173 -0
  189. package/lib/newsroom-base-path.ts +48 -0
  190. package/lib/newsroom-card-layout.ts +102 -0
  191. package/lib/newsroom-category-drilldown.ts +133 -0
  192. package/lib/newsroom-demo-dashboard.ts +267 -0
  193. package/lib/newsroom-demo-profile.ts +46 -0
  194. package/lib/newsroom-forum-routes.ts +153 -0
  195. package/lib/newsroom-index-filters.ts +202 -0
  196. package/lib/newsroom-knowledge-query-request.ts +51 -0
  197. package/lib/newsroom-list-selection.ts +42 -0
  198. package/lib/newsroom-nav.ts +42 -0
  199. package/lib/newsroom-references.ts +116 -0
  200. package/lib/newsroom-sections.ts +52 -0
  201. package/lib/newsroom-topics.ts +118 -0
  202. package/lib/papyrus-object-kinds.ts +24 -0
  203. package/lib/papyrus-web-locations.ts +480 -0
  204. package/lib/pilobol-sample.ts +167 -0
  205. package/lib/presentation-footer.ts +92 -0
  206. package/lib/pretext-layout.ts +179 -0
  207. package/lib/public-placeholder-config.ts +102 -0
  208. package/lib/publication-items.ts +181 -0
  209. package/lib/reader-route-config.ts +1 -0
  210. package/lib/reader-static-params.ts +56 -0
  211. package/lib/reader-storage-url.ts +63 -0
  212. package/lib/reference-display.ts +74 -0
  213. package/lib/reference-policy.ts +97 -0
  214. package/lib/reference-source-preview.ts +175 -0
  215. package/lib/renderer-config.ts +50 -0
  216. package/lib/renderer.ts +87 -0
  217. package/lib/reporting-story-budget.ts +638 -0
  218. package/lib/semantic-graph.ts +554 -0
  219. package/lib/site-brand-tenant.ts +41 -0
  220. package/lib/site-brand.ts +179 -0
  221. package/lib/site-renderer.ts +17 -0
  222. package/lib/site-stack.ts +195 -0
  223. package/lib/themed-image.ts +30 -0
  224. package/lib/utils.ts +6 -0
  225. package/middleware.ts +78 -0
  226. package/package.json +296 -1
  227. package/publications/anth_us/brand.ts +68 -0
  228. package/publications/anth_us/theme.css +66 -0
  229. package/publications/papyrus/theme.css +11 -0
  230. package/publications/pilobol_us/brand.ts +37 -0
  231. package/publications/pilobol_us/theme.css +140 -0
  232. package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
  233. package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
  234. package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
  235. package/publications/threat_intelligence/brand.ts +40 -0
  236. package/publications/threat_intelligence/pictograms/art.tsx +608 -0
  237. package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
  238. package/publications/threat_intelligence/pictograms/registry.ts +30 -0
  239. package/publications/threat_intelligence/pictograms/system.tsx +305 -0
  240. package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
  241. package/publications/threat_intelligence/theme.css +1440 -0
  242. package/renderers/markus/index.ts +25 -0
  243. package/renderers/markus/stubs.tsx +33 -0
  244. package/renderers/pretext/article-page.tsx +174 -0
  245. package/renderers/pretext/index.ts +30 -0
  246. package/renderers/pretext/layouts.ts +16 -0
  247. package/renderers/pretext/presentation-shell.tsx +477 -0
  248. package/routes.manifest.json +182 -0
  249. package/scripts/fixtures/pilobol-sample.ast.json +234 -0
  250. package/src/backend.js +1 -0
  251. package/src/backend.ts +3 -0
  252. package/src/sync.mjs +87 -0
  253. package/src/with-papyrus.d.mts +2 -0
  254. package/src/with-papyrus.mjs +47 -0
@@ -0,0 +1,636 @@
1
+ export const BLOG_DEFENSE_VIEWBOX_WIDTH = 1000;
2
+ export const BLOG_DEFENSE_VIEWBOX_HEIGHT = 700;
3
+
4
+ export type BlogDefenseNodeRole = "core" | "attack" | "perimeter" | "interior";
5
+ export type BlogDefenseZone = "core" | "left_arm" | "right_arm" | "right_corridor";
6
+
7
+ export type BlogDefenseNode = {
8
+ id: string;
9
+ x: number;
10
+ y: number;
11
+ radius: number;
12
+ role: BlogDefenseNodeRole;
13
+ zone: BlogDefenseZone;
14
+ protectedIngress?: boolean;
15
+ };
16
+
17
+ export type BlogDefenseEdge = {
18
+ id: string;
19
+ from: string;
20
+ to: string;
21
+ };
22
+
23
+ export type VisibleAttackPathInput = {
24
+ nodes: BlogDefenseNode[];
25
+ edges: BlogDefenseEdge[];
26
+ coreId?: string;
27
+ stochastic?: boolean;
28
+ };
29
+
30
+ export type VisibleAttackPath = {
31
+ nodeIds: string[];
32
+ edgeIds: string[];
33
+ };
34
+
35
+ export const BLOG_DEFENSE_CORE_NODE_ID = "core";
36
+
37
+ export const BLOG_DEFENSE_NODES: BlogDefenseNode[] = [
38
+ { id: "core", x: 770, y: 175, radius: 12, role: "core", zone: "core" },
39
+ { id: "halo_n", x: 770, y: 32, radius: 7, role: "perimeter", zone: "core" },
40
+ { id: "halo_ne", x: 913, y: 58, radius: 7, role: "perimeter", zone: "core" },
41
+ { id: "halo_e", x: 913, y: 175, radius: 7, role: "perimeter", zone: "core" },
42
+ { id: "halo_se", x: 878, y: 284, radius: 7, role: "perimeter", zone: "core" },
43
+ { id: "halo_s", x: 770, y: 318, radius: 7, role: "perimeter", zone: "core" },
44
+ { id: "halo_sw", x: 662, y: 284, radius: 7, role: "perimeter", zone: "core" },
45
+ { id: "halo_w", x: 627, y: 175, radius: 7, role: "perimeter", zone: "core" },
46
+ { id: "halo_nw", x: 662, y: 66, radius: 7, role: "perimeter", zone: "core" },
47
+ { id: "outer_n", x: 770, y: 56, radius: 7, role: "perimeter", zone: "core" },
48
+ { id: "outer_ne", x: 854, y: 91, radius: 7, role: "perimeter", zone: "core" },
49
+ { id: "outer_e", x: 889, y: 175, radius: 7, role: "perimeter", zone: "core" },
50
+ { id: "outer_se", x: 854, y: 259, radius: 7, role: "perimeter", zone: "core" },
51
+ { id: "outer_s", x: 770, y: 294, radius: 7, role: "perimeter", zone: "core" },
52
+ { id: "outer_sw", x: 686, y: 259, radius: 7, role: "perimeter", zone: "core" },
53
+ { id: "outer_w", x: 651, y: 175, radius: 7, role: "perimeter", zone: "core" },
54
+ { id: "outer_nw", x: 686, y: 91, radius: 7, role: "perimeter", zone: "core" },
55
+ { id: "ring_n", x: 770, y: 84, radius: 7, role: "perimeter", zone: "core" },
56
+ { id: "ring_ne", x: 833, y: 111, radius: 7, role: "perimeter", zone: "core" },
57
+ { id: "ring_e", x: 858, y: 175, radius: 7, role: "perimeter", zone: "core" },
58
+ { id: "ring_se", x: 833, y: 240, radius: 7, role: "perimeter", zone: "core" },
59
+ { id: "ring_s", x: 770, y: 266, radius: 7, role: "perimeter", zone: "core" },
60
+ { id: "ring_sw", x: 706, y: 240, radius: 7, role: "perimeter", zone: "core" },
61
+ { id: "ring_w", x: 682, y: 175, radius: 7, role: "perimeter", zone: "core" },
62
+ { id: "ring_nw", x: 706, y: 111, radius: 7, role: "perimeter", zone: "core" },
63
+
64
+ { id: "mid_n", x: 770, y: 111, radius: 5, role: "interior", zone: "core" },
65
+ { id: "mid_ne", x: 814, y: 130, radius: 5, role: "interior", zone: "core" },
66
+ { id: "mid_e", x: 832, y: 175, radius: 5, role: "interior", zone: "core" },
67
+ { id: "mid_se", x: 814, y: 220, radius: 5, role: "interior", zone: "core" },
68
+ { id: "mid_s", x: 770, y: 239, radius: 5, role: "interior", zone: "core" },
69
+ { id: "mid_sw", x: 726, y: 220, radius: 5, role: "interior", zone: "core" },
70
+ { id: "mid_w", x: 708, y: 175, radius: 5, role: "interior", zone: "core" },
71
+ { id: "mid_nw", x: 726, y: 130, radius: 5, role: "interior", zone: "core" },
72
+
73
+ { id: "left_attach_top", x: 640, y: 42, radius: 6, role: "interior", zone: "left_arm" },
74
+ { id: "left_attach_bottom", x: 626, y: 114, radius: 6, role: "interior", zone: "left_arm" },
75
+ { id: "left_bridge_top", x: 678, y: 32, radius: 6, role: "perimeter", zone: "left_arm" },
76
+ { id: "left_a0", x: 640, y: 32, radius: 6, role: "perimeter", zone: "left_arm" },
77
+ { id: "left_a1", x: 600, y: 32, radius: 6, role: "perimeter", zone: "left_arm" },
78
+ { id: "left_a2", x: 556, y: 32, radius: 6, role: "perimeter", zone: "left_arm" },
79
+ { id: "left_a3", x: 514, y: 32, radius: 6, role: "perimeter", zone: "left_arm" },
80
+ { id: "left_b0", x: 628, y: 72, radius: 6, role: "interior", zone: "left_arm" },
81
+ { id: "left_b3", x: 534, y: 44, radius: 6, role: "interior", zone: "left_arm" },
82
+ { id: "left_c0", x: 632, y: 120, radius: 6, role: "interior", zone: "left_arm" },
83
+ { id: "left_c3", x: 548, y: 60, radius: 6, role: "interior", zone: "left_arm" },
84
+ { id: "left_tip", x: 492, y: 32, radius: 5, role: "perimeter", zone: "left_arm" },
85
+ { id: "left_tail_1", x: 450, y: 32, radius: 5, role: "perimeter", zone: "left_arm" },
86
+ { id: "left_tail_2", x: 408, y: 32, radius: 5, role: "perimeter", zone: "left_arm" },
87
+ { id: "left_tail_3", x: 366, y: 32, radius: 5, role: "perimeter", zone: "left_arm" },
88
+ { id: "left_tail_4", x: 326, y: 32, radius: 5, role: "perimeter", zone: "left_arm" },
89
+
90
+ { id: "right_attach_bottom", x: 820, y: 308, radius: 6, role: "interior", zone: "right_arm" },
91
+ { id: "right_bridge_top", x: 905, y: 74, radius: 6, role: "perimeter", zone: "right_arm" },
92
+ { id: "right_bridge_peak", x: 913, y: 42, radius: 6, role: "perimeter", zone: "right_arm" },
93
+ { id: "right_a0", x: 906, y: 240, radius: 7, role: "perimeter", zone: "right_arm" },
94
+ { id: "right_a1", x: 910, y: 316, radius: 7, role: "perimeter", zone: "right_arm" },
95
+ { id: "right_a2", x: 912, y: 390, radius: 7, role: "perimeter", zone: "right_arm" },
96
+ { id: "right_a3", x: 912, y: 470, radius: 7, role: "perimeter", zone: "right_arm" },
97
+ { id: "right_a4", x: 912, y: 552, radius: 7, role: "perimeter", zone: "right_arm" },
98
+ { id: "right_a5", x: 912, y: 632, radius: 7, role: "perimeter", zone: "right_arm" },
99
+ { id: "right_b0", x: 834, y: 286, radius: 6, role: "interior", zone: "right_arm" },
100
+ { id: "right_b1", x: 848, y: 350, radius: 6, role: "interior", zone: "right_arm" },
101
+ { id: "right_b2", x: 872, y: 424, radius: 6, role: "interior", zone: "right_arm" },
102
+ { id: "right_b3", x: 907, y: 502, radius: 6, role: "interior", zone: "right_arm" },
103
+ { id: "right_b4", x: 910, y: 582, radius: 6, role: "interior", zone: "right_arm" },
104
+ { id: "right_b5", x: 911, y: 654, radius: 6, role: "interior", zone: "right_arm" },
105
+ { id: "right_c0", x: 790, y: 328, radius: 6, role: "interior", zone: "right_arm" },
106
+ { id: "right_c1", x: 812, y: 366, radius: 6, role: "interior", zone: "right_arm" },
107
+ { id: "right_c2", x: 836, y: 444, radius: 6, role: "interior", zone: "right_arm" },
108
+ { id: "right_c3", x: 900, y: 528, radius: 6, role: "interior", zone: "right_arm" },
109
+ { id: "right_c4", x: 908, y: 608, radius: 6, role: "interior", zone: "right_arm" },
110
+ { id: "right_c5", x: 910, y: 676, radius: 6, role: "interior", zone: "right_arm" },
111
+
112
+ { id: "corridor_0", x: 970, y: 260, radius: 7, role: "attack", zone: "right_corridor", protectedIngress: true },
113
+ { id: "corridor_1", x: 972, y: 328, radius: 7, role: "attack", zone: "right_corridor", protectedIngress: true },
114
+ { id: "corridor_2", x: 974, y: 398, radius: 7, role: "attack", zone: "right_corridor", protectedIngress: true },
115
+ { id: "corridor_3", x: 976, y: 472, radius: 8, role: "attack", zone: "right_corridor", protectedIngress: true },
116
+ { id: "corridor_4", x: 978, y: 548, radius: 8, role: "attack", zone: "right_corridor", protectedIngress: true },
117
+ { id: "corridor_5", x: 980, y: 620, radius: 9, role: "attack", zone: "right_corridor", protectedIngress: true },
118
+ { id: "corridor_6", x: 982, y: 684, radius: 10, role: "attack", zone: "right_corridor", protectedIngress: true },
119
+ ];
120
+
121
+ export const BLOG_DEFENSE_EDGES: BlogDefenseEdge[] = [
122
+ { id: "halo_1", from: "halo_n", to: "halo_ne" },
123
+ { id: "halo_2", from: "halo_ne", to: "halo_e" },
124
+ { id: "halo_3", from: "halo_e", to: "halo_se" },
125
+ { id: "halo_4", from: "halo_se", to: "halo_s" },
126
+ { id: "halo_5", from: "halo_s", to: "halo_sw" },
127
+ { id: "halo_6", from: "halo_sw", to: "halo_w" },
128
+ { id: "halo_7", from: "halo_w", to: "halo_nw" },
129
+ { id: "halo_8", from: "halo_nw", to: "halo_n" },
130
+ { id: "halo_to_outer_n", from: "halo_n", to: "outer_n" },
131
+ { id: "halo_to_outer_ne", from: "halo_ne", to: "outer_ne" },
132
+ { id: "halo_to_outer_e", from: "halo_e", to: "outer_e" },
133
+ { id: "halo_to_outer_se", from: "halo_se", to: "outer_se" },
134
+ { id: "halo_to_outer_s", from: "halo_s", to: "outer_s" },
135
+ { id: "halo_to_outer_sw", from: "halo_sw", to: "outer_sw" },
136
+ { id: "halo_to_outer_w", from: "halo_w", to: "outer_w" },
137
+ { id: "halo_to_outer_nw", from: "halo_nw", to: "outer_nw" },
138
+ { id: "outer_1", from: "outer_n", to: "outer_ne" },
139
+ { id: "outer_2", from: "outer_ne", to: "outer_e" },
140
+ { id: "outer_3", from: "outer_e", to: "outer_se" },
141
+ { id: "outer_4", from: "outer_se", to: "outer_s" },
142
+ { id: "outer_5", from: "outer_s", to: "outer_sw" },
143
+ { id: "outer_6", from: "outer_sw", to: "outer_w" },
144
+ { id: "outer_7", from: "outer_w", to: "outer_nw" },
145
+ { id: "outer_8", from: "outer_nw", to: "outer_n" },
146
+ { id: "outer_to_ring_n", from: "outer_n", to: "ring_n" },
147
+ { id: "outer_to_ring_ne", from: "outer_ne", to: "ring_ne" },
148
+ { id: "outer_to_ring_e", from: "outer_e", to: "ring_e" },
149
+ { id: "outer_to_ring_se", from: "outer_se", to: "ring_se" },
150
+ { id: "outer_to_ring_s", from: "outer_s", to: "ring_s" },
151
+ { id: "outer_to_ring_sw", from: "outer_sw", to: "ring_sw" },
152
+ { id: "outer_to_ring_w", from: "outer_w", to: "ring_w" },
153
+ { id: "outer_to_ring_nw", from: "outer_nw", to: "ring_nw" },
154
+ { id: "ring_1", from: "ring_n", to: "ring_ne" },
155
+ { id: "ring_2", from: "ring_ne", to: "ring_e" },
156
+ { id: "ring_3", from: "ring_e", to: "ring_se" },
157
+ { id: "ring_4", from: "ring_se", to: "ring_s" },
158
+ { id: "ring_5", from: "ring_s", to: "ring_sw" },
159
+ { id: "ring_6", from: "ring_sw", to: "ring_w" },
160
+ { id: "ring_7", from: "ring_w", to: "ring_nw" },
161
+ { id: "ring_8", from: "ring_nw", to: "ring_n" },
162
+ { id: "spoke_n", from: "ring_n", to: "core" },
163
+ { id: "spoke_e", from: "ring_e", to: "core" },
164
+ { id: "spoke_s", from: "ring_s", to: "core" },
165
+ { id: "spoke_w", from: "ring_w", to: "core" },
166
+ { id: "mid_ring_1", from: "mid_n", to: "mid_ne" },
167
+ { id: "mid_ring_2", from: "mid_ne", to: "mid_e" },
168
+ { id: "mid_ring_3", from: "mid_e", to: "mid_se" },
169
+ { id: "mid_ring_4", from: "mid_se", to: "mid_s" },
170
+ { id: "mid_ring_5", from: "mid_s", to: "mid_sw" },
171
+ { id: "mid_ring_6", from: "mid_sw", to: "mid_w" },
172
+ { id: "mid_ring_7", from: "mid_w", to: "mid_nw" },
173
+ { id: "mid_ring_8", from: "mid_nw", to: "mid_n" },
174
+ { id: "mid_spoke_n", from: "mid_n", to: "core" },
175
+ { id: "mid_spoke_e", from: "mid_e", to: "core" },
176
+ { id: "mid_spoke_s", from: "mid_s", to: "core" },
177
+ { id: "mid_spoke_w", from: "mid_w", to: "core" },
178
+ { id: "mid_to_outer_n", from: "mid_n", to: "ring_n" },
179
+ { id: "mid_to_outer_e", from: "mid_e", to: "ring_e" },
180
+ { id: "mid_to_outer_s", from: "mid_s", to: "ring_s" },
181
+ { id: "mid_to_outer_w", from: "mid_w", to: "ring_w" },
182
+ { id: "left_a_chain_1", from: "left_a0", to: "left_a1" },
183
+ { id: "left_a_chain_0", from: "left_bridge_top", to: "left_a0" },
184
+ { id: "left_a_chain_2", from: "left_a1", to: "left_a2" },
185
+ { id: "left_a_chain_3", from: "left_a2", to: "left_a3" },
186
+ { id: "left_a_chain_4", from: "left_a3", to: "left_tip" },
187
+ { id: "left_b_chain_1", from: "left_b0", to: "left_b3" },
188
+ { id: "left_b_chain_4", from: "left_b3", to: "left_tip" },
189
+ { id: "left_c_chain_1", from: "left_c0", to: "left_c3" },
190
+ { id: "left_c_chain_4", from: "left_c3", to: "left_tip" },
191
+ { id: "left_tail_chain_1", from: "left_tip", to: "left_tail_1" },
192
+ { id: "left_tail_chain_2", from: "left_tail_1", to: "left_tail_2" },
193
+ { id: "left_tail_chain_3", from: "left_tail_2", to: "left_tail_3" },
194
+ { id: "left_tail_chain_4", from: "left_tail_3", to: "left_tail_4" },
195
+ { id: "left_tri_1", from: "left_a0", to: "left_b0" },
196
+ { id: "left_tri_2", from: "left_b0", to: "left_c0" },
197
+ { id: "left_tri_7", from: "left_a3", to: "left_b3" },
198
+ { id: "left_tri_8", from: "left_b3", to: "left_c3" },
199
+ { id: "left_tri_9", from: "left_a3", to: "left_tip" },
200
+ { id: "left_tri_10", from: "left_c3", to: "left_tip" },
201
+ { id: "left_cross_1", from: "left_b0", to: "left_a1" },
202
+ { id: "left_cross_7", from: "left_b3", to: "left_tip" },
203
+ { id: "left_cross_8", from: "left_c3", to: "left_tip" },
204
+ { id: "left_attach_1", from: "left_attach_top", to: "left_a0" },
205
+ { id: "left_attach_1b", from: "left_attach_top", to: "left_bridge_top" },
206
+ { id: "left_attach_2", from: "left_attach_top", to: "left_b0" },
207
+ { id: "left_attach_3", from: "left_attach_bottom", to: "left_b0" },
208
+ { id: "left_attach_4", from: "left_attach_bottom", to: "left_c0" },
209
+ { id: "left_to_outer_1", from: "left_attach_top", to: "halo_nw" },
210
+ { id: "left_to_outer_2", from: "left_attach_bottom", to: "halo_nw" },
211
+ { id: "left_to_outer_3", from: "left_c0", to: "halo_w" },
212
+ { id: "left_to_outer_4", from: "left_c0", to: "halo_nw" },
213
+ { id: "left_to_outer_5", from: "left_a0", to: "halo_nw" },
214
+ { id: "left_to_outer_11", from: "left_bridge_top", to: "halo_nw" },
215
+ { id: "left_to_outer_12", from: "left_bridge_top", to: "halo_n" },
216
+ { id: "left_to_outer_6", from: "left_b0", to: "halo_nw" },
217
+ { id: "left_to_outer_10", from: "left_c3", to: "halo_nw" },
218
+
219
+ { id: "right_a_chain_1", from: "right_a0", to: "right_a1" },
220
+ { id: "right_a_chain_2", from: "right_a1", to: "right_a2" },
221
+ { id: "right_a_chain_3", from: "right_a2", to: "right_a3" },
222
+ { id: "right_a_chain_4", from: "right_a3", to: "right_a4" },
223
+ { id: "right_a_chain_5", from: "right_a4", to: "right_a5" },
224
+ { id: "right_b_chain_1", from: "right_b0", to: "right_b1" },
225
+ { id: "right_b_chain_2", from: "right_b1", to: "right_b2" },
226
+ { id: "right_b_chain_3", from: "right_b2", to: "right_b3" },
227
+ { id: "right_b_chain_4", from: "right_b3", to: "right_b4" },
228
+ { id: "right_b_chain_5", from: "right_b4", to: "right_b5" },
229
+ { id: "right_c_chain_1", from: "right_c0", to: "right_c1" },
230
+ { id: "right_c_chain_2", from: "right_c1", to: "right_c2" },
231
+ { id: "right_c_chain_3", from: "right_c2", to: "right_c3" },
232
+ { id: "right_c_chain_4", from: "right_c3", to: "right_c4" },
233
+ { id: "right_c_chain_5", from: "right_c4", to: "right_c5" },
234
+ { id: "right_tri_1", from: "right_a0", to: "right_b0" },
235
+ { id: "right_tri_2", from: "right_a1", to: "right_b1" },
236
+ { id: "right_tri_3", from: "right_a2", to: "right_b2" },
237
+ { id: "right_tri_4", from: "right_a3", to: "right_b3" },
238
+ { id: "right_tri_5", from: "right_a4", to: "right_b4" },
239
+ { id: "right_tri_6", from: "right_a5", to: "right_b5" },
240
+ { id: "right_thick_1", from: "right_b0", to: "right_c0" },
241
+ { id: "right_thick_2", from: "right_b1", to: "right_c1" },
242
+ { id: "right_thick_3", from: "right_b2", to: "right_c2" },
243
+ { id: "right_thick_4", from: "right_b3", to: "right_c3" },
244
+ { id: "right_thick_5", from: "right_b4", to: "right_c4" },
245
+ { id: "right_thick_6", from: "right_b5", to: "right_c5" },
246
+ { id: "right_cross_1", from: "right_b0", to: "right_a1" },
247
+ { id: "right_cross_2", from: "right_b1", to: "right_a2" },
248
+ { id: "right_cross_3", from: "right_b2", to: "right_a3" },
249
+ { id: "right_cross_4", from: "right_b3", to: "right_a4" },
250
+ { id: "right_cross_5", from: "right_b4", to: "right_a5" },
251
+ { id: "right_cross_7", from: "right_c1", to: "right_b2" },
252
+ { id: "right_cross_8", from: "right_c2", to: "right_b3" },
253
+ { id: "right_cross_9", from: "right_c3", to: "right_b4" },
254
+ { id: "right_cross_10", from: "right_c4", to: "right_b5" },
255
+ { id: "right_attach_3", from: "right_attach_bottom", to: "right_b0" },
256
+ { id: "right_attach_4", from: "right_attach_bottom", to: "right_c0" },
257
+ { id: "right_to_outer_12", from: "right_bridge_top", to: "halo_ne" },
258
+ { id: "right_to_outer_13", from: "right_bridge_top", to: "halo_e" },
259
+ { id: "right_to_outer_14", from: "right_bridge_peak", to: "halo_ne" },
260
+ { id: "right_to_outer_15", from: "right_bridge_peak", to: "halo_n" },
261
+ { id: "right_bridge_chain_1", from: "right_bridge_top", to: "right_bridge_peak" },
262
+
263
+ { id: "corridor_chain_1", from: "corridor_0", to: "corridor_1" },
264
+ { id: "corridor_chain_2", from: "corridor_1", to: "corridor_2" },
265
+ { id: "corridor_chain_3", from: "corridor_2", to: "corridor_3" },
266
+ { id: "corridor_chain_4", from: "corridor_3", to: "corridor_4" },
267
+ { id: "corridor_chain_5", from: "corridor_4", to: "corridor_5" },
268
+ { id: "corridor_chain_6", from: "corridor_5", to: "corridor_6" },
269
+ { id: "corridor_to_arm_1", from: "corridor_0", to: "right_c1" },
270
+ { id: "corridor_to_arm_2", from: "corridor_1", to: "right_c2" },
271
+ { id: "corridor_to_arm_3", from: "corridor_2", to: "right_c3" },
272
+ { id: "corridor_to_arm_4", from: "corridor_3", to: "right_b2" },
273
+ { id: "corridor_to_arm_5", from: "corridor_4", to: "right_b4" },
274
+ { id: "corridor_to_arm_6", from: "corridor_5", to: "right_a5" },
275
+ { id: "corridor_to_arm_7", from: "corridor_5", to: "right_c3" },
276
+ { id: "corridor_to_arm_8", from: "corridor_5", to: "right_b2" },
277
+ ];
278
+
279
+ export const BLOG_DEFENSE_COMPROMISE_PATH: string[] = [
280
+ "corridor_6",
281
+ "corridor_5",
282
+ "corridor_4",
283
+ "corridor_3",
284
+ "right_b3",
285
+ "right_b2",
286
+ "right_b1",
287
+ "right_b0",
288
+ "ring_s",
289
+ "mid_s",
290
+ "core",
291
+ ];
292
+
293
+ export type BlogDefenseCompromiseStep =
294
+ | { kind: "node"; nodeId: string }
295
+ | { kind: "edge"; from: string; to: string; edgeId: string };
296
+
297
+ function edgeKey(a: string, b: string): string {
298
+ return a < b ? `${a}::${b}` : `${b}::${a}`;
299
+ }
300
+
301
+ function normalizeAngle(angle: number): number {
302
+ while (angle > Math.PI) angle -= Math.PI * 2;
303
+ while (angle < -Math.PI) angle += Math.PI * 2;
304
+ return angle;
305
+ }
306
+
307
+ function buildGraphIndex(edges: BlogDefenseEdge[]): {
308
+ adjacency: Map<string, string[]>;
309
+ edgeByNodes: Map<string, BlogDefenseEdge>;
310
+ } {
311
+ const adjacency = new Map<string, string[]>();
312
+ const edgeByNodes = new Map<string, BlogDefenseEdge>();
313
+ for (const edge of edges) {
314
+ if (!adjacency.has(edge.from)) adjacency.set(edge.from, []);
315
+ if (!adjacency.has(edge.to)) adjacency.set(edge.to, []);
316
+ adjacency.get(edge.from)?.push(edge.to);
317
+ adjacency.get(edge.to)?.push(edge.from);
318
+ edgeByNodes.set(edgeKey(edge.from, edge.to), edge);
319
+ }
320
+ return { adjacency, edgeByNodes };
321
+ }
322
+
323
+ export function getBlogDefensePathEdge(from: string, to: string): BlogDefenseEdge | undefined {
324
+ return BLOG_DEFENSE_EDGES.find(
325
+ (edge) => (edge.from === from && edge.to === to) || (edge.from === to && edge.to === from),
326
+ );
327
+ }
328
+
329
+ export function buildCompromiseSequenceFromPath(nodeIds: string[], edges: BlogDefenseEdge[]): BlogDefenseCompromiseStep[] {
330
+ const steps: BlogDefenseCompromiseStep[] = [];
331
+ if (!nodeIds.length) return steps;
332
+ const { edgeByNodes } = buildGraphIndex(edges);
333
+ steps.push({ kind: "node", nodeId: nodeIds[0] });
334
+ for (let index = 0; index < nodeIds.length - 1; index += 1) {
335
+ const from = nodeIds[index];
336
+ const to = nodeIds[index + 1];
337
+ const edge = edgeByNodes.get(edgeKey(from, to));
338
+ if (!edge) continue;
339
+ steps.push({ kind: "edge", from, to, edgeId: edge.id });
340
+ steps.push({ kind: "node", nodeId: to });
341
+ }
342
+ return steps;
343
+ }
344
+
345
+ function shortestPathWithTieBreak(
346
+ startId: string,
347
+ targetId: string,
348
+ nodeById: Map<string, BlogDefenseNode>,
349
+ adjacency: Map<string, string[]>,
350
+ blockedNodeIds: Set<string> = new Set(),
351
+ ): string[] {
352
+ const queue: string[] = [startId];
353
+ const visited = new Set<string>([startId]);
354
+ const prev = new Map<string, string>();
355
+ const targetNode = nodeById.get(targetId);
356
+ while (queue.length) {
357
+ const current = queue.shift();
358
+ if (!current) break;
359
+ if (current === targetId) break;
360
+ const neighbors = [...(adjacency.get(current) ?? [])];
361
+ neighbors.sort((a, b) => {
362
+ const aNode = nodeById.get(a);
363
+ const bNode = nodeById.get(b);
364
+ if (!aNode || !bNode || !targetNode) return 0;
365
+ const aDist = Math.hypot(aNode.x - targetNode.x, aNode.y - targetNode.y);
366
+ const bDist = Math.hypot(bNode.x - targetNode.x, bNode.y - targetNode.y);
367
+ return aDist - bDist;
368
+ });
369
+ for (const neighbor of neighbors) {
370
+ if (neighbor !== targetId && blockedNodeIds.has(neighbor)) continue;
371
+ if (visited.has(neighbor)) continue;
372
+ visited.add(neighbor);
373
+ prev.set(neighbor, current);
374
+ queue.push(neighbor);
375
+ }
376
+ }
377
+
378
+ if (!visited.has(targetId)) return [];
379
+ const path: string[] = [targetId];
380
+ let current = targetId;
381
+ while (current !== startId) {
382
+ const parent = prev.get(current);
383
+ if (!parent) return [];
384
+ path.unshift(parent);
385
+ current = parent;
386
+ }
387
+ return path;
388
+ }
389
+
390
+ function nodeApproachPriority(node: BlogDefenseNode): number {
391
+ if (node.id.startsWith("mid_")) return 0;
392
+ if (node.id.startsWith("ring_")) return 1;
393
+ if (node.id.startsWith("outer_")) return 2;
394
+ if (node.id.startsWith("halo_")) return 3;
395
+ return 4;
396
+ }
397
+
398
+ function resolveCoreApproachCandidates(
399
+ startId: string,
400
+ coreId: string,
401
+ nodeById: Map<string, BlogDefenseNode>,
402
+ adjacency: Map<string, string[]>,
403
+ ): string[] {
404
+ const coreNode = nodeById.get(coreId);
405
+ const startNode = nodeById.get(startId);
406
+ if (!coreNode || !startNode) return [];
407
+
408
+ const startAngle = Math.atan2(startNode.y - coreNode.y, startNode.x - coreNode.x);
409
+ return [...(adjacency.get(coreId) ?? [])]
410
+ .map((id) => nodeById.get(id))
411
+ .filter((node): node is BlogDefenseNode => Boolean(node))
412
+ .sort((left, right) => {
413
+ const leftPriority = nodeApproachPriority(left);
414
+ const rightPriority = nodeApproachPriority(right);
415
+ if (leftPriority !== rightPriority) return leftPriority - rightPriority;
416
+
417
+ const leftAngle = Math.atan2(left.y - coreNode.y, left.x - coreNode.x);
418
+ const rightAngle = Math.atan2(right.y - coreNode.y, right.x - coreNode.x);
419
+ const leftDelta = Math.abs(normalizeAngle(leftAngle - startAngle));
420
+ const rightDelta = Math.abs(normalizeAngle(rightAngle - startAngle));
421
+ if (Math.abs(leftDelta - rightDelta) > 0.001) return leftDelta - rightDelta;
422
+
423
+ const leftDistance = Math.hypot(startNode.x - left.x, startNode.y - left.y);
424
+ const rightDistance = Math.hypot(startNode.x - right.x, startNode.y - right.y);
425
+ return leftDistance - rightDistance;
426
+ })
427
+ .map((node) => node.id);
428
+ }
429
+
430
+ function buildPathToCoreWithPreferredApproach(
431
+ startId: string,
432
+ coreId: string,
433
+ nodeById: Map<string, BlogDefenseNode>,
434
+ adjacency: Map<string, string[]>,
435
+ ): string[] {
436
+ const approachCandidates = resolveCoreApproachCandidates(startId, coreId, nodeById, adjacency);
437
+ for (const approachId of approachCandidates) {
438
+ const approachPath = shortestPathWithTieBreak(startId, approachId, nodeById, adjacency, new Set([coreId]));
439
+ if (!approachPath.length) continue;
440
+ if (!(adjacency.get(approachId) ?? []).includes(coreId)) continue;
441
+ return [...approachPath, coreId];
442
+ }
443
+
444
+ return shortestPathWithTieBreak(startId, coreId, nodeById, adjacency);
445
+ }
446
+
447
+ function hopDistanceToCore(
448
+ coreId: string,
449
+ adjacency: Map<string, string[]>,
450
+ blockedNodeIds: Set<string> = new Set(),
451
+ ): Map<string, number> {
452
+ const distances = new Map<string, number>();
453
+ const queue: string[] = [coreId];
454
+ distances.set(coreId, 0);
455
+
456
+ while (queue.length) {
457
+ const current = queue.shift();
458
+ if (!current) break;
459
+ const currentDistance = distances.get(current) ?? 0;
460
+ for (const neighbor of adjacency.get(current) ?? []) {
461
+ if (neighbor !== coreId && blockedNodeIds.has(neighbor)) continue;
462
+ if (distances.has(neighbor)) continue;
463
+ distances.set(neighbor, currentDistance + 1);
464
+ queue.push(neighbor);
465
+ }
466
+ }
467
+
468
+ return distances;
469
+ }
470
+
471
+ function chooseWeightedIndex(weights: number[]): number {
472
+ const total = weights.reduce((sum, weight) => sum + (weight > 0 ? weight : 0), 0);
473
+ if (total <= 0) return 0;
474
+ let roll = Math.random() * total;
475
+ for (let index = 0; index < weights.length; index += 1) {
476
+ roll -= Math.max(0, weights[index]);
477
+ if (roll <= 0) return index;
478
+ }
479
+ return Math.max(0, weights.length - 1);
480
+ }
481
+
482
+ function buildStochasticInwardPath(
483
+ startId: string,
484
+ coreId: string,
485
+ nodeById: Map<string, BlogDefenseNode>,
486
+ adjacency: Map<string, string[]>,
487
+ ): string[] {
488
+ const approachCandidates = resolveCoreApproachCandidates(startId, coreId, nodeById, adjacency);
489
+ const preferredApproachId = approachCandidates[0];
490
+ if (!preferredApproachId) {
491
+ return shortestPathWithTieBreak(startId, coreId, nodeById, adjacency);
492
+ }
493
+
494
+ const distances = hopDistanceToCore(preferredApproachId, adjacency, new Set([coreId]));
495
+ if (!distances.has(startId)) return [];
496
+ if (startId === preferredApproachId) return [preferredApproachId, coreId];
497
+
498
+ const maxSteps = Math.max(10, Math.min(40, nodeById.size + 6));
499
+ const path = [startId];
500
+ const visitCounts = new Map<string, number>([[startId, 1]]);
501
+ let current = startId;
502
+
503
+ for (let step = 0; step < maxSteps && current !== preferredApproachId; step += 1) {
504
+ const neighbors = (adjacency.get(current) ?? []).filter((neighbor) => distances.has(neighbor));
505
+ if (!neighbors.length) break;
506
+
507
+ const currentHop = distances.get(current) ?? Number.POSITIVE_INFINITY;
508
+ const improving = neighbors.filter((neighbor) => (distances.get(neighbor) ?? Number.POSITIVE_INFINITY) < currentHop);
509
+ const candidatePool = improving.length && Math.random() < 0.82 ? improving : neighbors;
510
+
511
+ const weights = candidatePool.map((neighbor) => {
512
+ const neighborHop = distances.get(neighbor) ?? Number.POSITIVE_INFINITY;
513
+ const hopDelta = currentHop - neighborHop;
514
+ const revisitPenalty = 1 / (1 + (visitCounts.get(neighbor) ?? 0) * 1.8);
515
+ const core = nodeById.get(preferredApproachId);
516
+ const neighborNode = nodeById.get(neighbor);
517
+ const euclideanBias = core && neighborNode
518
+ ? 1 / (1 + Math.hypot(neighborNode.x - core.x, neighborNode.y - core.y) * 0.015)
519
+ : 1;
520
+
521
+ let bias = 1;
522
+ if (hopDelta > 0) bias = 5 + hopDelta * 1.8;
523
+ else if (hopDelta === 0) bias = 0.9;
524
+ else bias = 0.18;
525
+ if (neighbor === preferredApproachId) bias *= 4;
526
+
527
+ return Math.max(0.01, bias * revisitPenalty * euclideanBias);
528
+ });
529
+
530
+ const next = candidatePool[chooseWeightedIndex(weights)];
531
+ path.push(next);
532
+ visitCounts.set(next, (visitCounts.get(next) ?? 0) + 1);
533
+ current = next;
534
+
535
+ if (current === preferredApproachId) return [...path, coreId];
536
+ }
537
+
538
+ if (current !== preferredApproachId) {
539
+ const completion = shortestPathWithTieBreak(current, preferredApproachId, nodeById, adjacency, new Set([coreId]));
540
+ if (completion.length > 1) path.push(...completion.slice(1));
541
+ }
542
+
543
+ return path[path.length - 1] === preferredApproachId ? [...path, coreId] : [];
544
+ }
545
+
546
+ export function buildVisibleAttackPath(input: VisibleAttackPathInput): VisibleAttackPath {
547
+ const coreId = input.coreId ?? BLOG_DEFENSE_CORE_NODE_ID;
548
+ const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
549
+ const visibleEdges = input.edges.filter((edge) => nodeById.has(edge.from) && nodeById.has(edge.to));
550
+ const { adjacency, edgeByNodes } = buildGraphIndex(visibleEdges);
551
+
552
+ const corridorCandidates = input.nodes
553
+ .filter((node) => node.zone === "right_corridor")
554
+ .sort((a, b) => {
555
+ if (b.y !== a.y) return b.y - a.y;
556
+ return b.x - a.x;
557
+ });
558
+
559
+ const starts = corridorCandidates.length
560
+ ? corridorCandidates
561
+ : input.nodes.slice().sort((a, b) => {
562
+ if (b.y !== a.y) return b.y - a.y;
563
+ return b.x - a.x;
564
+ });
565
+
566
+ if (input.stochastic && starts.length) {
567
+ const stochasticStartWeights = starts.map((node, index) => {
568
+ const yBias = Math.max(1, node.y);
569
+ const rankBias = Math.max(0.35, 1 - index * 0.06);
570
+ return yBias * rankBias;
571
+ });
572
+ const stochasticStart = starts[chooseWeightedIndex(stochasticStartWeights)];
573
+ const stochasticPath = buildStochasticInwardPath(stochasticStart.id, coreId, nodeById, adjacency);
574
+ if (stochasticPath.length) {
575
+ const sequence = buildCompromiseSequenceFromPath(stochasticPath, visibleEdges);
576
+ return {
577
+ nodeIds: sequence.filter((step) => step.kind === "node").map((step) => step.nodeId),
578
+ edgeIds: sequence.filter((step) => step.kind === "edge").map((step) => step.edgeId),
579
+ };
580
+ }
581
+ }
582
+
583
+ for (const start of starts) {
584
+ const path = buildPathToCoreWithPreferredApproach(start.id, coreId, nodeById, adjacency);
585
+ if (!path.length) continue;
586
+ const sequence = buildCompromiseSequenceFromPath(path, visibleEdges);
587
+ return {
588
+ nodeIds: sequence.filter((step) => step.kind === "node").map((step) => step.nodeId),
589
+ edgeIds: sequence.filter((step) => step.kind === "edge").map((step) => step.edgeId),
590
+ };
591
+ }
592
+
593
+ const fallback = input.nodes
594
+ .filter((node) => node.id !== coreId)
595
+ .sort((a, b) => {
596
+ const core = nodeById.get(coreId);
597
+ if (!core) return 0;
598
+ const aDist = Math.hypot(a.x - core.x, a.y - core.y);
599
+ const bDist = Math.hypot(b.x - core.x, b.y - core.y);
600
+ return bDist - aDist;
601
+ });
602
+ for (const start of fallback) {
603
+ const path = buildPathToCoreWithPreferredApproach(start.id, coreId, nodeById, adjacency);
604
+ if (!path.length) continue;
605
+ const sequence = buildCompromiseSequenceFromPath(path, visibleEdges);
606
+ return {
607
+ nodeIds: sequence.filter((step) => step.kind === "node").map((step) => step.nodeId),
608
+ edgeIds: sequence.filter((step) => step.kind === "edge").map((step) => step.edgeId),
609
+ };
610
+ }
611
+
612
+ return {
613
+ nodeIds: nodeById.has(coreId) ? [coreId] : [],
614
+ edgeIds: [],
615
+ };
616
+ }
617
+
618
+ export function buildBlogDefenseCompromiseSequence(): BlogDefenseCompromiseStep[] {
619
+ return buildCompromiseSequenceFromPath(BLOG_DEFENSE_COMPROMISE_PATH, BLOG_DEFENSE_EDGES);
620
+ }
621
+
622
+ export function isBlogDefenseAttackEdge(from: string, to: string): boolean {
623
+ for (let index = 0; index < BLOG_DEFENSE_COMPROMISE_PATH.length - 1; index += 1) {
624
+ const a = BLOG_DEFENSE_COMPROMISE_PATH[index];
625
+ const b = BLOG_DEFENSE_COMPROMISE_PATH[index + 1];
626
+ if ((from === a && to === b) || (from === b && to === a)) return true;
627
+ }
628
+ return false;
629
+ }
630
+
631
+ export function edgeIdForNodes(from: string, to: string, edges: BlogDefenseEdge[]): string | null {
632
+ const edge = edges.find((entry) => (
633
+ (entry.from === from && entry.to === to) || (entry.from === to && entry.to === from)
634
+ ));
635
+ return edge ? edge.id : null;
636
+ }