Blueprint v1.5.0: AI Context Optimization — Dynamic Characters & Scene State

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 00:01:47 -05:00
parent fd4ce634d4
commit f71a04c03c
4 changed files with 42 additions and 11 deletions

View File

@@ -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"""