114748a54f
- Delete portal_mode_manager.gd, portal_door.gd, portal_door.tscn - Strip all Tekton Doors logic from main.gd, player.gd, lobby.gd, lobby_room.gd, lobby_manager.gd, camera_context_manager.gd, music_manager.gd, tekton.gd, enhanced_gridmap.gd, playerboard_manager.gd, special_tiles_manager.gd - Remove TK enum (TEKTON_DOORS=2), mode_config schema, arena area - Update tests: 3 modes instead of 4 - Strip HowToPlay tab from main.tscn
14 KiB
14 KiB
Tekton Armageddon - Server Architecture
High-level architecture of the Nakama Lua backend. For detailed RPC reference (params, returns, errors), see Nakama-Server-API.
System Overview
flowchart LR
Client[Godot Client<br/>player.tscn] -->|RPC calls| Nakama[Nakama Server<br/>port 7350/7351]
Nakama -->|wallet_update| Wallet[Wallet Engine]
Nakama -->|storage_read/write| Storage[Nakama Storage DB<br/>PostgreSQL]
Nakama -->|leaderboard_*| LB[Native Leaderboard]
Nakama -->|match_*| Match[Match Handler]
Nakama -->|channel_*| Chat[Lobby Chat]
Client -->|nk.match_join| Match
Client -->|Direct RPC| Lua[Lua RPC Handlers<br/>server/nakama/lua/]
The Lua backend runs inside the Nakama process. Lua modules hook into Nakama's lifecycle (after-authentication, RPC dispatch, match signals). All game transactions (wallet, inventory, gacha, shop) are server-authoritative — the Godot client sends RPC requests and the Lua code validates and executes.
Module Architecture
flowchart TD
main["main.lua<br/>Entry point: loads all modules"] --> utils["utils.lua<br/>Auth guards, channel resolver"]
main --> core["core.lua<br/>Authentication hooks<br/>Wallet init, ban check"]
main --> economy["economy.lua<br/>Shop catalog, currency IAP<br/>Item purchases"]
main --> gacha["gacha.lua<br/>Gacha pulls, pity system<br/>RNG on server"]
main --> leaderboard["leaderboard.lua<br/>Score submission, sync<br/>Global rankings"]
main --> inbox["inbox.lua<br/>Global/personal mail<br/>Reward claiming"]
main --> daily["daily_rewards.lua<br/>Daily login rewards<br/>Monthly schedule"]
main --> user["user.lua<br/>Profile updates, identity<br/>Friend sync"]
main --> admin["admin.lua<br/>Kick/ban, stats, chat mgmt<br/>Role management"]
core --> utils
economy --> utils
gacha --> utils
leaderboard --> utils
inbox --> utils
daily --> utils
user --> utils
admin --> utils
economy -->|Wallet deduction| Wallet[(Wallet Engine)]
gacha -->|Wallet deduction| Wallet
inbox -->|Claim rewards| Wallet
daily -->|Daily claim| Wallet
user -->|Profile load| Wallet
Dependency order (loading sequence)
utils.lua— no deps (loaded first)economy.lua— depends on utilscore.lua— depends on utilsadmin.lua— depends on utilsdaily_rewards.lua— depends on utilsuser.lua— depends on utilsleaderboard.lua— depends on utilsinbox.lua— depends on utilsgacha.lua— no deps beyond utils
Authentication Flow
sequenceDiagram
participant C as Godot Client
participant N as Nakama Core
participant A as after_hooks (core.lua)
participant DB as Nakama Storage
C->>N: AuthenticateDevice/Custom/Email
N->>A: after_authenticate(context)
A->>DB: storage_read(profiles/profile)
alt First login (no profile)
A->>DB: storage_write(initial profile)
A->>N: wallet_update(gold=100, star=500)
end
A->>DB: storage_read(profiles/profile)
alt metadata.banned == true
A-->>C: error("Account banned")
end
A->>C: session token returned
Boot sequence per player
- Godot client calls
AuthenticateDevice(or Email/Custom/Steam). - Nakama core calls
after_authenticate()hook incore.lua. - Hook reads
profiles/profilefrom storage. - If first login: initializes default profile and grants starting wallet (
gold: 100, star: 500). - Ban check: If
metadata.banned == true, raises error (player rejected). - Client receives session token and proceeds to lobby.
Wallet & Economy Flow
flowchart LR
subgraph Client Side
ShopUI[Shop Panel] -->|buy_currency RPC| Server
ShopUI -->|purchase_item RPC| Server
GachaUI[Gacha Panel] -->|perform_gacha_pull RPC| Server
MailUI[Inbox] -->|claim_mail_reward RPC| Server
DailyUI[Daily Rewards] -->|claim_daily_reward RPC| Server
end
subgraph Server Side
Server[Lua RPC Handler]
Server -->|nk.wallet_update| Wallet[(Wallet)]
Server -->|nk.storage_write| Inv[(Inventory Storage)]
Server -->|nk.storage_write| Rec[(Receipts Storage)]
Server -->|nk.storage_write| Frag[(Fragments Storage)]
end
subgraph Client Refresh
Wallet -->|Wallet updated| Client
Client -->|get_account RPC| Wallet
Client -->|emit profile_updated| UI[All UI Panels<br/>update labels]
end
Currency types
| Currency | Purpose | Earned by |
|---|---|---|
gold |
Shop purchases, star conversion | IAP (real money), admin topup |
star |
Gacha pulls | Gold conversion, daily rewards, mail rewards |
All wallet changesets
| Operation | Changeset |
|---|---|
| First login grant | {gold: 100, star: 500} |
| Buy gold IAP | {gold: +N} (N=100/550/1150/2400/6250/13000) |
| Buy stars (gold convert) | {gold: -N, star: +M} |
| Buy shop item | {gold: -price} or {star: -price} |
| Gacha pull | {star: -cost} or {gold: -cost} |
| Claim mail reward | {gold: +N} and/or {star: +N} |
| Claim daily reward | {star: +N} or {gold: +N} |
| Admin topup | {gold: 999999} |
Gacha Flow
flowchart TD
A[Client: perform_gacha_pull] --> B{Check banner}
B -->|star/gold| C[Read wallet balance]
C --> D{Sufficient funds?}
D -->|No| E[error: Insufficient currency]
D -->|Yes| F{Check pity}
F -->|pity >= 90| G[Force real_prize rarity]
F -->|pity < 90| H[Roll rarity by drop rates]
G --> I[Pick from real_prize pool]
H --> J[Pick from rarity pool]
I --> K[Deduct wallet cost]
J --> K
K --> L{real_prize?}
L -->|Yes| M[Add to inventory storage]
L -->|No| N[Increment fragment count]
M --> O[Return results]
N --> O
Drop rates
| Rarity | Rate | Result |
|---|---|---|
| Common | 60% | Fragment (frag_common) |
| Uncommon | 25% | Fragment (frag_uncommon) |
| Rare | 14% | Fragment (frag_rare) |
| Real Prize | 1% | Skin from catalog |
Pity: Guaranteed Real Prize at 90 pulls. Pity counter resets on any Real Prize win.
Mail/Inbox System
flowchart TD
Admin[Admin RPC] -->|admin_send_mail| Global[Global Mail<br/>config/global_mail<br/>system user]
Admin -->|admin_send_mail<br/>with target_user_id| Personal[Personal Mail<br/>inbox/personal<br/>per user]
Client -->|get_mail| Merge[Merge global + personal]
Merge --> Filter[Filter by: not deleted,<br/>within date range,<br/>not expired]
Filter --> Response[Return to client]
Client -->|claim_mail_reward| CW{Check claimed_ids}
CW -->|Already claimed| Err[error: Reward already claimed]
CW -->|Not claimed| Grant[Grant rewards:<br/>gold/star -> wallet<br/>fragments -> fragment storage<br/>skins -> inventory storage]
Grant --> UpdateState[Update state:<br/>add mailId to claimed_ids]
```[Global Mail<br/>config/global_mail<br/>system user]
Admin -->|admin_send_mail<br/>with target_user_id| Personal[Personal Mail<br/>inbox/personal<br/>per user]
Client -->|get_mail| Merge[Merge global + personal]
Merge --> Filter[Filter by: not deleted,<br/>within date range,<br/>not expired]
Filter --> Response[Return to client]
Client -->|claim_mail_reward| CW[Check claimed_ids]
CW -->|Already claimed| Err[error: Reward already claimed]
CW -->|Not claimed| Grant[Grant rewards:<br/>gold/star -> wallet<br/>fragments -> fragment storage<br/>skins -> inventory storage]
Grant --> UpdateState[Update state:<br/>add mailId to claimed_ids]
Client-Server Data Flow
sequenceDiagram
participant C as Godot Client
participant L as Lua RPC
participant S as Nakama Storage
participant W as Wallet
Note over C,W: Purchase Flow
C->>L: purchase_item(item_id, idempotency_key)
L->>L: Look up item in SHOP_CATALOG_DEFS
L->>W: wallet_update(-price)
alt Insufficient funds
W-->>L: error
L-->>C: error("NotEnoughFunds")
else Success
L->>S: storage_write(inventory/item_id)
L->>S: storage_write(receipts/idempotency_key)
L-->>C: {success: true, item: item_id}
end
Note over C,W: Wallet State Sync
C->>L: get_account (Nakama SDK call)
S->>C: wallet JSON string
C->>C: Parse wallet JSON
C->>C: emit profile_updated signal
C->>C: All UI panels update labels
All transactions are idempotent via idempotency_key — if the same key is used twice, the server returns the previous result instead of re-executing.
Admin Hierarchy
| Role | Can | Guarded by |
|---|---|---|
player (default) |
Nothing special | — |
moderator |
Match-related admin: kick players, get server stats | utils.require_admin_or_host (also checks match host) |
admin |
All moderation: ban/unban, manage mail, manage chat, view users | utils.require_admin(context) |
owner |
Everything admin can + set user roles | Inline check: callerMetadata.role == "owner" |
Guard functions (utils.lua)
utils.require_admin(context) -- errors if role not admin or owner
utils.require_admin_or_host(context, match_id) -- errors if not admin/owner AND not match host
utils.is_banned(metadata) -- returns boolean
utils.resolve_channel_id(channelId) -- channel name → hashed ID
Storage Collections
| Collection | Owner | Key | Public R | Public W | Purpose |
|---|---|---|---|---|---|
profiles |
User | "profile" |
1 | 0 | User metadata, role, ban status, loadout |
profiles |
User | "pity_counters" |
1 | 0 | Per-banner gacha pity counts |
profiles |
User | "fragments" |
1 | 0 | Accumulated gacha fragments |
inventory |
User | Item ID | 1 | 0 | Owned cosmetic items |
inventory |
User | "fragments" |
1 | 0 | Fragment counts (legacy) |
stats |
User | "game_stats" |
1 | 0 | Player stats (wins, kills, score) |
receipts |
User | Idempotency key | 1 | 0 | Purchase receipts (IAP + shop) |
inbox |
User | "personal" |
1 | 0 | Personalized mail inbox |
inbox |
User | "state" |
1 | 0 | claimed_ids, deleted_ids, read_ids |
daily_rewards |
User | "state" |
1 | 0 | Daily reward claim tracking |
config |
SYSTEM | "global_mail" |
2 | 0 | Global mail sent to all players |
config |
SYSTEM | "daily_rewards" |
2 | 0 | Monthly reward schedule |
config |
SYSTEM | "lobby_chat" |
2 | 0 | Chat prefix/max_messages config |
shop_config |
SYSTEM | "featured_banners" |
2 | 0 | Featured shop banners (max 3) |
bans |
SYSTEM | User ID | 2 | 0 | Ban records (redundant with metadata) |
vs Nakama-Server-API
| Aspect | Architecture-Server (this page) | Nakama-Server-API |
|---|---|---|
| Audience | Architects, new devs | Implementers, AI agents |
| Detail level | High-level flow, diagrams | Per-function: params, returns, errors |
| Diagrams | Mermaid flowcharts | None |
| RPC listing | Summary table with key flows | Full 48 RPC documentation |
| Storage | Conceptual collection overview | Exact schema per collection |
| Best for | Understanding the system | Calling RPCs without reading code |
Quick Reference: All 48 Registered RPCs
| RPC Name | Module | Auth | Purpose |
|---|---|---|---|
update_display_name |
user | required | Change display name |
update_avatar |
user | required | Change avatar URL |
sync_profile |
user | required | Push profile to server |
change_identity |
user | required | Link new device/email |
set_password |
user | required | Set email password |
sync_friends |
user | required | Push friend list |
get_shop_catalog |
economy | required | Get catalog + featured |
buy_currency |
economy | required | IAP gold/star purchase |
purchase_item |
economy | required | Buy cosmetic item |
perform_gacha_pull |
gacha | required | Roll gacha |
get_leaderboard_stats |
leaderboard | no | Get top 50 scores |
submit_score |
leaderboard | required | Record match score |
sync_leaderboard |
leaderboard | required | Sync stats → leaderboard |
reset_stats |
leaderboard | required | Clear own stats |
get_mail |
inbox | required | Get available mail |
claim_mail_reward |
inbox | required | Claim mail rewards |
delete_mail |
inbox | required | Soft-delete mail |
save_mail_state |
inbox | required | Mark as read |
claim_daily_reward |
daily_rewards | required | Claim today's reward |
get_daily_reward_state |
daily_rewards | required | View monthly schedule |
set_daily_reward_config |
daily_rewards | admin | Set reward schedule |
get_daily_reward_config_admin |
daily_rewards | admin | Get reward config |
admin_* (18 RPCs) |
admin | admin/owner | Moderation tools |
For full params/returns/errors on any RPC above, see Nakama-Server-API.
Deployment Topology
flowchart TD
subgraph VPS [VPS 52.74.133.55]
Gitea[Gitea Server<br/>port 3000]
Nakama[Nakama Server<br/>port 7350/7351]
PG[(PostgreSQL<br/>Nakama DB)]
Act[act_runner<br/>CI/CD Worker]
end
Client[Godot Player] -->|HTTPS| Gitea
Client -->|gRPC/WebSocket| Nakama
Nakama --> PG
GitHub_mirror[GitHub Mirror] -->|Push| Gitea
Gitea -->|Webhook/Manual| Act
Act -->|Build| Binary[Binary Releases]
Act -->|Build| Patch[patch.pck on patches branch]
Client -->|Check version| Gitea
Client -->|Download patch| Gitea