From f71a04c03c4dd7816cd63301cc40e1dc422c3a4b Mon Sep 17 00:00:00 2001 From: Mike Wichers Date: Sat, 21 Feb 2026 00:01:47 -0500 Subject: [PATCH] =?UTF-8?q?Blueprint=20v1.5.0:=20AI=20Context=20Optimizati?= =?UTF-8?q?on=20=E2=80=94=20Dynamic=20Characters=20&=20Scene=20State?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - writer.py: Dynamic character injection — only POV + beat-named characters are sent to the writer prompt, eliminating token waste and hallucinations from characters unrelated to the current scene. - writer.py: Smart tail truncation — prev_content trimmed to last 1,000 tokens (the actual chapter ending) instead of a blind 2,000-token head slice, preserving the exact hand-off point for continuity. - writer.py: Scene state injected into char_visuals — current_location, time_of_day, and held_items now surfaced per relevant character in prompt. - bible_tracker.py: update_tracking expanded to record current_location, time_of_day, and held_items per character after each chapter. - core/config.py: VERSION bumped 1.4.0 → 1.5.0. - README.md: Story Generation section and tracking_characters.json schema updated to document new context optimization features. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 8 +++++++- core/config.py | 2 +- story/bible_tracker.py | 5 ++++- story/writer.py | 38 ++++++++++++++++++++++++++++++-------- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 82eca44..a6e2305 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,9 @@ Open `http://localhost:5000`. - **Series Continuity:** When generating Book 2+, carries forward character visual tracking, established relationships, plot threads, and a cumulative "Story So Far" summary. - **Persona Refinement Loop:** Every 5 chapters, analyzes actual written text to refine the author persona model, maintaining stylistic consistency throughout the book. - **Consistency Checker (`editor.py`):** Scores chapters on 8 rubrics (engagement, voice, sensory detail, scene execution, etc.) and flags AI-isms ("tapestry", "palpable tension") and weak filter verbs ("felt", "realized"). +- **Dynamic Character Injection (`writer.py`):** Only injects characters explicitly named in the chapter's `scene_beats` plus the POV character into the writer prompt. Eliminates token waste from unused characters and reduces hallucinated appearances. +- **Smart Context Tail (`writer.py`):** Extracts the final ~1,000 tokens of the previous chapter (the actual ending) rather than blindly truncating from the front. Ensures the hand-off point — where characters are standing and what was last said — is always preserved. +- **Stateful Scene Tracking (`bible_tracker.py`):** After each chapter, the tracker records each character's `current_location`, `time_of_day`, and `held_items` in addition to appearance and events. This scene state is injected into subsequent chapter prompts so the writer knows exactly where characters are, what time it is, and what they're carrying. ### Marketing Assets (`marketing/`) - **Cover Art:** Generates a visual prompt from book themes and tracking data, then calls Imagen (Gemini or Vertex AI) to produce the cover. Evaluates image quality with multimodal AI critique before accepting. @@ -316,7 +319,10 @@ data/ "descriptors": ["Blue eyes", "Tall"], "likes_dislikes": ["Loves coffee"], "last_worn": "Red dress (Ch 4)", - "major_events": ["Injured leg in Ch 2"] + "major_events": ["Injured leg in Ch 2"], + "current_location": "The King's Throne Room", + "time_of_day": "Late afternoon", + "held_items": ["Iron sword", "Stolen ledger"] } } ``` diff --git a/core/config.py b/core/config.py index 63b5f1b..f61ffe7 100644 --- a/core/config.py +++ b/core/config.py @@ -65,4 +65,4 @@ LENGTH_DEFINITIONS = { } # --- SYSTEM --- -VERSION = "1.4.0" +VERSION = "1.5.0" diff --git a/story/bible_tracker.py b/story/bible_tracker.py index 2f48ded..ced46da 100644 --- a/story/bible_tracker.py +++ b/story/bible_tracker.py @@ -70,12 +70,15 @@ def update_tracking(folder, chapter_num, chapter_text, current_tracking): OPERATIONS: 1. EVENTS: Append 1-3 key plot points to 'events'. - 2. CHARACTERS: Update 'descriptors', 'likes_dislikes', 'speech_style', 'last_worn', 'major_events'. + 2. CHARACTERS: Update 'descriptors', 'likes_dislikes', 'speech_style', 'last_worn', 'major_events', 'current_location', 'time_of_day', 'held_items'. - "descriptors": List of strings. Add PERMANENT physical traits (height, hair, eyes), specific items (jewelry, weapons). Avoid duplicates. - "likes_dislikes": List of strings. Add specific preferences, likes, or dislikes mentioned (e.g., "Hates coffee", "Loves jazz"). - "speech_style": String. Describe how they speak (e.g. "Formal, no contractions", "Uses slang", "Stutters", "Short sentences"). - "last_worn": String. Update if specific clothing is described. IMPORTANT: If a significant time jump occurred (e.g. next day) and no new clothing is described, reset this to "Unknown". - "major_events": List of strings. Log significant life-altering events occurring in THIS chapter (e.g. "Lost an arm", "Married", "Betrayed by X"). + - "current_location": String. The character's physical location at the END of this chapter (e.g., "The King's Throne Room", "Aboard the Nighthawk ship"). Update whenever the character moves. + - "time_of_day": String. The approximate time of day at the END of this chapter (e.g., "Dawn", "Late afternoon", "Midnight"). Reset to "Unknown" if unclear. + - "held_items": List of strings. Items the character is actively carrying or holding at chapter end (e.g., "Iron sword", "Stolen ledger"). Remove items they have dropped or given away. 3. WARNINGS: Append new 'content_warnings'. OUTPUT_FORMAT (JSON): Return the updated tracking object structure. diff --git a/story/writer.py b/story/writer.py index 4145d3a..5b6b1c5 100644 --- a/story/writer.py +++ b/story/writer.py @@ -46,10 +46,30 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None, if samples: persona_info += "\nWRITING STYLE SAMPLES:\n" + "\n".join(samples) + # Only inject characters named in the chapter beats + the POV character + beats_text = " ".join(str(b) for b in chap.get('beats', [])) + pov_lower = pov_char.lower() if pov_char else "" + chars_for_writer = [ + {"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")} + for c in bp.get('characters', []) + if c.get("name") and ( + c["name"].lower() in beats_text.lower() or + (pov_lower and c["name"].lower() == pov_lower) + ) + ] + if not chars_for_writer: + chars_for_writer = [ + {"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")} + for c in bp.get('characters', []) + ] + relevant_names = {c["name"] for c in chars_for_writer} + char_visuals = "" if tracking and 'characters' in tracking: - char_visuals = "\nCHARACTER TRACKING (Visuals & Preferences):\n" + char_visuals = "\nCHARACTER TRACKING (Visuals, State & Scene Position):\n" for name, data in tracking['characters'].items(): + if name not in relevant_names: + continue desc = ", ".join(data.get('descriptors', [])) likes = ", ".join(data.get('likes_dislikes', [])) speech = data.get('speech_style', 'Unknown') @@ -62,6 +82,13 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None, if worn and worn != 'Unknown': char_visuals += f" * Last Worn: {worn} (NOTE: Only relevant if scene is continuous from previous chapter)\n" + location = data.get('current_location', '') + items = data.get('held_items', []) + if location: + char_visuals += f" * Current Location: {location}\n" + if items: + char_visuals += f" * Held Items: {', '.join(items)}\n" + style_block = "\n".join([f"- {k.replace('_', ' ').title()}: {v}" for k, v in style.items() if isinstance(v, (str, int, float))]) if 'tropes' in style and isinstance(style['tropes'], list): style_block += f"\n- Tropes: {', '.join(style['tropes'])}" @@ -71,13 +98,8 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None, prev_context_block = "" if prev_content: - trunc_content = utils.truncate_to_tokens(prev_content, 2000) - prev_context_block = f"\nPREVIOUS CHAPTER TEXT (For Tone & Continuity):\n{trunc_content}\n" - - chars_for_writer = [ - {"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")} - for c in bp.get('characters', []) - ] + trunc_content = utils.truncate_to_tokens(prev_content, 1000) + prev_context_block = f"\nPREVIOUS CHAPTER TEXT (Last ~1000 Tokens — For Immediate Continuity):\n{trunc_content}\n" total_chapters = ls.get('chapters', '?') prompt = f"""