FLESHNOTE / DOCS / ENTITIES / KNOWLEDGE STATES

Epistemic Knowledge States & POV Filtering

EPISTEMIC ENGINE

Structural tracking of who knows what and when they learned it: preventing author continuity leaks, managing multiple POV lenses, and dynamically reacting to paragraph-level time overrides.

Why Epistemic Filtering Matters #

In complex multi-POV novels, mystery thrillers, and non-linear historical epics, one of the most frequent writing errors is epistemic leakage — a character reacting to or acting upon a fact that they haven't actually learned yet.

FleshNote eliminates this problem by decomposing character bios into discrete, time-anchored Facts in the knowledge_states table. When inspecting an entity in the sidebar, the IDE applies an epistemic filter lens based on the current scene.

Anatomy of a Knowledge State #

Every piece of lore or secret in the database is represented with strict temporal ownership:

Field Type Description & Function
fact TEXT The literal statement of knowledge (e.g. "Alice is the lost heir to the Iron Citadel").
character_id UUID / INT Who knows this fact? The subject possessing this knowledge.
source_entity_id UUID / INT What entity is this fact about? Links to characters, locations, lore items, or groups.
learned_in_chapter INT / NULL Narrative Time Constraint: The chapter number where the character acquired the fact (NULL = knows from story start).
world_time TEXT / NULL Chronological Time Constraint: In-universe date string (e.g. 1422-04-12) for non-linear timelines.
is_secret BOOLEAN When 1, the fact is marked as author-only meta-context and hidden from character POV views.

The Three POV Filter Lenses #

The top bar of the Character Inspector provides a 3-way toggle powered by POST /api/project/knowledge/for_character:

1. Author View (Omniscient / Unfiltered)

Bypasses all timestamps and chapter boundaries. Displays every fact associated with the entity, including confidential author secrets. This mode is used when editing the core worldbuilding database.

2. Narrative View (Chapter-Bound)

Constrains knowledge dynamically based on the chapter currently active in the editor. Any fact where learned_in_chapter > current_chapter is cleanly omitted. If a character learns a secret in Chapter 12, inspecting that character while drafting Chapter 4 will hide the fact.

3. World Time View (In-Universe Chronological)

Crucial for non-linear structures (e.g., Chapter 1 is Year 2026, Chapter 2 is a Year 1990 flashback). Evaluates the in-universe calendar timestamp of each fact against the chapter's effective world time.

Time Gutter Overrides & Flashback Reactivity #

FleshNote allows authors to apply Paragraph-Level Time Overrides via the editor's time gutter.

Dynamic Time Shift on Knowledge States: When you set a single paragraph to be a flashback (e.g. 15 years earlier), moving your cursor into that paragraph immediately shifts the Effective World Time of the IDE. The Character Inspector instantly recalculates visible knowledge facts and character age to match the flashback moment.

FastAPI Backend Query Implementation #

In backend/routes/knowledge.py, epistemic filtering evaluates linear calendar math and chapter sequences:

Python / FastAPI
@router.post("/for_character")
async def get_knowledge_for_character(payload: KnowledgeForCharacter):
    conn = _get_db(payload.project_path)
    cur = conn.cursor()
    
    # Base query for all facts known by character
    query = """
        SELECT ks.*, e.name as source_entity_name 
        FROM knowledge_states ks
        LEFT JOIN entities e ON ks.source_entity_id = e.id
        WHERE ks.character_id = ?
    """
    params = [payload.character_id]
    
    # Narrative Mode: filter by chapter index
    if payload.filter_mode == "narrative" and payload.current_chapter is not None:
        query += " AND (ks.learned_in_chapter IS NULL OR ks.learned_in_chapter <= ?)"
        params.append(payload.current_chapter)
        
    # World Time Mode: filter by chronological calendar days
    elif payload.filter_mode == "world_time" and payload.current_world_time:
        cal_config = load_calendar_config(payload.project_path)
        cur_day = world_time_to_linear_day(payload.current_world_time, cal_config)
        # Filters facts whose linear day <= cur_day
        ...
        
    cur.execute(query, params)
    return {"facts": [dict(row) for row in cur.fetchall()]}
On This Page