2sagaco48 1.0.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.
- package/diagnostics.mjs +141 -0
- package/dist/compute.worker-j5ahIpF-.js +1 -0
- package/dist/graph.worker-DG6iGCB9.js +1 -0
- package/dist/index.html +2314 -0
- package/flatten-guide.md +248 -0
- package/index.html +12 -0
- package/package.json +38 -0
- package/src/App.orig.tsx +204 -0
- package/src/App.orig1.tsx +27 -0
- package/src/App.orig2.tsx +25 -0
- package/src/App.tsx +3 -0
- package/src/BaseApp.tsx +18 -0
- package/src/INTEGRATION_LEDGER.md +22 -0
- package/src/PERSIST_CANARY.orig.txt +1 -0
- package/src/PERSIST_CANARY.txt +1 -0
- package/src/REGRESSION_LEDGER.md +651 -0
- package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
- package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
- package/src/chat/VeritasChat.tsx +69 -0
- package/src/chat/synthesis.ts +31 -0
- package/src/chat/tier.ts +45 -0
- package/src/components/AdaptersPage.orig.tsx +99 -0
- package/src/components/AdaptersPage.tsx +3 -0
- package/src/components/AdversarialPanel.tsx +136 -0
- package/src/components/AnchorBaselinePanel.tsx +75 -0
- package/src/components/AnswerPanel.tsx +60 -0
- package/src/components/ArtifactPanel.tsx +43 -0
- package/src/components/ChatApp.orig.tsx +1582 -0
- package/src/components/ChatApp.orig1.tsx +722 -0
- package/src/components/ChatApp.tsx +95 -0
- package/src/components/ChatAugmentPanels.tsx +73 -0
- package/src/components/CitationLedgerPanel.tsx +313 -0
- package/src/components/ComputeSandboxPanel.tsx +130 -0
- package/src/components/ConfigPanel.tsx +110 -0
- package/src/components/ControlPlanePage.tsx +195 -0
- package/src/components/CreativeTreeLifePage.tsx +234 -0
- package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
- package/src/components/DebugTracePanel.tsx +137 -0
- package/src/components/DeepReasoningTrace.tsx +48 -0
- package/src/components/EntitySheetPanel.tsx +51 -0
- package/src/components/FailureModesPanel.tsx +104 -0
- package/src/components/GBSDashboard.tsx +239 -0
- package/src/components/GraphView.tsx +116 -0
- package/src/components/HUD.tsx +125 -0
- package/src/components/HypothesisPanel.tsx +67 -0
- package/src/components/InnovationGenomeEngine.tsx +792 -0
- package/src/components/InnovationPersonaGuide.tsx +348 -0
- package/src/components/InnovationPersonaPanel.tsx +289 -0
- package/src/components/LiveResourceHUD.tsx +109 -0
- package/src/components/LongWriterPanel.tsx +112 -0
- package/src/components/MainPipelineV10Bridge.tsx +72 -0
- package/src/components/MarkdownLite.tsx +63 -0
- package/src/components/MemoryInspector.tsx +38 -0
- package/src/components/MemoryMonitor.tsx +52 -0
- package/src/components/MemoryStressPanel.tsx +176 -0
- package/src/components/ModulesPage.tsx +171 -0
- package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
- package/src/components/PipelineDebugConsole.tsx +3 -0
- package/src/components/PreFlightHUD.tsx +67 -0
- package/src/components/PrismaFetchTracePanel.tsx +87 -0
- package/src/components/ReportOSPanel.tsx +165 -0
- package/src/components/ResourceEstimatorPage.tsx +327 -0
- package/src/components/RichText.tsx +181 -0
- package/src/components/SagaOsPanel.tsx +605 -0
- package/src/components/SagaOsReference.tsx +421 -0
- package/src/components/SagaOsV2Panel.tsx +594 -0
- package/src/components/SagaStyleTab.tsx +382 -0
- package/src/components/SageBlueprintPanel.tsx +416 -0
- package/src/components/SharedChatInput.tsx +77 -0
- package/src/components/StatFinancePanel.tsx +190 -0
- package/src/components/StylePersonaPanel.tsx +128 -0
- package/src/components/TemplatesPage.tsx +300 -0
- package/src/components/TestPanel.tsx +63 -0
- package/src/components/TraceLog.tsx +89 -0
- package/src/components/V15CalibrationAugment.tsx +1134 -0
- package/src/components/V15CalibrationDialog.tsx +1871 -0
- package/src/components/V15Overlay.orig.tsx +120 -0
- package/src/components/V15Overlay.tsx +4 -0
- package/src/components/V15OverlayWrapper.tsx +362 -0
- package/src/components/V15Toggle.tsx +34 -0
- package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
- package/src/console/EngineeringConsole.tsx +103 -0
- package/src/console/FictionSystemPanels.tsx +882 -0
- package/src/console/InspectorPanels.tsx +254 -0
- package/src/console/ManuscriptGraderPanel.tsx +182 -0
- package/src/console/RecursiveReviserPanel.tsx +245 -0
- package/src/console/SeedControlPanel.tsx +135 -0
- package/src/console/StoryControllerPanel.tsx +443 -0
- package/src/console/engineBridge.ts +258 -0
- package/src/console/gemini.ts +106 -0
- package/src/console/grading.ts +250 -0
- package/src/console/util.ts +45 -0
- package/src/fiction/continuity.ts +372 -0
- package/src/fiction/crash-protection.ts +111 -0
- package/src/fiction/diagnostics.ts +208 -0
- package/src/fiction/diff.ts +74 -0
- package/src/fiction/entity-state-machine.ts +214 -0
- package/src/fiction/nive-engine.ts +404 -0
- package/src/fiction/pipeline-selftest.ts +132 -0
- package/src/fiction/seed-control.ts +193 -0
- package/src/fiction/story-engine.ts +326 -0
- package/src/fiction/types.ts +68 -0
- package/src/flatten-guide.md +379 -0
- package/src/index.css +14 -0
- package/src/index.orig.css +13 -0
- package/src/lib/academic-sources.base.ts +188 -0
- package/src/lib/academic-sources.orig.ts +28 -0
- package/src/lib/academic-sources.ts +10 -0
- package/src/lib/advanced-math.ts +119 -0
- package/src/lib/adversarial-engine.base.ts +409 -0
- package/src/lib/adversarial-engine.ts +82 -0
- package/src/lib/ai-revision.ts +623 -0
- package/src/lib/app-state.tsx +426 -0
- package/src/lib/artifact-registry.ts +97 -0
- package/src/lib/artifacts.ts +180 -0
- package/src/lib/atlas-dr.ts +167 -0
- package/src/lib/browser-mtls.ts +127 -0
- package/src/lib/browser-queue.ts +221 -0
- package/src/lib/browser-search-scraper.base.ts +382 -0
- package/src/lib/browser-search-scraper.orig.ts +90 -0
- package/src/lib/browser-search-scraper.ts +10 -0
- package/src/lib/calc-interceptor.ts +82 -0
- package/src/lib/citation-lane-tap.ts +78 -0
- package/src/lib/citation-ledger-store.ts +369 -0
- package/src/lib/citation-ledger.ts +203 -0
- package/src/lib/compute-sandbox.ts +215 -0
- package/src/lib/compute.worker.ts +153 -0
- package/src/lib/connectors/gemini.ts +120 -0
- package/src/lib/connectors/jina.ts +233 -0
- package/src/lib/connectors/marketdata.ts +51 -0
- package/src/lib/connectors/prismafetch.base.ts +175 -0
- package/src/lib/connectors/prismafetch.ts +1 -0
- package/src/lib/connectors/serpapi.ts +65 -0
- package/src/lib/connectors/wikidata.ts +44 -0
- package/src/lib/constraints.ts +331 -0
- package/src/lib/continuation-detector.base.ts +153 -0
- package/src/lib/continuation-detector.ts +48 -0
- package/src/lib/contradraft.ts +168 -0
- package/src/lib/cors-proxy.ts +120 -0
- package/src/lib/coverage.ts +81 -0
- package/src/lib/debug/architecture-prescription.ts +556 -0
- package/src/lib/debug/covea-repair.ts +543 -0
- package/src/lib/debug/helios-ground.ts +319 -0
- package/src/lib/debug/intent-decomposer.ts +281 -0
- package/src/lib/debug/intent-lattice.ts +349 -0
- package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
- package/src/lib/debug/pipeline-trace-bus.ts +424 -0
- package/src/lib/debug/prompt-forge.ts +678 -0
- package/src/lib/debug/repair-sites.ts +513 -0
- package/src/lib/debug/research-phase.ts +266 -0
- package/src/lib/debug/retrieval-context.ts +76 -0
- package/src/lib/debug/retrieval-hardener.ts +142 -0
- package/src/lib/debug/scraper-debug-runner.ts +576 -0
- package/src/lib/debug/scraper-forensics.ts +418 -0
- package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
- package/src/lib/debug/self-test.ts +415 -0
- package/src/lib/debug/step-attribution.ts +211 -0
- package/src/lib/debug/template-rubric.ts +301 -0
- package/src/lib/debug/unified-innovation.ts +126 -0
- package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
- package/src/lib/defense-registry.ts +104 -0
- package/src/lib/defenses.ts +162 -0
- package/src/lib/deterministic-citation-ledger.ts +174 -0
- package/src/lib/elo-registry.base.ts +117 -0
- package/src/lib/elo-registry.ts +1 -0
- package/src/lib/entity-resolver.ts +291 -0
- package/src/lib/failure-modes.ts +267 -0
- package/src/lib/feature-registry.ts +69 -0
- package/src/lib/flaw-registry.ts +143 -0
- package/src/lib/flaws/_template.ts +52 -0
- package/src/lib/flaws/builtins.ts +63 -0
- package/src/lib/flaws/finance.ts +169 -0
- package/src/lib/flaws/fixers.ts +10 -0
- package/src/lib/flaws/index.ts +32 -0
- package/src/lib/flaws/legal.ts +157 -0
- package/src/lib/flaws/medical.ts +188 -0
- package/src/lib/flaws/original-defenses-pack.ts +81 -0
- package/src/lib/flaws/sample-declarative-pack.json +38 -0
- package/src/lib/flaws/selftest.ts +201 -0
- package/src/lib/flaws/software-extended.ts +125 -0
- package/src/lib/flaws/software-rn-webgl.ts +182 -0
- package/src/lib/flaws/statistics-advanced.ts +46 -0
- package/src/lib/flaws/statistics.ts +61 -0
- package/src/lib/gbse/config.ts +20 -0
- package/src/lib/gbse/engine.ts +226 -0
- package/src/lib/gbse/graph.ts +118 -0
- package/src/lib/gbse/graph.worker.ts +21 -0
- package/src/lib/gbse/tests.ts +117 -0
- package/src/lib/gbse/tiers.ts +65 -0
- package/src/lib/gbse/types.ts +94 -0
- package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
- package/src/lib/innovation-genome-engine-v2.ts +3 -0
- package/src/lib/innovation-genome-engine.orig.ts +330 -0
- package/src/lib/innovation-genome-engine.ts +3 -0
- package/src/lib/innovation-genome-v10.orig.ts +314 -0
- package/src/lib/innovation-genome-v10.ts +2 -0
- package/src/lib/innovation-genome-v3.orig.ts +2015 -0
- package/src/lib/innovation-genome-v3.ts +2 -0
- package/src/lib/innovation-genome-v4.orig.ts +1959 -0
- package/src/lib/innovation-genome-v4.ts +2 -0
- package/src/lib/innovation-genome-v5.orig.ts +1210 -0
- package/src/lib/innovation-genome-v5.ts +2 -0
- package/src/lib/innovation-genome-v7.orig.ts +2549 -0
- package/src/lib/innovation-genome-v7.ts +2 -0
- package/src/lib/innovation-genome-v8.orig.ts +1485 -0
- package/src/lib/innovation-genome-v8.ts +2 -0
- package/src/lib/innovation-genome-v9.orig.ts +643 -0
- package/src/lib/innovation-genome-v9.ts +2 -0
- package/src/lib/jina.base.ts +293 -0
- package/src/lib/jina.ts +1 -0
- package/src/lib/live-telemetry.ts +61 -0
- package/src/lib/longwriter.ts +84 -0
- package/src/lib/manuscript-grade-v2.ts +298 -0
- package/src/lib/memory-governor.ts +173 -0
- package/src/lib/memory-stress-tests.ts +248 -0
- package/src/lib/model-intelligence.ts +48 -0
- package/src/lib/model-rotator.ts +75 -0
- package/src/lib/models.orig.ts +513 -0
- package/src/lib/models.ts +151 -0
- package/src/lib/n-deep.base.ts +592 -0
- package/src/lib/n-deep.ts +51 -0
- package/src/lib/nih-simulator.ts +56 -0
- package/src/lib/nih-vulnerability-fixes.ts +460 -0
- package/src/lib/omega-templates.base.ts +294 -0
- package/src/lib/omega-templates.ts +225 -0
- package/src/lib/omni-nexus.ts +64 -0
- package/src/lib/oracle-adapters.ts +67 -0
- package/src/lib/oracle-registry.ts +163 -0
- package/src/lib/orchestrator.ts +357 -0
- package/src/lib/output-boundary.ts +235 -0
- package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
- package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
- package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
- package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
- package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
- package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
- package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
- package/src/lib/overrides/vite-native-scraper.ts +848 -0
- package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
- package/src/lib/overrides/vite-native-selftest.ts +183 -0
- package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
- package/src/lib/philosophy-toolkit.ts +75 -0
- package/src/lib/pipeline.orig.ts +659 -0
- package/src/lib/pipeline.ts +42 -0
- package/src/lib/precache.ts +128 -0
- package/src/lib/py-sandbox.ts +205 -0
- package/src/lib/quality-score.ts +96 -0
- package/src/lib/quant-engine.ts +231 -0
- package/src/lib/quant-lib.ts +80 -0
- package/src/lib/reportos.ts +175 -0
- package/src/lib/research-os.ts +306 -0
- package/src/lib/resource-estimator.ts +114 -0
- package/src/lib/rpm-governor.ts +93 -0
- package/src/lib/runtime-estimator.ts +135 -0
- package/src/lib/sage/empty-space-v6.ts +560 -0
- package/src/lib/sage/empty-space-v7.ts +222 -0
- package/src/lib/sage/empty-space.ts +733 -0
- package/src/lib/sage/engine.ts +786 -0
- package/src/lib/sage/index.ts +17 -0
- package/src/lib/sage/judge.ts +246 -0
- package/src/lib/sage/prng.ts +53 -0
- package/src/lib/sage/reality-grounding.ts +380 -0
- package/src/lib/sage/repetition.ts +151 -0
- package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
- package/src/lib/sage/saga-os-v2.ts +274 -0
- package/src/lib/sage/saga-os-v3-core.ts +1491 -0
- package/src/lib/sage/saga-os-v3-release.ts +739 -0
- package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
- package/src/lib/sage/saga-os-v4.ts +212 -0
- package/src/lib/sage/saga-os-v5.ts +205 -0
- package/src/lib/sage/saga-os-v6.ts +346 -0
- package/src/lib/sage/saga-os-v7.ts +314 -0
- package/src/lib/sage/saga-os-v8.ts +505 -0
- package/src/lib/sage/saga-os.ts +329 -0
- package/src/lib/sage/taxonomy.ts +403 -0
- package/src/lib/sage/unified-roll.ts +524 -0
- package/src/lib/sage/worked-sketches.ts +44 -0
- package/src/lib/sage.ts +166 -0
- package/src/lib/scraper-debug-bus.ts +53 -0
- package/src/lib/scraper-enhanced.orig.ts +153 -0
- package/src/lib/scraper-enhanced.ts +10 -0
- package/src/lib/scraper-hardener.base.orig.ts +155 -0
- package/src/lib/scraper-hardener.base.ts +317 -0
- package/src/lib/scraper-hardener.ts +1 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
- package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
- package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
- package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
- package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
- package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
- package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
- package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
- package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
- package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
- package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
- package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
- package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
- package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
- package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
- package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
- package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
- package/src/lib/scraper-vnext/query-strategist.ts +423 -0
- package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
- package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
- package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
- package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
- package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
- package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
- package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
- package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
- package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
- package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
- package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
- package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
- package/src/lib/scraper-vnext/strata-engine.ts +13 -0
- package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
- package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
- package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
- package/src/lib/scraper-vnext/terminal-final.ts +277 -0
- package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
- package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
- package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
- package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
- package/src/lib/search-cache.ts +138 -0
- package/src/lib/sloop-runner.base.ts +223 -0
- package/src/lib/sloop-runner.ts +104 -0
- package/src/lib/sloop.ts +114 -0
- package/src/lib/small-model-adapters.ts +82 -0
- package/src/lib/sscp.ts +86 -0
- package/src/lib/system-map.ts +54 -0
- package/src/lib/uni-node.ts +86 -0
- package/src/lib/universal-rigor-guard.ts +433 -0
- package/src/lib/v15-gate-testbed.ts +274 -0
- package/src/lib/v15-gemini.base.ts +89 -0
- package/src/lib/v15-gemini.ts +1 -0
- package/src/lib/v15-grounding.orig.ts +241 -0
- package/src/lib/v15-grounding.ts +79 -0
- package/src/lib/v15-pipeline.base.ts +769 -0
- package/src/lib/v15-pipeline.orig.ts +1154 -0
- package/src/lib/v15-pipeline.ts +674 -0
- package/src/lib/v15-questions.ts +64 -0
- package/src/lib/v15-rate-limiter.orig.ts +91 -0
- package/src/lib/v15-rate-limiter.ts +48 -0
- package/src/lib/v15-state.base.ts +87 -0
- package/src/lib/v15-state.ts +28 -0
- package/src/lib/visual-table-generator.tsx +207 -0
- package/src/lib/wasm-runtime.ts +133 -0
- package/src/lib/williams-persona-guide.ts +265 -0
- package/src/lib/williams-style.base.ts +464 -0
- package/src/lib/williams-style.ts +59 -0
- package/src/lib/worker-pool.ts +96 -0
- package/src/lib/writing-tiers.ts +125 -0
- package/src/main.orig.tsx +10 -0
- package/src/main.tsx +10 -0
- package/src/utils/cn.orig.ts +6 -0
- package/src/utils/cn.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/unify.mjs +537 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAGE Engine — Story Archetype Generation Engine
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Seed-driven fiction blueprint compiler. Same contract family as Williams
|
|
5
|
+
* style persona:
|
|
6
|
+
* one integer seed → deterministic selection across the full taxonomy →
|
|
7
|
+
* a complete story blueprint + LLM-ready directive + chapter plan.
|
|
8
|
+
*
|
|
9
|
+
* Does NOT invent prose. It compiles structure. Generation is delegated to
|
|
10
|
+
* whatever LLM the host already uses. It has no other-engine dependency;
|
|
11
|
+
* story scale is caller-selected, never seed-picked.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
MACRO_STRUCTURES,
|
|
15
|
+
CAMPBELL_BEATS,
|
|
16
|
+
PROPP_FUNCTIONS,
|
|
17
|
+
MICRO_UNITS,
|
|
18
|
+
SETTINGS,
|
|
19
|
+
HUMAN_CHARS,
|
|
20
|
+
NONHUMAN_CHARS,
|
|
21
|
+
STORY_ELEMENTS,
|
|
22
|
+
LOAD_BEARING,
|
|
23
|
+
META_FRAMEWORKS,
|
|
24
|
+
CONFLICT_AXES,
|
|
25
|
+
POV_OPTIONS,
|
|
26
|
+
ENDING_TYPES,
|
|
27
|
+
TONE_PALETTE,
|
|
28
|
+
LENGTH_PROFILES,
|
|
29
|
+
TAXONOMY_TOTAL,
|
|
30
|
+
EXPANDED_TAXONOMY_TOTAL,
|
|
31
|
+
EXPANDED_TAXONOMY_BANDS,
|
|
32
|
+
type LengthId,
|
|
33
|
+
type Taxon,
|
|
34
|
+
} from "./taxonomy";
|
|
35
|
+
import { mulberry32, randPick, randPickN, newSageSeed, mix32 } from "./prng";
|
|
36
|
+
import { EMPTY_SPACE_BANDS, type EmptySpaceEntry } from "./empty-space";
|
|
37
|
+
import { buildSagaDirectiveBlock } from "./saga-os";
|
|
38
|
+
|
|
39
|
+
function pickFromBands(rng: () => number, keys: string[]): EmptySpaceEntry | null {
|
|
40
|
+
const pool = EMPTY_SPACE_BANDS.filter((b) => keys.includes(b.key)).flatMap((b) =>
|
|
41
|
+
b.rows.map(([id, name, desc, tag]) => ({ id, band: b.key, name, desc, tag })),
|
|
42
|
+
);
|
|
43
|
+
if (pool.length === 0) return null;
|
|
44
|
+
return randPick(rng, pool);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type SageCharacter = {
|
|
48
|
+
role: string;
|
|
49
|
+
archetype: Taxon;
|
|
50
|
+
want: string;
|
|
51
|
+
need: string;
|
|
52
|
+
wound: string;
|
|
53
|
+
voiceNote: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type SageChapterPlan = {
|
|
57
|
+
index: number;
|
|
58
|
+
title: string;
|
|
59
|
+
dramaticJob: string;
|
|
60
|
+
campbellBeat?: string;
|
|
61
|
+
proppFunctions: string[];
|
|
62
|
+
microBeats: string[];
|
|
63
|
+
settingFocus: string;
|
|
64
|
+
targetWords: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type SageLengthProfile = {
|
|
68
|
+
id: LengthId;
|
|
69
|
+
label: string;
|
|
70
|
+
chapters: number;
|
|
71
|
+
wordsPerChapter: number;
|
|
72
|
+
pages: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type SageBlueprint = {
|
|
76
|
+
seed: number;
|
|
77
|
+
titleHint: string;
|
|
78
|
+
premise: string;
|
|
79
|
+
theme: string;
|
|
80
|
+
dramaticQuestion: string;
|
|
81
|
+
macro: Taxon;
|
|
82
|
+
secondaryMacro: Taxon | null;
|
|
83
|
+
length: SageLengthProfile;
|
|
84
|
+
pov: string;
|
|
85
|
+
conflictAxis: string;
|
|
86
|
+
tone: string;
|
|
87
|
+
ending: string;
|
|
88
|
+
primarySetting: Taxon;
|
|
89
|
+
secondarySettings: Taxon[];
|
|
90
|
+
protagonist: SageCharacter;
|
|
91
|
+
deuteragonist: SageCharacter;
|
|
92
|
+
antagonist: SageCharacter;
|
|
93
|
+
ensemble: SageCharacter[];
|
|
94
|
+
nonhumanPresence: Taxon[];
|
|
95
|
+
campbellPath: Taxon[];
|
|
96
|
+
proppSequence: { id: string; name: string }[];
|
|
97
|
+
microDevices: Taxon[];
|
|
98
|
+
storyElements: Taxon[];
|
|
99
|
+
loadBearing: Taxon[];
|
|
100
|
+
metaFrameworkHint: string;
|
|
101
|
+
chapters: SageChapterPlan[];
|
|
102
|
+
growthAxes: SageGrowthAxes;
|
|
103
|
+
rarityTier: SageTier;
|
|
104
|
+
rarityPercent: number;
|
|
105
|
+
catalogCoverage: number;
|
|
106
|
+
cadence: SageProseCadence;
|
|
107
|
+
worldMechanism: EmptySpaceEntry | null;
|
|
108
|
+
beatMechanism: EmptySpaceEntry | null;
|
|
109
|
+
relationshipMechanism: EmptySpaceEntry | null;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Continuous 0–1 axes that steer development inside SAGE only. */
|
|
113
|
+
export type SageGrowthAxes = {
|
|
114
|
+
mythicDensity: number;
|
|
115
|
+
interiority: number;
|
|
116
|
+
structuralComplexity: number;
|
|
117
|
+
moralAmbiguity: number;
|
|
118
|
+
worldBuildingWeight: number;
|
|
119
|
+
tempo: number;
|
|
120
|
+
ironyDistance: number;
|
|
121
|
+
ensembleBreadth: number;
|
|
122
|
+
motifRecurrence: number;
|
|
123
|
+
endingOpenness: number;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type SageTier = "Legendary" | "Epic" | "Rare" | "Uncommon" | "Common";
|
|
127
|
+
|
|
128
|
+
/** Seed-driven controllable variation for sentence/paragraph/chapter rhythm. */
|
|
129
|
+
export type SageProseCadence = {
|
|
130
|
+
seed: number;
|
|
131
|
+
sentenceMinWords: number;
|
|
132
|
+
sentenceMaxWords: number;
|
|
133
|
+
paragraphMinSentences: number;
|
|
134
|
+
paragraphMaxSentences: number;
|
|
135
|
+
chapterVarianceMinPct: number;
|
|
136
|
+
chapterVarianceMaxPct: number;
|
|
137
|
+
motifRepetitionCap: number;
|
|
138
|
+
label: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export type SageRollOpts = {
|
|
142
|
+
seed?: number;
|
|
143
|
+
lengthId?: LengthId;
|
|
144
|
+
chapters?: number;
|
|
145
|
+
wordsPerChapter?: number;
|
|
146
|
+
premise?: string;
|
|
147
|
+
/** Optional locked macro id (e.g. "M01"). */
|
|
148
|
+
forceMacroId?: string;
|
|
149
|
+
/** Independent reroll for prose cadence; defaults to derive from `seed`. */
|
|
150
|
+
cadenceSeed?: number;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const WANTS = [
|
|
154
|
+
"to reclaim a stolen birthright",
|
|
155
|
+
"to prevent a prophecy from completing",
|
|
156
|
+
"to map a forbidden frontier",
|
|
157
|
+
"to atone for a hidden betrayal",
|
|
158
|
+
"to protect a fragile community",
|
|
159
|
+
"to master a dangerous craft",
|
|
160
|
+
"to expose a legitimized lie",
|
|
161
|
+
"to reverse an irreversible loss",
|
|
162
|
+
"to win a contest whose rules keep changing",
|
|
163
|
+
"to return home with a truth no one wants",
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const NEEDS = [
|
|
167
|
+
"to accept limits without surrendering agency",
|
|
168
|
+
"to trust a rival",
|
|
169
|
+
"to tell the unsoftened truth",
|
|
170
|
+
"to forgive without erasing accountability",
|
|
171
|
+
"to choose community over glory",
|
|
172
|
+
"to abandon a flattering self-story",
|
|
173
|
+
"to act without certainty",
|
|
174
|
+
"to hold two opposing loyalties without splitting",
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const WOUNDS = [
|
|
178
|
+
"survivor's guilt after a failed rescue",
|
|
179
|
+
"a public humiliation never answered",
|
|
180
|
+
"exile from the only place that made sense",
|
|
181
|
+
"complicity in a quiet injustice",
|
|
182
|
+
"a mentor's unfinished lesson",
|
|
183
|
+
"a name taken or rewritten by power",
|
|
184
|
+
"love that arrived as a trap",
|
|
185
|
+
"a childhood bargain still collecting interest",
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
const VOICES = [
|
|
189
|
+
"spare and observational",
|
|
190
|
+
"ornate and ceremonial",
|
|
191
|
+
"dryly comic under pressure",
|
|
192
|
+
"feverish and associative",
|
|
193
|
+
"formal, almost legalistic",
|
|
194
|
+
"lyric and image-dense",
|
|
195
|
+
"blunt and tactical",
|
|
196
|
+
"hesitant, self-correcting",
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const THEMES = [
|
|
200
|
+
"since death is certain, meaning lies in works and love",
|
|
201
|
+
"power without restraint becomes the hero's cautionary origin",
|
|
202
|
+
"home is legible only after departure",
|
|
203
|
+
"truth arrives as a cost, not a prize",
|
|
204
|
+
"every rescue rewrites the rescuer",
|
|
205
|
+
"memory is a contested territory",
|
|
206
|
+
"the community is the true protagonist",
|
|
207
|
+
"desire is a technology that rewrites its user",
|
|
208
|
+
"law and identity are negotiable at the frontier",
|
|
209
|
+
"the story you tell about yourself is the last antagonist",
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
function tierFor(percent: number): SageTier {
|
|
213
|
+
if (percent < 2) return "Legendary";
|
|
214
|
+
if (percent < 6) return "Epic";
|
|
215
|
+
if (percent < 12) return "Rare";
|
|
216
|
+
if (percent < 25) return "Uncommon";
|
|
217
|
+
return "Common";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const CADENCE_LABELS = ["Clipped & Spare", "Balanced & Varied", "Flowing & Discursive", "Volatile & Ragged", "Measured & Formal"];
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Deterministic prose-cadence roll. Bounds derive from macro/tone logic (not
|
|
224
|
+
* fixed constants): tragic/lyric tones widen sentence variance; hardboiled/
|
|
225
|
+
* ironic tones narrow it. Absolute floors/ceilings keep prose readable.
|
|
226
|
+
*/
|
|
227
|
+
export function rollProseCadence(cadenceSeed: number, tone: string, macroName: string): SageProseCadence {
|
|
228
|
+
const rng = mulberry32(cadenceSeed ^ 0x9e3779b9);
|
|
229
|
+
const lyricBias = /lyrical|oneiric|baroque|mythic/i.test(tone) ? 1 : /hardboiled|ironic|comic/i.test(tone) ? -1 : 0;
|
|
230
|
+
const structuralBias = /stream|circular|epistolary/i.test(macroName) ? 1 : 0;
|
|
231
|
+
|
|
232
|
+
const sentenceMinWords = Math.max(3, Math.round(4 + rng() * 5 - lyricBias));
|
|
233
|
+
const sentenceMaxWords = Math.min(48, Math.round(sentenceMinWords + 14 + rng() * 16 + lyricBias * 6 + structuralBias * 6));
|
|
234
|
+
|
|
235
|
+
const paragraphMinSentences = Math.max(1, Math.round(1 + rng() * 2));
|
|
236
|
+
const paragraphMaxSentences = Math.min(11, Math.round(paragraphMinSentences + 2 + rng() * 4 + lyricBias));
|
|
237
|
+
|
|
238
|
+
const chapterVarianceMinPct = Math.round((0.65 + rng() * 0.15) * 100) / 100;
|
|
239
|
+
const chapterVarianceMaxPct = Math.round((1.15 + rng() * 0.35) * 100) / 100;
|
|
240
|
+
|
|
241
|
+
const motifRepetitionCap = Math.max(1, Math.round(2 + rng() * 2 - Math.abs(lyricBias)));
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
seed: cadenceSeed >>> 0,
|
|
245
|
+
sentenceMinWords,
|
|
246
|
+
sentenceMaxWords,
|
|
247
|
+
paragraphMinSentences,
|
|
248
|
+
paragraphMaxSentences,
|
|
249
|
+
chapterVarianceMinPct,
|
|
250
|
+
chapterVarianceMaxPct,
|
|
251
|
+
motifRepetitionCap,
|
|
252
|
+
label: randPick(rng, CADENCE_LABELS),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function axisFromSeed(seed: number, index: number): number {
|
|
257
|
+
return Math.round((mix32(seed, index) / 0xffffffff) * 100) / 100;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function growthFromSeed(seed: number): SageGrowthAxes {
|
|
261
|
+
return {
|
|
262
|
+
mythicDensity: axisFromSeed(seed, 100),
|
|
263
|
+
interiority: axisFromSeed(seed, 101),
|
|
264
|
+
structuralComplexity: axisFromSeed(seed, 102),
|
|
265
|
+
moralAmbiguity: axisFromSeed(seed, 103),
|
|
266
|
+
worldBuildingWeight: axisFromSeed(seed, 104),
|
|
267
|
+
tempo: axisFromSeed(seed, 105),
|
|
268
|
+
ironyDistance: axisFromSeed(seed, 106),
|
|
269
|
+
ensembleBreadth: axisFromSeed(seed, 107),
|
|
270
|
+
motifRecurrence: axisFromSeed(seed, 108),
|
|
271
|
+
endingOpenness: axisFromSeed(seed, 109),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function makeCharacter(
|
|
276
|
+
rng: () => number,
|
|
277
|
+
role: string,
|
|
278
|
+
pool: readonly Taxon[],
|
|
279
|
+
): SageCharacter {
|
|
280
|
+
return {
|
|
281
|
+
role,
|
|
282
|
+
archetype: randPick(rng, pool),
|
|
283
|
+
want: randPick(rng, WANTS),
|
|
284
|
+
need: randPick(rng, NEEDS),
|
|
285
|
+
wound: randPick(rng, WOUNDS),
|
|
286
|
+
voiceNote: randPick(rng, VOICES),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function pickProppSequence(rng: () => number, complexity: number): { id: string; name: string }[] {
|
|
291
|
+
// Propp allows optional deletion while preserving order. Complexity steers density.
|
|
292
|
+
const keepChance = 0.45 + complexity * 0.5;
|
|
293
|
+
const out: { id: string; name: string }[] = [];
|
|
294
|
+
for (const fn of PROPP_FUNCTIONS) {
|
|
295
|
+
// Always keep core spine functions.
|
|
296
|
+
const core = ["P08", "P11", "P16", "P18", "P19", "P20", "P27", "P31"].includes(fn.id);
|
|
297
|
+
if (core || rng() < keepChance) out.push(fn);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function pickCampbellPath(rng: () => number, macroName: string, complexity: number): Taxon[] {
|
|
303
|
+
if (/Monomyth|Quest|Voyage|Overcoming|Rebirth/i.test(macroName)) {
|
|
304
|
+
// Full path with light optional deletion on middle beats.
|
|
305
|
+
return CAMPBELL_BEATS.filter((_b, i) => i < 2 || i > 9 || rng() < 0.55 + complexity * 0.4);
|
|
306
|
+
}
|
|
307
|
+
// Non-monomyth macros still borrow a compressed beat spine.
|
|
308
|
+
const idxs = [0, 3, 5, 9, 11].filter(() => true);
|
|
309
|
+
if (complexity > 0.6) idxs.splice(2, 0, 6, 8);
|
|
310
|
+
return idxs.map((i) => CAMPBELL_BEATS[i]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function chapterTitle(index: number, beat: string | undefined, setting: string, rng: () => number): string {
|
|
314
|
+
const stems = [
|
|
315
|
+
`Threshold at ${setting}`,
|
|
316
|
+
beat ? `${beat}` : `Movement ${index + 1}`,
|
|
317
|
+
`The ${setting} Reckoning`,
|
|
318
|
+
`After ${setting}`,
|
|
319
|
+
`Letters from ${setting}`,
|
|
320
|
+
`What ${setting} Remembers`,
|
|
321
|
+
];
|
|
322
|
+
return `${index + 1}. ${randPick(rng, stems)}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function buildChapters(
|
|
326
|
+
rng: () => number,
|
|
327
|
+
length: SageLengthProfile,
|
|
328
|
+
campbell: Taxon[],
|
|
329
|
+
propp: { id: string; name: string }[],
|
|
330
|
+
micro: Taxon[],
|
|
331
|
+
settings: Taxon[],
|
|
332
|
+
growth: SageGrowthAxes,
|
|
333
|
+
cadence: SageProseCadence,
|
|
334
|
+
): SageChapterPlan[] {
|
|
335
|
+
const n = length.chapters;
|
|
336
|
+
const chapters: SageChapterPlan[] = [];
|
|
337
|
+
for (let i = 0; i < n; i += 1) {
|
|
338
|
+
const t = n === 1 ? 0.5 : i / (n - 1);
|
|
339
|
+
const cBeat = campbell[Math.min(campbell.length - 1, Math.floor(t * campbell.length))];
|
|
340
|
+
const pStart = Math.floor(t * Math.max(0, propp.length - 1));
|
|
341
|
+
const pSlice = propp.slice(pStart, pStart + 1 + Math.floor(growth.structuralComplexity * 2));
|
|
342
|
+
const mCount = 1 + Math.floor(growth.motifRecurrence * 2);
|
|
343
|
+
const microSlice = randPickN(rng, micro, mCount);
|
|
344
|
+
const settingFocus = settings[i % settings.length]?.name ?? settings[0].name;
|
|
345
|
+
// Per-chapter seeded variance within the rolled cadence range so chapters
|
|
346
|
+
// are not all identical length — fixes "word count always the same".
|
|
347
|
+
const chapterMix = mix32(cadence.seed, 300 + i) / 0xffffffff;
|
|
348
|
+
const variancePct = cadence.chapterVarianceMinPct + chapterMix * (cadence.chapterVarianceMaxPct - cadence.chapterVarianceMinPct);
|
|
349
|
+
const words = Math.max(200, Math.round(length.wordsPerChapter * variancePct));
|
|
350
|
+
chapters.push({
|
|
351
|
+
index: i,
|
|
352
|
+
title: chapterTitle(i, cBeat?.name, settingFocus, rng),
|
|
353
|
+
dramaticJob:
|
|
354
|
+
t < 0.2
|
|
355
|
+
? "Establish world, want, and the dramatic question"
|
|
356
|
+
: t < 0.45
|
|
357
|
+
? "Escalate trials; complicate alliances; plant payoffs"
|
|
358
|
+
: t < 0.7
|
|
359
|
+
? "Midpoint reversal / false victory; raise moral cost"
|
|
360
|
+
: t < 0.9
|
|
361
|
+
? "All-is-lost → earned approach to climax"
|
|
362
|
+
: "Climax, recognition, and ending that reorganizes the middle",
|
|
363
|
+
campbellBeat: cBeat?.name,
|
|
364
|
+
proppFunctions: pSlice.map((p) => p.name),
|
|
365
|
+
microBeats: microSlice.map((m) => m.name),
|
|
366
|
+
settingFocus,
|
|
367
|
+
targetWords: words,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return chapters;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function rarityForMacro(macroId: string, seed: number): { tier: SageTier; percent: number } {
|
|
374
|
+
// Sampled frequency over a fixed lattice (reproducible), no external engine input.
|
|
375
|
+
const SAMPLES = 800;
|
|
376
|
+
const counts = new Map<string, number>();
|
|
377
|
+
for (let i = 0; i < SAMPLES; i += 1) {
|
|
378
|
+
const s = (i * 2654435761) >>> 0;
|
|
379
|
+
const rng = mulberry32(s ^ 0x5a6e);
|
|
380
|
+
const m = randPick(rng, MACRO_STRUCTURES);
|
|
381
|
+
counts.set(m.id, (counts.get(m.id) ?? 0) + 1);
|
|
382
|
+
}
|
|
383
|
+
// Mix seed so UI rarity still feels seed-sensitive when macros collide.
|
|
384
|
+
void seed;
|
|
385
|
+
const hits = counts.get(macroId) ?? 1;
|
|
386
|
+
const percent = Math.round((hits / SAMPLES) * 1000) / 10;
|
|
387
|
+
return { tier: tierFor(percent), percent };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function rollSage(opts: SageRollOpts = {}): SageBlueprint {
|
|
391
|
+
const seed = (opts.seed ?? newSageSeed()) >>> 0;
|
|
392
|
+
const rng = mulberry32(seed);
|
|
393
|
+
const growth = growthFromSeed(seed);
|
|
394
|
+
|
|
395
|
+
const baseLength = LENGTH_PROFILES.find((l) => l.id === opts.lengthId) ?? LENGTH_PROFILES.find((l) => l.id === "novel") ?? LENGTH_PROFILES[0];
|
|
396
|
+
const length = {
|
|
397
|
+
...baseLength,
|
|
398
|
+
chapters: Math.max(1, Math.min(80, Math.floor(opts.chapters ?? baseLength.chapters))),
|
|
399
|
+
wordsPerChapter: Math.max(300, Math.min(12000, Math.floor(opts.wordsPerChapter ?? baseLength.wordsPerChapter))),
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const macro =
|
|
403
|
+
(opts.forceMacroId && MACRO_STRUCTURES.find((m) => m.id === opts.forceMacroId)) ||
|
|
404
|
+
randPick(rng, MACRO_STRUCTURES);
|
|
405
|
+
|
|
406
|
+
const secondaryMacro =
|
|
407
|
+
growth.structuralComplexity > 0.55 && rng() > 0.35
|
|
408
|
+
? randPick(
|
|
409
|
+
rng,
|
|
410
|
+
MACRO_STRUCTURES.filter((m) => m.id !== macro.id),
|
|
411
|
+
)
|
|
412
|
+
: null;
|
|
413
|
+
|
|
414
|
+
const primarySetting = randPick(rng, SETTINGS);
|
|
415
|
+
const secondarySettings = randPickN(
|
|
416
|
+
rng,
|
|
417
|
+
SETTINGS.filter((s) => s.id !== primarySetting.id),
|
|
418
|
+
1 + Math.floor(growth.worldBuildingWeight * 3),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const protagonist = makeCharacter(rng, "Protagonist", HUMAN_CHARS);
|
|
422
|
+
const deuteragonist = makeCharacter(
|
|
423
|
+
rng,
|
|
424
|
+
"Deuteragonist / companion",
|
|
425
|
+
HUMAN_CHARS.filter((h) => h.id !== protagonist.archetype.id),
|
|
426
|
+
);
|
|
427
|
+
const antagonistPool = rng() > 0.55 ? HUMAN_CHARS : NONHUMAN_CHARS;
|
|
428
|
+
const antagonist = makeCharacter(rng, "Antagonist / opposing force", antagonistPool);
|
|
429
|
+
|
|
430
|
+
const ensembleCount = Math.floor(growth.ensembleBreadth * 4);
|
|
431
|
+
const used = new Set([protagonist.archetype.id, deuteragonist.archetype.id, antagonist.archetype.id]);
|
|
432
|
+
const ensemble: SageCharacter[] = [];
|
|
433
|
+
const humanPool = HUMAN_CHARS.filter((h) => !used.has(h.id));
|
|
434
|
+
for (const arch of randPickN(rng, humanPool, ensembleCount)) {
|
|
435
|
+
ensemble.push(makeCharacter(rng, "Ensemble", [arch]));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const nonhumanPresence = randPickN(
|
|
439
|
+
rng,
|
|
440
|
+
NONHUMAN_CHARS,
|
|
441
|
+
growth.mythicDensity > 0.5 ? 1 + Math.floor(growth.mythicDensity * 2) : rng() > 0.6 ? 1 : 0,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const campbellPath = pickCampbellPath(rng, macro.name, growth.structuralComplexity);
|
|
445
|
+
const proppSequence = pickProppSequence(rng, growth.structuralComplexity);
|
|
446
|
+
const microDevices = randPickN(rng, MICRO_UNITS, 3 + Math.floor(growth.motifRecurrence * 4));
|
|
447
|
+
const storyElements = randPickN(rng, STORY_ELEMENTS, 5 + Math.floor(growth.structuralComplexity * 6));
|
|
448
|
+
const loadBearing = randPickN(rng, LOAD_BEARING, 4 + Math.floor(growth.interiority * 4));
|
|
449
|
+
|
|
450
|
+
const pov = randPick(rng, POV_OPTIONS);
|
|
451
|
+
const conflictAxis = randPick(rng, CONFLICT_AXES);
|
|
452
|
+
const tone = randPick(rng, TONE_PALETTE);
|
|
453
|
+
const ending =
|
|
454
|
+
growth.endingOpenness > 0.7
|
|
455
|
+
? randPick(rng, ENDING_TYPES.filter((e) => /open|ambiguous|circular|bittersweet/i.test(e)))
|
|
456
|
+
: randPick(rng, ENDING_TYPES);
|
|
457
|
+
|
|
458
|
+
const theme = randPick(rng, THEMES);
|
|
459
|
+
const premise =
|
|
460
|
+
(opts.premise && opts.premise.trim()) ||
|
|
461
|
+
`In ${primarySetting.name.toLowerCase()}, ${protagonist.archetype.name.toLowerCase()} who wants ${protagonist.want} must face ${antagonist.archetype.name.toLowerCase()} under a ${macro.name.toLowerCase()} design.`;
|
|
462
|
+
|
|
463
|
+
const dramaticQuestion = `Can ${protagonist.archetype.name.toLowerCase()} achieve ${protagonist.want} without forfeiting ${protagonist.need}?`;
|
|
464
|
+
|
|
465
|
+
const cadenceSeed = (opts.cadenceSeed ?? seed) >>> 0;
|
|
466
|
+
const cadence = rollProseCadence(cadenceSeed, tone, macro.name);
|
|
467
|
+
|
|
468
|
+
const worldMechanism = pickFromBands(rng, ["ES-HI", "ES-HP", "ES-HS", "GS-I", "GS-P", "ES-F"]);
|
|
469
|
+
const beatMechanism = pickFromBands(rng, ["ES-HB", "ES-O"]);
|
|
470
|
+
const relationshipMechanism = pickFromBands(rng, ["ES-HR", "ES-HL", "GS-R", "ES-HC"]);
|
|
471
|
+
|
|
472
|
+
const allSettings = [primarySetting, ...secondarySettings];
|
|
473
|
+
const chapters = buildChapters(rng, length, campbellPath, proppSequence, microDevices, allSettings, growth, cadence);
|
|
474
|
+
|
|
475
|
+
const titleHint = randPick(rng, [
|
|
476
|
+
`The ${primarySetting.name.replace(/^The /, "")} ${macro.name.split("/")[0].trim()}`,
|
|
477
|
+
`${protagonist.archetype.name} of ${primarySetting.name.replace(/^The /, "")}`,
|
|
478
|
+
`After ${primarySetting.name}`,
|
|
479
|
+
`Letters from ${secondarySettings[0]?.name ?? primarySetting.name}`,
|
|
480
|
+
`The Last ${antagonist.archetype.name}`,
|
|
481
|
+
]);
|
|
482
|
+
|
|
483
|
+
const rarity = rarityForMacro(macro.id, seed);
|
|
484
|
+
const selectedCount =
|
|
485
|
+
1 +
|
|
486
|
+
(secondaryMacro ? 1 : 0) +
|
|
487
|
+
1 +
|
|
488
|
+
secondarySettings.length +
|
|
489
|
+
3 +
|
|
490
|
+
ensemble.length +
|
|
491
|
+
nonhumanPresence.length +
|
|
492
|
+
campbellPath.length +
|
|
493
|
+
proppSequence.length +
|
|
494
|
+
microDevices.length +
|
|
495
|
+
storyElements.length +
|
|
496
|
+
loadBearing.length;
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
seed,
|
|
500
|
+
titleHint,
|
|
501
|
+
premise,
|
|
502
|
+
theme,
|
|
503
|
+
dramaticQuestion,
|
|
504
|
+
macro,
|
|
505
|
+
secondaryMacro,
|
|
506
|
+
length,
|
|
507
|
+
pov,
|
|
508
|
+
conflictAxis,
|
|
509
|
+
tone,
|
|
510
|
+
ending,
|
|
511
|
+
primarySetting,
|
|
512
|
+
secondarySettings,
|
|
513
|
+
protagonist,
|
|
514
|
+
deuteragonist,
|
|
515
|
+
antagonist,
|
|
516
|
+
ensemble,
|
|
517
|
+
nonhumanPresence,
|
|
518
|
+
campbellPath,
|
|
519
|
+
proppSequence,
|
|
520
|
+
microDevices,
|
|
521
|
+
storyElements,
|
|
522
|
+
loadBearing,
|
|
523
|
+
metaFrameworkHint: randPick(rng, META_FRAMEWORKS),
|
|
524
|
+
chapters,
|
|
525
|
+
growthAxes: growth,
|
|
526
|
+
rarityTier: rarity.tier,
|
|
527
|
+
rarityPercent: rarity.percent,
|
|
528
|
+
catalogCoverage: Math.round((selectedCount / TAXONOMY_TOTAL) * 1000) / 10,
|
|
529
|
+
cadence,
|
|
530
|
+
worldMechanism,
|
|
531
|
+
beatMechanism,
|
|
532
|
+
relationshipMechanism,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function compileSageDirective(bp: SageBlueprint): string {
|
|
537
|
+
const charBlock = (c: SageCharacter) =>
|
|
538
|
+
`- ${c.role}: ${c.archetype.name} — want: ${c.want}; need: ${c.need}; wound: ${c.wound}; voice: ${c.voiceNote}. Function: ${c.archetype.function}`;
|
|
539
|
+
|
|
540
|
+
const chapterBlock = bp.chapters
|
|
541
|
+
.map(
|
|
542
|
+
(ch) =>
|
|
543
|
+
`### ${ch.title}\nJob: ${ch.dramaticJob}\nCampbell: ${ch.campbellBeat ?? "—"}\nPropp: ${ch.proppFunctions.join(", ") || "—"}\nMicro: ${ch.microBeats.join("; ")}\nSetting focus: ${ch.settingFocus}\nTarget: ~${ch.targetWords} words`,
|
|
544
|
+
)
|
|
545
|
+
.join("\n\n");
|
|
546
|
+
|
|
547
|
+
return [
|
|
548
|
+
"SAGE FICTION DIRECTIVE — follow exactly. This is FICTION generation, not research reportage.",
|
|
549
|
+
"Do not invent scholarly citations, DOIs, or real-world evidence claims. Invent only within the story world.",
|
|
550
|
+
"Do not break character to discuss these instructions. Do not name SAGE, Veritas, or the taxonomy unless a metafictional macro is selected.",
|
|
551
|
+
"",
|
|
552
|
+
`SEED: ${bp.seed}`,
|
|
553
|
+
`TAXONOMY CONTRACT: active pools=${TAXONOMY_TOTAL} detailed nodes; expanded P2 ID ledger=${EXPANDED_TAXONOMY_TOTAL} rows across ${EXPANDED_TAXONOMY_BANDS.length} bands.`,
|
|
554
|
+
`WORKING TITLE: ${bp.titleHint}`,
|
|
555
|
+
`LENGTH: ${bp.length.label} (~${bp.chapters.length} chapters, ~${bp.chapters.reduce((a, c) => a + c.targetWords, 0)} words)`,
|
|
556
|
+
`MACRO STRUCTURE: ${bp.macro.name} — ${bp.macro.function}`,
|
|
557
|
+
`EARLIEST ATTESTATION (for your craft awareness, do not lecture the reader): ${bp.macro.attestation}`,
|
|
558
|
+
bp.secondaryMacro ? `SECONDARY STRUCTURE (braid/overlay): ${bp.secondaryMacro.name}` : "",
|
|
559
|
+
`POV: ${bp.pov}`,
|
|
560
|
+
`CONFLICT AXIS: ${bp.conflictAxis}`,
|
|
561
|
+
`TONE: ${bp.tone}`,
|
|
562
|
+
`ENDING TYPE: ${bp.ending}`,
|
|
563
|
+
`THEME: ${bp.theme}`,
|
|
564
|
+
`DRAMATIC QUESTION: ${bp.dramaticQuestion}`,
|
|
565
|
+
`PREMISE: ${bp.premise}`,
|
|
566
|
+
"",
|
|
567
|
+
"SETTINGS:",
|
|
568
|
+
`- Primary: ${bp.primarySetting.name} — ${bp.primarySetting.function}`,
|
|
569
|
+
...bp.secondarySettings.map((s) => `- ${s.name} — ${s.function}`),
|
|
570
|
+
"",
|
|
571
|
+
"CHARACTERS:",
|
|
572
|
+
charBlock(bp.protagonist),
|
|
573
|
+
charBlock(bp.deuteragonist),
|
|
574
|
+
charBlock(bp.antagonist),
|
|
575
|
+
...bp.ensemble.map(charBlock),
|
|
576
|
+
bp.nonhumanPresence.length
|
|
577
|
+
? `NON-HUMAN PRESENCE:\n${bp.nonhumanPresence.map((n) => `- ${n.name}: ${n.function}`).join("\n")}`
|
|
578
|
+
: "",
|
|
579
|
+
"",
|
|
580
|
+
"CAMPBELL PATH (adapt if macro is non-monomyth; do not force where it fights Kishōtenketsu etc.):",
|
|
581
|
+
bp.campbellPath.map((b) => `- ${b.name}: ${b.function}`).join("\n"),
|
|
582
|
+
"",
|
|
583
|
+
"PROPP SEQUENCE (ordered; deletions already applied):",
|
|
584
|
+
bp.proppSequence.map((p) => `- ${p.name}`).join("\n"),
|
|
585
|
+
"",
|
|
586
|
+
"MICRO DEVICES TO DEPLOY:",
|
|
587
|
+
bp.microDevices.map((m) => `- ${m.name}: ${m.function}`).join("\n"),
|
|
588
|
+
"",
|
|
589
|
+
"STORY ELEMENTS / TWISTS / DEVICES:",
|
|
590
|
+
bp.storyElements.map((e) => `- ${e.name}: ${e.function}`).join("\n"),
|
|
591
|
+
"",
|
|
592
|
+
"LOAD-BEARING WALLS (invisible craft constraints):",
|
|
593
|
+
bp.loadBearing.map((l) => `- ${l.name}: ${l.function}`).join("\n"),
|
|
594
|
+
"",
|
|
595
|
+
`META-FRAMEWORK HINT (analytic, not a template to dump): ${bp.metaFrameworkHint}`,
|
|
596
|
+
"",
|
|
597
|
+
"GROWTH AXES (0–1):",
|
|
598
|
+
Object.entries(bp.growthAxes)
|
|
599
|
+
.map(([k, v]) => `- ${k}: ${v}`)
|
|
600
|
+
.join("\n"),
|
|
601
|
+
"",
|
|
602
|
+
"CHAPTER PLAN (write in this order; use the exact ## headings):",
|
|
603
|
+
chapterBlock,
|
|
604
|
+
"",
|
|
605
|
+
"CONTINUITY RULES:",
|
|
606
|
+
"- Carry forward names, wounds, planted objects (Chekhov), and motif images.",
|
|
607
|
+
"- Each chapter must load-bear the dramatic question.",
|
|
608
|
+
"- Midpoint must recontextualize the goal (false victory or false defeat).",
|
|
609
|
+
"- Ending must retroactively organize the middle (Aristotle).",
|
|
610
|
+
"- If tone is ironic/metafictional, keep the wall-break intentional and rare.",
|
|
611
|
+
"- Prefer scene (mimesis) over summary (diegesis) at turning points.",
|
|
612
|
+
"",
|
|
613
|
+
"OUTPUT: Write the fiction now. Start with the title as # heading, then chapters as ## headings.",
|
|
614
|
+
]
|
|
615
|
+
.filter(Boolean)
|
|
616
|
+
.join("\n");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function compileChapterPrompt(bp: SageBlueprint, chapterIndex: number, priorSummaries: string[]): string {
|
|
620
|
+
const ch = bp.chapters[chapterIndex];
|
|
621
|
+
if (!ch) throw new Error(`SAGE: chapter ${chapterIndex} out of range`);
|
|
622
|
+
return [
|
|
623
|
+
compileSageDirective(bp),
|
|
624
|
+
"",
|
|
625
|
+
`NOW WRITE ONLY THIS CHAPTER:`,
|
|
626
|
+
`Exact heading: ## ${ch.title}`,
|
|
627
|
+
`Dramatic job: ${ch.dramaticJob}`,
|
|
628
|
+
`Target length: ~${ch.targetWords} words`,
|
|
629
|
+
`Campbell beat: ${ch.campbellBeat ?? "n/a"}`,
|
|
630
|
+
`Propp: ${ch.proppFunctions.join(", ") || "n/a"}`,
|
|
631
|
+
`Micro devices to realize in-scene: ${ch.microBeats.join("; ")}`,
|
|
632
|
+
`Setting focus: ${ch.settingFocus}`,
|
|
633
|
+
priorSummaries.length
|
|
634
|
+
? `PRIOR CHAPTER CONTINUITY (do not repeat; advance):\n${priorSummaries.map((s, i) => `${i + 1}. ${s}`).join("\n")}`
|
|
635
|
+
: "This is the opening chapter.",
|
|
636
|
+
"No other chapters. No craft commentary. No placeholders.",
|
|
637
|
+
].join("\n\n");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Compact per-chapter prompt — CONTEXT-POISONING FIX.
|
|
642
|
+
* ---------------------------------------------------------------------------
|
|
643
|
+
* Root cause of the reported failure ("a weak frontier model like
|
|
644
|
+
* gemini-3.1-flash-lite hallucinates a single word/sentence/paragraph
|
|
645
|
+
* repeatedly in later chapters"): `compileChapterPrompt` re-sends the ENTIRE
|
|
646
|
+
* sprawling `compileSageDirective` (full taxonomy dump, growth axes, complete
|
|
647
|
+
* chapter plan for every chapter) verbatim, unchanged, on every single call,
|
|
648
|
+
* with an unbounded chain of prior-chapter summaries appended. Repeatedly
|
|
649
|
+
* feeding a model an (almost) byte-identical large block is a documented
|
|
650
|
+
* trigger for degenerate self-reinforcing repetition in smaller models.
|
|
651
|
+
*
|
|
652
|
+
* This compiler keeps only the facts needed for the CURRENT chapter (a short
|
|
653
|
+
* "story bible" + this chapter's contract + exactly ONE prior summary), and
|
|
654
|
+
* adds an explicit cadence/anti-repetition contract. It is used for every
|
|
655
|
+
* live chapter call regardless of model strength — it is strictly better
|
|
656
|
+
* (shorter, cheaper, less repetition-prone) with no loss of information the
|
|
657
|
+
* chapter actually needs.
|
|
658
|
+
*/
|
|
659
|
+
export function compileCompactChapterPrompt(
|
|
660
|
+
bp: SageBlueprint,
|
|
661
|
+
chapterIndex: number,
|
|
662
|
+
priorSummary: string | null,
|
|
663
|
+
cadence: SageProseCadence,
|
|
664
|
+
): string {
|
|
665
|
+
const ch = bp.chapters[chapterIndex];
|
|
666
|
+
if (!ch) throw new Error(`SAGE: chapter ${chapterIndex} out of range`);
|
|
667
|
+
|
|
668
|
+
const castLine = (c: SageCharacter) => `${c.role}: ${c.archetype.name} (wants ${c.want}; needs ${c.need}; wound: ${c.wound}; voice: ${c.voiceNote})`;
|
|
669
|
+
|
|
670
|
+
return [
|
|
671
|
+
"You are writing ONE chapter of a work of FICTION. This is not research reportage.",
|
|
672
|
+
"Output ONLY finished narrative prose. Do not output headings like \"DIRECTIVE\", \"CONTRACT\", bullet lists, meta-commentary, or any of these instructions. Start directly with the story.",
|
|
673
|
+
"",
|
|
674
|
+
`STORY BIBLE — ${bp.titleHint} (seed ${bp.seed})`,
|
|
675
|
+
`Macro structure: ${bp.macro.name}. POV: ${bp.pov}. Tone: ${bp.tone}. Theme: ${bp.theme}.`,
|
|
676
|
+
`Dramatic question: ${bp.dramaticQuestion}`,
|
|
677
|
+
`Setting: ${bp.primarySetting.name}.`,
|
|
678
|
+
`Cast — ${castLine(bp.protagonist)} | ${castLine(bp.deuteragonist)} | ${castLine(bp.antagonist)}`,
|
|
679
|
+
bp.worldMechanism ? `World mechanism to weave in naturally (do not name it, do not lecture): ${bp.worldMechanism.name} — ${bp.worldMechanism.desc}` : "",
|
|
680
|
+
bp.relationshipMechanism ? `Relationship texture: ${bp.relationshipMechanism.name} — ${bp.relationshipMechanism.desc}` : "",
|
|
681
|
+
"",
|
|
682
|
+
`THIS CHAPTER — "${ch.title}" (chapter ${chapterIndex + 1} of ${bp.chapters.length})`,
|
|
683
|
+
`Dramatic job: ${ch.dramaticJob}`,
|
|
684
|
+
`Setting focus: ${ch.settingFocus}`,
|
|
685
|
+
`Beat to realize in-scene: ${ch.microBeats.join("; ")}`,
|
|
686
|
+
bp.beatMechanism ? `Beat-level device to try once: ${bp.beatMechanism.name} — ${bp.beatMechanism.desc}` : "",
|
|
687
|
+
`Target length: ~${ch.targetWords} words.`,
|
|
688
|
+
"",
|
|
689
|
+
"PROSE CADENCE CONTRACT (vary naturally; do not make every sentence/paragraph the same length):",
|
|
690
|
+
`- Sentence length should range roughly ${cadence.sentenceMinWords}-${cadence.sentenceMaxWords} words, mixing short and long.`,
|
|
691
|
+
`- Paragraphs should range roughly ${cadence.paragraphMinSentences}-${cadence.paragraphMaxSentences} sentences.`,
|
|
692
|
+
`- Do not repeat any sentence, or any phrase of 4+ words, anywhere in this chapter.`,
|
|
693
|
+
`- Do not reuse the same distinctive image/motif word more than ${cadence.motifRepetitionCap} times in this chapter.`,
|
|
694
|
+
"- If you notice yourself about to repeat a prior sentence or phrase, stop and write something different instead.",
|
|
695
|
+
"",
|
|
696
|
+
priorSummary
|
|
697
|
+
? `WHAT JUST HAPPENED (previous chapter only — advance from here, do not restate it):\n${priorSummary}`
|
|
698
|
+
: "This is the opening chapter of the book.",
|
|
699
|
+
"",
|
|
700
|
+
buildSagaDirectiveBlock(bp.chapters.length),
|
|
701
|
+
"",
|
|
702
|
+
`Exact heading to use: ## ${ch.title}`,
|
|
703
|
+
"Write ONLY this chapter now.",
|
|
704
|
+
]
|
|
705
|
+
.filter(Boolean)
|
|
706
|
+
.join("\n");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const LEAK_MARKERS = [
|
|
710
|
+
/^\s*#{0,6}\s*SAGE FICTION DIRECTIVE.*$/gim,
|
|
711
|
+
/^\s*STORY BIBLE\b.*$/gim,
|
|
712
|
+
/^\s*THIS CHAPTER\b.*$/gim,
|
|
713
|
+
/^\s*PROSE CADENCE CONTRACT\b.*$/gim,
|
|
714
|
+
/^\s*WHAT JUST HAPPENED\b.*$/gim,
|
|
715
|
+
/^\s*Exact heading to use:.*$/gim,
|
|
716
|
+
/^\s*TEMPLATE STYLE OVERRIDE ACTIVE:.*$/gim,
|
|
717
|
+
/^\s*STYLE PERSONA \(apply silently.*$/gim,
|
|
718
|
+
/^\s*GROWTH AXES.*$/gim,
|
|
719
|
+
/^\s*CAMPBELL PATH\b.*$/gim,
|
|
720
|
+
/^\s*PROPP SEQUENCE\b.*$/gim,
|
|
721
|
+
/^\s*MICRO DEVICES TO DEPLOY.*$/gim,
|
|
722
|
+
/^\s*CONTINUITY RULES:.*$/gim,
|
|
723
|
+
/^\s*Write ONLY this chapter now\.?$/gim,
|
|
724
|
+
];
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Sanitizes any accidentally-echoed instruction/directive text out of a
|
|
728
|
+
* generated chapter before it is ever shown to the user. Weak models
|
|
729
|
+
* occasionally copy fragments of their own system/user instructions into
|
|
730
|
+
* the visible completion; this is a defense-in-depth net on top of the
|
|
731
|
+
* compact-prompt fix above (which removes most of the temptation to echo).
|
|
732
|
+
*/
|
|
733
|
+
export function scrubLeakedDirective(text: string): string {
|
|
734
|
+
let out = text;
|
|
735
|
+
for (const re of LEAK_MARKERS) out = out.replace(re, "");
|
|
736
|
+
return out.replace(/\n{3,}/g, "\n\n").trim();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export type SageFractalScene = {
|
|
740
|
+
title: string;
|
|
741
|
+
beat: string;
|
|
742
|
+
paragraphIdeas: string[];
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
export type SageFractalChapter = {
|
|
746
|
+
index: number;
|
|
747
|
+
title: string;
|
|
748
|
+
scenes: SageFractalScene[];
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Deterministic, non-LLM structural preview: chapter -> scene (subchapter) ->
|
|
753
|
+
* paragraph-level ideas. Pure function of the blueprint's own seed, so it is
|
|
754
|
+
* cheap to recompute live as the user adjusts controls, and it never
|
|
755
|
+
* fabricates prose — only a structural outline of what each roll would
|
|
756
|
+
* produce.
|
|
757
|
+
*/
|
|
758
|
+
export function buildFractalPreview(bp: SageBlueprint): SageFractalChapter[] {
|
|
759
|
+
return bp.chapters.map((ch, ci) => {
|
|
760
|
+
const rng = mulberry32(mix32(bp.seed, 700 + ci));
|
|
761
|
+
const sceneCount = 2 + Math.floor(rng() * 3); // 2-4 scenes per chapter
|
|
762
|
+
const scenes: SageFractalScene[] = [];
|
|
763
|
+
for (let si = 0; si < sceneCount; si += 1) {
|
|
764
|
+
const beatPool = [ch.campbellBeat, ...ch.proppFunctions, ...ch.microBeats].filter(Boolean) as string[];
|
|
765
|
+
const beat = beatPool.length ? beatPool[si % beatPool.length] : ch.dramaticJob;
|
|
766
|
+
const paraCount = 2 + Math.floor(rng() * 3); // 2-4 paragraph ideas per scene
|
|
767
|
+
const paragraphIdeas: string[] = [];
|
|
768
|
+
const actors = [bp.protagonist, bp.deuteragonist, bp.antagonist, ...bp.ensemble];
|
|
769
|
+
for (let pi = 0; pi < paraCount; pi += 1) {
|
|
770
|
+
const actor = actors[Math.floor(rng() * actors.length)];
|
|
771
|
+
const device = bp.microDevices[Math.floor(rng() * bp.microDevices.length)];
|
|
772
|
+
const templates = [
|
|
773
|
+
`${actor.role} reacts to ${beat.toLowerCase()} at ${ch.settingFocus}, colored by their want (${actor.want}).`,
|
|
774
|
+
`A concrete sensory beat at ${ch.settingFocus} that plants or pays off: ${device?.name ?? "a recurring image"}.`,
|
|
775
|
+
`Dialogue or interior beat surfacing ${actor.role}'s wound: ${actor.wound}.`,
|
|
776
|
+
`Turn: the scene's local stakes shift, tightening toward "${bp.dramaticQuestion}".`,
|
|
777
|
+
];
|
|
778
|
+
paragraphIdeas.push(templates[Math.floor(rng() * templates.length)]);
|
|
779
|
+
}
|
|
780
|
+
scenes.push({ title: `Scene ${si + 1}`, beat, paragraphIdeas });
|
|
781
|
+
}
|
|
782
|
+
return { index: ci, title: ch.title, scenes };
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export { newSageSeed, TAXONOMY_TOTAL };
|