Domain · Specifications

Hathor Domain — Technical Specifications

client into src/generated/client;

11sections28 minread

On this page

Worldbuilding, Narrative Design, and World Simulation Platform

This document specifies what is implemented in libs/hathor/* and apps/hathor/*. Every model, field, enum, endpoint, event, and library entry below is taken directly from the source: primarily database/prisma/schema.prisma, the world-api, narrative-api, and simulation-worker route files, event-publisher/src/*, event-handlers/src/index.ts, contracts/src/events/hathor.ts, the svc-veilborn-* app files, lore-compiler/src/*, validation/src/index.ts, and the package.json/project.json of each project.

The Hathor domain spans 17 libraries and 7 applications. Section 10 is the only forward-looking section; everything in it is labelled (planned) and sourced from the V2 contract docs under V2/.


1. Data Models (@hathor/database)#

@hathor/database is the Prisma schema and client for Hathor. The schema is in libs/hathor/database/prisma/schema.prisma. The Prisma generator emits the client into src/generated/client; previewFeatures = ["fullTextSearchPostgres"] is enabled. The datasource is PostgreSQL via the HATHOR_DATABASE_URL environment variable. The schema does not declare a multi-schema namespace — all tables live in the connection's default schema. Every model id is a cuid() stored as VarChar(25).

The schema defines 20 models in total: World, WorldVersion, Branch, MergeRequest, Entity, EntityRelation, StoryGraph, StoryNode, StoryArc, DialogueTree, DialogueNode, DialogueSession, Quest, QuestObjective, QuestReward, SimulationRun, SimulationEvent, SimulationSnapshot, ValidationResult, and AuditLog.

1.1 World#

The World model is the top-level container for a simulation universe. Every other model belongs to a world, directly or indirectly.

Table: world | Unique: [userId, name]

Field Type Default Description
id String (cuid) auto Primary key
name String (≤200) World name
description String? (Text) Description
status WorldStatus DRAFT Lifecycle status
genre WorldGenre FANTASY Genre classification
scope WorldScope MEDIUM World size/scope
technologyLevel TechnologyLevel MEDIEVAL Technology era
magicLevel MagicLevel MEDIUM Magic prevalence
userId String (≤50) Owner user ID
organizationId String? (≤50) Organization
settings Json {} World settings
stats Json {} Aggregate statistics
tags String[] [] Tags
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp
publishedAt DateTime? Publish timestamp
deletedAt DateTime? Soft-delete marker

Relations: versions, branches, entities, quests, storyGraphs, dialogueTrees, simulations. Indexes: status, genre, userId, organizationId, createdAt, deletedAt.


1.2 WorldVersion#

WorldVersion represents an immutable snapshot of world state. Versions form a parent-child chain analogous to git commits: each version has a parentVersionId pointing to the previous one, and isCurrent marks the HEAD of each branch.

Table: world_version | Unique: [worldId, version], [worldId, branchId, version]

Field Type Default Description
id String auto Primary key
version Int 1 Version number
name String? (≤200) Version name
description String? (Text) Version description
worldId String FK to World (cascade delete)
branchId String? FK to Branch
parentVersionId String? Self-FK to parent WorldVersion
changes Json [] List of changes (delta)
snapshot Json {} Full state snapshot
createdBy String? (≤50) Creator user ID
createdAt DateTime now() Creation timestamp
isCurrent Boolean false Whether this is the branch's HEAD

Self-relation VersionParent provides childVersions. Also has merge-request back-relations mergeRequestsSource and mergeRequestsTarget.


1.3 Branch#

A Branch represents an alternate timeline or development track within a world. Each branch diverges from a specific branchPointVersionId, accumulates its own WorldVersion records, and can be merged back via a MergeRequest.

Table: branch | Unique: [worldId, name]

Field Type Default Description
id String auto Primary key
name String (≤200) Branch name
description String? (Text) Branch description
worldId String FK to World (cascade delete)
branchPointVersionId String? Version where branch split
isMain Boolean false Whether main branch
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp

Relations: versions.


1.4 MergeRequest#

A MergeRequest tracks a proposal to merge changes from one version into another. The conflicts JSON field records any entities changed on both sides; each conflict must be resolved (source, target, or custom) before the merge can execute.

Table: merge_request

Field Type Default Description
id String auto Primary key
title String (≤200) Merge request title
description String? (Text) Description
sourceVersionId String FK to source WorldVersion
targetVersionId String FK to target WorldVersion
status MergeRequestStatus OPEN Status
conflicts Json [] Conflict details
resolution Json? Resolution details
createdBy String? (≤50) Creator user ID
reviewedBy String? (≤50) Reviewer user ID
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp
mergedAt DateTime? Merge timestamp

1.5 Entity#

Entity is the universal container for all objects in a world. Rather than having separate tables for characters, factions, locations, etc., all 10 entity types share this one table and store type-specific properties in the properties JSON field. This design allows new entity types to be added without schema migrations.

The 10 supported entity types are: CHARACTER, FACTION, LOCATION, EVENT, RELATIONSHIP, ERA, REGION, ITEM, CULTURE, RELIGION.

Table: entity | Unique: [worldId, type, name]

Field Type Default Description
id String auto Primary key
name String (≤200) Entity name
description String? (Text) Description
type EntityType Entity type (10 values)
worldId String FK to World (cascade delete)
versionId String? Version reference
properties Json {} Type-specific properties
tags String[] [] Tags
aliases String[] [] Alternate names for search
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp
deletedAt DateTime? Soft-delete marker
searchVector Unsupported(tsvector) Full-text search vector

Relations: outgoingRelations, incomingRelations (both EntityRelation[]).


1.6 EntityRelation#

EntityRelation models a directed relationship between two entities — for example, "character A is allied with faction B" or "location C is inside region D". The type field is free-text so any relationship semantics can be expressed without schema changes. The bidirectional flag indicates whether the relationship applies equally in both directions.

Table: entity_relation | Unique: [sourceId, targetId, type]

Field Type Default Description
id String auto Primary key
sourceId String FK to source Entity (cascade)
targetId String FK to target Entity (cascade)
type String (≤100) Relationship type (free-text)
properties Json {} Additional properties
strength Float 1.0 Relationship strength / weight
bidirectional Boolean false Whether bidirectional
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp

1.7 StoryGraph, StoryNode, StoryArc#

The three story models together represent a non-linear branching narrative. A StoryGraph is the top-level container; StoryNode records represent individual scenes, choices, or conditions; StoryArc records are the directed edges connecting nodes.

StoryGraph — Branching narrative graph. Table: story_graph | Unique: [worldId, name, version]. Fields: id, name, description?, worldId (FK, cascade), version (Int, default 1), isActive (Boolean, default true), tags, createdAt, updatedAt, publishedAt?. Relations: nodes (StoryNode[]), arcs (StoryArc[]).

StoryNode — A node in a story graph. Table: story_node. Fields: id, storyGraphId (FK, cascade), type (StoryNodeType, default SCENE), name, content?, positionX/positionY (Float, default 0, for the visual editor), conditions (Json []), actions (Json []), metadata (Json {}), createdAt, updatedAt. Relations: outgoingArcs, incomingArcs.

StoryArc — Directed edge between two StoryNodes. Table: story_arc | Unique: [storyGraphId, sourceId, targetId]. Fields: id, storyGraphId (FK, cascade), sourceId/targetId (FK to StoryNode, cascade), label? (≤200, choice text), conditions (Json [], traversal conditions), priority (Int, default 0, for auto-selection), createdAt.


1.8 DialogueTree, DialogueNode, DialogueSession#

The three dialogue models support both authoring (the tree structure) and runtime playback (active sessions). A DialogueTree holds the authored structure; DialogueNode records hold individual lines and choices; DialogueSession tracks a live playback instance, recording which node the participant is currently at and what choices have been made.

DialogueTree — Tree structure for character conversations. Table: dialogue_tree | Unique: [worldId, name]. Fields: id, name, description?, worldId (FK, cascade), speakers (Json [], array of entity IDs), locationId? (location context), tags, createdAt, updatedAt. Relations: nodes, sessions.

DialogueNode — A single dialogue step. Table: dialogue_node. Fields: id, dialogueTreeId (FK, cascade), type (DialogueNodeType, default STATEMENT), speakerId? (entity ID), content?, emotion? (≤50, emotion/tone tag), conditions (Json []), actions (Json []), isStart (Boolean, default false), isEnd (Boolean, default false), choices (Json []), nextNodes (Json [], branching node IDs), createdAt, updatedAt.

DialogueSession — A runtime instance of a dialogue tree being played. Table: dialogue_session. Fields: id, dialogueTreeId (FK, cascade), currentNodeId?, history (Json [], visited nodes), context (Json {}, session state), participantId? (≤50), isComplete (Boolean, default false), startedAt, completedAt?.


1.9 Quest, QuestObjective, QuestReward#

Quests are stored across three related tables. Quest holds the top-level definition; QuestObjective holds the individual tasks a player must complete; QuestReward holds the items, currency, or reputation changes granted on completion.

Quest — A quest definition. Table: quest | Unique: [worldId, name]. Fields: id, name, description?, worldId (FK, cascade), category (QuestCategory, default SIDE), priority (QuestPriority, default NORMAL), status (QuestStatus, default DRAFT), giverId? (quest-giver entity), locationId?, minLevel?/maxLevel? (Int), prerequisites (Json [], quest IDs), tags, createdAt, updatedAt, publishedAt?. Relations: objectives, rewards.

QuestObjective — An objective within a quest. Table: quest_objective. Fields: id, questId (FK, cascade), type (ObjectiveType, default CUSTOM), description (Text, required), targetCount (Int, default 1), currentProgress (Int, default 0), isCompleted (Boolean, default false), isOptional (Boolean, default false), orderIndex (Int, default 0), targetEntityId?, metadata (Json {}), createdAt, updatedAt.

QuestReward — A reward granted on quest completion. Table: quest_reward. Fields: id, questId (FK, cascade), type (String, ≤50), amount? (Int, for currency/XP), itemId? (entity ID for item rewards), description? (≤500), isChoice (Boolean, default false), createdAt.


1.10 SimulationRun, SimulationEvent, SimulationSnapshot#

Simulation state is tracked across three models. SimulationRun is the job record, tracking status and progress from submission through completion. SimulationEvent records individual events emitted during the run (e.g., a price spike, an alliance formed). SimulationSnapshot captures the full simulation state at a specific tick so it can be compared or restored later.

SimulationRun — A simulation job. Table: simulation_run.

Field Type Default Description
id String auto Primary key
worldId String FK to World (cascade)
type SimulationType FULL Simulation type
status SimulationStatus PENDING Job status
priority SimulationPriority NORMAL Priority
input Json {} Input parameters
output Json? Output results
progress Float 0 Progress (0–1)
currentTick Int 0 Current simulation tick
totalTicks Int? Total ticks to run
errorMessage String? (Text) Error message
errorDetails Json? Structured error detail
workerId String? (≤50) Worker assignment
startVersionId String? Version at sim start
endVersionId String? Version after sim
createdAt DateTime now() Creation timestamp
updatedAt DateTime auto Update timestamp
startedAt DateTime? Start timestamp
completedAt DateTime? Completion timestamp

Relations: events, snapshots.

SimulationEvent — An event generated during simulation at a specific tick. Table: simulation_event. Fields: id, simulationId (FK, cascade), type (String, ≤100), tick (Int), data (Json {}), affectedEntities (Json [], entity IDs), createdAt.

SimulationSnapshot — Full state snapshot at a tick. Table: simulation_snapshot | Unique: [simulationId, tick]. Fields: id, simulationId (FK, cascade), tick (Int), state (Json {}), createdAt.


1.11 ValidationResult#

ValidationResult stores the output of lore consistency validation checks so results can be reviewed later and compared across runs.

Table: validation_result

Field Type Description
id String Primary key
worldId String World reference
entityId String? Entity reference
validationType String (≤100) Validation type
isValid Boolean Whether valid
issues Json ([]) Issues found
suggestions Json ([]) Improvement suggestions
createdAt DateTime Creation timestamp

1.12 AuditLog#

AuditLog records a tamper-evident trail of tracked changes, capturing the previous state alongside the new changes so that the history of any entity can be reconstructed.

Table: audit_log. Fields: id, entityType (String, ≤50), entityId (String, ≤25), action (String, ≤50), userId? (≤50), changes (Json {}), previousState (Json?), createdAt. Indexes: [entityType, entityId], userId, action, createdAt.


2. Database Enumerations#

All enums below are defined in schema.prisma and re-exported from @hathor/database. They form the canonical persistence contract — see the note at the end of this section for differences between these Prisma enums and the per-service string-union types used at the API wire layer.

WorldStatus (4)#

DRAFT, ACTIVE, ARCHIVED, DELETED

WorldGenre (8)#

FANTASY, SCIENCE_FICTION, HISTORICAL, CONTEMPORARY, HORROR, MYSTERY, MYTHOLOGY, CUSTOM

WorldScope (5)#

SMALL (village/city), MEDIUM (region/country), LARGE (continent), EPIC (world), COSMIC (multi-world/universe)

TechnologyLevel (9)#

PRIMITIVE, ANCIENT, MEDIEVAL, RENAISSANCE, INDUSTRIAL, MODERN, FUTURISTIC, ADVANCED, MIXED

MagicLevel (5)#

NONE, LOW, MEDIUM, HIGH, EPIC

EntityType (10)#

CHARACTER, FACTION, LOCATION, EVENT, RELATIONSHIP, ERA, REGION, ITEM, CULTURE, RELIGION

CharacterStatus (5)#

ALIVE, DECEASED, UNKNOWN, MISSING, LEGENDARY

FactionType (10)#

GOVERNMENT, MILITARY, RELIGIOUS, CRIMINAL, MERCHANT, GUILD, SECRET_SOCIETY, FAMILY, TRIBAL, OTHER

LocationType (11)#

CITY, TOWN, VILLAGE, FORTRESS, TEMPLE, DUNGEON, WILDERNESS, LANDMARK, REGION, REALM, PLANE

QuestStatus (6)#

DRAFT, AVAILABLE, ACTIVE, COMPLETED, FAILED, CANCELLED

QuestCategory (6)#

MAIN, SIDE, EXPLORATION, FACTION, DAILY, WORLD_EVENT

QuestPriority (4)#

LOW, NORMAL, HIGH, CRITICAL

ObjectiveType (8)#

COLLECT, KILL, TALK, EXPLORE, ESCORT, DELIVER, CRAFT, CUSTOM

StoryNodeType (6)#

START, SCENE, CHOICE, CONDITION, ACTION, END

DialogueNodeType (6)#

GREETING, STATEMENT, QUESTION, RESPONSE, CHOICE, END

SimulationType (5)#

ECONOMY, POLITICS, CULTURE, SCENARIO, FULL

SimulationStatus (6)#

PENDING, QUEUED, RUNNING, COMPLETED, FAILED, CANCELLED

SimulationPriority (4)#

LOW, NORMAL, HIGH, CRITICAL

MergeRequestStatus (5)#

OPEN, APPROVED, MERGED, REJECTED, CANCELLED

ValidationSeverity (4)#

INFO, WARNING, ERROR, CRITICAL

Note on string-union types in services. The API apps and the SDK declare their own lowercase string-union types that are not always identical to the Prisma enums above. For example apps/hathor/world-api/src/types.ts declares WorldStatus = 'draft' | 'in_progress' | 'review' | 'published' | 'archived' and MergeRequestStatus = 'open' | 'merged' | 'closed' | 'conflict', and @hathor/validation (src/types.ts) declares ValidationSeverity = 'error' | 'warning' | 'info' (three values, no CRITICAL). Treat the Prisma enums above as the persistence contract and the per-service unions as the wire/runtime contract.


3. Database Schema Highlights#

This section summarizes the structural design decisions in the schema that are most important to understand before working with Hathor's database layer.

  • ORM: Prisma 6 | DB: PostgreSQL.
  • Generated client: emitted into database/src/generated/client.
  • 20 models: World, WorldVersion, Branch, MergeRequest, Entity, EntityRelation, StoryGraph, StoryNode, StoryArc, DialogueTree, DialogueNode, DialogueSession, Quest, QuestObjective, QuestReward, SimulationRun, SimulationEvent, SimulationSnapshot, ValidationResult, AuditLog.
  • Version-control model: Git-like versioning with branches, parent-child version chains (WorldVersion.parentVersionId), and merge requests. isCurrent on WorldVersion marks the HEAD.
  • Entity design: a single Entity table with properties: Json for type-specific fields supports all 10 entity types without per-type tables.
  • Full-text search: an Unsupported("tsvector") column on entity (fullTextSearchPostgres preview feature enabled).
  • Client lifecycle: @hathor/database exposes getHathorClient, createHathorClient, and disconnectHathorClient (database/src/client.ts), plus seed scripts (prisma/seed.ts, prisma/seed-test.ts).

4. API Surface#

The Hathor domain ships three HTTP worldbuilding services (World API, Narrative API, and Simulation Worker) plus two Veilborn game services. The worldbuilding services store data through in-memory service stores backed by repositories; the simulation worker uses an in-memory job queue.

4.1 World API (@hathor/world-api, Port 3001)#

The World API is a Hono server (apps/hathor/world-api/src/app.ts). Global middleware includes secureHeaders, cors, request logger, prettyJSON, and a rateLimit middleware applied to /api/*. Routes are mounted under /api/v1. Validation uses @hono/zod-validator.

Health: GET /health, GET /ready (readiness probes the world and version services).

World CRUD (routes/worlds.ts)#

Method Path Description
GET /api/v1/worlds List worlds (filterable)
POST /api/v1/worlds Create a world
GET /api/v1/worlds/:id Get world by ID
PUT /api/v1/worlds/:id Update world properties
DELETE /api/v1/worlds/:id Delete a world
POST /api/v1/worlds/:id/clone Clone a world
GET /api/v1/worlds/:id/export Export world as JSON file
PUT /api/v1/worlds/:id/status Update world status
PATCH /api/v1/worlds/:id/settings Update world settings
PATCH /api/v1/worlds/:id/stats Update world stats

Collaborators (routes/worlds.ts)#

Method Path Description
POST /api/v1/worlds/:id/collaborators Add a collaborator
DELETE /api/v1/worlds/:id/collaborators/:userId Remove a collaborator

Collaborators are tracked by the world service; there is no Collaborator Prisma table — collaborator state is part of the in-memory world record.

Branches (routes/worlds.ts)#

Method Path Description
GET /api/v1/worlds/:id/branches List branches
POST /api/v1/worlds/:id/branches Create a branch
GET /api/v1/worlds/:id/branches/:branchId Get branch details
DELETE /api/v1/worlds/:id/branches/:branchId Delete a branch

Versioning (routes/versions.ts)#

The versioning routes implement the full git-like version lifecycle: committing changes, viewing history, reconstructing state at any point, and marking official releases.

Method Path Description
GET /api/v1/worlds/:worldId/versions List versions
POST /api/v1/worlds/:worldId/versions Create a version (commit)
GET /api/v1/worlds/:worldId/versions/:versionId Get version by ID
GET …/versions/:versionId/history Get ancestor chain
GET …/versions/:versionId/state Reconstruct state at a version
GET …/versions/:versionId/changes List changes in a version
GET /api/v1/worlds/:worldId/releases List releases
POST …/versions/:versionId/release Mark version as a release
POST …/versions/:versionId/snapshot Create a snapshot for a version
GET …/versions/:versionId/snapshot Get the snapshot for a version
POST …/versions/:versionId/revert Revert world to a version
GET …/versions/:fromId/compare/:toId Diff between two versions
GET /api/v1/worlds/:worldId/current Get the current (HEAD) version
GET /api/v1/worlds/:worldId/state Get current reconstructed state

Merge System (routes/merge.ts)#

The merge routes cover the full merge workflow: diffing branches, creating a merge request, resolving individual conflicts, and executing the merge. Cherry-pick and rebase are also exposed here.

Method Path Description
GET …/diff/:fromId/:toId Diff two versions
GET …/branches/:sourceId/diff/:targetId Diff two branches
GET …/merge-requests List merge requests
POST …/merge-requests Create a merge request
GET …/merge-requests/:mrId Get merge request
POST …/merge-requests/:mrId/close Close a merge request
GET …/merge-requests/:mrId/conflicts List conflicts
POST …/merge-requests/:mrId/conflicts/resolve Resolve a single conflict
POST …/merge-requests/:mrId/conflicts/resolve-all Resolve all (source/target)
POST …/merge-requests/:mrId/merge Execute the merge
POST …/cherry-pick Cherry-pick a version's changes
POST …/rebase Rebase a branch onto another

Merge-request creation in routes/merge.ts takes a sourceBranchId / targetBranchId body and the merge service resolves their HEAD versions.

Query Engine (routes/query.ts)#

The query engine exposes general-purpose search and graph-traversal operations over world entities. POST …/relationships performs an actual BFS over an entity's ID-reference fields up to a requested depth; inferTargetType() maps field names (e.g. factionId, allies, prerequisites) to entity types.

Method Path Description
POST …/query General entity query
GET …/search?q=… Full-text search
GET …/factions Query factions
GET …/characters Query characters
GET …/locations Query locations
GET …/events Query timeline events
GET …/entities/:type/:id Get one entity
POST …/entities/:type/batch Get many entities by ID
GET …/entities/:type/:id/related/:field Get entities related by field
POST …/relationships BFS graph traversal
POST …/paths Find paths between entities
POST …/aggregate Aggregation (count/sum/avg/min/max/group_by)

Auth and Rate Limiting#

  • Auth: X-User-ID request header (forwarded by the gateway), read by mutating routes.
  • Rate limit: rateLimit({ limit: 100, windowMs: 60_000 }) on /api/*. Exposes X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

4.2 Narrative API (@hathor/narrative-api, Port 3002)#

The Narrative API is a Hono server (apps/hathor/narrative-api/src/app.ts) with middleware: cors, logger, timing, requestId, errorHandler. Validation uses @hono/zod-validator. Routes are mounted under /api.

Health: GET /health; service info at GET /.

Story Graphs (routes/story-graphs.ts)#

CRUD at /api/story-graphs plus: arc add/update/delete (/:id/arcs, /:id/arcs/:arcId); node add/update/delete (/:id/nodes, /:id/nodes/:nodeId); connect nodes (POST /:id/nodes/:nodeId/connect); state update (PUT /:id/state); variable and flag setters (PUT /:id/variables/:name, PUT /:id/flags/:name); clone (POST /:id/clone); export (GET /:id/export).

Quests (routes/quests.ts)#

CRUD at /api/quests plus lifecycle transitions (POST /:id/start|complete|fail|abandon); objective progress (PUT /:id/objectives/:objId/progress, POST /:id/objectives/:objId/complete); clone (POST /:id/clone); export (GET /:id/export).

Dialogues (routes/dialogues.ts)#

CRUD at /api/dialogues plus: node CRUD (/:id/nodes, /:id/nodes/:nodeId); choice CRUD (/:id/nodes/:nodeId/choices/...); speaker CRUD (/:id/speakers/...); runtime sessions (POST /:id/sessions, GET /sessions/:sessionId, GET /sessions/:sessionId/current, POST /sessions/:sessionId/advance, DELETE /sessions/:sessionId); clone (POST /:id/clone); export (GET /:id/export).

Validation (routes/validation.ts)#

The narrative validation routes allow validating individual resources (a story graph, quest, or dialogue) or batching multiple resources in a single request.

Method Path Description
POST /api/validation/story-graphs Validate a story graph
POST /api/validation/quests Validate a quest
POST /api/validation/dialogues Validate a dialogue tree
POST /api/validation/batch Batch-validate multiple entities
POST /api/validation/constraints/check Check a single constraint

4.3 Simulation Worker (@hathor/simulation-worker, Port 3004)#

The Simulation Worker is a Hono server (apps/hathor/simulation-worker/src/app.ts) with middleware: cors, logger. Routes are mounted under /api. The default worker-pool configuration has one worker each for economy, politics, culture, and scenario job types.

Method Path Description
POST /api/jobs Submit a simulation job
GET /api/jobs List jobs (filter by status/type)
GET /api/jobs/:jobId Get job status, position, ETA
POST /api/jobs/:jobId/cancel Cancel a running or queued job
POST /api/jobs/:jobId/retry Retry a failed job
GET /api/workers Get all worker health statuses
GET /api/workers/:workerId Get one worker's health
GET /api/stats Queue and worker statistics
POST /api/cleanup Trigger cleanup of completed jobs

Job submission validates the simulation type against economy | politics | culture | scenario | integrated, validates the startDate/endDate, and rejects when the queue is full (HTTP 503).

Note that the simulation worker uses its own string-union types that differ slightly from the Prisma enums in § 2. SimulationType in the worker is 'economy' | 'politics' | 'culture' | 'scenario' | 'integrated'. SimulationStatus adds paused to the Prisma set; SimulationPriority is 'urgent' | 'high' | 'normal' | 'low' | 'background' (apps/hathor/simulation-worker/src/types/index.ts). The five simulation input shapes are EconomySimulationInput, PoliticsSimulationInput, CultureSimulationInput, ScenarioSimulationInput, IntegratedSimulationInput (all extend BaseSimulationInput), with matching *SimulationOutput types.


4.4 Veilborn Game Services#

Two Fastify services under apps/hathor/ implement Veilborn Chronicles, a tabletop-RPG / wargame game built on the Hathor domain. Both use @lilith/fastify-core and @lilith/service-lib and run on port 8080 by default (PORT env var).

@hathor/veilborn-core (apps/hathor/svc-veilborn-core)#

The core game engine for the tabletop RPG and wargame. The HTTP surface (src/app.ts) exposes /, /health, /ready, /live, and a /metrics endpoint (JSON or Prometheus). The game systems are implemented as ~30 domain-logic modules under src/ (~42k lines total), each fully unit-tested under src/__tests__/integration/:

  • combat.ts — hex-grid tactical combat ("Confluence"): axial/cube hex coordinates, Tide Initiative turn order, an Impulse/Flow/Surge/Torrent action economy, line-of-sight and cover.
  • character-creation.ts, character-sheet.ts, abilities.ts, awakening.ts, paths.ts, resonance.ts — character build, progression, and abilities.
  • cards.ts — card system (the largest module).
  • dice.ts — dice resolution.
  • world-gen.ts — seeded procedural generation of realms, regions, locations, items, creatures, and factions (SeededRandom).
  • wargame.ts, territory-control.ts, combat-ai.ts, veylmaster.ts — wargame and AI systems.
  • weave.ts, threads.ts, social.ts, multiplayer.ts, game-state.ts, tutorial.ts, launch-content.ts, mobile-ui.ts, ui-system.ts, beta-program.ts, asset-gen.ts, data-access.ts, database-schema.ts — social, persistence, content, and UI systems.

@hathor/veilborn-strategy (apps/hathor/svc-veilborn-strategy)#

Strategy game engine for the "The Veil War" mode. Fastify service with optional @fastify/websocket real-time sync and @lilith/service-lib JWT authentication. REST surface (src/app.ts) under /api/v1:

Method Path Description
GET /api/v1/games List games
POST /api/v1/games Create a game
GET /api/v1/games/:gameId Get full game state
POST /api/v1/games/:gameId/join Join a game
GET /api/v1/games/:gameId/state Player view (fog-of-war)
POST /api/v1/games/:gameId/actions Submit an action
GET /api/v1/games/:gameId/actions Action history
GET /api/v1/games/:gameId/players List players
POST /api/v1/games/:gameId/snapshots Save game state
GET /api/v1/games/:gameId/snapshots List snapshots
POST /api/v1/games/:gameId/snapshots/:snapshotId/load Restore a snapshot
DELETE /api/v1/games/:gameId/snapshots/:snapshotId Delete a snapshot
GET /api/v1/ws/game WebSocket sync (when enabled)
GET /api/v1/realtime/stats Real-time stats

Five game modes are wired at game-creation time, each with its own engine and route module: Echoes of Fate (echoes-of-fate/timeline-engine.ts, routes/temporal-routes.ts), Resonance Wars (resonance-wars/combat-engine.ts, routes/resonance-wars-routes.ts), Weave Conspiracy social deduction (weave-conspiracy/conspiracy-engine.ts, routes/weave-conspiracy-routes.ts), Eternal Game area control (eternal-game/influence-engine.ts, routes/eternal-game-routes.ts), and Grand Campaign multi-session meta-game (grand-campaign/campaign-manager.ts, routes/grand-campaign-routes.ts). The service also includes per-mode AI opponents (src/ai/*) and a balance pipeline (src/balance/* — skill rating, pattern tracking, tournament runner).

4.5 Studio Web (@hathor/studio-web)#

apps/hathor/studio-web is a small package that defines the V2 fighting-game narrative content map (src/v2-narrative-content.ts). It declares five narrative surfaces (story, side_story, krypt, chronicles, dj_story), the content packs and beats per surface, and the compiled-artifact plans (dialogue banks, sequencer outlines, quest graphs, timeline data, rivalry matrices, codex entries, localization keys) emitted to V2/ue/Content/Generated/.... Its only dependency is @hathor/lore-compiler. It is not an HTTP server.


5. Events#

All events are delivered via @oshun/event-bus (Redis-backed in @hathor/event-publisher). The event contracts are defined in @oshun/contracts (libs/contracts/src/events/hathor.ts).

5.1 Events Published (@hathor/event-publisher)#

HathorEventPublisher (libs/hathor/event-publisher/src/hathor-event-publisher.ts) publishes seven event types. It uses a singleton accessor pattern (getHathorEventPublisher / factory createHathorEventPublisher) and a default Redis key prefix of oshun:events. Publish failures are swallowed (logged, not thrown).

Event Publisher method Default targets
hathor.world.created publishWorldCreated isis, bellona
hathor.world.published publishWorldPublished bellona, yemaya
hathor.element.added publishElementAdded (none)
hathor.narrative.generated publishNarrativeGenerated sophia
hathor.simulation.started publishSimulationStarted (none)
hathor.simulation.completed publishSimulationCompleted bellona
hathor.world.validated publishWorldValidated (none)

The Zod payload schemas (from @oshun/contracts) carry richer information than a simple ID list. Key fields per event type:

  • HathorWorldCreatedPayloadworldId, projectId, userId, name, type (WorldType: narrative | game | simulation | virtual_production | educational | experimental), description?, and settings (scale, timeScale, physics).
  • HathorWorldPublishedPayloadworldId, projectId, userId, version, previousVersion?, statistics (location/character/item/rule counts, total assets and bytes), exportFormats, validationPassed, publishedAt.
  • HathorElementAddedPayloadelementId, worldId, projectId, elementType (location | character | item | event | rule | relationship), name, parentElementId?, attributes?.
  • HathorNarrativeGeneratedPayloadnarrativeId, worldId, projectId, userId, type (backstory | plot | dialogue | description | lore), title, characterCount, wordCount, referencedElements, coherenceScore?, generationTimeMs.
  • HathorSimulationStartedPayload / HathorSimulationCompletedPayloadsimulationId, worldId, projectId, type (physics | social | economic | ecological | narrative), simulation parameters/results, artifacts, duration, success.
  • HathorWorldValidatedPayloadworldId, projectId, validationId, passed, checks[] (each with name, category, passed, severity, message, affectedElements?), score (0–100), durationMs.

5.2 Events Consumed (@hathor/event-handlers)#

setupHathorEventHandlers (libs/hathor/event-handlers/src/index.ts) subscribes to four upstream events via @oshun/event-bus. Subscriptions use consumer-group support, graceful shutdown, and a getHandlerRegistrations() helper that assigns per-event concurrency so a burst from one source does not block the others.

Event Handler file Concurrency
sophia.document.ingested handlers/sophia-document-ingested.ts 5
isis.asset.generated handlers/isis-asset-generated.ts 10
yemaya.project.created handlers/yemaya-project-created.ts 3
yemaya.character.created handlers/yemaya-character-created.ts 10

Handlers receive a HathorHandlerContext with repositories for worlds, suggestions, document references, asset links, character visual attributes, character summaries, relationship suggestions, character arcs, and a job-queue client (event-handlers/src/types.ts).


6. Configuration and Environment Variables#

The following environment variables must be set for a full Hathor deployment. Port defaults are hard-coded in each service and overridden by PORT (Veilborn services) or DEFAULT_APP_CONFIG (simulation worker).

bash
# Database (Prisma)
HATHOR_DATABASE_URL=postgresql://...

# Event bus (Redis)
REDIS_URL=redis://localhost:6379
HATHOR_EVENTS_ENABLED=true     # set to "false" to disable publishing

# Service ports (defaults)
# world-api          3001
# narrative-api      3002
# simulation-worker  3004
# veilborn-core      8080 (PORT)
# veilborn-strategy  8080 (PORT)

@hathor/event-publisher reads REDIS_URL and HATHOR_EVENTS_ENABLED. The simulation worker's default port (3004) is hard-coded in DEFAULT_APP_CONFIG.


7. Integration Points#

7.1 Sophia Integration (@hathor/sophia-integration)#

Sophia is the only domain that Hathor calls synchronously (via direct API call rather than events). This is because citation lookup and lore fact-checking are interactive operations that require an immediate response. The three services in this library each address a different aspect of the Sophia integration:

Service File Description
CitationService citation-service.ts Link world lore to Sophia research sources
ResearchGroundingService research-grounding.ts Generate world content grounded in researched knowledge
LoreValidationService lore-validation.ts Fact-check world lore against Sophia's research corpus

7.2 Bellona Consumption of Hathor Artifacts#

The Bellona-facing compiler and consumer are implemented in the Bellona domain, not in Hathor. This boundary exists because Bellona owns knowledge of engine project formats — Hathor produces engine-neutral compiled artifacts and Bellona converts them to engine-native form. The relevant Bellona files are libs/bellona/integration/src/hathor/lore-compiler.ts and libs/bellona/integration/src/hathor/artifact-consumer.ts. When hathor.world.published is emitted, Bellona consumes it to produce engine-native artifacts. Hathor's own @hathor/lore-compiler produces the upstream compiled artifacts (see § 8).

7.3 Shared Oshun Libraries Used#

Library Usage
@oshun/event-bus Event publishing and subscription
@oshun/contracts Hathor event schemas and HathorEventTypes
@oshun/logging Structured logging

8. Library Catalog#

This section documents the implementation details of each Hathor library — the files that exist, the entry points, and the key types and functions exported.

8.1 @hathor/lore-compiler#

Compiles narrative content into engine-ready and film-ready artifacts (libs/hathor/lore-compiler/src). Three compilers are exposed via factory functions:

  • Quest Compiler (engine/quest-compiler.ts, createQuestCompiler) — compiles quests for target engines. TargetEngine is unreal | unity | godot | blender | custom; EngineArtifactFormat is json | yaml | xml | binary | sqlite.
  • Screenplay Compiler (film/screenplay-compiler.ts, createScreenplayCompiler) — compiles a story into a CompiledScreenplay and exports it. FilmArtifactFormat is fountain | fdx | pdf | html | json | avid_log. Beats use the 15-beat FilmBeatType set (Save-the-Cat structure).
  • Bellona Package Builder (bellona/package-builder.ts, BellonaPackageBuilder) — assembles a BellonaPackage (manifest, world, quests, characters, locations, items, dialogues, optional screenplay, assets, localization).

Key compiled types: CompiledQuest, CompiledObjective, QuestPrerequisite, QuestReward, DialogueTree/DialogueNode, CompiledScreenplay, CompiledBeat, CompiledScene, CompilationResult<T>.

8.2 @hathor/validation#

Four lore validators plus a combined runner (libs/hathor/validation/src): createTimelineValidator, createCausalityValidator, createTaxonomyValidator, createContradictionDetector, and createLoreValidator() which runs all four. LoreValidator.validate() accepts events, eras, timeline, characters, and locations, respects failFast and maxIssuesPerCategory, and returns a LoreValidationResult with per-validator results, allIssues, and a summary (errorCount, warningCount, infoCount, validatorsRun, duration). The validation lib's ValidationSeverity has three values: error | warning | info.

8.3 @hathor/narrative#

Engine-agnostic narrative systems (libs/hathor/narrative/src): quest/ (quest engine), dialogue/ (dialogue engine), story-graph/ (typed-node graph with traversal), journal/ (journal/codex system), branching/ (branching utilities), and export/ — exporters for Ink (ink-exporter.ts), Yarn Spinner (yarn-exporter.ts), and JSON (json-exporter.ts) selected by exporter-factory.ts.

8.4 @hathor/simulation#

The largest domain library (libs/hathor/simulation/src): four simulation managers — economy/manager.ts, politics/manager.ts, culture/manager.ts, scenario/manager.ts — each with a providers.ts and typed inputs; an NPC behavior-tree system (npc/behavior-tree.ts); state persistence (state/state-store.ts, state/postgres-storage.ts, state/redis-cache.ts); and a physics engine (physics/engine.ts, physics/math.ts). There is no ecology module.

8.5 @hathor/llm-npc#

LLM-powered NPC system (libs/hathor/llm-npc/src): brain/ — personality (personality-manager.ts), emotional state, memory, world awareness; dialogue/dialogue-generator.ts, safety-filter.ts; behavior/behavior-controller.ts, npc-conversation.ts; platforms/llm-client.ts, nvidia-ace-client.ts, inworld-client.ts, convai-client.ts; a provider-chain.ts with circuit-breaker failover; and an advanced/npc-sota.ts module.

8.6 @hathor/theory#

Three theory subsystems (libs/hathor/theory/src): mda/ (MDA framework), narrative/ (narrative theory — fabula/syuzhet, beat sheets, structure templates), cinematography/ (shot/composition/lighting/color planning). Each has a manager.ts, types.ts, and constants.ts.

8.7 @hathor/pre-production#

Pre-production tooling (libs/hathor/pre-production/src):

  • scriptwriting/ — chronicle document parser (parser.ts), templates, and an exporter.
  • storyboard/storyboard-manager.ts with frames, sequences, layers, camera specs, transitions, character placement, and an exporter.
  • planning/ — a project-manager.ts for worldbuilding project management (phases, milestones, tasks, dependencies, checklists.ts), plus a very large library of virtual-production / on-set systems (~240 *-system.ts and drone-* modules covering drone choreography and safety, virtual sets, LED-wall ICVFX, relighting, motion capture, switching, color, and continuity). Each module has a matching .test.ts.

8.8 @hathor/domain-models#

Engine-agnostic worldbuilding domain models (libs/hathor/domain-models/src). Nine subdomains, each with types.ts, constants.ts, factories.ts, utils.ts, validators.ts: common/, faction/, economy/, law/, culture/, geography/, timeline/, character/, location/. This library holds the pure world type system. The Git-like versioning code (versions, branches, merge requests) lives in @hathor/world-api's services, not here.

8.9 @hathor/client#

TypeScript SDK for the Hathor HTTP APIs (libs/hathor/client/src). HathorClient / createClient / createClientFromEnv expose three resources: WorldResource (api/world.ts), NarrativeResource (api/narrative.ts), SimulationResource (api/simulation.ts). Branded ID types: WorldId, VersionId, BranchId, EntityId, StoryGraphId, QuestId, DialogueId, JobId. Helpers (helpers/): world-state (getWorldStateSnapshot, buildRelationshipMap, findConnectedEntities, getTimelineRange, getEntityTimeline, analyzeFaction, compareVersions, searchWorld, findEntities); quest generation (generateQuestFromTemplate, generateQuestChain, analyzeQuest, getQuestRecommendations, QUEST_TEMPLATES); lore validation (validateWorldLore, validateEntity, checkLoreImpact, getValidationRules).

8.10 Façade Libraries#

Four thin façade libraries adapt Hathor domain types for cross-domain (CGI / production) consumption. The façade pattern keeps the CGI pipeline's interface stable — consumers see a minimal, stable packet type rather than the full internal domain model. Each façade exports metadata, a packet builder, a validator, and a serializer:

  • @hathor/characters — CGI character-definition import facade. Maps Character records into a HathorCharactersCgiPacket (appearance, costume designs, visual references); createHathorCharactersCgiPacket, validateHathorCharactersCgiPacket. Depends on @hathor/domain-models and @hathor/pre-production.
  • @hathor/world — CGI scene-definition import facade. Depends on @hathor/domain-models.
  • @hathor/timeline — narrative scene-order facade (story-order vs. shoot-order reconciliation, causal-dependency checking, world-state continuity). Depends on @hathor/domain-models.
  • @hathor/quests — scene-dependency / story-arc capture facade. Depends on @hathor/narrative.

9. Common Error Format#

All three Hono-based Hathor APIs return errors in a consistent JSON envelope. Understanding this shape is important when building integrations that need to distinguish error types programmatically.

json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "World not found: wld_xyz"
  }
}

Common codes seen in the route handlers: NOT_FOUND, VALIDATION_ERROR, CREATE_FAILED, CLONE_FAILED, QUERY_FAILED, MERGE_FAILED, CONFLICTS_EXIST, INVALID_TYPE, QUEUE_FULL, INTERNAL_ERROR. Zod validation failures surface via the global onError handler with a details array. The Veilborn Fastify services use a different envelope: a top-level { success, data?, error?, timestamp } shape with error.code and error.message.

SDK Error Hierarchy (@hathor/client)#

The SDK maps HTTP error responses to a typed exception hierarchy so callers can catch specific error conditions without inspecting response codes manually. HathorError is the base, with subclasses NetworkError, TimeoutError, AbortError, NotFoundError, ValidationError, ConfigError, UnauthorizedError, ForbiddenError, ConflictError, RateLimitError. createErrorFromResponse maps HTTP responses to the right subclass.


10. V2 Fighting-Game Narrative Contract (planned)#

The V2 fighting-game project (V2/) is a named Hathor consumer for Story Mode, per-fighter Side Stories, the Krypt, Chronicles of the Sword, Def Jam Story, and the rivalry matrix. The integration contract is documented in V2/V2_ARCHITECTURE.md, V2/V2_DEPENDENCIES.md, and V2/docs/integration/hathor-narrative-contract.md.

Implemented today: apps/hathor/studio-web/src/v2-narrative-content.ts defines the V2 narrative content map (five surfaces, content packs, beats, and compiled-artifact plans) and a coverage assertion. It targets @hathor/lore-compiler and the unreal engine, JSON output, compiled-artifact-only consumption.

Planned (not yet in code). The following are described in the V2 contract docs as reciprocal Hathor backlog and are not implemented in libs/hathor/* today. They are listed here because they are an explicit design contract, not invented detail:

  1. Extend @hathor/domain-models / @hathor/narrative with fighting-game entity shapes: FighterTimeline, RivalryMatrix, LadderEndingGraph, SignatureStageContext (and related FactionMatrix, SideStorySequencerOutline). The LadderEndingGraph is the per-fighter arcade/ladder ending branch tree that the lore compiler emits to V2/ue/Content/Generated/Quests; per the contract, every fighter has at least one ladder ending graph and each arcadeEndingIds entry must resolve to one.
  2. Extend @hathor/llm-npc with combat-bark and fighting-game personality presets, plus open-world NPC hooks (npc.encounter.start, npc.dialogue.request, npc.behavior.tick, npc.combat.barks); NPC outputs cached off-rollback at V2/ue/Content/Generated/NPCDialogue/<hash>.
  3. Extend @hathor/lore-compiler with the V2 emission contract (dialogue banks, Sequencer outlines, quest graphs, fighter-timeline and rivalry-matrix UE data assets, codex entries, localization keys).
  4. Extend @hathor/validation with rivalry-consistency and Themis NIL / venue rights gates.
  5. Add a fighting-game scenario-generation preset to @hathor/simulation.

Planned V2-related events on the bus (per the contract doc): hathor.dialogue.ready, hathor.rivalry.updated, hathor.timeline.branched. These are not in @oshun/contracts today — only the seven events in § 5.1 are implemented.


11. Acceptance Criteria#

A Hathor deployment is correct when all of the following hold:

  1. @hathor/database migrates cleanly and exposes the 20 models / 19 enums in § 1–2; getHathorClient connects and disconnects.
  2. world-api (3001) serves the world, branch, version, merge, and query routes in § 4.1 behind the 100-req/min rate limit and the X-User-ID mutation check; /ready reports both the world and version services.
  3. narrative-api (3002) serves the story-graph, quest, dialogue (including runtime sessions), and validation routes in § 4.2.
  4. simulation-worker (3004) accepts, runs, lists, cancels, and retries simulation jobs of all five types and reports worker health and queue stats.
  5. @hathor/event-publisher publishes the seven hathor.* events with the contract payloads in § 5.1; @hathor/event-handlers subscribes to the four upstream events in § 5.2.
  6. @hathor/lore-compiler produces quest, screenplay, and Bellona-package artifacts; @hathor/validation's createLoreValidator() runs all four validators.
  7. The Veilborn services build and serve their game and strategy APIs (§ 4.4) with passing integration tests.
  8. @hathor/client round-trips against all three HTTP APIs with its branded ID types and error hierarchy.