Substrate loop

The Substrate only appends history. Nothing inside Cardinal gets edited out.

Ticks

Micro tick

Every frame

Scintilla movement and physics. Resource consumption and regeneration. Environmental state update. Collision and proximity detection.

Cognitive tick

Every N micro ticks

Need state evaluation. Action selection via policy. Continuity read/write. Social evaluation. Communication signal emission and reception.

Chronicle tick

Every simulated day

Event aggregation and tagging. Continuity decay application. History compression. Cultural marker evaluation. Groq narration. SQLite flush.

95% pure heuristic JS. Groq only for chronicle narration, Continuity distillation, and communication utterances, through groq/client.js only.

Implementation

What the Substrate runs on

Engine

Node.js

Persistence

SQLite / better-sqlite3

LLM

Groq

World grid

Float32Array

Deploy

Railway

Observer

Express + HTML/CSS/JS (Railway)

cardinal/
├── world/          # grid, seasons, entities
├── scintillae/     # state, perception, policy
├── continuity/     # layers, decay, knowledge
├── communication/  # signals, language
├── substrate/      # micro, cognitive, chronicle ticks
├── db/             # schema, flush
├── observer/       # immutable event log
├── groq/           # all Groq calls
├── config.js
└── index.js

Event system

Immutable events

EVENT_SCINTILLA_BORNEVENT_SCINTILLA_DIEDEVENT_SCINTILLA_REPRODUCEDEVENT_KNOWLEDGE_TRANSFERREDEVENT_KNOWLEDGE_LOSTEVENT_FIRE_DISCOVEREDEVENT_SHELTER_BUILTEVENT_CONFLICT_INITIATEDEVENT_CONFLICT_RESOLVEDEVENT_SETTLEMENT_FORMEDEVENT_LANGUAGE_TOKEN_FORMEDEVENT_SEASON_CHANGED

Chronicle tick reads the event stream and builds compressed narrative: not “scintilla 0042 gathered berries at tick 18273” but “group near the river valley has sustained itself on river resources for 14 simulated years.”

Logging

The why alongside the what

{
  "tick": 18273,
  "scintilla": "scintilla_0042",
  "action": "gather",
  "resource": "berries",
  "location": [22, 91],
  "need_state": {
    "hunger": 0.61,
    "thirst": 0.43,
    "fatigue": 0.28,
    "fear": 0.12
  },
  "trigger": "hunger_threshold_crossed"
}

An action without its trigger is just noise. Invisible to Scintillae. Readable by the Administrator.

Observer layer