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:
caoimhinr 2026-06-02 23:33:06 +02:00
commit fb00954954
37 changed files with 2730 additions and 0 deletions

127
tools/validate_content.py Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""validate_content.py — Validate all content YAML files against their schemas.
Usage:
python3 tools/validate_content.py
Requires:
pip install pyyaml jsonschema
Exit codes:
0 all files valid
1 one or more validation errors
"""
import sys
import pathlib
import yaml
import json
try:
import jsonschema
except ImportError:
print("ERROR: jsonschema not installed. Run: pip install pyyaml jsonschema")
sys.exit(1)
CONTENT_ROOT = pathlib.Path(__file__).parent.parent / "content"
SCHEMA_DIR = CONTENT_ROOT / "schema"
DATA_DIR = CONTENT_ROOT / "data"
# Map schema file names to data subdirectories
SCHEMA_DATA_MAP = {
"character.yaml": DATA_DIR / "characters",
"job.yaml": DATA_DIR / "jobs",
"resource.yaml": DATA_DIR / "resources",
"banner.yaml": DATA_DIR / "banners",
"event.yaml": DATA_DIR / "events",
}
errors_found = 0
def load_yaml(path: pathlib.Path) -> list[dict]:
"""Load a YAML file, returning a list of documents (multi-doc files are split)."""
with open(path) as f:
docs = list(yaml.safe_load_all(f))
return [d for d in docs if d is not None]
def yaml_schema_to_jsonschema(schema_doc: dict) -> dict:
"""
Our schema files are JSON-Schema-like YAML. Extract the JSON Schema portion.
We strip the custom '$schema' key and pass the rest to jsonschema.
"""
s = dict(schema_doc)
s.pop("$schema", None)
return s
def validate_directory(schema_path: pathlib.Path, data_dir: pathlib.Path) -> int:
"""Validate all YAML files in data_dir against schema_path. Returns error count."""
global errors_found
if not data_dir.exists():
print(f" [SKIP] Directory not found: {data_dir}")
return 0
schema_docs = load_yaml(schema_path)
if not schema_docs:
print(f" [WARN] Empty schema: {schema_path.name}")
return 0
schema = yaml_schema_to_jsonschema(schema_docs[0])
local_errors = 0
yaml_files = sorted(data_dir.glob("*.yaml"))
if not yaml_files:
print(f" [INFO] No data files in {data_dir.name}/")
return 0
for data_file in yaml_files:
docs = load_yaml(data_file)
for i, doc in enumerate(docs):
try:
# jsonschema uses JSON Schema draft-07 by default
jsonschema.validate(instance=doc, schema=schema)
except jsonschema.ValidationError as e:
print(f" [FAIL] {data_file.name} (doc {i+1}): {e.message}")
local_errors += 1
except jsonschema.SchemaError as e:
print(f" [SCHEMA ERROR] {schema_path.name}: {e.message}")
local_errors += 1
break
if local_errors == 0:
pass # silent success; only print failures
if local_errors == 0:
print(f" [OK] {data_dir.name}/ — {len(yaml_files)} file(s) valid")
return local_errors
def main() -> None:
print("Xyvera content validation\n")
for schema_name, data_dir in SCHEMA_DATA_MAP.items():
schema_path = SCHEMA_DIR / schema_name
if not schema_path.exists():
print(f" [WARN] Schema not found: {schema_name}")
continue
print(f"Schema: {schema_name}")
errs = validate_directory(schema_path, data_dir)
errors_found += errs
print()
if errors_found:
print(f"FAILED — {errors_found} validation error(s) found.")
sys.exit(1)
else:
print("All content valid.")
sys.exit(0)
if __name__ == "__main__":
main()