Files
tekton/wiki/architecture_server.md
T
god 114748a54f experimental: remove Tekton Doors entirely
- 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
2026-07-06 00:18:59 +08:00

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.

Back to top


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.

Back to top


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)

  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


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

  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


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}

Back to top


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.

Back to top


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]

Back to top


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.

Back to 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)

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


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


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


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.

Back to top


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

Back to top