# Tekton Armageddon - Server Architecture High-level architecture of the Nakama Lua backend. For detailed RPC reference (params, returns, errors), see [Nakama-Server-API](../Nakama-Server-API). [Back to top](#top) --- ## System Overview ```mermaid flowchart LR Client[Godot Client
player.tscn] -->|RPC calls| Nakama[Nakama Server
port 7350/7351] Nakama -->|wallet_update| Wallet[Wallet Engine] Nakama -->|storage_read/write| Storage[Nakama Storage DB
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
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. [Back to top](#top) --- ## Module Architecture ```mermaid flowchart TD main["main.lua
Entry point: loads all modules"] --> utils["utils.lua
Auth guards, channel resolver"] main --> core["core.lua
Authentication hooks
Wallet init, ban check"] main --> economy["economy.lua
Shop catalog, currency IAP
Item purchases"] main --> gacha["gacha.lua
Gacha pulls, pity system
RNG on server"] main --> leaderboard["leaderboard.lua
Score submission, sync
Global rankings"] main --> inbox["inbox.lua
Global/personal mail
Reward claiming"] main --> daily["daily_rewards.lua
Daily login rewards
Monthly schedule"] main --> user["user.lua
Profile updates, identity
Friend sync"] main --> admin["admin.lua
Kick/ban, stats, chat mgmt
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) 1. `utils.lua` — no deps (loaded first) 2. `economy.lua` — depends on utils 3. `core.lua` — depends on utils 4. `admin.lua` — depends on utils 5. `daily_rewards.lua` — depends on utils 6. `user.lua` — depends on utils 7. `leaderboard.lua` — depends on utils 8. `inbox.lua` — depends on utils 9. `gacha.lua` — no deps beyond utils [Back to top](#top) --- ## Authentication Flow ```mermaid 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 1. Godot client calls `AuthenticateDevice` (or Email/Custom/Steam). 2. Nakama core calls `after_authenticate()` hook in `core.lua`. 3. Hook reads `profiles/profile` from storage. 4. **If first login:** initializes default profile and grants starting wallet (`gold: 100, star: 500`). 5. **Ban check:** If `metadata.banned == true`, raises error (player rejected). 6. Client receives session token and proceeds to lobby. [Back to top](#top) --- ## Wallet & Economy Flow ```mermaid 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
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}` | [Back to top](#top) --- ## Gacha Flow ```mermaid 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. [Back to top](#top) --- ## Mail/Inbox System ```mermaid flowchart TD Admin[Admin RPC] -->|admin_send_mail| Global[Global Mail
config/global_mail
system user] Admin -->|admin_send_mail
with target_user_id| Personal[Personal Mail
inbox/personal
per user] Client -->|get_mail| Merge[Merge global + personal] Merge --> Filter[Filter by: not deleted,
within date range,
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:
gold/star -> wallet
fragments -> fragment storage
skins -> inventory storage] Grant --> UpdateState[Update state:
add mailId to claimed_ids] ```[Global Mail
config/global_mail
system user] Admin -->|admin_send_mail
with target_user_id| Personal[Personal Mail
inbox/personal
per user] Client -->|get_mail| Merge[Merge global + personal] Merge --> Filter[Filter by: not deleted,
within date range,
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:
gold/star -> wallet
fragments -> fragment storage
skins -> inventory storage] Grant --> UpdateState[Update state:
add mailId to claimed_ids] ``` [Back to top](#top) --- ## Client-Server Data Flow ```mermaid 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. [Back to top](#top) --- ## 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) ```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 ``` [Back to top](#top) --- ## 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) | [Back to top](#top) --- ## 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 | [Back to top](#top) --- ## 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](../Nakama-Server-API). [Back to top](#top) --- ## Deployment Topology ```mermaid flowchart TD subgraph VPS [VPS 52.74.133.55] Gitea[Gitea Server
port 3000] Nakama[Nakama Server
port 7350/7351] PG[(PostgreSQL
Nakama DB)] Act[act_runner
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 ``` [Back to top](#top)