Initial scaffold: Nakama backend, Godot client, content schema
Sets up the Step 1 (Project Foundation) and Step 2 (Content Schemas) scaffold from the implementation sequence. Includes Docker Compose stack, Nakama TypeScript module stubs, PostgreSQL migration, Godot 4 project shell with scene stubs and NakamaClient singleton, YAML content schemas for all entity types, and example data for 3 characters, 2 jobs, and all 7 core resources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
fb00954954
37 changed files with 2730 additions and 0 deletions
185
migrations/001_initial_schema.sql
Normal file
185
migrations/001_initial_schema.sql
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
-- 001_initial_schema.sql
|
||||
-- Xyvera game database — initial schema
|
||||
-- Applied automatically to the xyvera PostgreSQL container on first boot.
|
||||
--
|
||||
-- This schema covers Steps 1–3 of the implementation sequence:
|
||||
-- - player profiles and accounts
|
||||
-- - resource inventory and currency ledger
|
||||
-- - character roster
|
||||
-- - gacha pull history and pity counters
|
||||
--
|
||||
-- Nakama has its own tables in CockroachDB. This Postgres DB stores
|
||||
-- game-specific state that Nakama does not model natively:
|
||||
-- - economy ledger (full audit trail)
|
||||
-- - character progression state
|
||||
-- - village assignment history
|
||||
-- - content configuration cache (loaded from YAML by the content importer)
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ── Extensions ──────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_uuid()
|
||||
|
||||
-- ── Players ──────────────────────────────────────────────────────────────────
|
||||
-- Mirrors Nakama user IDs. All economy and progression data links here.
|
||||
|
||||
CREATE TABLE players (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
nakama_user_id TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT NOT NULL DEFAULT 'Traveller',
|
||||
region TEXT NOT NULL DEFAULT 'heartwood_vale',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_players_nakama_user_id ON players(nakama_user_id);
|
||||
|
||||
-- ── Resource Ledger ───────────────────────────────────────────────────────────
|
||||
-- Every grant and spend recorded. Never update; only insert.
|
||||
-- This makes the economy fully auditable (Economy Rule from spec).
|
||||
|
||||
CREATE TABLE resource_ledger (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
|
||||
resource_id TEXT NOT NULL, -- matches resource schema id
|
||||
delta INTEGER NOT NULL, -- positive = grant, negative = spend
|
||||
balance_after INTEGER NOT NULL, -- denormalized for easy dashboards
|
||||
source TEXT NOT NULL, -- e.g. 'lumber_camp_collect', 'gacha_pull'
|
||||
source_ref TEXT, -- optional FK to another table (e.g. pull_id)
|
||||
notes JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resource_ledger_player ON resource_ledger(player_id, resource_id);
|
||||
CREATE INDEX idx_resource_ledger_source ON resource_ledger(source);
|
||||
CREATE INDEX idx_resource_ledger_created ON resource_ledger(created_at);
|
||||
|
||||
-- ── Character Roster ─────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE roster (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
|
||||
character_id TEXT NOT NULL, -- matches character schema id
|
||||
presentation TEXT NOT NULL DEFAULT 'female' CHECK (presentation IN ('female', 'male')),
|
||||
level INTEGER NOT NULL DEFAULT 1 CHECK (level >= 1 AND level <= 100),
|
||||
ascension_tier INTEGER NOT NULL DEFAULT 0 CHECK (ascension_tier >= 0 AND ascension_tier <= 6),
|
||||
bond_level INTEGER NOT NULL DEFAULT 0,
|
||||
assignment_mastery JSONB NOT NULL DEFAULT '{}', -- {job_id: hours_accumulated}
|
||||
skills JSONB NOT NULL DEFAULT '{}', -- {skill_id: current_level}
|
||||
obtained_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
obtained_source TEXT NOT NULL, -- 'gacha_standard', 'gacha_event', 'starter_seed'
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_roster_player_char ON roster(player_id, character_id);
|
||||
CREATE INDEX idx_roster_player ON roster(player_id);
|
||||
|
||||
-- ── Village Assignment State ──────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE village_assignments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
|
||||
roster_id UUID NOT NULL REFERENCES roster(id) ON DELETE CASCADE,
|
||||
job_id TEXT NOT NULL, -- matches job schema id
|
||||
assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
last_collected_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
UNIQUE (player_id, roster_id) -- one assignment per character
|
||||
);
|
||||
|
||||
CREATE INDEX idx_village_assignments_player ON village_assignments(player_id);
|
||||
|
||||
-- ── Manual Work State ────────────────────────────────────────────────────────
|
||||
-- Tracks daily manual work action counts for diminishing-returns enforcement.
|
||||
|
||||
CREATE TABLE manual_work_state (
|
||||
player_id UUID PRIMARY KEY REFERENCES players(id) ON DELETE CASCADE,
|
||||
actions_today INTEGER NOT NULL DEFAULT 0,
|
||||
actions_reset_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- midnight UTC of current day
|
||||
total_actions_lifetime BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ── Gacha Pull History ────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE pulls (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
|
||||
banner_id TEXT NOT NULL,
|
||||
character_id TEXT NOT NULL,
|
||||
rarity TEXT NOT NULL CHECK (rarity IN ('R', 'SR', 'SSR', 'UR')),
|
||||
pity_count_at_pull INTEGER NOT NULL,
|
||||
was_soft_pity BOOLEAN NOT NULL DEFAULT false,
|
||||
was_hard_pity BOOLEAN NOT NULL DEFAULT false,
|
||||
was_featured BOOLEAN NOT NULL DEFAULT false,
|
||||
pulled_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pulls_player ON pulls(player_id, pulled_at DESC);
|
||||
CREATE INDEX idx_pulls_banner ON pulls(banner_id, pulled_at DESC);
|
||||
|
||||
-- ── Pity Counters ─────────────────────────────────────────────────────────────
|
||||
-- One row per (player, banner). Updated after every pull.
|
||||
|
||||
CREATE TABLE pity_counters (
|
||||
player_id UUID NOT NULL REFERENCES players(id) ON DELETE CASCADE,
|
||||
banner_id TEXT NOT NULL,
|
||||
current_pity INTEGER NOT NULL DEFAULT 0,
|
||||
featured_guarantee_due BOOLEAN NOT NULL DEFAULT false, -- lost the 50/50; next SSR guaranteed
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
PRIMARY KEY (player_id, banner_id)
|
||||
);
|
||||
|
||||
-- ── Content Cache Tables ──────────────────────────────────────────────────────
|
||||
-- These are populated by tools/import_content.py from the YAML content files.
|
||||
-- They are NOT the source of truth (YAML files are); they are the runtime cache.
|
||||
|
||||
CREATE TABLE content_characters (
|
||||
id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
schema_version TEXT NOT NULL DEFAULT 'v1',
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE content_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
schema_version TEXT NOT NULL DEFAULT 'v1',
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE content_resources (
|
||||
id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
schema_version TEXT NOT NULL DEFAULT 'v1',
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE content_banners (
|
||||
id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT false,
|
||||
schema_version TEXT NOT NULL DEFAULT 'v1',
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE content_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT false,
|
||||
schema_version TEXT NOT NULL DEFAULT 'v1',
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ── Schema Version Tracking ───────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE schema_migrations (
|
||||
version TEXT PRIMARY KEY,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO schema_migrations(version) VALUES ('001_initial_schema');
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Add a link
Reference in a new issue