Back to Eliza

Feed Simulation Engine: Critical Audit Report

packages/feed/tools/dag-visualizer/SIMULATION-AUDIT.md

2.0.311.9 KB
Original Source

Feed Simulation Engine: Critical Audit Report

Executive Summary

A thorough inspection of the game-tick execution flow, narrative arc system, event generation, NPC decision-making, and data flow reveals 4 critical, 8 high, and 12 medium severity issues. The most fundamental problems are:

  1. NPCs have access to predetermined question outcomes — insider/deceiver roles explicitly leak answer information into NPC decision context
  2. Dual arc systems operate without synchronization — parent question arcs and child timeframed market arcs can contradict each other
  3. Events cannot trigger arc state transitions — the state machine is purely calendar-driven, making dramatic "proof" events narratively impotent
  4. Circular price-trade feedback loop — NPCs see effects of their own prior trades reflected back as "market signals"

CRITICAL: Information Leakage to NPCs

C1. NPCs Receive Predetermined Question Outcomes

Files: packages/agents/src/plugins/feed/providers/npc-game-context.ts:227-232

NPCs receive "intuitions" derived from the predetermined question outcome:

const outcome = true; // TODO: Fetch actual outcome
const signal = getSignalDirection(arcPlan, phase, agentId, outcome);

Insider NPCs get "strong gut feelings" that directly encode the answer. Deceiver NPCs get inverted signals. Regular NPCs get phase-weighted signals. All of these are derived from question.outcome — the predetermined YES/NO answer.

Impact: NPC trades are not emergent from market analysis. They're biased by leaked answer information disguised as intuition.

C2. Market Signals Are Circular

Files: packages/engine/src/services/market-context-service.ts:212-214

Feed posts are generated by NPCs and content systems that have access to predetermined outcomes. Those posts are then analyzed by SignalExtractionService to produce "market signals" fed back to NPCs. The signals encode predetermined knowledge through a laundering pipeline:

Predetermined outcome → Event generation → Feed posts → Signal extraction → NPC context → NPC decision

C3. Dual Arc Systems Without Synchronization

Files: packages/engine/src/services/narrative-event-processor.ts, packages/engine/src/services/timeframe-arc-processor.ts

Two parallel arc state machines exist:

  • Long-term arcs (ArcState table): 6-state, day-based, one per question
  • Timeframed market arcs (TimeframedMarket.arcState): variable states per timeframe

No code synchronizes them. A long-term question can resolve while child flash/intraday markets are still in crisis. Events from the parent arc don't flow to child markets.

C4. Events Cannot Trigger Arc State Transitions

Files: packages/engine/src/services/narrative-event-processor.ts:196-223

Arc state transitions are purely calendar-driven:

evaluateStateTransition(arc, dayNumber) {
  const expectedState = getExpectedState(dayNumber); // Day-based ONLY
}

If a "proof" event fires on Day 12, it cannot push the arc from escalation to revelation early. The system generates dramatic events but they have no structural impact on the narrative state machine.


HIGH: Data Flow & Architecture

H1. Context Fragmentation Across Cron Jobs

The simulation runs as 5+ independent cron jobs:

  • game-tick (:00) — world events, arcs, questions, volatility
  • npc-tick (:30) — NPC trading and social behavior
  • markets-tick — question resolution and payouts
  • article-tick — article generation
  • world-facts — RSS/parody headline generation

Events created in game-tick at :00 are NOT available to NPC trading decisions at :30 because the NPC context is built from the database snapshot, not from in-memory state. There's a 30-second lag where NPCs trade without seeing the latest world events.

H2. NPC Price Data Is Stale

Files: packages/engine/src/services/market-context-service.ts:205-215

Market context is built ONCE at npc-tick start and shared across all NPCs:

const [marketSnapshots] = await Promise.all([this.getMarketSnapshots()]);
// Same snapshot used for ALL 80 NPCs

Early NPCs trade on fresh data. Late NPCs trade on the same stale snapshot, even though earlier NPCs' trades changed prices. All 80 NPCs see identical prices regardless of execution order.

H3. Circular Trade-Price Feedback Loop

NPCs see prices from their own previous trades:

  • Tick T: NPC A buys YES → price goes up
  • Tick T+1: NPC B sees higher YES price → also buys YES
  • Tick T+2: Both A and B see even higher price → reinforcement

No dampening mechanism exists. The system has positive feedback, not independent decision-making.

H4. Orphaned Arc States After Question Resolution

Files: packages/engine/src/services/narrative-event-processor.ts (no cleanup)

When a question resolves, its ArcState persists. The processArcTick() function doesn't check question status before generating events. Pre-scheduled events can fire AFTER resolution, creating contradictory signals (e.g., a "rumor" suggesting NO after the question already resolved YES).

H5. NPC Memory Persists Across Ticks

Files: packages/engine/src/services/npc-memory-service.ts:88-205

NPCs retain up to 50 memories across ticks. This means tick T depends on tick T-1 decisions. While realistic, this creates serial dependencies that make the simulation non-reproducible and hard to debug.

H6. Article Pacing Engine Is In-Memory Only

Files: packages/engine/src/services/event-generation-helpers.ts:33-49

The NewsArticlePacingEngine tracks which events were covered by which news orgs, but this state is in-memory only. After serverless cold starts or deploys, the same events get re-reported by the same organizations, creating duplicate articles.

H7. NPCs React To Each Other Within Same Tick

NPC A posts, NPC B sees the post and comments — all within the same npc-tick execution. There's no temporal separation between NPC actions within a tick, creating real-time feedback loops that don't match the simulation's intended temporal resolution.

