Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
011c8a24c2 | ||
|
|
917bb0248e |
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"slug": "barricelli-inventor-proof",
|
||||
"name": "Scene 7 · Prove Barricelli was an inventor",
|
||||
"title": "The Barricelli Files",
|
||||
"subtitle": "Scene 7 · Demonstrate OSINT skill",
|
||||
"brief": {
|
||||
"body": "DEMONSTRATE OSINT SKILL\n\nProve that Nils Aall Barricelli was an inventor. Find a reliable source online, take a screenshot, and paste it directly onto this board. Connect the source to the authored claim with red thread, explain what the evidence proves, and submit the Case Report.",
|
||||
"concepts": []
|
||||
},
|
||||
"documents": [],
|
||||
"folders": [],
|
||||
"claims": [
|
||||
{
|
||||
"key": "nils-inventor",
|
||||
"statement": "Nils Aall Barricelli was an inventor.",
|
||||
"x": 940,
|
||||
"y": 360,
|
||||
"width": 330,
|
||||
"height": 190
|
||||
}
|
||||
],
|
||||
"report": {
|
||||
"title": "Barricelli Inventor Finding",
|
||||
"requiredForCompletion": true
|
||||
},
|
||||
"goals": [
|
||||
{
|
||||
"key": "barricelli.inventor-proof",
|
||||
"title": "Prove Nils Aall Barricelli was an inventor",
|
||||
"instructions": "Paste a reliable screenshot, connect it to the claim, and submit a properly cited Case Report.",
|
||||
"completionMessage": "CASE REPORT ACCEPTED — NILS AALL BARRICELLI: INVENTOR",
|
||||
"requiredFlags": ["scene7.nils_inventor_proved"]
|
||||
}
|
||||
],
|
||||
"evidenceMatchRules": [
|
||||
{
|
||||
"name": "Google Patents · GB695913A",
|
||||
"sourceLabel": "Google Patents · GB695913A · Improved chest of drawers",
|
||||
"sourceUri": "https://patents.google.com/patent/GB695913A/en",
|
||||
"flagKey": "scene7.nils_inventor_proved",
|
||||
"minimumAnchorMatches": 2,
|
||||
"anchors": [
|
||||
{
|
||||
"phrase": "GB695913A Improved chest of drawers",
|
||||
"minimumSimilarity": 0.7
|
||||
},
|
||||
{
|
||||
"phrase": "Nils Aall Barricelli",
|
||||
"minimumSimilarity": 0.72
|
||||
},
|
||||
{
|
||||
"phrase": "695913 Chests of drawers BARRICELLI N A May 31 1951",
|
||||
"minimumSimilarity": 0.68
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Nasjonalbiblioteket · Patent 93585",
|
||||
"sourceLabel": "Nasjonalbiblioteket · Patent 93585 · Koffert-kommode",
|
||||
"sourceUri": "https://www.nb.no/items/921545b51acef0b05054cfc1f4666975?page=9&searchText=baricelli",
|
||||
"flagKey": "scene7.nils_inventor_proved",
|
||||
"minimumAnchorMatches": 2,
|
||||
"anchors": [
|
||||
{
|
||||
"phrase": "Nr 75 348 Kl 33 b-9 Fra 31 mai 1948 93585 Koffert-kommode",
|
||||
"minimumSimilarity": 0.68
|
||||
},
|
||||
{
|
||||
"phrase": "Niels Aall Baricelli Oslo",
|
||||
"minimumSimilarity": 0.72
|
||||
},
|
||||
{
|
||||
"phrase": "Patentpaastand Kommode som er satt sammen av flere enkeltdeler som hver er utfort som koffert",
|
||||
"minimumSimilarity": 0.62
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"evidenceSemanticRules": [
|
||||
{
|
||||
"goalKey": "barricelli.inventor-proof",
|
||||
"name": "Barricelli inventor claim",
|
||||
"targetSubject": "Nils Aall Barricelli",
|
||||
"relatedSubject": "Nils Aall Barricelli's father",
|
||||
"assertion": "The source states or directly demonstrates that Nils Aall Barricelli was an inventor or a named patent applicant for an invention.",
|
||||
"successFlagKey": "scene7.nils_inventor_proved",
|
||||
"relatedFlagKey": "scene7.father_inventor_discovered",
|
||||
"minimumConfidence": 0.88,
|
||||
"evaluatorVersion": "barricelli_inventor_v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,8 +16,8 @@ type MysteryDocument = {
|
||||
}
|
||||
type MysteryGraph = {
|
||||
entry: string
|
||||
nodes: { key: string; type: 'cutscene' | 'dialogue' | 'level' | 'det_gate' | 'llm_gate'; label?: string; x: number; y: number
|
||||
componentKey?: string; templateSlug?: string; version?: 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; awardsFlag?: string
|
||||
terminals?: { key: string; label?: string; to?: string | null }[]
|
||||
utterances?: { npc?: string; pose?: string; text: string; utterer?: 'npc' | 'player' }[] }[]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import jwt from 'jsonwebtoken'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import type { CaseState, DocumentExhibit, EventExhibit, FolderExhibit, NoteExhibit, PartyExhibit, TimelineView } from '../src/types.js'
|
||||
import { runMigrations } from './migrations.js'
|
||||
import type { StoryGraphDto } from './storyGraphRepository.js'
|
||||
|
||||
const { Client } = pg
|
||||
const baseDatabaseUrl = process.env.TEST_DATABASE_URL
|
||||
@@ -252,6 +253,7 @@ suite('normalized level persistence API', () => {
|
||||
const { importMysteryTemplate } = await import('../scripts/importMysteryTemplate.js')
|
||||
const manifestPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'mysteries', 'barricelli-scene-7', 'mystery.json')
|
||||
const imported = await importMysteryTemplate(manifestPath, baseUrl, adminAuthorization.replace(/^Bearer /, ''))
|
||||
expect(imported.mystery).toEqual({ slug:'barricelli-inventor-proof' })
|
||||
expect(imported.playableLevel).toMatchObject({ title:'The Barricelli Files',exhibits:[expect.objectContaining({ type:'claim',statement:'Nils Aall Barricelli was an inventor.' })],report:expect.objectContaining({
|
||||
title:'Barricelli Inventor Finding',requiredForCompletion:true,status:'draft',
|
||||
}),goals:[expect.objectContaining({
|
||||
@@ -270,6 +272,12 @@ suite('normalized level persistence API', () => {
|
||||
expect.objectContaining({ goalKey:'barricelli.inventor-proof',targetSubject:'Nils Aall Barricelli',
|
||||
relatedFlagKey:'scene7.father_inventor_discovered' }),
|
||||
])
|
||||
const mysteries = await (await adminFetch(`${baseUrl}/api/admin/mysteries`)).json() as { id:string;slug:string }[]
|
||||
const mysteryId = mysteries.find(mystery => mystery.slug === 'barricelli-inventor-proof')!.id
|
||||
const graph = await (await adminFetch(`${baseUrl}/api/admin/mysteries/${mysteryId}/graph`)).json() as StoryGraphDto
|
||||
expect(graph.nodes).toHaveLength(2)
|
||||
expect(graph.nodes.find(node => node.nodeType === 'level')).toMatchObject({ label:'Demonstrate OSINT skill',levelTemplateVersionId:expect.any(String) })
|
||||
expect(graph.nodes.find(node => node.nodeType === 'merit')).toMatchObject({ label:'The Barricelli Luggage',awardsFlag:'barricelli_luggage' })
|
||||
|
||||
const fixtureDir = path.join(path.dirname(manifestPath), 'fixtures')
|
||||
const fatherUpload = new FormData()
|
||||
|
||||
@@ -144,8 +144,12 @@ suite('narrative graph runtime', () => {
|
||||
const mysteries = await (await authFetch(`${baseUrl}/api/admin/mysteries`, adminAuthorization)).json() as { id:string;slug:string }[]
|
||||
const mysteryId = mysteries.find(mystery => mystery.slug === 'goal-mystery')!.id
|
||||
expect((await authFetch(`${baseUrl}/api/admin/mysteries/${mysteryId}/graph`, adminAuthorization, { method:'POST',headers:json,
|
||||
body:JSON.stringify({ entry:'level',nodes:[{ key:'level',type:'level',label:'Prove it',templateSlug:'goal-chapter',x:0,y:0,
|
||||
terminals:[{ key:'continue',label:'Continue',to:null }] }] }) })).status).toBe(201)
|
||||
body:JSON.stringify({ entry:'level',nodes:[
|
||||
{ key:'level',type:'level',label:'Prove it',templateSlug:'goal-chapter',x:0,y:0,
|
||||
terminals:[{ key:'continue',label:'Continue',to:'merit' }] },
|
||||
{ key:'merit',type:'merit',label:'Inventor Merit',awardsFlag:'demo.inventor-merit',x:200,y:0,
|
||||
terminals:[{ key:'continue',label:'Accept',to:null }] },
|
||||
] }) })).status).toBe(201)
|
||||
|
||||
const created = await authFetch(`${baseUrl}/api/playthroughs`, undefined, { method:'POST',headers:json,body:JSON.stringify({ mystery:'goal-mystery' }) })
|
||||
expect(created.status).toBe(201)
|
||||
@@ -167,10 +171,15 @@ suite('narrative graph runtime', () => {
|
||||
|
||||
const completed = await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/advance`, undefined, { method:'POST',headers:json,body:'{}' })
|
||||
expect(completed.status).toBe(200)
|
||||
expect((await completed.json() as PlaythroughState).playthrough.status).toBe('finished')
|
||||
expect(await (await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/achievements`, undefined)).json()).toContain('demo.inventor-proved')
|
||||
expect(await completed.json()).toMatchObject({ playthrough:{ status:'active' },node:{ kind:'merit',label:'Inventor Merit',awardsFlag:'demo.inventor-merit' } })
|
||||
expect(await (await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/achievements`, undefined)).json())
|
||||
.toEqual(expect.arrayContaining(['demo.inventor-proved','demo.inventor-merit']))
|
||||
const achievement = await appPool.query<{ awarded_by_node_id:string | null }>(
|
||||
'SELECT awarded_by_node_id FROM osint.achievements WHERE playthrough_id=$1 AND flag_key=$2', [playthroughId,'demo.inventor-proved'])
|
||||
expect(achievement.rows[0].awarded_by_node_id).toBe(atLevel.node!.id)
|
||||
|
||||
const finished = await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/advance`, undefined, { method:'POST',headers:json,body:'{}' })
|
||||
expect(finished.status).toBe(200)
|
||||
expect((await finished.json() as PlaythroughState).playthrough.status).toBe('finished')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Pool, PoolClient } from 'pg'
|
||||
|
||||
export type GraphSpecNode = {
|
||||
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 }[]
|
||||
utterances?: { npc?: string; pose?: string; text: string; utterer?: Utterer }[]
|
||||
}
|
||||
@@ -261,8 +261,8 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
versionId = version.rows[0]?.id ?? null
|
||||
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)',
|
||||
[id, mysteryId, node.type, node.label || node.type, node.x, node.y, Boolean(node.utterances?.length), versionId, node.componentKey || null])
|
||||
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, node.awardsFlag?.trim() || null])
|
||||
for (const [index, terminal] of (node.terminals || []).entries()) {
|
||||
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)',
|
||||
|
||||
Reference in New Issue
Block a user