Backend & Chain/Oracle Backend
GitHub

Oracle Backend

The scoring engine that reads World API data, computes civilization health, and writes on-chain.

Overview

The Oracle Backend is a Node.js service that runs on a 10-minute cron cycle. Each cycle follows a three-phase pipeline: Ingest game data, Process scores, and Write results to the Sui blockchain via batched Programmable Transaction Blocks.

Oracle Cycle Pipeline

1

Fetch Systems

GET /v2/solarsystems — all 24,502 systems (parallel paginated)

2

Enrich Data

GET /v2/smartassemblies + /v2/killmails — infrastructure + combat

3

Score Systems

computeSystemHealth() — activity, trust, infra, combat per system

4

Score Players

computePlayerReputation() — 5 dimensions + archetype per player

5

Compute CHI

computeGlobalCHI() — 6 sub-indices → overall score + diagnosis

6

Detect Anomalies

detectAnomalies() — pattern matching for blackouts, trust collapses, etc.

7

Write On-Chain

Batched PTBs (50/batch) → PulseRegistry on Sui Testnet

src/index.ts — Cycle Overview
async function runCycle() {
  // 1. Fetch solar systems from World API
  const systems = await fetchAllSystems();

  // 2. Enrich with smart assembly + killmail data
  const enrichment = await enrichSystems(systems);

  // 3. Compute system health scores (up to 500 per cycle)
  const systemScores = systemSubset.map(sys =>
    computeSystemHealth(sys, enrichment.get(sys.id))
  );

  // 4. Detect anomalies
  const alerts = detectAnomalies(systemScores, systemNames);

  // 5. Compute global CHI
  const chi = computeGlobalCHI(systemScores);

  // 6. Compute player reputations from enrichment
  const playerScores = computePlayerReputation(addr, stats);

  // 7. Write to Sui in batches (50 per tx)
  await writeSystemHealthBatch(oracleCapId, batch);
  await writePlayerReputationBatch(oracleCapId, batch);
  await writeGlobalCHI(oracleCapId, chi);
  await emitAlertsBatch(oracleCapId, alerts);
}

System Health Scoring

Each star system receives a health snapshot based on observable data. When real enrichment data (smart assemblies, killmails) is available, it's used. Otherwise, a deterministic hash function provides consistent fallback scores.

Real Data Path

src/scoring.ts
// Inputs from World API enrichment:
const playerCount = enrichment.activePlayerAddresses.size;
const infraCount  = enrichment.smartAssemblyCount;
const kills       = enrichment.recentKills;

// Activity: weighted combination of players, infrastructure, combat
const playerScore = Math.min(playerCount * 5, 100);
const infraScore  = Math.min(infraCount * 3, 100);
const combatScore = Math.min(kills * 8, 100);
const activityLevel = clamp(
  playerScore * 0.4 + infraScore * 0.35 + combatScore * 0.25
);

// Trust: inverse of combat ratio, boosted by infrastructure
const combatRatio = playerCount > 0 ? kills / playerCount : 0;
const baseTrust   = clamp(100 - combatRatio * 50);
const infraBoost  = Math.min(infraCount * 2, 20);
const trustLevel  = clamp(baseTrust + infraBoost);

// Local CHI: 40% activity + 60% trust
const localChi = Math.floor(
  (activityLevel * 40 + trustLevel * 60) / 100
);

Deterministic Fallback

For systems without enrichment data, a seeded hash function generates consistent pseudo-random scores per system ID. The same algorithm runs in both the oracle (scoring.ts) and frontend (vitals.ts) for display consistency.

Player Reputation (Agora Engine)

The Agora Engine computes 5 behavioral dimensions from observable on-chain activity:

src/scoring.ts
// Inputs: assemblyCount, killCount, deathCount, systemsVisited

reliability  = clamp(40 + assemblyCount * 5 + systemsVisited * 2)
commerce     = clamp(30 + assemblyCount * 8)
diplomacy    = clamp(50 - aggressionRatio * 40 + systemsVisited * 3)
stewardship  = clamp(20 + assemblyCount * 10)
volatility   = clamp(totalCombat * 5 + |kills - deaths| * 3)

// Archetype classification (first match):
Civilization Builder → stewardship >= 80 && reliability >= 70
Trusted Trader      → commerce >= 80 && reliability >= 70
Diplomat            → diplomacy >= 75 && volatility < 30
Warlord             → volatility >= 70 && commerce < 40
Wildcard            → volatility >= 50 && volatility < 70
Newcomer            → default

Global CHI Calculation

The Civilization Health Index aggregates all system health scores into a single composite score (0-100) using 6 weighted sub-indices:

src/scoring.ts
// Sub-index computation (from all system scores):
economicVitality = avg(txFrequency) * 0.6 + avg(infraCount * 5) * 0.4
securityIndex    = clamp(100 - avg(combatIncidents) * 8)
growthRate       = (activeSystems / totalSystems) * 100
connectivity     = avg(activityLevel) * 1.1
trustIndex       = avg(trustLevel)
socialCohesion   = trustIndex * 0.4 + securityIndex * 0.3
                 + avg(playerCount) * 3 * 0.3

// Weighted overall (matches smart contract formula):
overall = (
  economicVitality * 20 +
  securityIndex    * 15 +
  growthRate       * 15 +
  connectivity     * 15 +
  trustIndex       * 20 +
  socialCohesion   * 15
) / 100

// Diagnosis thresholds:
// ≥ 80 Flourishing | ≥ 65 Thriving  | ≥ 50 Stable
// ≥ 35 Stressed    | ≥ 20 Declining | < 20 Collapsing

Anomaly Detection

Each cycle, the oracle scans system scores for unusual patterns and emits alerts as on-chain events:

Alert TypeTriggerSeverity
Blackoutinfra > 5 but activity < 10Critical (0)
Trust Collapsetrust < 20 with players > 5High (1)
Combat Hotspotcombat incidents > 8Medium (2)
Trade Spiketx > 85 with players > 20Warning (3)

On-Chain Writer

The suiWriter.ts module handles all blockchain interactions using the Sui TypeScript SDK. Scores are written via Programmable Transaction Blocks (PTBs), which bundle multiple Move calls into a single transaction for gas efficiency.

src/suiWriter.ts — Batch Write Example
export async function writeSystemHealthBatch(
  oracleCapId: string,
  scores: SystemHealthScore[],
): Promise<string> {
  const tx = new Transaction();

  for (const s of scores) {
    tx.moveCall({
      target: target("update_system_health"),
      arguments: [
        tx.object(oracleCapId),
        tx.object(config.registryId),
        tx.object(SUI_CLOCK),
        tx.pure.u64(BigInt(s.systemId)),
        tx.pure.u64(BigInt(s.activityLevel)),
        tx.pure.u64(BigInt(s.trustLevel)),
        tx.pure.u64(BigInt(s.playerCount)),
        tx.pure.u64(BigInt(s.infrastructureCount)),
        tx.pure.u64(BigInt(s.txFrequency)),
        tx.pure.u64(BigInt(s.combatIncidents)),
      ],
    });
  }

  return await signAndExecute(tx);
}
The oracle batches writes at 50 systems per transaction to stay within gas limits. Private key supports three formats: Bech32 (suiprivkey1...), hex, and base64.