secure keys off NODE_ENV so the auth_token cookie is HTTPS-only in
prod while still working over plain HTTP on localhost in dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CORS_ORIGIN defaults to https://gupi.${DOMAIN} (the TLS frontend served by the
proxy); the app still reaches Postgres/MinIO by their docker hostnames over
plain HTTP on the shared network. Clarify the split in .env.prod.example and
fix the deploy success URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point deploy.sh at /opt/gupi (its own dir + .env.prod) so it uploads, builds
on the server, migrates, and runs `docker compose -f
/opt/gupi/docker-compose.prod.yml --env-file /opt/gupi/.env.prod up -d`.
The app-only prod stack still joins the shared network and uses gu_common's
Postgres/MinIO. Adds .env.prod.example as the fill-in template.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
resolveUserId now returns null for an anonymous request in production (no
shared identity); the fixed dev user only applies locally. Player-scoped
routes (create/advance/goto, achievements, reach, notebook, phone, dial)
require a user and verify ownership via ownsPlaythrough — a player can no
longer read or act on another's playthrough. Verified: dev anon still plays;
cross-user access returns 403/404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
continueAfterGoal sent non-level next nodes to '/' (the splash), forcing a
manual Resume; use the /?resume=1 fast-path so the debrief dialogue plays
immediately, matching the phone-connect handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The splash now requires a signed-in investigator: a rudimentary character
screen (display name + handle + password, with a placeholder avatar) enrols
or signs in, then reveals the case picker. GET /api/auth/me gates the front
door; the signed-in investigator's name/avatar and a sign-out show on the
splash. /node and ?resume paths are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GUPI-issued local auth (path A): osint.users (handle + scrypt password_hash +
display_name + avatar_url; external_id reserved for a later glitch.university
key-exchange). POST /api/auth/register|login set the auth_token cookie via a
new signPlayerToken (sub=user id, role=player); /logout and /me added. Because
playthroughs already bind to resolveUserId, two players each "Begin" and stay
fully isolated. Verification stays issuer-agnostic for the external swap later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Its geometry hangs below the hinge pivot, so it sat low; lift it so the
handset is vertically centred in the tool carousel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The phone and notebook render a bit smaller; the phone sits higher on screen;
in-game the handset opens automatically (session mode); and the tool view
gains an EXIT ✕ that closes the inventory straight back to the board.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The field notebook gets a handwriting input to add a custom note manually;
it posts to the same notebook store and can be torn to the board like a
captured one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dialogue line gains a "✎ Note this" capture that saves it to a
per-playthrough notebook (migration 035 + notebook endpoints). The inventory
notebook lists captured pages in a handwriting font (Google "Reenie Beanie")
and "✂ Tear to board" drops a page onto the current board as a note exhibit
(reusing addNote), then removes the page. Board notes render handwritten too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The phone gains a session mode that reads the live directory (/phone) and
dials (/dial); the inventory threads it through. On a campaign level the
board shows an INVENTORY nav button that opens the tool rack, and connecting
a call hands back to the campaign via /?resume=1 (a fast-path that resumes
the current node instead of the splash). On Barricelli's note-board: open
inventory -> phone -> dial 5550100 -> Glitch Hunter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /playthroughs/:id/phone returns the directory of the phone node the
current node is wired to; POST /playthroughs/:id/dial resolves a number to
connect (advance to the wired dialogue), voicemail (known contact, no line
here), or not-in-service. Barricelli's note-board is wired to the phone node
and Glitch Hunter's decline returns to it. Frontend (inventory on the board)
is 2b.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dobby now sends the player to a one-note level ("PHONE FOR GLITCH HUNTER ·
5550100") whose report-back reaches Glitch Hunter — the intended note→phone
beat, with the note-board's report-back standing in for dialing until the
phone runtime lands. The note-board is a frozen barricelli-phone-note level
template (single note exhibit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A narrative-only manifest wrapping the existing barricelli-inventor-proof
level template: Dobby intro/tasks (branching, awards dobby.knows_barricelli*),
a phone node bound to Glitch Hunter, the gated Glitch-Hunter interrogation
(options require the Dobby flags, converge via a terminal), Scene 7 level,
Scene 8 debrief, the barricelli_luggage merit ceremony, and the Scene 10
deep-web branch. Phone dialing still stubbed — the phone node is authored
structure off the walkable path (Dobby → Hunter is direct for now).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dialogue line can award an achievement when reached (granted server-side,
validated against the player's current node so it can't be forged) and an
option can require an achievement to be offered (filtered out of the resolved
tree otherwise). authorGraph gains a branching form (utterance key/parent/
terminal) so option trees — not just linear lines — can be seeded. This is
gaps 1 & 2 for the Barricelli Dobby/Glitch-Hunter dialogues; phone dialing
stays stubbed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
A tool-driven inventory overlay — a three.js rack you cycle with ◀ ▶ and
open with USE. Registry-based (not tied to a level type) so new tools just
add a model + component. Reuses the phone's 3D model; notebook is a blocky
placeholder tool. Dev route /?inventory=1; drop <Inventory/> into the navbar
after the merges.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admin NPC editor gains phone number and email fields. The mystery graph
editor gets Phone and Merit in the node palette; a phone node's terminals
each get an NPC picker (bind the callee you reach by dialing their number),
and merit nodes get an "awards achievement" field. Node summaries + type
colors for both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NPCs gain phone_number and email (the phone book). A new 'phone' node type
holds a directory whose terminals each bind to an NPC via npc_id (the callee
reached by dialing that number) and wire to the dialogue that plays on
connect. Migration 029 adds the columns/type; repositories and admin API
thread contacts and the per-terminal NPC binding. Authoring foundation only
— the dial runtime and tools sidebar come next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A merit node awards a configured achievement (awards_flag) to the player
the moment the playthrough arrives on it, with node provenance, then
presents a ceremony. Migration 028 extends node_type + the component_key
CHECK and adds awards_flag; the runtime grants the flag in advance/goto/
new-game write paths; MeritHost renders the ceremony (a component_key can
supply a bespoke one, e.g. a 3D model). Verified: teleport to a merit node
auto-grants its achievement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/mysteries returns only mysteries with an entrypoint (empties are
filtered out), and the splash now lists them as case files with a per-case
BEGIN / RESUME action instead of a single hardcoded New Game.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vite.config.ts now reads WEB_PORT (Vite) and PORT (API) from .env via
loadEnv, with the /api proxy following PORT and strictPort on — so two
branch checkouts can each run `npm run dev` on their own ports.
Play now always lands on the splash at the bare root instead of silently
resuming an active playthrough; a Resume action continues one when present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents the URL routing (/, /node/:id teleport, /level/:id, /admin,
?phone=1), the demo game-state model (playthrough current_node_id +
achievements), and quick test recipes for the campaign, node teleport,
and the phone achievement seam.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A diegetic 90s handset rendered as a small three.js scene in a fixed 1:2
portrait stage (visual spike at /?phone=1, three.js lazy-loaded so the
board bundle is unchanged). Blocky flat-shaded clamshell with glowing
keys, a flip-open animation with an intro camera orbit that settles
head-on before the DOM screen appears, a hinge barrel on the pivot axis,
and a chubby antenna. Pressable 3D keypad (raycast + keyboard) with DTMF
tones drives a dialer: connect / voicemail / SIT "unobtainable", against
a stub number->node directory with flag-gated node enablement.
The screen UI is real DOM positioned in percent of the stage, kept
pixel-exact by the head-on ortho camera.
Also documents the persistent-board flow model (docs/persistent-boards.md):
board_key reuse across level nodes, present-but-hidden flag-gated exhibits
with live arrival reveals, reset/new-game semantics, and A/B/M citation codes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author a music track and volume (0-100%) per story node; the runtime
scales it to 0-1 and applies it without restarting the track when only
the level changes. Adds migration 024, threads music_volume through the
repository/runtime, and gives the node inspector a volume slider.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dry synthesized key-clack (filtered noise burst with pitch jitter) plays as
each line types out — every other non-space character — for a detective-terminal
feel. Skipped in the editor preview and when reduced-motion is set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sfx() bailed whenever the context wasn't already 'running', so the autoplay-unlock
race dropped the blips. It's invoked from a click, so resume the context there and
always schedule the tone; bump the envelope and use a triangle wave (with a small
up-chirp on choices) for a more audible cue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- migration 023: story_nodes.music_asset_id (references the asset store).
- src/audio.ts: a tiny audio manager — one looping scene-music track (fade,
dedup, gesture-primed autoplay) and synthesized one-shot SFX; global mute.
- Runtime: RuntimeNode.musicUrl; music follows the current node (null inherits,
a finished playthrough stops). SFX blips on dialogue advance/choice (not in the
editor preview). Floating mute toggle during play.
- Graph inspector: a "Scene music" picker sourced from uploaded audio assets.
No external library (we did not adopt react-winamp — it is a React 16 CRA app,
not an installable package). Bundle grew ~2 KB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DialoguePlayer gains an `inline` mode (fills its container, no window key
capture) and a `startId` to jump the walk to a given utterance.
- New DialoguePreview: a scaled-down mini player that fetches the resolved tree
from GET /api/admin/story-nodes/:id/dialogue (same resolver as playback).
- Mystery graph: preview appears next to a selected dialogue node.
- Utterance editor: docked preview that jumps to the clicked/selected utterance
and refreshes after each edit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uploaded manifest assets previously became application/octet-stream because the
Blob carried no type, so images/audio/pdf didn't display or thumbnail correctly.
Derive the mime from the extension. (Existing deduplicated assets keep their old
mime; only fresh uploads are affected.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Mysteries tab: "+ New" (slug + title) and per-row delete; opens the graph editor.
- Assets tab: upload images/audio/PDFs to the shared osint.assets store, grid with
thumbnails/icons, copy id/url, delete (blocked with 409 when the asset is in use).
- Cutscene component_key is now a datalist of registered keys with a
"not registered" warning, instead of free text.
Backend: DELETE /api/admin/mysteries/:id and POST/GET/DELETE /api/admin/assets
(reusing the deduplicated, MinIO-backed asset store).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Input on top, output on the bottom, exit sinks in a row below; wires flow
downward. Cards auto-expand, so their heights are measured (offsetHeight) to
place the bottom output port. Seed and Tab-created utterances now stack downward.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- migration 022 drops utterances.advances_to_utterance_id and .effect, which the
parent-only/child-count dialogue model never used; purge their references.
- rewrite docs/story-graph.md to match what was built (parent-child utterances,
vertical mystery graph, graph runtime, gate stubs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce the narrative layer as a directed story-flow graph: an authored
campaign a player walks node by node, replacing the interim slot/chapter model.
Schema (migrations 015-021):
- mysteries, global NPC templates + named poses, per-user playthroughs
- story_nodes, terminals, utterances (the flow graph and dialogue trees)
- clean cutover: retire slot cutscenes/chapters/seen_dialogue
Runtime:
- New Game creates a playthrough bound to the JWT identity (dev test-user fallback)
- advance() walks the graph cutscene -> dialogue -> level -> ..., auto-skipping gates
- branching dialogue: player choices route out through node terminals
Admin authoring:
- NPC editor: upload named poses to the gupi MinIO bucket
- mystery graph editor: vertical node canvas, wiring, entrypoint, delete-by-click
- dialogue crafter: utterance tree, Tab to add child, 1/2 speaker, undo
Content authored via the manifest importer / admin panel and seeded for Glass
Harbour. MinIO added to the dev stack; dev container runs in development mode.
Also includes a folder-widget simplification (removes open/close) and a
resolveUserId auth helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>