// ============================================================ // Resumable Story Writer // // Pipeline: // 0. Bootstrap document if missing // 1. Expand seed premise into scene OUTLINE blocks // 2. Expand each scene block by adding a second variation // 3. Write each expanded scene as a chapter TEXT block // 4. Add a chapter SUMMARY after each chapter // // Resume strategy: // - scene labels track workflow state // - scene variations track planning maturity // - chapter/summary blocks carry scene- linkage // // Conventions: // - title block: TEXT label contains "title" // - seed block: OUTLINE label contains "seed" // - scene blocks: OUTLINE label contains "scene" // - chapter blocks: TEXT label contains "chapter scene-" // - chapter summary: SUMMARY label contains "chapter-summary scene-" // - lore blocks: LORE labels: "world", "characters", "style" // - project summary: SUMMARY label contains "project" // // Scene state examples: // - "scene" // - "scene expanded" // - "scene expanded drafted" // - "scene expanded drafted locked" // ============================================================ // -------------------- // Generic helpers // -------------------- sub firstTagged(text, prefix) let rows = lines(text) for row in rows let t = trim(row) if starts_with_nocase(t, prefix + ":") then return trim(after(t, ":")) endif next row return "" end sub sub taggedItems(text, prefix) let out = [] let rows = lines(text) for row in rows let t = trim(row) if starts_with_nocase(t, prefix + ":") then let item = trim(after(t, ":")) if len(item) > 0 then out = append(out, item) endif endif next row return out end sub sub hasLabel(block, tag) if block == null then return false endif let parts = split(trim(block.label), " ") for p in parts if lower(trim(p)) == lower(trim(tag)) then return true endif next p return false end sub sub appendLabel(block, tag) if block == null then return endif if not hasLabel(block, tag) then let current = trim(block.label) if len(current) == 0 then block.label = tag else block.label = current + " " + tag endif endif end sub sub removeLabelToken(block, tag) if block == null then return endif let parts = split(trim(block.label), " ") let kept = [] for p in parts let t = trim(p) if len(t) > 0 then if lower(t) <> lower(trim(tag)) then kept = append(kept, t) endif endif next p block.label = join(kept, " ") end sub sub isLocked(block) return hasLabel(block, "locked") end sub sub findFirstByLabel(tag) for block in document if hasLabel(block, tag) then return block endif next block return null end sub sub countByLabel(tag) let n = 0 for block in document if hasLabel(block, tag) then n = n + 1 endif next block return n end sub sub storyExists() return findFirstByLabel("seed") != null end sub sub sceneKey(sceneBlock) return "sceneID" + str(sceneBlock.id) end sub sub proseBlockForScene(sceneBlock) let wanted = sceneKey(sceneBlock) for block in document if block.type == TEXT and hasLabel(block, "chapter") then if contains_nocase(block.label, wanted) then return block endif endif next block return null end sub sub summaryBlockForScene(sceneBlock) let wanted = sceneKey(sceneBlock) for block in document if block.type == SUMMARY and hasLabel(block, "chapter-summary") then if contains_nocase(block.label, wanted) then return block endif endif next block return null end sub sub previousChapterBlock(sceneBlock) let idx = document.getBlockIndex(sceneBlock) if idx <= 0 then return null endif for i = idx - 1 to 0 step -1 let b = document.blocks[i] if b.type == TEXT and hasLabel(b, "chapter") then return b endif next i return null end sub // -------------------- // Prompt builders // -------------------- sub buildBootstrapPrompt(premise, characters, style, tone) return """ Create the initial setup for a fiction project. Return only plain tagged lines in exactly this format: TITLE: project title WORLD: one compact paragraph of world / setting lore CHARACTERS: one compact paragraph of core character lore STYLE: one compact paragraph of style, POV, voice, and tone rules PROJECT: one compact paragraph describing the overall story arc SEED: one compact outline paragraph that restates the premise in a clear story-ready form Rules: - No markdown - No commentary - No numbering - Keep each tag on a single line - STYLE should preserve requested POV and flavour - PROJECT should describe the broad arc - SEED should be a clean, writable short outline Premise: """ + premise + """ Characters: """ + characters + """ Style: """ + style + """ Tone: """ + tone end sub sub buildSceneOutlinePrompt(seedText, loreText, summaryText) return """ Expand the seed premise into scene starter blocks. Return only plain tagged lines in exactly this format: SCENE: one concise scene overview SCENE: one concise scene overview SCENE: one concise scene overview Rules: - No markdown - No commentary - No numbering - Return between 6 and 12 SCENE lines - Each SCENE line should be one distinct story unit in order - Do not write prose yet Lore: """ + loreText + """ Project Summary: """ + summaryText + """ Seed Outline: """ + seedText end sub sub buildSceneExpansionPrompt(sceneText, loreText, summaryText) return """ Expand the following scene overview into a richer scene plan. Return only plain tagged lines in exactly this format: SCENETITLE: short scene title POV: viewpoint and tense guidance LOCATION: where the scene happens EMOTIONALTURN: emotional movement SUMMARY: one compact scene-purpose summary MUSTKEEP: one crucial continuity requirement BEAT: story beat BEAT: story beat BEAT: story beat Rules: - No markdown - No commentary - No numbering - Keep the plan compact but more detailed than the source - Do not write full prose - Preserve continuity - Return at least 3 BEAT lines Lore: """ + loreText + """ Summaries So Far: """ + summaryText + """ Scene Overview: """ + sceneText end sub sub buildChapterPrompt(scenePlan) return """ Write the next chapter from the following expanded scene plan. Rules: - Output prose only - No title - No commentary - Stay faithful to the scene plan - End at the natural end of this chapter - Do not summarize future events Expanded Scene Plan: """ + scenePlan end sub sub buildChapterSummaryPrompt(chapterText) return """ Summarize this chapter for continuity tracking. Return only plain text. Rules: - one compact paragraph - no markdown - no commentary - focus on what happened, what changed, and what must be preserved later Chapter: """ + chapterText end sub // -------------------- // Phase 0: bootstrap // -------------------- sub bootstrapIfMissing() if storyExists() then return false endif input.begin("Create Story Project") input.addMultiLine("premise", "Premise / short outline", "A disgraced royal pudding inspector discovers that the kingdom's desserts are being weaponized to open a portal beneath the moon, and only a stolen accordion, a haunted goose, and a counterfeit duke can stop the uprising before the Great Custard Parliament convenes.", 5) input.addMultiLine("characters", "Character notes", "Edda Crumb, former royal pudding inspector, brilliant, suspicious, emotionally overinvested in proper texture. Lord Wibber Vale, counterfeit duke, charming, cowardly, surprisingly good at forgery. Sister Honkferatu, haunted goose in a lace collar, vindictive, prophetic, impossible to silence.", 4) input.addMultiLine("style", "Style / POV / flavour", "Third person limited, deadpan absurdist fantasy, played completely straight despite escalating nonsense.", 4) input.addText("tone", "Tone", "Ridiculous, ominous, theatrical, and weirdly sincere") if not input.show() then stop endif let premise = trim(input.getText("premise")) let characters = trim(input.getText("characters")) let style = trim(input.getText("style")) let tone = trim(input.getText("tone")) llm.reset() llm.temperature = 0.8 let result = llm.call(buildBootstrapPrompt(premise, characters, style, tone)) if len(trim(result)) == 0 then print "Bootstrap failed." stop endif let title = firstTagged(result, "TITLE") let worldText = firstTagged(result, "WORLD") let charsText = firstTagged(result, "CHARACTERS") let styleText = firstTagged(result, "STYLE") let projectText = firstTagged(result, "PROJECT") let seedText = firstTagged(result, "SEED") if len(title) == 0 then title = "Untitled Story" endif if len(seedText) == 0 then seedText = premise endif let insertAfter = null let titleBlock = document.insertBlockAfter(insertAfter, TEXT, title) if titleBlock != null then titleBlock.label = "title" insertAfter = titleBlock endif if len(worldText) > 0 then let b = document.insertBlockAfter(insertAfter, LORE, worldText) if b != null then b.label = "world" insertAfter = b endif endif if len(charsText) > 0 then let b = document.insertBlockAfter(insertAfter, LORE, charsText) if b != null then b.label = "characters" insertAfter = b endif endif if len(styleText) > 0 then let b = document.insertBlockAfter(insertAfter, LORE, styleText) if b != null then b.label = "style" insertAfter = b endif endif if len(projectText) > 0 then let b = document.insertBlockAfter(insertAfter, SUMMARY, projectText) if b != null then b.label = "project" insertAfter = b endif endif let seedBlock = document.insertBlockAfter(insertAfter, OUTLINE, seedText) if seedBlock != null then seedBlock.label = "seed" endif print "Created story project bootstrap." return true end sub // -------------------- // Phase 1: seed -> scene blocks // -------------------- sub generateSceneBlocksIfMissing() if countByLabel("scene") > 0 then return false endif let seedBlock = findFirstByLabel("seed") if seedBlock == null then return false endif let loreText = document.getLore(seedBlock) let summaryText = document.getSummaries(seedBlock) llm.reset() llm.temperature = 0.7 let result = llm.call(buildSceneOutlinePrompt(seedBlock.text, loreText, summaryText)) if len(trim(result)) == 0 then print "Scene generation failed." stop endif let scenes = taggedItems(result, "SCENE") if len(scenes) == 0 then print "No SCENE tags found." stop endif let insertAfter = seedBlock for sceneText in scenes let b = document.insertBlockAfter(insertAfter, OUTLINE, sceneText) if b != null then b.label = "scene" insertAfter = b endif next sceneText print "Generated " + str(len(scenes)) + " scene blocks." return true end sub // -------------------- // Phase 2: expand next unfinished scene // -------------------- sub expandNextScene() for block in document if block.type == OUTLINE and hasLabel(block, "scene") then if isLocked(block) then continue endif if block.variationCount == 1 then let loreText = document.getLore(block) let summaryText = document.getSummaries(block) llm.reset() llm.temperature = 0.75 let result = llm.call(buildSceneExpansionPrompt(block.text, loreText, summaryText)) if len(trim(result)) == 0 then print "Scene expansion failed." stop endif block.addVariation(result) appendLabel(block, "expanded") print "Expanded scene block " + str(block.id) return true endif endif next block return false end sub // -------------------- // Phase 3: draft next missing chapter // -------------------- sub draftNextChapter() for block in document if block.type == OUTLINE and hasLabel(block, "scene") then if isLocked(block) then continue endif print("**** block: "+ block.label) if hasLabel(block, "drafted") then continue endif if block.variationCount < 2 then continue endif let existingChapter = proseBlockForScene(block) if existingChapter != null then appendLabel(block, "drafted") continue endif let loreText = document.getLore(block) let summariesText = document.getSummaries(block) let prevChapter = previousChapterBlock(block) llm.reset() llm.temperature = 0.78 llm.systemPrompt = """ You are writing a fiction chapter inside a structured editorial workflow. Preserve lore, continuity, chronology, and style. Write only the requested chapter prose. Established Lore: """ + loreText if len(summariesText) > 0 then llm.addUser("Story summaries so far. Treat them as established continuity facts.") llm.addAssistant(summariesText) endif if prevChapter != null then llm.addUser("Previous chapter text follows. Use it as a style anchor only.") llm.addAssistant(prevChapter.text) endif print "Drafting chapter for scene " + str(block.id) let result = llm.call(buildChapterPrompt(block.text)) if len(trim(result)) == 0 then print "Chapter drafting failed for sceneid" + str(block.id) stop endif print("**** block Before: "+ block.label) let chapterBlock = document.insertBlockAfter(block, TEXT, result) print("**** block After: "+ block.label) if chapterBlock != null then chapterBlock.label = "chapter " + sceneKey(block) print("block: " + block.label + " " + block.type) appendLabel(block, "drafted") document.selectedBlock = chapterBlock print "[OK] Drafted chapter for sceneid" + str(block.id) return true endif endif next block return false end sub // -------------------- // Phase 4: summarize next drafted chapter // -------------------- sub summarizeNextChapter() for block in document if block.type == OUTLINE and hasLabel(block, "scene") then let chapterBlock = proseBlockForScene(block) if chapterBlock == null then continue endif let sumBlock = summaryBlockForScene(block) if sumBlock != null then continue endif let loreText = document.getLore(chapterBlock) llm.reset() llm.temperature = 0.4 llm.systemPrompt = """ You are creating a continuity summary for a fiction manuscript. Summaries must be concise, factual, and useful for later chapter writing. Established Lore: """ + loreText print "Writing chapter summary for scene " + str(block.id) let result = llm.call(buildChapterSummaryPrompt(chapterBlock.text)) if len(trim(result)) == 0 then print "Summary failed for scene " + str(block.id) stop endif let newSum = document.insertBlockAfter(chapterBlock, SUMMARY, result) if newSum != null then newSum.label = "chapter-summary " + sceneKey(block) print "Added chapter summary for scene " + str(block.id) return true endif endif next block return false end sub // -------------------- // Repair pass // -------------------- sub repairSceneDraftFlags() let repaired = 0 for block in document if block.type == OUTLINE and hasLabel(block, "scene") then let chapterBlock = proseBlockForScene(block) if chapterBlock != null then if not hasLabel(block, "drafted") then appendLabel(block, "drafted") repaired = repaired + 1 endif endif endif next block if repaired > 0 then print "Repaired drafted labels on " + str(repaired) + " scene blocks." return true endif return false end sub // -------------------- // Completion counters // -------------------- sub pendingSceneExpansions() let n = 0 for block in document if block.type == OUTLINE and hasLabel(block, "scene") and not isLocked(block) then if block.variationCount == 1 then n = n + 1 endif endif next block return n end sub sub pendingChapterDrafts() let n = 0 for block in document if block.type == OUTLINE and hasLabel(block, "scene") and not isLocked(block) then if hasLabel(block, "drafted") then continue endif if block.variationCount >= 2 then if proseBlockForScene(block) == null then n = n + 1 endif endif endif next block return n end sub sub pendingChapterSummaries() let n = 0 for block in document if block.type == OUTLINE and hasLabel(block, "scene") then let chapterBlock = proseBlockForScene(block) if chapterBlock != null then if summaryBlockForScene(block) == null then n = n + 1 endif endif endif next block return n end sub // -------------------- // Main loop // -------------------- let steps = 0 let maxSteps = 500 while true steps = steps + 1 if steps > maxSteps then print "Stopped after " + str(maxSteps) + " steps for safety." break endif if bootstrapIfMissing() then continue endif if generateSceneBlocksIfMissing() then continue endif if repairSceneDraftFlags() then continue endif if summarizeNextChapter() then continue endif if expandNextScene() then continue endif if draftNextChapter() then continue endif break wend print "Done." print "Pending scene expansions: " + str(pendingSceneExpansions()) print "Pending chapter drafts: " + str(pendingChapterDrafts()) print "Pending chapter summaries: " + str(pendingChapterSummaries())