prologue: wire background_id into new player creation on server

This commit is contained in:
otivm
2026-05-03 01:32:20 +00:00
parent f8c323858c
commit 8e4e1df7f5

View File

@@ -1,4 +1,4 @@
// OTIVM server — OTIVM-III // OTIVM server — OTIVM-IV
// Per-player SQLite integration. // Per-player SQLite integration.
// TESSERA world database (data/otivm.sqlite3) — read-only, unchanged. // TESSERA world database (data/otivm.sqlite3) — read-only, unchanged.
// Player databases (data/saves/{token}.sqlite3) — one per player, // Player databases (data/saves/{token}.sqlite3) — one per player,
@@ -7,6 +7,12 @@
// JSON save files (data/saves/{token}.json) — never deleted, migrated // JSON save files (data/saves/{token}.json) — never deleted, migrated
// transparently on first SQLite access if present. // transparently on first SQLite access if present.
// Frontend interface unchanged — GET/POST /api/save/:token same as before. // Frontend interface unchanged — GET/POST /api/save/:token same as before.
//
// Change 3 (OTIVM-IV): background_id from the frontend is now a real
// canonical identifier (e.g. 'former_legionary'). On new player creation,
// seed actor_parameters from background_starting_values for that background.
// The previous default of 'unknown' is kept only as a fallback for legacy
// saves that predate Change 3.
import Fastify from 'fastify' import Fastify from 'fastify'
import fastifyStatic from '@fastify/static' import fastifyStatic from '@fastify/static'
@@ -296,6 +302,13 @@ function autToInt(band) {
// Writes parameter changes as new actor_parameters rows (append-only). // Writes parameter changes as new actor_parameters rows (append-only).
// Appends new events to the events table. // Appends new events to the events table.
// Marks superseded parameter rows. // Marks superseded parameter rows.
//
// On new player creation (no actor_profile row yet):
// - Uses background_id from the request body (sent by frontend after
// player chooses on the Prologue tab).
// - Seeds actor_parameters from background_starting_values for that background.
// - Sets liquiditas to the background's starting_den value from the body.
// - Falls back to 'unknown' only if no background_id is present (legacy saves).
function writePlayerState(token, pdb, body) { function writePlayerState(token, pdb, body) {
const now = new Date().toISOString() const now = new Date().toISOString()
@@ -307,29 +320,57 @@ function writePlayerState(token, pdb, body) {
).get(actorId) ).get(actorId)
if (!profile) { if (!profile) {
// New player — use the background_id sent by the frontend.
// The Prologue tab ensures this is a canonical identifier before sending.
const backgroundId = body.background_id || 'unknown'
const actorName = body.actor_name || 'Mercator'
pdb.prepare(` pdb.prepare(`
INSERT OR IGNORE INTO actor_profile INSERT OR IGNORE INTO actor_profile
(actor_id, session_id, background_id, actor_name, epoch, schema_version, recorded_at) (actor_id, session_id, background_id, actor_name, epoch, schema_version, recorded_at)
VALUES (?, ?, ?, ?, 'roman_14bce', 3, ?) VALUES (?, ?, ?, ?, 'roman_14bce', 4, ?)
`).run( `).run(actorId, actorId, backgroundId, actorName, now)
actorId,
actorId, // Seed all parameters from background_starting_values for chosen background.
body.background_id || 'unknown', // auctoritas gets value_social seeded from the same row's value_true.
body.actor_name || 'Mercator',
now
)
// Seed parameters from background_starting_values
pdb.prepare(` pdb.prepare(`
INSERT OR IGNORE INTO actor_parameters INSERT OR IGNORE INTO actor_parameters
(actor_id, parameter_token, scope, layer, (actor_id, parameter_token, scope, layer,
value_true, value_perceived, confidence_tag, observable_level, value_true, value_perceived, value_social,
drift_source, recorded_at) confidence_tag, observable_level, drift_source, recorded_at)
SELECT ?, parameter_token, 'actor', 'roman', SELECT
value_true, value_perceived, confidence_tag, observable_level, ?, parameter_token, 'actor',
'initial', ? CASE WHEN parameter_token IN ('auctoritas','clientela','liquiditas','fama',
'disciplina','mercatus_scientia','itineris_scientia','ius_accessus',
'periculum_tolerantia','negotiatio','litterae','officia_burden')
THEN 'roman' ELSE 'universal' END,
value_true, value_perceived,
CASE WHEN parameter_token = 'auctoritas' THEN value_true ELSE NULL END,
confidence_tag, observable_level, 'initial', ?
FROM background_starting_values FROM background_starting_values
WHERE background_id = ? WHERE background_id = ?
`).run(actorId, now, body.background_id || 'unknown') `).run(actorId, now, backgroundId)
// Override liquiditas with the den value from the body (which was set
// from BACKGROUNDS[x].starting_den on the frontend). This is the
// authoritative starting value — the seed row is superseded immediately.
if (body.den !== undefined) {
// Supersede the seed row
pdb.prepare(`
UPDATE actor_parameters
SET superseded_at = ?
WHERE actor_id = ? AND parameter_token = 'liquiditas' AND superseded_at IS NULL
`).run(now, actorId)
// Insert the confirmed starting value
pdb.prepare(`
INSERT INTO actor_parameters
(actor_id, parameter_token, scope, layer,
value_true, value_perceived,
confidence_tag, observable_level, drift_source, recorded_at)
VALUES (?, 'liquiditas', 'actor', 'roman',
?, ?, 'measured', 'full', 'initial', ?)
`).run(actorId, String(body.den), String(body.den), now)
}
} }
// Update liquiditas if den changed // Update liquiditas if den changed
@@ -540,7 +581,7 @@ fastify.setNotFoundHandler((req, reply) => {
try { try {
await fastify.listen({ port: 3000, host: '0.0.0.0' }) await fastify.listen({ port: 3000, host: '0.0.0.0' })
console.log('OTIVM server running on port 3000 — OTIVM-III') console.log('OTIVM server running on port 3000 — OTIVM-IV')
} catch (err) { } catch (err) {
console.error(err) console.error(err)
process.exit(1) process.exit(1)