Decides where chunk/object content lives at rest and who may write it, now
that there are two server-side processes per world (the always-on FastAPI
backend and the per-join, reaper-killed Godot dedicated server).
Decisions: chunk data lives in the existing Postgres DB (not world.md's
per-world SQLite file); the FastAPI backend is the sole writer and the editor
saves to it over HTTP; the dedicated server pulls content read-only at startup
over a loopback /api/internal route with a per-spawn token; full-chunk PUT with
base_revision instead of JSON-Patch deltas; the client streams chunks over HTTP
through the existing ChunkStreamManager.set_server_rpc() seam.
Adds docs/architecture/world_persistence_impl.md and marks the superseded
sections of world.md (SS4, SS5) and editor.md (the request_chunk_edit path).
Design only -- no implementation code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2FESCaMXeJpC9gSctiEeD
POST /api/auth/register now requires a real date_of_birth (ISO YYYY-MM-DD).
The server computes age (auth.calculate_age) and rejects under-18s with 403;
a future/nonsensical date is 400. The DOB is stored and surfaced only to
that user (/api/auth/me).
Still self-attested (a user can enter a false DOB) -- but a concrete,
stored, legally-meaningful data point and the seam for a future ID-check
pass. Encryption-at-rest for the column and the Tier-2 hook are not done;
the limitation is stated plainly in docs/architecture/content_moderation.md 5.2.
- auth.py: RegisterRequest.date_of_birth (date); MIN_AGE=18; register()
computes + enforces; UserResponse.date_of_birth typed `date | None`
(the old `str | None` would have 500'd /me once a DOB was actually stored).
- api_client.gd / main_menu.gd: the Over18 checkbox is now a
"Date of birth (YYYY-MM-DD)" field with a client-side sanity check.
- tests: test_auth_age_gate.py rewritten around the endpoint + age math
(adult/minor/exactly-18/future-dob/stored); ~25 over_18 references across
6 backend test files + 6 game smoke tests updated. Backend 74 passed;
game smokes green (auth 6/6, menu_full_flow 12/12, e2e 4/4, etc.);
fresh-clone boot PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
New avatar_loadouts table (one row per user, loadout stored as a single
JSONB document matching the client's AvatarLoadout.to_json() shape) and
GET/PUT /api/avatar/loadout.
The server has no copy of the asset catalog, so it validates only the
envelope (JSON object, non-empty base_body, object slots, size cap) --
which-asset-in-which-slot validation stays client-side. New accounts get a
server default (base_human_male_01, no slots) with is_default:true so the
client can tell "never customised" from "customised back to base".
- models/avatar_loadout.py + 0004 migration + User.avatar_loadout relation
- routers/avatar.py wired into main.py
- tests/test_avatar_loadout.py (7 tests: default, save/read, replace,
validation, auth) -- full suite 68 passed
- lifted the duplicated _override_db fixture from 3 test files into
conftest.py (new test files need it too)
- docs/architecture/avatar.md §9a: the persistence design
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
Verified all three options from the task against Godot 4.6.2 directly
(godot --headless), rather than trusting the probe scripts' own printed
output from earlier sessions:
- ScriptServer doesn't exist as a class; no disable_os/disable_file_access/
disable_network project settings exist either. Nothing to configure.
- SubViewport isolates rendering/input only, not script capabilities — a
script inside one has identical OS/FileAccess/network access.
- Expression is not safe by default (can still reach unsafe methods through
a base instance); blocklisting dangerous substrings is trivially bypassed.
Recommendation: don't sandbox GDScript in-process (nothing to sandbox
with) — use a narrow non-Turing-complete content DSL for the common case,
and OS-level process isolation only if genuinely general scripting turns
out to be required.