Seed phone + merit nodes (and NPC contacts) via the graph seeder
authorGraph now seeds merit nodes' awards_flag and phone-node terminals bound to an NPC (npc key -> npc_id, the callee dialed); authorMystery seeds cast phone_number/email. Importer types updated so mystery.json can carry these, matching how Glass Harbour is seeded — the Barricelli graph can now be authored as data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,13 +16,13 @@ type MysteryDocument = {
|
|||||||
}
|
}
|
||||||
type MysteryGraph = {
|
type MysteryGraph = {
|
||||||
entry: string
|
entry: string
|
||||||
nodes: { key: string; type: 'cutscene' | 'dialogue' | 'level' | 'det_gate' | 'llm_gate'; label?: string; x: number; y: number
|
nodes: { key: string; type: 'cutscene' | 'dialogue' | 'level' | 'det_gate' | 'llm_gate' | 'merit' | 'phone'; label?: string; x: number; y: number
|
||||||
componentKey?: string; templateSlug?: string; version?: number
|
componentKey?: string; templateSlug?: string; version?: number; awardsFlag?: string
|
||||||
terminals?: { key: string; label?: string; to?: string | null }[]
|
terminals?: { key: string; label?: string; to?: string | null; npc?: string }[]
|
||||||
utterances?: { npc?: string; pose?: string; text: string; utterer?: 'npc' | 'player' }[] }[]
|
utterances?: { npc?: string; pose?: string; text: string; utterer?: 'npc' | 'player' }[] }[]
|
||||||
}
|
}
|
||||||
type MysteryNarrative = {
|
type MysteryNarrative = {
|
||||||
cast: { key: string; name: string; role?: string; defaultPose?: string; poses?: { poseKey: string; assetId: string }[] }[]
|
cast: { key: string; name: string; role?: string; defaultPose?: string; phoneNumber?: string; email?: string; poses?: { poseKey: string; assetId: string }[] }[]
|
||||||
graph?: MysteryGraph
|
graph?: MysteryGraph
|
||||||
}
|
}
|
||||||
type MysteryGoal = {
|
type MysteryGoal = {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export type PlaythroughAdvanceResult = {
|
|||||||
export type MysteryAuthoring = {
|
export type MysteryAuthoring = {
|
||||||
slug: string
|
slug: string
|
||||||
title: string
|
title: string
|
||||||
cast: { key: string; name: string; role?: string; defaultPose?: string; poses?: { poseKey: string; assetId: string }[] }[]
|
cast: { key: string; name: string; role?: string; defaultPose?: string; phoneNumber?: string; email?: string; poses?: { poseKey: string; assetId: string }[] }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -212,8 +212,8 @@ export function createNarrativeRepository(pool: Pool, objectStorage: ObjectStora
|
|||||||
const existing = await client.query('SELECT 1 FROM osint.npcs WHERE npc_key=$1 AND mystery_id IS NULL', [npc.key])
|
const existing = await client.query('SELECT 1 FROM osint.npcs WHERE npc_key=$1 AND mystery_id IS NULL', [npc.key])
|
||||||
if (existing.rows[0]) continue
|
if (existing.rows[0]) continue
|
||||||
const npcId = randomUUID()
|
const npcId = randomUUID()
|
||||||
await client.query('INSERT INTO osint.npcs (id,mystery_id,npc_key,name,role,default_pose_key) VALUES ($1,NULL,$2,$3,$4,$5)',
|
await client.query('INSERT INTO osint.npcs (id,mystery_id,npc_key,name,role,default_pose_key,phone_number,email) VALUES ($1,NULL,$2,$3,$4,$5,$6,$7)',
|
||||||
[npcId, npc.key, npc.name, npc.role || '', npc.defaultPose || null])
|
[npcId, npc.key, npc.name, npc.role || '', npc.defaultPose || null, npc.phoneNumber || null, npc.email || null])
|
||||||
for (const pose of npc.poses || []) await client.query(
|
for (const pose of npc.poses || []) await client.query(
|
||||||
'INSERT INTO osint.npc_poses (id,npc_id,pose_key,asset_id) VALUES ($1,$2,$3,$4)', [randomUUID(), npcId, pose.poseKey, pose.assetId])
|
'INSERT INTO osint.npc_poses (id,npc_id,pose_key,asset_id) VALUES ($1,$2,$3,$4)', [randomUUID(), npcId, pose.poseKey, pose.assetId])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import type { Pool, PoolClient } from 'pg'
|
|||||||
|
|
||||||
export type GraphSpecNode = {
|
export type GraphSpecNode = {
|
||||||
key: string; type: StoryNodeType; label?: string; x: number; y: number
|
key: string; type: StoryNodeType; label?: string; x: number; y: number
|
||||||
componentKey?: string; templateSlug?: string; version?: number
|
componentKey?: string; templateSlug?: string; version?: number; awardsFlag?: string
|
||||||
terminals?: { key: string; label?: string; to?: string | null }[]
|
terminals?: { key: string; label?: string; to?: string | null; npc?: string }[]
|
||||||
utterances?: { npc?: string; pose?: string; text: string; utterer?: Utterer }[]
|
utterances?: { npc?: string; pose?: string; text: string; utterer?: Utterer }[]
|
||||||
}
|
}
|
||||||
export type GraphSpec = { entry: string; nodes: GraphSpecNode[] }
|
export type GraphSpec = { entry: string; nodes: GraphSpecNode[] }
|
||||||
@@ -261,12 +261,18 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
|||||||
versionId = version.rows[0]?.id ?? null
|
versionId = version.rows[0]?.id ?? null
|
||||||
if (!versionId) throw new Error(`Graph node ${node.key}: unknown level template ${node.templateSlug}`)
|
if (!versionId) throw new Error(`Graph node ${node.key}: unknown level template ${node.templateSlug}`)
|
||||||
}
|
}
|
||||||
await client.query('INSERT INTO osint.story_nodes (id,mystery_id,node_type,label,xpos,ypos,has_utterances,level_template_version_id,component_key) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)',
|
await client.query('INSERT INTO osint.story_nodes (id,mystery_id,node_type,label,xpos,ypos,has_utterances,level_template_version_id,component_key,awards_flag) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',
|
||||||
[id, mysteryId, node.type, node.label || node.type, node.x, node.y, Boolean(node.utterances?.length), versionId, node.componentKey || null])
|
[id, mysteryId, node.type, node.label || node.type, node.x, node.y, Boolean(node.utterances?.length), versionId, node.componentKey || null, node.awardsFlag || null])
|
||||||
for (const [index, terminal] of (node.terminals || []).entries()) {
|
for (const [index, terminal] of (node.terminals || []).entries()) {
|
||||||
const terminalId = randomUUID(); terminalIds.set(`${node.key}:${terminal.key}`, terminalId)
|
const terminalId = randomUUID(); terminalIds.set(`${node.key}:${terminal.key}`, terminalId)
|
||||||
await client.query('INSERT INTO osint.story_node_terminals (id,parent_node_id,terminal_key,label,sort_order) VALUES ($1,$2,$3,$4,$5)',
|
let npcId: string | null = null
|
||||||
[terminalId, id, terminal.key, terminal.label || terminal.key, index])
|
if (terminal.npc) { // phone-node terminal bound to an NPC (the callee)
|
||||||
|
const npc = await client.query<{ id: string }>('SELECT id FROM osint.npcs WHERE npc_key=$1 AND mystery_id IS NULL', [terminal.npc])
|
||||||
|
npcId = npc.rows[0]?.id ?? null
|
||||||
|
if (!npcId) throw new Error(`Graph node ${node.key}: terminal ${terminal.key} references unknown NPC ${terminal.npc}`)
|
||||||
|
}
|
||||||
|
await client.query('INSERT INTO osint.story_node_terminals (id,parent_node_id,terminal_key,label,sort_order,npc_id) VALUES ($1,$2,$3,$4,$5,$6)',
|
||||||
|
[terminalId, id, terminal.key, terminal.label || terminal.key, index, npcId])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wire terminals now that all nodes exist.
|
// Wire terminals now that all nodes exist.
|
||||||
|
|||||||
Reference in New Issue
Block a user