packages/feed/tools/dag-visualizer/SIMULATION-AUDIT.md
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:
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.
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
Files: packages/engine/src/services/narrative-event-processor.ts, packages/engine/src/services/timeframe-arc-processor.ts
Two parallel arc state machines exist:
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.
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.
The simulation runs as 5+ independent cron jobs:
game-tick (:00) — world events, arcs, questions, volatilitynpc-tick (:30) — NPC trading and social behaviormarkets-tick — question resolution and payoutsarticle-tick — article generationworld-facts — RSS/parody headline generationEvents 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.
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.
NPCs see prices from their own previous trades:
No dampening mechanism exists. The system has positive feedback, not independent decision-making.
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).
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.
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.
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.
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.
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.
When getAffectedStocksForQuestion() finds no matching organizations, events fire with zero market impact — appearing significant in the feed but having no effect on prices.
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.
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.
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.
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.
Market signals extracted from feed posts have no recency weighting. A 6-hour-old post and a 1-minute-old post carry equal weight.
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.
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.
MarketDecisionEngine still exists with full batch trading logic but is never called (replaced by MultiStepExecutor in npc-tick). Creates code confusion.
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.
Lines 2083-2388 of game-tick.ts contain deprecated world facts generation with distributed locks. Never called but adds complexity.
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
question.outcome