H8. Scheduled Events Can Contradict Each Other

Files: packages/engine/src/services/question-arc-planner.ts

The event schedule is generated once and never validated for temporal coherence. Two events on the same day can point to opposite answers:

Day 11: { type: 'confirmation', direction: 'YES' }  // "Company confirms"
Day 11: { type: 'rumor', direction: 'NO' }           // "Company denies"

The EventArcValidator checks signal ratios per phase but not temporal coherence.


MEDIUM: Design Gaps

M1. World Facts Generation Ignores Arc Phase

Files: packages/engine/src/services/world-facts-generator.ts

World facts are generated from recent events without phase context. Early-phase facts (murky, uncertain) persist unchanged into late-phase (clear, proven), creating tonal inconsistency. No mechanism ages out early-phase facts.

M2. Market Impacts Can Be Zero

When getAffectedStocksForQuestion() finds no matching organizations, events fire with zero market impact — appearing significant in the feed but having no effect on prices.

M3. Flash Market State Machine Deadlocks

Flash markets have only 2 states: live → resolving. The resolving state is terminal with no escape — no resolved state exists. Markets remain in resolving purgatory indefinitely.

M4. Group Chat Exposes Insider Information

NPCs in group chats see unfiltered messages from other NPCs. If an insider NPC posts about their "gut feeling" in a group chat, all group members gain insider-derived information. No scrubbing of outcome-revealing content.

M5. Personality Doesn't Affect Trading Strategy

NPC personality only changes LLM temperature (0.7-0.95), not the actual trading prompt or decision logic. All NPCs see the same trading context and get the same decision template regardless of archetype.

M6. Relationship-Based Trading Is Predetermined

The trading prompt instructs: "Rivals (sentiment < -0.5) = trade opposite, Allies (> 0.5) = trade same." Relationships seeded at bootstrap predetermine trading patterns before the simulation starts.

M7. Signal Analysis Lacks Temporal Freshness

Market signals extracted from feed posts have no recency weighting. A 6-hour-old post and a 1-minute-old post carry equal weight.

M8. Validation Warning Is Misleading

game-tick.ts:813-822 warns if no content was created, but posts and articles are now handled by separate crons. The warning fires incorrectly.

M9. Arc Plan Can Be Null During Event Generation

If a question was created without an arc plan (legacy data), events fire with zero actor assignments. No warning is logged at the null discovery point.

M10. MarketDecisionEngine Is Dead Code

MarketDecisionEngine still exists with full batch trading logic but is never called (replaced by MultiStepExecutor in npc-tick). Creates code confusion.

M11. Prediction Auto-AMM and NPC Trades Can Conflict

Both update market prices in different cron jobs without coordination. Auto-AMM runs in game-tick, NPC trades run in npc-tick. Both could update the same market.

M12. Deprecated World Facts Code Still In Game-Tick

Lines 2083-2388 of game-tick.ts contain deprecated world facts generation with distributed locks. Never called but adds complexity.


Data Flow Diagram: What Actually Happens

GAME-TICK (:00)
  ├─ Load 14 active questions from DB
  ├─ Generate 3 world events (LLM) ← knows predetermined outcomes
  │   └─ Events written to DB
  ├─ Process narrative arcs (LLM) ← day-based state machine, NOT event-driven
  │   └─ Arc transitions written to DB
  ├─ Process timeframed markets ← INDEPENDENT of parent arcs
  ├─ Auto-AMM adjusts prediction market prices ← uses narrative signals
  ├─ Update game state (day counter)
  ├─ Refresh widget caches, trending tags
  ├─ Sync reputation to blockchain
  ├─ Evolve NPC relationships (every 10 ticks, LLM)
  └─ Process NPC group dynamics (joins, kicks, messages)

NPC-TICK (:30) — 30 seconds later, SEPARATE process
  ├─ Load market context ONCE (stale within tick)
  ├─ For each of 80 NPCs:
  │   ├─ Build context: prices (stale), events (from DB), signals (circular)
  │   ├─ Add "intuitions" from predetermined outcomes ← INFORMATION LEAK
  │   ├─ LLM decides: TRADE / POST / COMMENT / LIKE / GROUP_MESSAGE / SKIP
  │   ├─ Execute trades (updates positions, NOT prices in real-time)
  │   └─ NPC B can see NPC A's post from THIS tick ← WITHIN-TICK FEEDBACK
  └─ Prices updated after all NPCs finish

MARKETS-TICK (periodic)
  ├─ Find questions past resolution date
  ├─ Generate resolution event (LLM)
  ├─ Settle positions (transaction)
  └─ Publish oracle reveal to blockchain

Recommendations (Priority Order)

Immediate (breaks simulation integrity)

  1. Remove predetermined outcomes from NPC context — NPCs should not have "intuitions" derived from question.outcome
  2. Make events trigger arc transitions — proof/confirmation events should advance the state machine, not just calendar days
  3. Synchronize parent/child arc systems — child markets should inherit phase signals from parent arcs

Short-term (improves simulation quality)

  1. Update prices after each NPC trade — next NPC sees fresh prices, not batch-stale
  2. Add event temporal coherence validation — no contradictory events on the same day
  3. Clean up orphaned arc states — check question status before generating arc events
  4. Persist article pacing state to DB — prevent duplicate articles after cold starts

Medium-term (design improvements)

  1. Phase-aware world facts — tag facts with arc phase, age them out as clarity emerges
  2. Personality-differentiated trading prompts — different archetypes get different trading strategies
  3. Signal freshness decay — weight recent posts higher than old ones
  4. Remove deprecated code — MarketDecisionEngine, world facts in game-tick