Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ International-friendly framing. Explained via science (Nibirium, biotech, consci

### Backend

- **Supabase Auth** (reuse DOS.Me pattern, do not invent new auth)
- **Supabase Postgres** (durable state: profile, inventory, quest progress, NFT lock state, cultivation tier)
- **Supabase Realtime** (chat global, presence, friend, party invite, notification - NOT for combat / movement sync)
- **Supabase Storage** (avatar, screenshot, UGC)
- **Nakama OSS** (primary game backend: account mapping, server runtime RPCs, social/game metadata, profile, inventory, wallet, leaderboard, matchmaking-adjacent systems)
- **Nakama database** (MVP production may use the Supabase project with dedicated role `nakama_second` and private schema `second`; local dev uses Docker Postgres; keep Nakama-owned tables behind Nakama APIs)
- **Supabase Auth** (identity bridge / app account layer, reusing DOS.Me pattern where useful)
- **Supabase Postgres / Storage / Realtime** (app/admin/supporting data only; Supabase can host Nakama schema `second`, but Unity clients must not read or write Nakama tables directly through Supabase APIs)
- **Go LLM Gateway** (reuse DOSRouter pattern, self-host VPS, low-latency)
- **Redis** (session, rate limit, transient cache)
- **Hiro / Satori** (deferred; commercial / license-dependent, do not adopt without pricing review and ADR)

### LLM

Expand Down Expand Up @@ -303,7 +304,7 @@ OUT of scope for vertical slice:
2. **NEVER let LLM mutate authoritative game state.** Server validates all intent.
3. **NEVER put API keys (Anthropic, OpenAI, Convai, ElevenLabs) in Unity client.** All LLM calls go through Go gateway.
4. **NEVER use Host Mode for production.** Server Mode dedicated only.
5. **NEVER add Nakama, OpenAuth, or new auth / social stack.** Reuse Supabase + DOS.Me patterns.
5. **NEVER add OpenAuth or replace the backend / auth / social stack without an ADR and JOY approval.** Nakama OSS is approved as the primary game-backend direction by ADR 0010. Supabase schema `second` is an acceptable MVP production host for Nakama if accessed only by a dedicated Nakama role. Hiro and Satori remain deferred until license and pricing are reviewed.
6. **NEVER change Unity Asset Serialization away from Force Text.** Breaks LFS + diff.
7. **NEVER claim "done" without reviewer pass** (JOY is non-coder, cannot review code himself).
8. **ALWAYS edit BOTH `.claude/CLAUDE.md` and `AGENTS.md` together when updating project context.** They are sister files - Claude Code auto-loads CLAUDE.md, Codex CLI / Cursor / Copilot auto-load AGENTS.md. Edit one without the other = drift; the un-updated file lies to whichever agent reads it. Both files MUST be identical except for the sister-file comment header at line 1.
Expand All @@ -318,3 +319,4 @@ OUT of scope for vertical slice:
- Voice NPC vendor (OpenAI Realtime vs ElevenLabs vs self-host)
- Dedicated server hosting (Hetzner specs, region)
- Photon Fusion 2 license tier when scaling beyond Cloud free 20 CCU
- Hiro / Satori adoption and pricing
11 changes: 7 additions & 4 deletions .github/workflows/backend-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ on:
push:
branches: [main]
paths:
- 'backend/**'
- 'backend/gateway/**'
- '.github/workflows/backend-test.yml'
pull_request:
paths:
- 'backend/**'
- 'backend/gateway/**'
- '.github/workflows/backend-test.yml'

jobs:
Expand All @@ -26,12 +26,15 @@ jobs:
with:
go-version: '1.23'
cache: true
cache-dependency-path: backend/gateway/go.sum
cache-dependency-path: backend/gateway/go.mod

- name: Verify go.mod is tidy
run: |
go mod tidy
git diff --exit-code go.mod go.sum
if [ -n "$(git status --porcelain -- go.mod go.sum)" ]; then
git status --short -- go.mod go.sum
exit 1
fi

- name: Vet
run: go vet ./...
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ appsettings.Local.json
# Backend / gateway local dev
backend/.env
backend/node_modules/
backend/**/node_modules/
backend/**/build/
backend/dist/

# Photon credentials (if checked-in template)
Expand Down
12 changes: 7 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ International-friendly framing. Explained via science (Nibirium, biotech, consci

### Backend

- **Supabase Auth** (reuse DOS.Me pattern, do not invent new auth)
- **Supabase Postgres** (durable state: profile, inventory, quest progress, NFT lock state, cultivation tier)
- **Supabase Realtime** (chat global, presence, friend, party invite, notification - NOT for combat / movement sync)
- **Supabase Storage** (avatar, screenshot, UGC)
- **Nakama OSS** (primary game backend: account mapping, server runtime RPCs, social/game metadata, profile, inventory, wallet, leaderboard, matchmaking-adjacent systems)
- **Nakama database** (MVP production may use the Supabase project with dedicated role `nakama_second` and private schema `second`; local dev uses Docker Postgres; keep Nakama-owned tables behind Nakama APIs)
- **Supabase Auth** (identity bridge / app account layer, reusing DOS.Me pattern where useful)
- **Supabase Postgres / Storage / Realtime** (app/admin/supporting data only; Supabase can host Nakama schema `second`, but Unity clients must not read or write Nakama tables directly through Supabase APIs)
- **Go LLM Gateway** (reuse DOSRouter pattern, self-host VPS, low-latency)
- **Redis** (session, rate limit, transient cache)
- **Hiro / Satori** (deferred; commercial / license-dependent, do not adopt without pricing review and ADR)

### LLM

Expand Down Expand Up @@ -303,7 +304,7 @@ OUT of scope for vertical slice:
2. **NEVER let LLM mutate authoritative game state.** Server validates all intent.
3. **NEVER put API keys (Anthropic, OpenAI, Convai, ElevenLabs) in Unity client.** All LLM calls go through Go gateway.
4. **NEVER use Host Mode for production.** Server Mode dedicated only.
5. **NEVER add Nakama, OpenAuth, or new auth / social stack.** Reuse Supabase + DOS.Me patterns.
5. **NEVER add OpenAuth or replace the backend / auth / social stack without an ADR and JOY approval.** Nakama OSS is approved as the primary game-backend direction by ADR 0010. Supabase schema `second` is an acceptable MVP production host for Nakama if accessed only by a dedicated Nakama role. Hiro and Satori remain deferred until license and pricing are reviewed.
6. **NEVER change Unity Asset Serialization away from Force Text.** Breaks LFS + diff.
7. **NEVER claim "done" without reviewer pass** (JOY is non-coder, cannot review code himself).
8. **ALWAYS edit BOTH `.claude/CLAUDE.md` and `AGENTS.md` together when updating project context.** They are sister files - Claude Code auto-loads CLAUDE.md, Codex CLI / Cursor / Copilot auto-load AGENTS.md. Edit one without the other = drift; the un-updated file lies to whichever agent reads it. Both files MUST be identical except for the sister-file comment header at line 1.
Expand All @@ -318,3 +319,4 @@ OUT of scope for vertical slice:
- Voice NPC vendor (OpenAI Realtime vs ElevenLabs vs self-host)
- Dedicated server hosting (Hetzner specs, region)
- Photon Fusion 2 license tier when scaling beyond Cloud free 20 CCU
- Hiro / Satori adoption and pricing
5 changes: 5 additions & 0 deletions backend/nakama/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
modules/node_modules/
modules/build/
*.log
.env
.env.*
16 changes: 16 additions & 0 deletions backend/nakama/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copy this file to `.env` for local Supabase-backed Nakama testing.
# Do not commit `.env`.

# Use the Supabase Session Pooler or a direct persistent connection.
# Format:
# nakama_second.<project-ref>:<password>@<region>.pooler.supabase.com:5432/postgres?sslmode=require
NAKAMA_DATABASE_ADDRESS=

# Required before any production deployment. Local dev may keep config/local.yml defaults.
NAKAMA_CONSOLE_USERNAME=
NAKAMA_CONSOLE_PASSWORD=
NAKAMA_CONSOLE_SIGNING_KEY=
NAKAMA_SOCKET_SERVER_KEY=
NAKAMA_SESSION_ENCRYPTION_KEY=
NAKAMA_SESSION_REFRESH_ENCRYPTION_KEY=
NAKAMA_RUNTIME_HTTP_KEY=
17 changes: 17 additions & 0 deletions backend/nakama/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM node:20-alpine AS module-builder

WORKDIR /modules

RUN apk add --no-cache git

COPY modules/package*.json ./
RUN npm install

COPY modules/tsconfig.json ./
COPY modules/src ./src
RUN npm run build

FROM registry.heroiclabs.com/heroiclabs/nakama:3.38.0

COPY --from=module-builder /modules/build/*.js /nakama/data/modules/build/
COPY config/local.yml /nakama/data/local.yml
91 changes: 91 additions & 0 deletions backend/nakama/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Second Spawn Nakama backend

Local Nakama OSS setup for SECOND SPAWN.

This folder stores project configuration and custom runtime modules only. Do not copy or fork Nakama source into this repository. The server is pulled from the official Heroic Labs Docker image.

## Services

- Nakama OSS: `registry.heroiclabs.com/heroiclabs/nakama:3.38.0`
- Nakama runtime types: `nakama-common#v1.45.0`
- Postgres: `postgres:16.14-alpine`

Nakama owns its own Postgres database. Do not point it at the Supabase app database.

## Local run

```powershell
cd backend/nakama
docker compose up --build
```

Endpoints:

- Nakama HTTP API: `http://127.0.0.1:7350`
- Nakama Console: `http://127.0.0.1:7351`
- Nakama Prometheus metrics: `http://127.0.0.1:9100`
- Console credentials: `admin` / `password`
- Local Postgres: `127.0.0.1:5433`

## Runtime modules

Custom server logic lives under `modules/src/`. The Docker build compiles TypeScript to JavaScript and copies the output into `/nakama/data/modules/build` inside the Nakama container.

Current RPCs:

- `secondspawn_health` - returns a small JSON health response and proves custom runtime loading.

The REST RPC payload must be sent as a JSON string. For example, send `"{}"` rather than `{}`.

## Upgrade policy

When upgrading Nakama:

1. Read the official Nakama release notes.
2. Update the Docker image tag in `Dockerfile`.
3. Update `nakama-runtime` in `modules/package.json` to the matching `nakama-common` version from the compatibility matrix.
4. Rebuild locally and verify the health RPC.

## Boundaries

- Photon Fusion remains authoritative for gameplay simulation.
- Nakama handles game backend and meta-game services.
- Supabase remains identity / app / admin layer only unless a future ADR changes this.
- Hiro and Satori are deferred until license and pricing review.

## Supabase Schema-Isolated Mode

For MVP production, Nakama may use a Supabase project through the Session Pooler if it has a dedicated role and an isolated schema.

Verified shape:

- Schema: `second`
- Role: `nakama_second`
- Pooler: Session Pooler on port `5432`
- Search path: `second, public`

Do not use the Supabase `postgres` role for Nakama in production. Do not commit the connection string.

Local run against Supabase:

```powershell
Copy-Item .env.example .env
# Fill NAKAMA_DATABASE_ADDRESS in .env with the Session Pooler connection string.
docker compose -f docker-compose.supabase.yml up --build
```

Before deploying outside local dev, also set non-default values for:

- `NAKAMA_CONSOLE_USERNAME`
- `NAKAMA_CONSOLE_PASSWORD`
- `NAKAMA_CONSOLE_SIGNING_KEY`
- `NAKAMA_SOCKET_SERVER_KEY`
- `NAKAMA_SESSION_ENCRYPTION_KEY`
- `NAKAMA_SESSION_REFRESH_ENCRYPTION_KEY`
- `NAKAMA_RUNTIME_HTTP_KEY`

Do not reuse the local `admin` / `password` console credentials in production.

## Alerting

Nakama exposes Prometheus metrics on port `9100`. Telegram alerts should be wired through Prometheus Alertmanager or Grafana Alerting with secrets stored outside Git.
15 changes: 15 additions & 0 deletions backend/nakama/config/local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: second-spawn

logger:
level: DEBUG

session:
token_expiry_sec: 7200
refresh_token_expiry_sec: 604800

console:
username: admin
password: password

runtime:
path: /nakama/data/modules/build
22 changes: 22 additions & 0 deletions backend/nakama/docker-compose.supabase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
services:
nakama:
build:
context: .
environment:
NAKAMA_DATABASE_ADDRESS: ${NAKAMA_DATABASE_ADDRESS:-}
entrypoint:
- "/bin/sh"
- "-ecx"
- >
: "$${NAKAMA_DATABASE_ADDRESS:?Set NAKAMA_DATABASE_ADDRESS in backend/nakama/.env}" &&
exec /nakama/nakama --config /nakama/data/local.yml --database.address "$$NAKAMA_DATABASE_ADDRESS" --metrics.prometheus_port 9100
ports:
- "7349:7349"
- "7350:7350"
- "7351:7351"
- "9100:9100"
healthcheck:
test: ["CMD", "/nakama/nakama", "healthcheck"]
interval: 10s
timeout: 5s
retries: 10
42 changes: 42 additions & 0 deletions backend/nakama/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
postgres:
image: postgres:16.14-alpine
environment:
POSTGRES_DB: nakama
POSTGRES_USER: nakama
POSTGRES_PASSWORD: localdb
volumes:
- nakama_postgres:/var/lib/postgresql/data
ports:
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U nakama -d nakama"]
interval: 3s
timeout: 3s
retries: 10

nakama:
build:
context: .
depends_on:
postgres:
condition: service_healthy
entrypoint:
- "/bin/sh"
- "-ecx"
- >
/nakama/nakama migrate up --database.address nakama:localdb@postgres:5432/nakama &&
exec /nakama/nakama --config /nakama/data/local.yml --database.address nakama:localdb@postgres:5432/nakama --metrics.prometheus_port 9100
ports:
- "7349:7349"
- "7350:7350"
- "7351:7351"
- "9100:9100"
healthcheck:
test: ["CMD", "/nakama/nakama", "healthcheck"]
interval: 10s
timeout: 5s
retries: 10

volumes:
nakama_postgres:
15 changes: 15 additions & 0 deletions backend/nakama/modules/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@second-spawn/nakama-modules",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc",
"type-check": "tsc --noEmit"
},
"dependencies": {
"nakama-runtime": "https://github.com/heroiclabs/nakama-common#v1.45.0"
},
"devDependencies": {
"typescript": "5.4.5"
}
}
24 changes: 24 additions & 0 deletions backend/nakama/modules/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const rpcIdHealth = "secondspawn_health";

function InitModule(
ctx: nkruntime.Context,
logger: nkruntime.Logger,
nk: nkruntime.Nakama,
initializer: nkruntime.Initializer
): void {
initializer.registerRpc(rpcIdHealth, rpcHealth);
logger.info("Second Spawn Nakama runtime loaded.");
}

function rpcHealth(
ctx: nkruntime.Context,
logger: nkruntime.Logger,
nk: nkruntime.Nakama,
payload: string
): string {
return JSON.stringify({
ok: true,
service: "second-spawn-nakama",
userId: ctx.userId || null
});
}
16 changes: 16 additions & 0 deletions backend/nakama/modules/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"files": [
"./src/main.ts"
],
"compilerOptions": {
"target": "es5",
"outFile": "./build/index.js",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"typeRoots": [
"./node_modules"
]
}
}
Loading
Loading