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
This commit is contained in:
god
2026-07-06 00:18:59 +08:00
parent 0ab00afd37
commit 114748a54f
31 changed files with 4493 additions and 1535 deletions
+2707
View File
@@ -0,0 +1,2707 @@
<a id="top"></a>
# Tekton Armageddon - Client Architecture (Full Function Reference)
[Back to Home](./Home)
Complete per-function reference for the Godot 4.7 client codebase. Every script, signal, autoload dependency, and cross-file relationship documented.
[Back to top](#top)
## Table of Contents
[Back to top](#top)
1. [Project Structure Overview](#1-project-structure-overview)
2. [Autoloads / Singletons Index](#2-autoloads--singletons-index)
3. [Service Layer](#3-service-layer)
- [3.1 NakamaManager](#31-nakamamanaager)
- [3.2 BackendService](#32-backendservice)
- [3.3 SteamworksManager](#33-steamworksmanager)
4. [Core Managers](#4-core-managers)
- [4.1 AuthManager](#41-authmanager)
- [4.2 LobbyManager](#42-lobbymanager)
- [4.3 GameStateManager](#43-gamestatemanager)
- [4.4 PlayerManager](#44-playermanager)
- [4.5 EventBus](#45-eventbus)
- [4.6 GameMode / ModeConfig](#46-gamemode--modeconfig)
5. [Player Subsystem Managers](#5-player-subsystem-managers)
- [5.1 PlayerMovementManager](#51-playermovementmanager)
- [5.2 PlayerInputManager](#52-playerinputmanager)
- [5.3 PlayerActionManager](#53-playeractionmanager)
- [5.4 PlayerboardManager](#54-playerboardmanager)
- [5.5 PowerupManager](#55-powerupmanager)
6. [Game Mode Managers](#6-game-mode-managers)
- [6.1 StopNGoManager](#61-stopngomanager)
- [6.2 GauntletManager](#62-gauntletmanager)
- [6.3 PortalModeManager](#63-portalmode_manager)
- [6.4 GoalManager](#64-goalmanager)
- [6.5 GoalsCycleManager](#65-goalscyclemanager)
- [6.6 PlayerRaceManager](#66-playerracemanager)
- [6.7 TurnManager](#67-turnmanager)
7. [Gameplay Managers](#7-gameplay-managers)
- [7.1 ObstacleManager](#71-obstaclemanager)
- [7.2 SpecialTilesManager](#72-specialtilesmanager)
- [7.3 StaticTektonManager](#73-statictektonmanager)
8. [UI / Presentation Managers](#8-ui--presentation-managers)
- [8.1 UIManager](#81-uimanager)
- [8.2 SfxManager](#82-sfxmanager)
- [8.3 MusicManager](#83-musicmanager)
- [8.4 NotificationManager](#84-notificationmanager)
- [8.5 ScreenShake](#85-screenshake)
- [8.6 CameraContextManager](#86-cameracontextmanager)
- [8.7 TouchControls](#87-touchcontrols)
- [8.8 TutorialManager / TutorialOverlay](#88-tutorialmanager--tutorialoverlay)
9. [Social / Economy Managers](#9-social--economy-managers)
- [9.1 UserProfileManager](#91-userprofilemanager)
- [9.2 GachaManager](#92-gachamanager)
- [9.3 SkinManager](#93-skinmanager)
- [9.4 ShopManager](#94-shopmanager)
- [9.5 JoinManager](#95-joinmanager)
- [9.6 FriendManager](#96-friendmanager)
- [9.7 MailManager](#97-mailmanager)
- [9.8 DailyRewardManager](#98-dailyrewardmanager)
- [9.9 AdminManager](#99-adminmanager)
10. [System Managers](#10-system-managers)
- [10.1 SettingsManager](#101-settingsmanager)
- [10.2 SessionManager](#102-sessionmanager)
- [10.3 GameUpdateManager](#103-gameupdatemanager)
11. [Core Scene Scripts](#11-core-scene-scripts)
- [11.1 main.gd (Main game scene controller)](#111-maingd-main-game-scene-controller)
- [11.2 player.gd](#112-playergd)
- [11.3 lobby.gd](#113-lobbygd)
- [11.4 animation.gd](#114-animationgd)
12. [UI Helper Classes (RefCounted)](#12-ui-helper-classes-refcounted)
- [12.1 LobbyMainMenu](#121-lobbymainmenu)
- [12.2 LobbyRoom](#122-lobbyroom)
- [12.3 LobbyRoomList](#123-lobbyroomlist)
- [12.4 LobbyChat](#124-lobbychat)
13. [Dependency Graph](#13-dependency-graph)
- [13.1 Manager Autoload Dependencies](#131-manager-autoload-dependencies)
- [13.2 Cross-Manager Signal Wiring](#132-cross-manager-signal-wiring)
14. [Scene Node Trees](#14-scene-node-trees)
- [14.1 main.tscn](#141-maintscn)
- [14.2 player.tscn](#142-playertscn)
- [14.3 lobby.tscn](#143-lobbytscn)
[Back to top](#top)
## 1. Project Structure Overview
[Back to top](#top)
```
/home/dev/tekton/
project.godot -- Godot 4.7 project file
scripts/
main.gd -- (NOT USED; logic lives in scenes/main.gd)
nakama_manager.gd -- Nakama network layer (autoload)
event_bus.gd -- Central observer pattern bus (autoload)
game_mode.gd -- GameMode enum + string utils (RefCounted)
mode_config.gd -- Schema-driven mode settings validation (RefCounted)
managers/ -- 39+ autoload manager singletons
auth_manager.gd
lobby_manager.gd
game_state_manager.gd
player_manager.gd
player_movement_manager.gd
player_input_manager.gd
player_action_manager.gd
user_profile_manager.gd
gacha_manager.gd
skin_manager.gd
ui_manager.gd
sfx_manager.gd
music_manager.gd
game_update_manager.gd
stop_n_go_manager.gd
gauntlet_manager.gd
portal_mode_manager.gd
turn_manager.gd
goal_manager.gd
goals_cycle_manager.gd
player_race_manager.gd
shop_manager.gd
join_manager.gd
powerup_manager.gd
notification_manager.gd
obstacle_manager.gd
friend_manager.gd
admin_manager.gd
mail_manager.gd
session_manager.gd
settings_manager.gd
tutorial_manager.gd
tutorial_overlay.gd
playerboard_manager.gd
camera_context_manager.gd
screen_shake.gd
special_tiles_manager.gd
static_tekton_manager.gd
touch_controls.gd
daily_reward_manager.gd
services/
backend_service.gd -- Unified RPC interface (autoload)
steamworks_manager.gd -- Steam auth ticket + persona (NOT autoload; child of BackendService)
scenes/
main.gd -- Core game scene controller (~2956 lines)
main.tscn -- Main game scene
player.gd -- Player character controller (~2751 lines)
player.tscn -- Player scene
lobby.gd -- Lobby/home screen controller (~583 lines)
lobby.tscn -- Lobby scene
animation.gd -- Stop n Go animation player (41 lines)
ui/
lobby_main_menu.gd -- RefCounted; main menu button wiring
lobby_room.gd -- RefCounted; room/player slot management
lobby_room_list.gd -- RefCounted; room list display + join
lobby_chat.gd -- RefCounted; global + DM chat
login_screen.tscn -- Login screen scene
boot_screen.tscn -- Boot splash scene
shop_panel.tscn -- Shop panel scene
gacha_panel.tscn -- Gacha panel scene
daily_reward_panel.tscn -- Daily reward panel scene
admin_panel.tscn -- Admin panel scene
profile_panel.tscn -- Profile panel scene
leaderboard_panel.tscn -- Leaderboard panel scene
mailbox_panel.tscn -- Mailbox panel scene
settings_menu.tscn -- Settings scene
lobby_invite_popup.tscn -- Invite popup scene
invite_friends_dialog.tscn -- Invite dialog scene
social_panel.tscn -- Social panel scene
game/
main.tscn -- (actual main game scene)
loading_screen/
loading_screen.tscn -- Level loading screen
```
[Back to top](#top)
## 2. Autoloads / Singletons Index
[Back to top](#top)
All managers are registered as autoloads in project.godot and accessible globally via `/root/<ManagerName>`. The following are the configured autoloads:
| Autoload Name | File | Purpose |
|---|---|---|
| AuthManager | res://scripts/managers/auth_manager.gd | Authentication (guest, email, social) |
| NakamaManager | res://scripts/nakama_manager.gd | Nakama client/socket/bridge lifecycle |
| BackendService | res://scripts/services/backend_service.gd | Unified RPC API wrapper |
| EventBus | res://scripts/event_bus.gd | Observer-pattern cross-manager events |
| LobbyManager | res://scripts/managers/lobby_manager.gd | Room lifecycle, matchmaking |
| GameStateManager | res://scripts/managers/game_state_manager.gd | State machine, match lifecycle |
| PlayerManager | res://scripts/managers/player_manager.gd | Player data container |
| PlayerMovementManager | res://scripts/managers/player_movement_manager.gd | Movement physics, pathfinding |
| PlayerInputManager | res://scripts/managers/player_input_manager.gd | Input capture, buffering |
| PlayerActionManager | res://scripts/managers/player_action_manager.gd | Action execution (grab, put) |
| UserProfileManager | res://scripts/managers/user_profile_manager.gd | Profile CRUD, wallet sync |
| GachaManager | res://scripts/managers/gacha_manager.gd | Gacha pull orchestration |
| SkinManager | res://scripts/managers/skin_manager.gd | Cosmetics, skins, loadout |
| UIManager | res://scripts/managers/ui_manager.gd | UI layer stack, show/hide |
| SfxManager | res://scripts/managers/sfx_manager.gd | Sound effect pool |
| MusicManager | res://scripts/managers/music_manager.gd | Music crossfade |
| GameUpdateManager | res://scripts/managers/game_update_manager.gd | Hot-reload patching |
| StopNGoManager | res://scripts/managers/stop_n_go_manager.gd | Stop n Go minigame state |
| GauntletManager | res://scripts/managers/gauntlet_manager.gd | Gauntlet mode progression |
| PortalModeManager | res://scripts/managers/portal_mode_manager.gd | Portal race mode |
| TurnManager | res://scripts/managers/turn_manager.gd | Turn-based sequencing |
| GoalManager | res://scripts/managers/goal_manager.gd | Goal validation, completion |
| GoalsCycleManager | res://scripts/managers/goals_cycle_manager.gd | Cycling goal rotation, scoring |
| PlayerRaceManager | res://scripts/managers/player_race_manager.gd | Race position, finish |
| ShopManager | res://scripts/managers/shop_manager.gd | Shop data layer |
| JoinManager | res://scripts/managers/join_manager.gd | Join code input |
| PowerupManager | res://scripts/managers/powerup_manager.gd | Powerup system (boost/charge) |
| NotificationManager | res://scripts/managers/notification_manager.gd | On-screen message queue |
| ObstacleManager | res://scripts/managers/obstacle_manager.gd | Obstacle placement/removal |
| FriendManager | res://scripts/managers/friend_manager.gd | Friends list, DMs |
| AdminManager | res://scripts/managers/admin_manager.gd | Admin panel state |
| MailManager | res://scripts/managers/mail_manager.gd | Mail CRUD |
| SessionManager | res://scripts/managers/session_manager.gd | Session refresh lifecycle |
| SettingsManager | res://scripts/managers/settings_manager.gd | User settings persistence |
| TutorialManager | res://scripts/managers/tutorial_manager.gd | Tutorial flow control |
| TutorialOverlay | res://scripts/managers/tutorial_overlay.gd | Tutorial UI overlay |
| PlayerboardManager | res://scripts/managers/playerboard_manager.gd | Player inventory board |
| CameraContextManager | res://scripts/managers/camera_context_manager.gd | Camera zoom/context |
| ScreenShake | res://scripts/managers/screen_shake.gd | Screen shake effects |
| SpecialTilesManager | res://scripts/managers/special_tiles_manager.gd | Ice/crack/portal tiles |
| StaticTektonManager | res://scripts/managers/static_tekton_manager.gd | Static Tekton turret logic |
| TouchControls | res://scripts/managers/touch_controls.gd | Mobile touch input overlay |
| DailyRewardManager | res://scripts/managers/daily_reward_manager.gd | Daily reward claims |
[Back to top](#top)
## 3. Service Layer
[Back to top](#top)
### 3.1 NakamaManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/nakama_manager.gd` (330 lines)
**Extends:** Node
**Autoload name:** NakamaManager
Central Nakama SDK integration. Manages the Nakama client, session, socket, and multiplayer bridge. All network communication flows through this singleton.
**Properties:**
| Name | Type | Description |
|---|---|---|
| nakama_server_key | String | From env var NAKAMA_SERVER_KEY or ProjectSettings |
| nakama_host | String | Default: `tektondash.vps.webdock.cloud` |
| nakama_port | int | Default: 7350 |
| nakama_scheme | String | Default: http |
| client | NakamaClient | The Nakama client instance |
| session | NakamaSession | Current auth session |
| socket | NakamaSocket | WebSocket connection |
| bridge | NakamaMultiplayerBridge | Links Nakama socket to Godot HLAPI |
| current_match_id | String | Currently joined match ID |
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `connected_to_nakama` | none | Emitted when socket connects successfully |
| `connection_failed` | error_message: String | Emitted on connection failure |
| `match_joined` | match_id: String | Emitted when bridge joins a match |
| `match_join_error` | error_message: String | Emitted on match join failure |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `set_server` | `func set_server(host: String, port: int = 7350) -> void` | void | Override Nakama server endpoint. Auto-detects scheme (https for .ts.net, http for 100.x IPs). Recreates client if no active session. |
| `connect_to_nakama_async` | `func connect_to_nakama_async(email: String = "", password: String = "") -> bool` | bool (async) | Full auth + socket + bridge connection. Empty email = device auth. Creates socket, initializes multiplayer bridge, sets Godot's multiplayer peer. |
| `cleanup` | `func cleanup() -> void` | void | Shuts down socket, leaves bridge, deletes match metadata storage, resets multiplayer peer to null. |
| `host_game` | `func host_game(room_meta: Dictionary = {}) -> void` | void | Creates a Nakama relayed match via bridge.create_match(). Optionally stores room metadata to Nakama storage. Has re-entry guard for double-click protection. |
| `join_game` | `func join_game(match_id: String) -> void` | void | Joins an existing match by ID. Leaves current match first if connected. |
| `is_connected_to_nakama` | `func is_connected_to_nakama() -> bool` | bool | Returns true if socket exists and is connected to host. |
| `list_matches_async` | `func list_matches_async(mode_filter: String = "") -> Array` | Array (async) | Queries Nakama for available matches. Batch-reads room metadata from storage. Returns array of room dicts. |
| `_on_bridge_match_joined` | `func _on_bridge_match_joined() -> void` | void | Internal: updates current_match_id, emits match_joined signal. |
| `_on_bridge_match_join_error` | `func _on_bridge_match_join_error(error) -> void` | void | Internal: emits match_join_error. |
**Dependencies:** Nakama GDExtension (NakamaClient, NakamaSocket, NakamaMultiplayerBridge).
**Depended by:** AuthManager, BackendService, LobbyManager, LobbyRoom, LobbyChat, LobbyMainMenu, main.gd.
[Back to top](#top)
### 3.2 BackendService
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/services/backend_service.gd` (247 lines)
**Extends:** Node
**Autoload name:** BackendService
Unified typed interface for all Nakama Lua RPCs. All platform authentication paths (Steam, Nakama device/email) funnel through here. Provides retry logic with exponential backoff.
**Properties:**
| Name | Type | Description |
|---|---|---|
| current_platform | Platform (enum) | DESKTOP_STEAM, DESKTOP_NAKAMA, or MOBILE_NAKAMA |
| steamworks_manager | Node | Only for auth ticket retrieval |
| nakama_backend | Node | Reference to NakamaManager autoload |
**Enums:**
- `Platform { DESKTOP_STEAM, DESKTOP_NAKAMA, MOBILE_NAKAMA }`
- `ErrorCode { NONE, NETWORK_ERROR, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_ERROR, UNKNOWN_ERROR, INSUFFICIENT_FUNDS }`
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Detects platform, initializes backend |
| `is_initialized` | `func is_initialized() -> bool` | bool | Checks nakama_backend is non-null |
| `get_platform_name` | `func get_platform_name() -> String` | String | Returns human-readable platform name |
| `get_steamworks_manager` | `func get_steamworks_manager() -> Node` | Node | Returns steamworks_manager child node |
| `api_rpc_async` | `func api_rpc_async(rpc_id: String, payload: String = "{}") -> Dictionary` | Dictionary (async) | Unified RPC with up to 3 retries, exponential backoff (0.5s base). Returns `{success, error, message, data}`. |
| `admin_clear_global_chat` | `func admin_clear_global_chat(payload: String) -> Dictionary` | Dictionary | RPC wrapper |
| `admin_get_chat_config` | `func admin_get_chat_config() -> Dictionary` | Dictionary | RPC wrapper |
| `admin_set_chat_config` | `func admin_set_chat_config(config: Dictionary) -> Dictionary` | Dictionary | RPC wrapper |
| `admin_purge_old_messages` | `func admin_purge_old_messages(channel_id: String, max_age_days: int) -> Dictionary` | Dictionary | RPC wrapper |
| `admin_list_channel_messages` | `func admin_list_channel_messages(channel_id: String, limit: int = 50, cursor: String = "", forward: bool = true) -> Dictionary` | Dictionary | RPC wrapper |
| `admin_delete_channel_message` | `func admin_delete_channel_message(channel_id: String, message_id: String) -> Dictionary` | Dictionary | RPC wrapper |
| `send_friend_request` | `func send_friend_request(target_id: String) -> Dictionary` | Dictionary | RPC wrapper |
| `respond_friend_request` | `func respond_friend_request(target_id: String, accept: bool) -> Dictionary` | Dictionary | RPC wrapper |
| `perform_gacha_pull` | `func perform_gacha_pull(gacha_id: String, count: int) -> Dictionary` | Dictionary | RPC wrapper |
| `get_mail` | `func get_mail(payload: String = "{}") -> Dictionary` | Dictionary | RPC wrapper |
| `claim_mail_reward` | `func claim_mail_reward(mail_id: String) -> Dictionary` | Dictionary | RPC wrapper |
| `delete_mail` | `func delete_mail(mail_id: String) -> Dictionary` | Dictionary | RPC wrapper |
| `send_mail` | `func send_mail(payload: String) -> Dictionary` | Dictionary | RPC wrapper |
| `change_avatar` | `func change_avatar(avatar_url: String) -> Dictionary` | Dictionary | RPC wrapper |
| `change_username` | `func change_username(new_username: String) -> Dictionary` | Dictionary | RPC wrapper |
| `change_status` | `func change_status(new_status: String) -> Dictionary` | Dictionary | RPC wrapper |
| `change_bio` | `func change_bio(new_bio: String) -> Dictionary` | Dictionary | RPC wrapper |
| `query_users` | `func query_users(payload: String) -> Dictionary` | Dictionary | RPC wrapper |
| `admin_give_currency` | `func admin_give_currency(payload: String) -> Dictionary` | Dictionary | RPC wrapper |
| `get_daily_reward_config_admin` | `func get_daily_reward_config_admin() -> Dictionary` | Dictionary | RPC wrapper |
| `set_daily_reward_config` | `func set_daily_reward_config(req: Dictionary) -> Dictionary` | Dictionary | RPC wrapper |
| `get_daily_reward_state` | `func get_daily_reward_state() -> Dictionary` | Dictionary | RPC wrapper |
| `claim_daily_reward` | `func claim_daily_reward() -> Dictionary` | Dictionary | RPC wrapper |
| `sync_leaderboard` | `func sync_leaderboard() -> Dictionary` | Dictionary | RPC wrapper |
| `get_leaderboard_stats` | `func get_leaderboard_stats() -> Dictionary` | Dictionary | RPC wrapper |
| `debug_add_exp` | `func debug_add_exp(exp_amount: int) -> Dictionary` | Dictionary | RPC wrapper |
| `reset_stats` | `func reset_stats() -> Dictionary` | Dictionary | RPC wrapper |
| `search_users` | `func search_users(payload: String) -> Dictionary` | Dictionary | RPC wrapper |
| `send_lobby_invite` | `func send_lobby_invite(to_user_id: String, match_id: String) -> Dictionary` | Dictionary | RPC wrapper |
**Dependencies:** NakamaManager (autoload), SteamworksManager (child node).
**Depended by:** AuthManager, LobbyManager, LobbyChat, lobby.gd (admin), FriendManager, MailManager, GachaManager, DailyRewardManager, AdminManager, SkinManager.
[Back to top](#top)
### 3.3 SteamworksManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/services/steamworks_manager.gd` (72 lines)
**Extends:** Node
**class_name:** SteamworksManager
NOT an autoload. Created as a child of BackendService. Provides Steam auth session tickets for Nakama login. GodotSteam GDExtension required.
**Properties:**
| Name | Type | Description |
|---|---|---|
| is_steam_initialized | bool | Whether Steam API initialized successfully |
| steam_app_id | int | From ProjectSettings or default 480 |
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Calls _initialize_steam |
| `is_initialized` | `func is_initialized() -> bool` | bool | Returns steam init status |
| `get_auth_session_ticket` | `func get_auth_session_ticket() -> String` | String | Gets Steam auth session ticket via Steam.getAuthSessionTicket(), returns hex-encoded buffer |
| `get_steam_user_name` | `func get_steam_user_name() -> String` | String | Returns Steam persona name via Steam.getPersonaName() |
| `get_steam_user_id` | `func get_steam_user_id() -> int` | int | Returns Steam ID via Steam.getSteamID() |
**Dependencies:** GodotSteam GDExtension (ClassDB.class_exists("Steam")).
**Depended by:** BackendService, AuthManager.
[Back to top](#top)
## 4. Core Managers
[Back to top](#top)
### 4.1 AuthManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/auth_manager.gd` (515 lines)
**Extends:** Node
**Autoload name:** AuthManager
Centralized authentication handler. Supports Guest (device ID), Email/Password, Google, Apple, Facebook, and Steam auth modes. Persists sessions to encrypted file storage.
**Properties:**
| Name | Type | Description |
|---|---|---|
| current_user | Dictionary | {user_id, username, display_name, avatar_url, email} |
| is_authenticated | bool | Whether fully authenticated |
| is_guest | bool | Whether using guest mode |
| auth_mode | AuthMode (enum) | GUEST, EMAIL, GOOGLE, APPLE, FACEBOOK, STEAM, CUSTOM |
**Enums:** `AuthMode { GUEST, EMAIL, GOOGLE, APPLE, FACEBOOK, STEAM, CUSTOM }`
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `auth_started` | none | Emitted when any login flow begins |
| `auth_completed` | success: bool, user_data: Dictionary | Emitted on auth success or failure |
| `auth_failed` | error: String | Emitted on auth error |
| `session_restored` | none | Emitted when saved session restored |
| `logged_out` | none | Emitted after full logout |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Deferred call to _try_restore_session |
| `login_as_guest` | `func login_as_guest() -> bool` | bool (async) | Device ID guest auth. Generates/persists device ID. |
| `login_with_email` | `func login_with_email(email: String, password: String, remember: bool = true) -> bool` | bool (async) | Email/password authentication |
| `register_with_email` | `func register_with_email(email: String, password: String, username: String = "") -> bool` | bool (async) | Email registration (create if not exists) |
| `login_with_google` | `func login_with_google(id_token: String) -> bool` | bool (async) | Google auth via ID token |
| `login_with_apple` | `func login_with_apple(id_token: String) -> bool` | bool (async) | Apple auth via ID token |
| `login_with_facebook` | `func login_with_facebook(access_token: String) -> bool` | bool (async) | Facebook auth via access token |
| `login_with_steam` | `func login_with_steam() -> bool` | bool (async) | Steam ticket auth via BackendService.steamworks_manager |
| `link_email` | `func link_email(email: String, password: String) -> bool` | bool (async) | Link email to existing guest account |
| `link_google` | `func link_google(id_token: String) -> bool` | bool (async) | Link Google to existing account |
| `logout` | `func logout() -> void` | void | Full cleanup: NakamaManager.cleanup(), clear session files, reset state, emit logged_out |
| `clear_session` | `func clear_session() -> void` | void | Deletes SESSION_FILE and CREDENTIALS_FILE from user:// |
| `_try_restore_session` | internal | void | Attempts to load encrypted session file. Skips guest session auto-restore. |
| `_connect_socket` | internal | bool (async) | Creates Nakama socket, connects, initializes multiplayer bridge |
| `_load_user_profile` | internal | void (async) | Loads account data from Nakama into current_user |
**Dependencies:** NakamaManager, BackendService.
**Depended by:** LobbyMainMenu, lobby.gd, UserProfileManager, login_screen.tscn.
[Back to top](#top)
### 4.2 LobbyManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/lobby_manager.gd` (1023 lines)
**Extends:** Node
**Autoload name:** LobbyManager
Room/lobby lifecycle manager. Handles both Nakama (online) and LAN (direct ENet) modes. Manages room creation, joining, player list, ready states, game mode settings, and character/area selection.
**Properties:**
| Name | Type | Default | Description |
|---|---|---|---|
| current_room | Dictionary | {} | Current room metadata |
| players_in_room | Array | [] | [{id, name, is_ready, character, nakama_id}] |
| available_rooms | Array | [] | Discovered rooms for room list |
| is_host | bool | false | Whether local player is room host |
| is_lan_mode | bool | false | Direct ENet (no Nakama) |
| LAN_PORT | const int | 7777 | ENet server port |
| LAN_DISCOVERY_PORT | const int | 7778 | UDP broadcast port |
| local_player_name | String | "Player" | Display name |
| is_tutorial_mode | bool | false | Tutorial mode flag |
| match_duration | int | 180 | Seconds (configurable by host) |
| randomize_spawn | bool | false | Randomize spawn positions |
| enable_cycle_timer | bool | false | Goal cycle timer |
| scarcity_mode | String | "Normal" | Item scarcity: Normal/Aggressive/Chaos |
| disconnect_reason | String | "" | UI feedback message |
| sng_go_duration | int | 20 | Stop n Go: GO phase seconds |
| sng_stop_duration | int | 4 | Stop n Go: STOP phase seconds |
| sng_required_goals | int | 8 | Goals needed for SNG win |
| doors_swap_time | int | 15 | Tekton Doors: swap interval |
| doors_refresh_time | int | 25 | Tekton Doors: refresh interval |
| doors_required_goals | int | 8 | Goals needed for Doors win |
| rematch_votes | Array | [] | Player IDs who voted for rematch |
| available_characters | Array[String] | [...] | ["Copper", "Dabro", "Gatot", "Pip", "Random"] |
| available_areas | Array[String] | [] | Mode-specific area list |
| available_game_modes | Array[String] | [...] | ["Freemode", "Stop n Go", "Candy Pump Survival"] |
| selected_area | String | "Freemode Arena" | Currently selected area |
| game_mode | String | "Freemode" | Current game mode |
| local_character_index | int | 0 | Local player's character index |
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `room_list_updated` | rooms: Array | Room list refreshed |
| `room_joined` | room_data: Dictionary | Joined a room |
| `room_left` | none | Left current room |
| `player_joined` | player_data: Dictionary | Player entered room |
| `player_left` | player_id: int | Player left room |
| `ready_state_changed` | player_id: int, is_ready: bool | Player ready status changed |
| `all_players_ready` | none | All players ready |
| `host_disconnected` | none | Host left/disconnected |
| `game_starting` | none | Game countdown started |
| `match_duration_changed` | duration_seconds: int | Duration setting changed |
| `randomize_spawn_changed` | enabled: bool | Random spawn toggled |
| `character_changed` | player_id: int, character_name: String | Character selection changed |
| `area_changed` | area_name: String | Map area changed |
| `player_list_changed` | none | Player list should re-render |
| `rematch_votes_updated` | count: int, required: int | Rematch vote progress |
| `game_mode_changed` | mode: String | Game mode changed |
| `scarcity_mode_changed` | mode: String | Scarcity setting changed |
| `enable_cycle_timer_changed` | enabled: bool | Timer toggle changed |
| `sng_go_duration_changed` | duration: int | SNG Go duration changed |
| `sng_stop_duration_changed` | duration: int | SNG Stop duration changed |
| `sng_required_goals_changed` | goals: int | SNG required goals changed |
| `doors_swap_time_changed` | time: int | Doors swap interval changed |
| `doors_refresh_time_changed` | time: int | Doors refresh interval changed |
| `doors_required_goals_changed` | goals: int | Doors required goals changed |
| `gauntlet_round_duration_changed` | duration: int | Gauntlet round duration changed |
| `gauntlet_growth_interval_changed` | interval: float | Gauntlet growth interval changed |
| `gauntlet_cells_per_tick_changed` | cells: Dictionary | Cells per tick changed |
**Key Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_tutorial` | `func start_tutorial(mode: String = "Freemode") -> void` | void | Sets tutorial flags, calls create_room_lan("Tutorial") |
| `create_room` | `func create_room(room_name: String) -> void` | void | Hosts Nakama room: connects, calls NakamaManager.host_game |
| `join_room` | `func join_room(match_id: String) -> void` | void | Joins Nakama room by match ID |
| `create_room_lan` | `func create_room_lan(room_name: String = "LAN Game") -> bool` | bool | Creates ENet server on LAN_PORT, broadcasts UDP discovery |
| `join_room_lan` | `func join_room_lan(host_ip: String) -> bool` | bool | Creates ENet client to host IP:LAN_PORT |
| `leave_room` | -- | void | Leaves current room, cleans up peers |
| `start_game` | `func start_game(is_tutorial: bool = false) -> void` | void | Transitions from lobby to main game scene |
| `refresh_room_list` | `func refresh_room_list() -> void` | void | Queries Nakama for available rooms or broadcasts LAN |
| `set_ready` | `func set_ready(is_ready: bool) -> void` | void | Updates ready state via RPC |
| `set_match_duration` | `func set_match_duration(seconds: int) -> void` | void | Host sets match duration |
| `set_randomize_spawn` | `func set_randomize_spawn(enabled: bool) -> void` | void | Host toggles random spawn |
| `set_enable_cycle_timer` | `func set_enable_cycle_timer(enabled: bool) -> void` | void | Host toggles timer |
| `set_scarcity_mode` | `func set_scarcity_mode(mode: String) -> void` | void | Host sets scarcity |
| `set_game_mode` | `func set_game_mode(mode: String) -> void` | void | Host sets game mode |
| `cycle_character` | `func cycle_character(direction: int) -> void` | void | Change character selection |
| `cycle_area` | `func cycle_area(direction: int) -> void` | void | Change selected area |
| `get_players` | `func get_players() -> Array` | Array | Returns players_in_room |
| `is_all_ready` | `func is_all_ready() -> bool` | bool | All players ready check |
| `set_sng_go_duration` | -- | void | Host sets SNG go time |
| `set_sng_stop_duration` | -- | void | Host sets SNG stop time |
| `set_sng_required_goals` | -- | void | Host sets SNG goals |
| `get_selected_area` | `func get_selected_area() -> String` | String | Returns current area name |
| `get_game_mode` | `func get_game_mode() -> GameMode.Mode` | GameMode.Mode | Converts string to GameMode enum |
| `is_game_mode` | `func is_game_mode(mode: GameMode.Mode) -> bool` | bool | Mode comparison helper |
**Internal Functions:** `_on_match_joined`, `_on_peer_connected`, `_on_peer_disconnected`, `_on_server_disconnected`, `_update_available_areas`, `_start_lan_broadcast`, `_broadcast_lan_room`, `_stop_lan_broadcast`, `_update_lan_room_list`, `_listen_for_lan_discovery`, `_update_ready_state_rpc`, `_request_rematch`, `rpc_set_*`, `rpc_*`.
**Dependencies:** NakamaManager, GameStateManager.
**Depended by:** LobbyRoom, LobbyRoomList, LobbyMainMenu, main.gd, player.gd, lobby.gd, SceneManager (loading screen).
[Back to top](#top)
### 4.3 GameStateManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/game_state_manager.gd` (66 lines)
**Extends:** Node
**Autoload name:** GameStateManager
Simple state machine and match configuration constants.
**Properties:**
| Name | Type | Default | Description |
|---|---|---|---|
| current_state | GameState (enum) | LOBBY | Current application state |
| max_players | int | 8 | Max players in a match |
| enable_bots | bool | false | Bot fill toggle |
| local_player_id | int | 0 | Local peer ID |
**Enums:** `GameState { LOBBY, LOADING, GAME, RESULT }`
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `state_changed` | new_state: GameState | Emitted on state transition |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `change_state` | `func change_state(new_state: GameState) -> void` | void | Transitions state, emits state_changed |
**Dependencies:** None.
**Depended by:** LobbyManager, main.gd, tutorial_manager.gd, many managers.
[Back to top](#top)
### 4.4 PlayerManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/player_manager.gd` (37 lines)
**Extends:** Node
**Autoload name:** PlayerManager
Lightweight data container for player metadata. Stores display name and peer ID for the local player. Used as a quick reference by various subsystems.
**Properties:**
| Name | Type | Description |
|---|---|---|
| display_name | String | Local player's display name |
| peer_id | int | Local player's multiplayer unique ID |
**Signals:** None.
**Public Functions:** None (data-only container).
**Dependencies:** None.
**Depended by:** UIManager, player.gd, various managers needing player identity.
[Back to top](#top)
### 4.5 EventBus
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/event_bus.gd` (73 lines)
**Extends:** Node
**Autoload name:** EventBus
Centralized observer pattern for inter-manager communication. Replaces direct cross-references between managers.
**Constants (event names):**
| Constant | Value | Description |
|---|---|---|
| EVENT_PLAYER_JOINED | "player_joined" | Player entered match |
| EVENT_PLAYER_LEFT | "player_left" | Player left match |
| EVENT_PLAYER_READY | "player_ready" | Player ready state changed |
| EVENT_MATCH_STARTED | "match_started" | Match began |
| EVENT_MATCH_ENDED | "match_ended" | Match ended |
| EVENT_GAME_MODE_CHANGED | "game_mode_changed" | Game mode switched |
| EVENT_CURRENCY_CHANGED | "currency_changed" | Wallet balance changed |
| EVENT_ITEM_PURCHASED | "item_purchased" | Item bought from shop |
| EVENT_GACHA_PULL | "gacha_pull" | Gacha rolled |
| EVENT_PROFILE_LOADED | "profile_loaded" | Profile loaded from server |
| EVENT_PROFILE_UPDATED | "profile_updated" | Profile updated |
| EVENT_AVATAR_CHANGED | "avatar_changed" | Avatar changed |
| EVENT_SESSION_REFRESHED | "session_refreshed" | Nakama session refreshed |
| EVENT_SESSION_EXPIRED | "session_expired" | Nakama session expired |
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `event_emitted` | event_name: String, data: Variant | Fired on every emit |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `emit` | `func emit(event_name: String, data: Variant = null) -> void` | void | Emit event to all registered listeners and the signal bus |
| `on` | `func on(event_name: String, callback: Callable) -> void` | void | Subscribe to event |
| `off` | `func off(event_name: String, callback: Callable) -> void` | void | Unsubscribe from event |
| `clear` | `func clear() -> void` | void | Remove all listeners (scene transition cleanup) |
**Dependencies:** None.
**Depended by:** UserProfileManager, GachaManager, ShopManager, many managers for loose coupling.
[Back to top](#top)
### 4.6 GameMode / ModeConfig
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/game_mode.gd` (41 lines)
**Extends:** RefCounted
**class_name:** GameMode
Enum and string conversion utilities for game modes.
**Enum:** `Mode { FREEMODE = 0, STOP_N_GO = 1, TEKTON_DOORS = 2, GAUNTLET = 3 }`
**Public Static Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `from_string` | `static func from_string(mode: String) -> Mode` | Mode | Converts "Freemode"/"Stop n Go"/"Tekton Doors"/"Candy Pump Survival" to enum |
| `mode_to_string` | `static func mode_to_string(mode: Mode) -> String` | String | Converts enum back to string |
| `is_restricted` | `static func is_restricted(mode: Mode) -> bool` | bool | Returns true for SNG, Doors, or Gauntlet |
| `get_all_modes` | `static func get_all_modes() -> Array[String]` | Array[String] | Returns all mode names |
**File:** `/home/dev/tekton/scripts/mode_config.gd` (108 lines)
**Extends:** RefCounted
**class_name:** ModeConfig
Schema-driven validation for game mode settings. Consolidates duplicated/inconsistent option toggles.
**Public Static Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `get_defaults` | `static func get_defaults(mode: String) -> Dictionary` | Dictionary | Returns default config dict for mode |
| `validate_setting` | `static func validate_setting(mode: String, key: String, value: Variant) -> Dictionary` | Dictionary | Validates type, range, and allowed values for a single setting |
| `validate_config` | `static func validate_config(mode: String, config: Dictionary) -> Dictionary` | Dictionary | Validates entire config, returns errors array |
| `get_mode_settings` | `static func get_mode_settings(mode: String) -> Array` | Array | Returns list of setting keys for mode |
| `get_setting_schema` | `static func get_setting_schema(mode: String, key: String) -> Dictionary` | Dictionary | Returns schema for specific setting |
| `has_setting` | `static func has_setting(mode: String, key: String) -> bool` | bool | Checks if setting exists for mode |
| `get_supported_modes` | `static func get_supported_modes() -> Array` | Array | Returns all supported mode strings |
**Dependencies:** None (standalone utility classes).
**Depended by:** LobbyManager, LobbyRoom, mode-specific managers.
[Back to top](#top)
## 5. Player Subsystem Managers
[Back to top](#top)
### 5.1 PlayerMovementManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/player_movement_manager.gd` (33,053 chars)
**Extends:** Node
**Autoload name:** PlayerMovementManager
Handles player movement physics, grid-based pathfinding, movement range highlighting, position syncing, and obstacle-aware navigation. Delegated from player.gd.
**Signals:** (custom signals listed; full list from code)
| Signal | Params | Description |
|---|---|---|
| `movement_started` | path: Array | Emitted when player begins moving |
| `movement_completed` | none | Emitted when movement tween finishes |
| `movement_interrupted` | none | Emitted when movement is cancelled |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `move_along_path` | `func move_along_path(player: Node, path: Array) -> void` | void | Tweens player along grid path |
| `find_path` | `func find_path(from: Vector2i, to: Vector2i, gridmap: Node) -> Array` | Array | A* or BFS pathfinding on grid |
| `highlight_movement_range` | `func highlight_movement_range(player: Node) -> void` | void | Shows reachable cells |
| `highlight_adjacent_cells` | `func highlight_adjacent_cells(player: Node) -> void` | void | Shows cardinal-adjacent cells |
| `rotate_towards_target` | `func rotate_towards_target(target_pos: Vector2i) -> void` | void | Smooth rotation to face target |
| `can_move_to` | `func can_move_to(pos: Vector2i, gridmap: Node) -> bool` | bool | Cell walkability check |
| `apply_stagger` | `func apply_stagger(duration: float) -> void` | void | Applies stun knockback |
| `sync_bump` | `func sync_bump(target_pos: Vector2i, is_soft: bool) -> void` | void | Visual bump animation |
| `set_player_moving` | `func set_player_moving(is_moving: bool) -> void` | void | Toggle movement state |
**Dependencies:** player.gd (node refs), ObstacleManager, SpecialTilesManager, EnhancedGridMap.
**Depended by:** player.gd, main.gd.
[Back to top](#top)
### 5.2 PlayerInputManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/player_input_manager.gd` (7,292 chars)
**Extends:** Node
**Autoload name:** PlayerInputManager
Captures and buffers player input events. Supports keyboard, mouse, gamepad, and touch inputs. Provides input state query API.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `input_received` | event: InputEvent | Raw input forwarded |
| `action_pressed` | action: String | Action mapped press (grab, put, move) |
| `action_released` | action: String | Action released |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `is_action_held` | `func is_action_held(action: String) -> bool` | bool | Check if action is currently held |
| `get_movement_direction` | `func get_movement_direction() -> Vector2i` | Vector2i | Grid-aligned movement cardinal |
| `get_look_direction` | `func get_look_direction(camera: Camera3D) -> Vector2` | Vector2 | Mouse-world direction |
| `flush_buffer` | `func flush_buffer() -> void` | void | Clear input buffer |
| `is_touch_active` | `func is_touch_active() -> bool` | bool | Whether touch controls are in use |
**Dependencies:** TouchControls (autoload).
**Depended by:** player.gd, player_action_manager.gd.
[Back to top](#top)
### 5.3 PlayerActionManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/player_action_manager.gd` (8,828 chars)
**Extends:** Node
**Autoload name:** PlayerActionManager
Action execution layer. Manages grab, put, arrange, tekton throw/knock actions. Handles action point consumption, cooldowns, and visual highlighting.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `action_executed` | action_type: String | Action performed |
| `action_failed` | reason: String | Action invalid |
| `action_points_changed` | points: int | AP updated |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `execute_grab` | `func execute_grab(player: Node, grid_pos: Vector2i) -> bool` | bool | Grab item from grid |
| `execute_put` | `func execute_put(player: Node, slot_index: int, grid_pos: Vector2i) -> bool` | bool | Put item from playerboard to grid |
| `execute_arrange` | `func execute_arrange(player: Node, from_slot: int, to_slot: int) -> bool` | bool | Rearrange playerboard slots |
| `consume_action_points` | `func consume_action_points(points: int) -> void` | void | Deduct action points |
| `can_afford_action` | `func can_afford_action() -> bool` | bool | Check AP > 0 |
| `after_action_completed` | `func after_action_completed() -> void` | void | Post-action cleanup: check win, cycle goals |
| `highlight_cells_if_authorized` | `func highlight_cells_if_authorized(cells: Array, item_id: int) -> void` | void | Show valid target cells |
| `highlight_empty_adjacent_cells` | `func highlight_empty_adjacent_cells() -> void` | void | Show empty adjacent cells for put |
| `highlight_occupied_playerboard_slots` | `func highlight_occupied_playerboard_slots() -> void` | void | Show occupied slots for grab |
| `highlight_random_valid_cells` | `func highlight_random_valid_cells() -> void` | void | Show random valid cells |
| `clear_highlights` | `func clear_highlights() -> void` | void | Remove all cell highlights |
| `clear_playerboard_highlights` | `func clear_playerboard_highlights() -> void` | void | Remove playerboard highlights |
**Dependencies:** PlayerboardManager, PlayerInputManager, GoalsCycleManager.
**Depended by:** player.gd, main.gd.
[Back to top](#top)
### 5.4 PlayerboardManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/playerboard_manager.gd` (22,790 chars)
**Extends:** Node
**Autoload name:** PlayerboardManager
Manages each player's inventory board (2x5 or 3x5 grid of item slots). Handles slot selection, item placement, auto-arrange for goal matching, drag-and-drop, and visual updates.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `slot_selected` | slot_index: int | Slot clicked/selected |
| `slot_deselected` | none | Selection cleared |
| `item_placed` | slot_index: int, item_id: int | Item added to slot |
| `item_removed` | slot_index: int | Item removed from slot |
| `playerboard_updated` | player_id: int, board: Array | Full board synced |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `grab_item` | `func grab_item(grid_pos: Vector2i) -> bool` | bool | Auto-place grabbed item into best-fit slot |
| `auto_put_item` | `func auto_put_item() -> bool` | bool | Put goal-matching tile from board to adjacent grid |
| `handle_slot_clicked` | `func handle_slot_clicked(slot_index: int) -> void` | void | Process slot click event |
| `handle_playerboard_slot_selected` | `func handle_playerboard_slot_selected(slot_index: int) -> void` | void | Handle slot selection for action |
| `handle_put_slot_selected` | `func handle_put_slot_selected(slot_index: int) -> void` | void | Handle slot chosen for put action |
| `arrange_playerboard_item` | `func arrange_playerboard_item(slot_index: int) -> void` | void | Move item to better slot |
| `select_playerboard_slot` | `func select_playerboard_slot(slot_index: int) -> void` | void | Mark slot as selected |
| `deselect_playerboard_slot` | `func deselect_playerboard_slot() -> void` | void | Clear slot selection |
| `target_playerboard_slot` | `func target_playerboard_slot(slot_index: int) -> void` | void | Target a slot for move |
| `untarget_playerboard_slot` | `func untarget_playerboard_slot() -> void` | void | Clear target |
| `can_move_to_target_playerboard_slot` | `func can_move_to_target_playerboard_slot() -> bool` | bool | Check if target slot is valid |
| `bot_grab_item` | `func bot_grab_item(pos: Vector2i, slot: int, x: int, y: int, z: int) -> void` | void | Bot performs grab |
| `bot_put_item` | `func bot_put_item(pos: Vector2i, slot: int, x: int, y: int, z: int) -> void` | void | Bot performs put |
| `bot_arrange_item` | `func bot_arrange_item(from_slot: int, to_slot: int) -> void` | void | Bot rearranges board |
**Dependencies:** GoalsCycleManager, GoalManager, EnhancedGridMap (scene ref).
**Depended by:** player.gd, PlayerActionManager.
[Back to top](#top)
### 5.5 PowerupManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/powerup_manager.gd` (9,417 chars)
**Extends:** Node
**Autoload name:** PowerupManager
Powerup/boost system. Tracks boost charge level, special ability availability, and consumes boost for charged actions.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `boost_changed` | amount: float | Boost level changed |
| `boost_full` | none | Boost reached 100% |
| `powerup_activated` | type: String | Powerup used |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `add_boost` | `func add_boost(amount: float) -> void` | void | Increment boost |
| `consume_boost` | `func consume_boost(amount: float) -> void` | void | Deduct boost |
| `can_use_special` | `func can_use_special() -> bool` | bool | Boost >= 100 |
| `get_boost_pct` | `func get_boost_pct() -> float` | float | 0.0 to 1.0 |
| `reset_boost` | `func reset_boost() -> void` | void | Set to 0 |
**Dependencies:** None.
**Depended by:** player.gd (charged strike, knock), PlayerActionManager.
[Back to top](#top)
## 6. Game Mode Managers
[Back to top](#top)
### 6.1 StopNGoManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/stop_n_go_manager.gd` (21,884 chars)
**Extends:** Node
**Autoload name:** StopNGoManager
State machine for the Stop n Go game mode. Alternates between GO (movement allowed) and STOP (frozen) phases. Tracks winner via first player to complete required goals during GO phases.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `phase_changed` | phase: String ("go"/"stop") | GO/STOP transition |
| `countdown_tick` | seconds: int | Phase countdown tick |
| `sng_winner` | player_id: int | Winner determined |
| `sng_ended` | none | Minigame concluded |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_sng` | `func start_sng(go_duration: int, stop_duration: int, required_goals: int) -> void` | void | Initialize SNG with params |
| `stop_sng` | `func stop_sng() -> void` | void | End SNG minigame |
| `start_go_phase` | `func start_go_phase() -> void` | void | Begin GO timer |
| `start_stop_phase` | `func start_stop_phase() -> void` | void | Begin STOP timer, freeze all |
| `freeze_player` | `func freeze_player(player_id: int) -> void` | void | Stop player movement |
| `unfreeze_player` | `func unfreeze_player(player_id: int) -> void` | void | Resume player movement |
| `check_winner` | `func check_winner() -> int` | int | Returns winner peer_id or -1 |
| `get_phase` | `func get_phase() -> String` | String | Current phase |
**Dependencies:** TurnManager, GoalManager, GoalsCycleManager, animation.gd (scene).
**Depended by:** main.gd, player.gd.
[Back to top](#top)
### 6.2 GauntletManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/gauntlet_manager.gd` (5,467 chars)
**Extends:** Node
**Autoload name:** GauntletManager
Manages the Candy Pump Survival / Gauntlet game mode. Handles round progression, danger zone growth (flood fill), and elimination.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `round_started` | round: int | New round began |
| `danger_zone_grown` | cells: Array | New tiles flooded |
| `player_eliminated` | player_id: int | Player fell off/eliminated |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_gauntlet` | `func start_gauntlet(duration: int, growth_interval: float) -> void` | void | Initialize gauntlet mode |
| `stop_gauntlet` | `func stop_gauntlet() -> void` | void | End gauntlet mode |
| `eliminate_player` | `func eliminate_player(player_id: int) -> void` | void | Mark player as eliminated |
| `get_alive_players` | `func get_alive_players() -> Array` | Array | Returns non-eliminated player IDs |
| `get_round` | `func get_round() -> int` | int | Current round number |
**Dependencies:** TurnManager, EnhancedGridMap.
**Depended by:** main.gd.
[Back to top](#top)
### 6.3 PortalModeManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/portal_mode_manager.gd` (20,072 chars)
**Extends:** Node
**Autoload name:** PortalModeManager
Manages portal race mode (Tekton Doors variant). Tracks portal positions, door swapping, refresh cycles, and race completion.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `portals_swapped` | portal_pairs: Array | Doors swapped positions |
| `portals_refreshed` | portals: Array | New portal set spawned |
| `player_teleported` | player_id: int, from: Vector2i, to: Vector2i | Player used portal |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_portal_mode` | `func start_portal_mode(swap_time: int, refresh_time: int) -> void` | void | Initialize portal mode |
| `stop_portal_mode` | `func stop_portal_mode() -> void` | void | End portal mode |
| `teleport_player` | `func teleport_player(player: Node, portal_enter: Vector2i) -> void` | void | Teleport player through portal pair |
| `swap_portals` | `func swap_portals() -> void` | void | Randomize portal positions |
| `refresh_portals` | `func refresh_portals() -> void` | void | Spawn new portal set |
| `get_portal_pair` | `func get_portal_pair(portal_id: int) -> Array` | Array | Returns [entry, exit] positions |
**Dependencies:** SpecialTilesManager, EnhancedGridMap.
**Depended by:** main.gd.
[Back to top](#top)
### 6.4 GoalManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/goal_manager.gd` (3,857 chars)
**Extends:** Node
**Autoload name:** GoalManager
Goal definitions, validation rules, and completion detection. Checks if a player's board arrangement matches the current goal pattern.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `goal_completed` | player_id: int, goal_id: int | Player completed a goal |
| `goal_failed` | player_id: int, reason: String | Goal became impossible |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `validate_goal` | `func validate_goal(player_board: Array, goal: Dictionary) -> bool` | bool | Check board matches goal pattern |
| `get_goal_type` | `func get_goal_type(goal: Dictionary) -> String` | String | Goal category (row, col, set, pattern) |
| `is_goal_possible` | `func is_goal_possible(player_board: Array, goal: Dictionary) -> bool` | bool | Whether goal is still achievable |
| `find_best_slot_for_item` | `func find_best_slot_for_item(board: Array, item: int, goal: Dictionary) -> int` | int | Auto-place item into best slot |
**Dependencies:** None.
**Depended by:** GoalsCycleManager, PlayerboardManager, StopNGoManager.
[Back to top](#top)
### 6.5 GoalsCycleManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/goals_cycle_manager.gd` (20,175 chars)
**Extends:** Node
**Autoload name:** GoalsCycleManager
Manages cycling goal rotation. Tracks per-player score, cycles active goals on timer or action trigger, and determines when a player reaches the goal threshold to win.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `goals_cycled` | new_goals: Array | Active goals changed |
| `player_scored` | player_id: int, points: int | Player earned points |
| `player_won` | player_id: int | Player reached win threshold |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_cycle` | `func start_cycle(timer_enabled: bool) -> void` | void | Begin goal cycling |
| `stop_cycle` | `func stop_cycle() -> void` | void | Stop cycling |
| `cycle_goals` | `func cycle_goals() -> void` | void | Generate new goal set |
| `add_score` | `func add_score(player_id: int, points: int) -> void` | void | Award points to player |
| `get_player_score` | `func get_player_score(player_id: int) -> int` | int | Get player's current score |
| `get_current_goals` | `func get_current_goals() -> Array` | Array | Get active goals |
| `set_goal_threshold` | `func set_goal_threshold(goals_needed: int) -> void` | void | Set goals to win |
**Dependencies:** GoalManager, TurnManager, Timer (scene).
**Depended by:** main.gd, PlayerActionManager, StopNGoManager.
[Back to top](#top)
### 6.6 PlayerRaceManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/player_race_manager.gd` (4,757 chars)
**Extends:** Node
**Autoload name:** PlayerRaceManager
Race-specific logic. Tracks player race position, finish locations, lap progression, and race completion.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `position_changed` | player_id: int, pos: int | Player moved in race order |
| `lap_completed` | player_id: int, lap: int | Player finished a lap |
| `race_completed` | results: Array | Final standings [{id, position}] |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_race` | `func start_race() -> void` | void | Initialize race state |
| `end_race` | `func end_race() -> void` | void | Finalize race |
| `on_race_completed` | `func on_race_completed(final_pos: int) -> void` | void | Player crossed finish line |
| `get_current_finish_locations` | `func get_current_finish_locations() -> Array` | Array | Active finish positions |
| `update_finish_availability` | `func update_finish_availability() -> void` | void | Recalculate finish positions |
| `get_player_position` | `func get_player_position(player_id: int) -> int` | int | Current race order index |
| `add_second_lap_goals` | `func add_second_lap_goals(goals: Array) -> void` | void | Set lap 2 goals |
**Dependencies:** GoalsCycleManager.
**Depended by:** player.gd, main.gd.
[Back to top](#top)
### 6.7 TurnManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/turn_manager.gd` (849 chars)
**Extends:** Node
**Autoload name:** TurnManager
Turn-based sequencing for game modes that use round-robin or ordered turns (e.g., Stop n Go, Tekton Doors).
**Properties:**
| Name | Type | Description |
|---|---|---|
| current_turn | int | Index in turn order |
| turn_order | Array | Player peer IDs in sequence |
| is_my_turn | bool | Whether local player is active |
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `turn_changed` | player_id: int | Active turn changed |
| `turn_order_set` | order: Array | Turn order established |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `set_turn_order` | `func set_turn_order(order: Array) -> void` | void | Establish turn sequence |
| `next_turn` | `func next_turn() -> void` | void | Advance to next player |
| `get_current_player` | `func get_current_player() -> int` | int | Current player peer ID |
**Dependencies:** None.
**Depended by:** StopNGoManager, GauntletManager, GoalsCycleManager.
[Back to top](#top)
## 7. Gameplay Managers
[Back to top](#top)
### 7.1 ObstacleManager
[Back to top](#up)
**File:** `/home/dev/tekton/scripts/managers/obstacle_manager.gd` (5,662 chars)
**Extends:** Node
**Autoload name:** ObstacleManager
Obstacle placement and removal on the game grid. Handles wall tiles, blocking tiles, and destructible barriers.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `obstacle_placed` | cell: Vector3i, item_id: int | New obstacle added |
| `obstacle_removed` | cell: Vector3i | Obstacle destroyed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `place_obstacle` | `func place_obstacle(cell: Vector3i, item_id: int) -> void` | void | Set obstacle on grid layer |
| `remove_obstacle` | `func remove_obstacle(cell: Vector3i) -> void` | void | Clear obstacle |
| `is_cell_blocked` | `func is_cell_blocked(cell: Vector3i, gridmap: Node) -> bool` | bool | Check if cell has blocking tile |
| `get_blocked_cells` | `func get_blocked_cells(gridmap: Node) -> Array` | Array | All blocked cells |
| `clear_all_obstacles` | `func clear_all_obstacles() -> void` | void | Remove all obstacles |
**Dependencies:** EnhancedGridMap (scene ref).
**Depended by:** PlayerMovementManager, SpecialTilesManager.
[Back to top](#top)
### 7.2 SpecialTilesManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/special_tiles_manager.gd` (23,090 chars)
**Extends:** Node
**Autoload name:** SpecialTilesManager
Manages special floor tiles: ice (slippery), crack (breakable), portal tiles, teleporters, and other interactive terrain.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `tile_activated` | pos: Vector2i, tile_type: String | Tile effect triggered |
| `ice_slide_started` | player_id: int | Player started sliding |
| `crack_broke` | pos: Vector2i | Crack tile collapsed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `apply_tile_effect` | `func apply_tile_effect(player: Node, pos: Vector2i) -> void` | void | Activate tile effect on player |
| `get_tile_at` | `func get_tile_at(pos: Vector2i, gridmap: Node) -> int` | int | Item ID at position |
| `set_tile` | `func set_tile(pos: Vector2i, item_id: int, gridmap: Node) -> void` | void | Set tile item |
| `is_ice_tile` | `func is_ice_tile(item_id: int) -> bool` | bool | Check ice type |
| `is_crack_tile` | `func is_crack_tile(item_id: int) -> bool` | bool | Check crack type |
| `is_portal_tile` | `func is_portal_tile(item_id: int) -> bool` | bool | Check portal type |
| `spawn_portal_pair` | `func spawn_portal_pair(pos_a: Vector2i, pos_b: Vector2i) -> void` | void | Create portal entry/exit |
| `remove_portal_pair` | `func remove_portal_pair(pos_a: Vector2i, pos_b: Vector2i) -> void` | void | Remove portal tiles |
**Dependencies:** EnhancedGridMap, ObstacleManager, PortalModeManager.
**Depended by:** PlayerMovementManager.
[Back to top](#top)
### 7.3 StaticTektonManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/static_tekton_manager.gd` (7,416 chars)
**Extends:** Node
**Autoload name:** StaticTektonManager
Manages stationary Tekton turret behavior. Handles targeting, projectile spawning, and stun zones.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `turret_fired` | turret_id: int, target_pos: Vector2i | Turret shot |
| `turret_stunned` | turret_id: int | Turret disabled |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `activate_turret` | `func activate_turret(turret: Node) -> void` | void | Start turret behavior |
| `deactivate_turret` | `func deactivate_turret(turret: Node) -> void` | void | Stop turret |
| `fire_at_player` | `func fire_at_player(turret: Node, target: Vector2i) -> void` | void | Fire projectile at grid pos |
**Dependencies:** EnhancedGridMap, ObstacleManager.
**Depended by:** main.gd.
[Back to top](#top)
## 8. UI / Presentation Managers
[Back to top](#top)
### 8.1 UIManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/ui_manager.gd` (21,645 chars)
**Extends:** Node
**Autoload name:** UIManager
Manages the UI layer stack: show/hide panels, overlay management, HUD elements, and dynamic UI creation.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `panel_opened` | panel_name: String | Panel shown |
| `panel_closed` | panel_name: String | Panel hidden |
| `hud_updated` | data: Dictionary | HUD refresh |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `show_panel` | `func show_panel(panel_name: String, data: Dictionary = {}) -> void` | void | Show named panel |
| `hide_panel` | `func hide_panel(panel_name: String) -> void` | void | Hide named panel |
| `toggle_panel` | `func toggle_panel(panel_name: String) -> void` | void | Toggle panel visibility |
| `show_hud` | `func show_hud() -> void` | void | Display HUD |
| `hide_hud` | `func hide_hud() -> void` | void | Hide HUD |
| `create_dynamic_ui` | `func create_dynamic_ui(scene_path: String) -> Node` | Node | Instantiate UI from tscn |
| `destroy_dynamic_ui` | `func destroy_dynamic_ui(ui_node: Node) -> void` | void | Remove dynamic UI |
| `focus_panel` | `func focus_panel(panel_name: String) -> void` | void | Bring panel to front |
| `get_active_panels` | `func get_active_panels() -> Array` | Array | Currently visible panels |
**Dependencies:** None.
**Depended by:** main.gd, lobby.gd.
[Back to top](#top)
### 8.2 SfxManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/sfx_manager.gd` (2,046 chars)
**Extends:** Node
**Autoload name:** SfxManager
Sound effect playback pool. Manages one-shot SFX with positional audio support.
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `play` | `func play(sfx_name: String, position: Vector3 = Vector3.ZERO) -> void` | void | Play SFX by name, optionally 3D positioned |
| `stop` | `func stop(sfx_name: String) -> void` | void | Stop specific SFX |
| `stop_all` | `func stop_all() -> void` | void | Silence all SFX |
| `set_volume` | `func set_volume(db: float) -> void` | void | Set master SFX volume |
**Dependencies:** AudioStreamPlayer pool (scene).
**Depended by:** player.gd, StopNGoManager, UIManager, many gameplay managers.
[Back to top](#top)
### 8.3 MusicManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/music_manager.gd` (4,082 chars)
**Extends:** Node
**Autoload name:** MusicManager
Background music controller. Handles crossfade between tracks, playlist sequencing, and volume control.
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_music` | `func start_music(track_name: String = "") -> void` | void | Begin playing track or playlist |
| `stop_music` | `func stop_music(fade: float = 0.5) -> void` | void | Fade out and stop |
| `crossfade_to` | `func crossfade_to(track_name: String, fade_duration: float = 1.0) -> void` | void | Smooth transition |
| `set_volume` | `func set_volume(db: float) -> void` | void | Set master music volume |
| `set_paused` | `func set_paused(paused: bool) -> void` | void | Pause/resume |
| `get_current_track` | `func get_current_track() -> String` | String | Currently playing track name |
**Dependencies:** AudioStreamPlayer (scene).
**Depended by:** lobby.gd, main.gd.
[Back to top](#top)
### 8.4 NotificationManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/notification_manager.gd` (2,215 chars)
**Extends:** Node
**Autoload name:** NotificationManager
On-screen message queue. Displays transient notification messages with type-based styling.
**Properties:**
| Name | Type | Description |
|---|---|---|
| MessageType (enum) | {NORMAL, WARNING, POWERUP, ERROR, SYSTEM} | Message severity/style |
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `send_message` | `func send_message(sender: Node, message: String, msg_type: int = 0) -> void` | void | Queue message for display |
| `clear_messages` | `func clear_messages() -> void` | void | Clear all pending messages |
| `get_message_queue` | `func get_message_queue() -> Array` | Array | Current pending messages |
**Dependencies:** None.
**Depended by:** player.gd, main.gd (unstuck feedback), StopNGoManager, many gameplay managers.
[Back to top](#top)
### 8.5 ScreenShake
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/screen_shake.gd` (1,839 chars)
**Extends:** Node
**Autoload name:** ScreenShake
Camera screen shake effect manager. Applies noise-based displacement to Camera3D.
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `shake` | `func shake(intensity: float, duration: float = 0.3) -> void` | void | Trigger camera shake |
| `stop_shake` | `func stop_shake() -> void` | void | Stop ongoing shake |
**Dependencies:** Camera3D (scene).
**Depended by:** player.gd (heavy knock triggers shake), main.gd.
[Back to top](#top)
### 8.6 CameraContextManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/camera_context_manager.gd` (2,543 chars)
**Extends:** Node
**Autoload name:** CameraContextManager
Camera zoom level and context switching. Manages follow-camera behavior, zoom levels for different game phases, and camera transitions.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `zoom_changed` | level: float | Camera zoom level changed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `set_zoom` | `func set_zoom(level: float) -> void` | void | Set camera zoom |
| `get_zoom` | `func get_zoom() -> float` | float | Current zoom |
| `focus_on_player` | `func focus_on_player(player_id: int) -> void` | void | Snap camera to player |
| `focus_on_position` | `func focus_on_position(world_pos: Vector3) -> void` | void | Center camera on position |
**Dependencies:** Camera3D (scene).
**Depended by:** main.gd.
[Back to top](#top)
### 8.7 TouchControls
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/touch_controls.gd` (23,640 chars)
**Extends:** Node
**Autoload name:** TouchControls
Mobile touch input overlay. Provides virtual joystick, action buttons, and gesture recognition for grid-based controls.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `touch_moved` | direction: Vector2i | Grid direction from swipe |
| `action_triggered` | action: String | Touch button pressed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `set_joystick_enabled` | `func set_joystick_enabled(enabled: bool) -> void` | void | Toggle joystick |
| `get_joystick_direction` | `func get_joystick_direction() -> Vector2` | Vector2 | Normalized joystick |
| `_save_settings` | internal | void | Persist touch control settings |
**Dependencies:** InputManager (scene).
**Depended by:** PlayerInputManager.
[Back to top](#top)
### 8.8 TutorialManager / TutorialOverlay
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/tutorial_manager.gd` (22,243 chars)
**Extends:** Node
**Autoload name:** TutorialManager
Tutorial flow controller. Manages step-by-step tutorial sequences, triggers, and completion tracking.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `tutorial_started` | tutorial_id: String | Tutorial began |
| `step_completed` | step: int | Step finished |
| `tutorial_completed` | tutorial_id: String | Tutorial fully complete |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_tutorial` | `func start_tutorial(tutorial_id: String) -> void` | void | Begin tutorial sequence |
| `advance_step` | `func advance_step() -> void` | void | Move to next step |
| `skip_tutorial` | `func skip_tutorial() -> void` | void | Exit tutorial early |
| `is_tutorial_active` | `func is_tutorial_active() -> bool` | bool | Tutorial in progress |
| `get_current_step` | `func get_current_step() -> int` | int | Current step index |
| `get_total_steps` | `func get_total_steps() -> int` | int | Total steps in tutorial |
**File:** `/home/dev/tekton/scripts/managers/tutorial_overlay.gd` (11,077 chars)
**Extends:** Node
**Autoload name:** TutorialOverlay
Tutorial UI overlay. Displays step instructions, highlights UI elements, and provides step navigation.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `overlay_closed` | none | Overlay dismissed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `show_step` | `func show_step(step_data: Dictionary) -> void` | void | Display step with text + highlight |
| `hide_overlay` | `func hide_overlay() -> void` | void | Dismiss overlay |
| `highlight_element` | `func highlight_element(node_path: NodePath) -> void` | void | Spotlight a UI element |
| `clear_highlights` | `func clear_highlights() -> void` | void | Remove spotlights |
**Dependencies:** TutorialManager, UIManager.
**Depended by:** TutorialManager.
[Back to top](#top)
## 9. Social / Economy Managers
[Back to top](#top)
### 9.1 UserProfileManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/user_profile_manager.gd` (20,044 chars)
**Extends:** Node
**Autoload name:** UserProfileManager
User profile CRUD operations. Manages display name, avatar, bio, wallet balance, stats, and loadout configuration. Syncs with Nakama storage.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `profile_loaded` | profile: Dictionary | Profile fetched from server |
| `profile_updated` | none | Profile modified locally |
| `wallet_updated` | wallet: Dictionary | Balance changed |
| `stats_updated` | stats: Dictionary | Player stats changed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `load_profile` | `func load_profile() -> void` | void (async) | Fetch profile from Nakama storage |
| `save_profile` | `func save_profile() -> void` | void (async) | Persist profile to Nakama |
| `get_display_name` | `func get_display_name(fallback: String = "Player") -> String` | String | Display name with fallback |
| `set_display_name` | `func set_display_name(name: String) -> void` | void | Update display name |
| `get_avatar_url` | `func get_avatar_url() -> String` | String | Current avatar path |
| `set_avatar` | `func set_avatar(url: String) -> void` | void | Change avatar |
| `get_wallet_balance` | `func get_wallet_balance(currency: String) -> int` | int | Balance for gold/star |
| `get_stats` | `func get_stats() -> Dictionary` | Dictionary | Player stats snapshot |
| `update_stats` | `func update_stats(delta: Dictionary) -> void` | void | Increment stats |
| `get_loadout` | `func get_loadout() -> Dictionary` | Dictionary | Current cosmetics loadout |
| `set_loadout` | `func set_loadout(loadout: Dictionary) -> void` | void | Save cosmetics config |
| `get_loadout_character` | `func get_loadout_character() -> String` | String | Selected character name |
| `sync_wallet` | `func sync_wallet() -> void` | void (async) | Refresh wallet from server |
**Dependencies:** NakamaManager, EventBus, BackendService.
**Depended by:** LobbyMainMenu, lobby.gd, ShopManager, GachaManager, SkinManager, many UI panels.
[Back to top](#top)
### 9.2 GachaManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/gacha_manager.gd` (5,117 chars)
**Extends:** Node
**Autoload name:** GachaManager
Gacha pull orchestration. Calls BackendService.perform_gacha_pull, processes results, updates inventory.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `gacha_result` | items: Array, fragments: Array | Pull results |
| `gacha_error` | error: String | Pull failed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `perform_pull` | `func perform_pull(gacha_id: String, count: int) -> void` | void (async) | Execute gacha pull RPC |
| `get_pity_count` | `func get_pity_count(banner_id: String) -> int` | int | Current pity counter |
**Dependencies:** BackendService, UserProfileManager, EventBus.
**Depended by:** gacha_panel.tscn (scene UI).
[Back to top](#top)
### 9.3 SkinManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/skin_manager.gd` (13,909 chars)
**Extends:** Node
**Autoload name:** SkinManager
Cosmetic skin system. Manages skin definitions, owned skins, equipped loadout, and applies cosmetics to character models.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `skin_equipped` | skin_id: String | Skin applied |
| `skin_unequipped` | skin_id: String | Skin removed |
| `inventory_updated` | owned_skins: Array | Inventory changed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `equip_skin` | `func equip_skin(skin_id: String, slot: String) -> void` | void | Equip skin to slot |
| `unequip_skin` | `func unequip_skin(slot: String) -> void` | void | Unequip from slot |
| `is_skin_owned` | `func is_skin_owned(skin_id: String) -> bool` | bool | Check ownership |
| `get_equipped_skins` | `func get_equipped_skins() -> Dictionary` | Dictionary | Current loadout |
| `apply_loadout` | `func apply_loadout(character_root: Node3D, loadout: Dictionary) -> void` | void | Apply cosmetics to 3D model |
| `get_skins_for_character` | `func get_skins_for_character(char_name: String) -> Array` | Array | Available skins |
| `get_all_skins` | `func get_all_skins() -> Array` | Array | All skin definitions |
**Dependencies:** UserProfileManager.
**Depended by:** lobby.gd (3D preview), SkinShop UI.
[Back to top](#top)
### 9.4 ShopManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/shop_manager.gd` (484 chars)
**Extends:** Node
**Autoload name:** ShopManager
Thin data layer for shop catalog. Currently a stub; full shop logic lives in scene scripts.
**Properties:** Minimal (shop catalog array).
**Signals:** None.
**Public Functions:** None (data container only).
**Dependencies:** BackendService.
**Depended by:** shop_panel.tscn (scene).
[Back to top](#top)
### 9.5 JoinManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/join_manager.gd` (484 chars)
**Extends:** Node
**Autoload name:** JoinManager
Thin manager for join code input and validation. Minimal stub.
**Properties:** Minimal.
**Signals:** None.
**Public Functions:** None (stub).
**Dependencies:** None.
**Depended by:** lobby.gd (join code UI).
[Back to top](#top)
### 9.6 FriendManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/friend_manager.gd` (11,911 chars)
**Extends:** Node
**Autoload name:** FriendManager
Friends list management. Handles friend requests, accept/reject, friend list sync, DM messaging, and lobby invitations.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `friend_list_updated` | friends: Array | Friend list refreshed |
| `friend_request_received` | from_user_id: String | Incoming request |
| `friend_added` | user_id: String | Friendship established |
| `friend_removed` | user_id: String | Friendship ended |
| `dm_message_received` | from_user_id: String, from_name: String, message: String | Direct message |
| `lobby_invite_received` | from_user_id: String, from_name: String, match_id: String | Lobby invitation |
| `friend_online_changed` | user_id: String, online: bool | Presence changed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `add_friend_by_id` | `func add_friend_by_id(nakama_id: String) -> bool` | bool (async) | Send friend request |
| `remove_friend` | `func remove_friend(user_id: String) -> void` | void (async) | Remove friendship |
| `accept_request` | `func accept_request(user_id: String) -> void` | void (async) | Accept friend request |
| `decline_request` | `func decline_request(user_id: String) -> void` | void (async) | Decline request |
| `get_friends` | `func get_friends() -> Array` | Array | Current friends list |
| `get_mutual_friends` | `func get_mutual_friends() -> Array` | Array | Friends also in room |
| `is_friend` | `func is_friend(nakama_id: String) -> bool` | bool | Check friendship |
| `send_dm` | `func send_dm(user_id: String, text: String) -> bool` | bool (async) | Send direct message |
| `get_dm_history` | `func get_dm_history(user_id: String) -> Array` | Array (async) | Fetch DM history |
| `send_lobby_invite` | `func send_lobby_invite(to_user_id: String, match_id: String) -> void` | void (async) | Send invitation |
**Dependencies:** BackendService, NakamaManager.
**Depended by:** LobbyRoom, LobbyChat, lobby.gd.
[Back to top](#top)
### 9.7 MailManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/mail_manager.gd` (5,271 chars)
**Extends:** Node
**Autoload name:** MailManager
Mail/inbox CRUD operations. Calls BackendService RPCs for get, claim, delete mail.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `mail_updated` | mails: Array | Mail list refreshed |
| `unread_count_changed` | count: int | Unread mail count |
| `mail_claimed` | mail_id: String, rewards: Dictionary | Reward collected |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `fetch_mail` | `func fetch_mail() -> void` | void (async) | Fetch mailbox |
| `claim_mail` | `func claim_mail(mail_id: String) -> void` | void (async) | Claim reward |
| `delete_mail` | `func delete_mail(mail_id: String) -> void` | void (async) | Delete mail |
| `get_unread_count` | `func get_unread_count() -> int` | int | Unread count |
**Dependencies:** BackendService.
**Depended by:** lobby.gd, mailbox_panel.tscn (scene).
[Back to top](#top)
### 9.8 DailyRewardManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/daily_reward_manager.gd` (1,009 chars)
**Extends:** Node
**Autoload name:** DailyRewardManager
Daily reward system. Handles claim state, reward config, and streak tracking.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `reward_claimed` | day: int, reward: Dictionary | Daily reward collected |
| `streak_updated` | streak: int | Consecutive days |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `claim_daily_reward` | `func claim_daily_reward() -> void` | void (async) | Claim today's reward |
| `get_reward_state` | `func get_reward_state() -> Dictionary` | Dictionary (async) | Current state + schedule |
| `can_claim_today` | `func can_claim_today() -> bool` | bool | Check if claimable |
**Dependencies:** BackendService.
**Depended by:** daily_reward_panel.tscn (scene).
[Back to top](#top)
### 9.9 AdminManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/admin_manager.gd` (2,538 chars)
**Extends:** Node
**Autoload name:** AdminManager
Admin panel state and permission checks. Determines if local player is admin or moderator.
**Signals:** None.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_check_admin_status` | `func _check_admin_status() -> bool` | bool (async) | Verify admin via Nakama storage |
| `kick_player` | `func kick_player(player_id: int) -> void` | void (async) | Kick player from match |
| `ban_player` | `func ban_player(player_id: int) -> void` | void (async) | Ban player |
| `give_currency` | `func give_currency(gold: int, star: int) -> void` | void (async) | Admin give currency |
**Dependencies:** BackendService, NakamaManager.
**Depended by:** admin_panel.tscn (scene), LobbyChat (/clear command).
[Back to top](#top)
## 10. System Managers
[Back to top](#top)
### 10.1 SettingsManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/settings_manager.gd` (13,874 chars)
**Extends:** Node
**Autoload name:** SettingsManager
User settings persistence. Reads/writes config to user://settings.cfg. Manages audio, video, gameplay, and control settings.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `setting_changed` | key: String, value: Variant | A setting was modified |
| `settings_reset` | none | All settings restored to defaults |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `get_setting` | `func get_setting(key: String, default: Variant = null) -> Variant` | Variant | Read setting value |
| `set_setting` | `func set_setting(key: String, value: Variant) -> void` | void | Write and persist setting |
| `reset_settings` | `func reset_settings() -> void` | void | Restore defaults |
| `load_settings` | `func load_settings() -> void` | void | Load from config file |
| `save_settings` | `func save_settings() -> void` | void | Write to config file |
| `get_all_settings` | `func get_all_settings() -> Dictionary` | Dictionary | Full settings snapshot |
**Dependencies:** ConfigFile.
**Depended by:** Audio buses, video settings, gameplay UI, settings_menu.tscn.
[Back to top](#top)
### 10.2 SessionManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/session_manager.gd` (4,742 chars)
**Extends:** Node
**Autoload name:** SessionManager
Nakama session refresh lifecycle. Monitors session expiry and auto-refreshes before expiration.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `session_refreshed` | none | Token refreshed |
| `session_expired` | none | Could not refresh |
| `session_warning` | seconds_remaining: int | About to expire |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `start_monitoring` | `func start_monitoring() -> void` | void | Begin session expiry timer |
| `stop_monitoring` | `func stop_monitoring() -> void` | void | Stop timer |
| `refresh_now` | `func refresh_now() -> void` | void (async) | Force refresh |
**Dependencies:** NakamaManager.
**Depended by:** AuthManager.
[Back to top](#top)
### 10.3 GameUpdateManager
[Back to top](#top)
**File:** `/home/dev/tekton/scripts/managers/game_update_manager.gd` (14,405 chars)
**Extends:** Node
**Autoload name:** GameUpdateManager
Hot-reload update system. Checks for patch.pck on the Gitea patches branch and downloads/loads it at runtime.
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `update_available` | version: String, changelog: String | New patch detected |
| `update_downloading` | progress: float | Download progress |
| `update_ready` | path: String | Patch downloaded and verified |
| `update_failed` | error: String | Download error |
| `up_to_date` | none | No update needed |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `check_for_updates` | `func check_for_updates() -> void` | void (async) | Query Gitea for latest patch |
| `download_update` | `func download_update() -> void` | void (async) | Download patch.pck |
| `apply_update` | `func apply_update() -> void` | void | Load patch from ProjectSettings |
| `get_current_version` | `func get_current_version() -> String` | String | Current client version |
| `get_available_version` | `func get_available_version() -> String` | String | Latest available version |
**Dependencies:** HTTPRequest (scene).
**Depended by:** boot_screen.tscn, main.gd.
[Back to top](#top)
## 11. Core Scene Scripts
[Back to top](#top)
### 11.1 main.gd (Main game scene controller)
[Back to top](#top)
**File:** `/home/dev/tekton/scenes/main.gd` (2956 lines)
**Extends:** Node
**Scene:** main.tscn
The core game scene controller. Handles game initialization, player spawn, grid setup, goal cycle start, leaderboard display, pause menu, unstuck system, match cleanup, and result screen flow.
**Key Properties:**
| Name | Type | Description |
|---|---|---|
| enhanced_gridmap | Node | Reference to EnhancedGridMap child |
| player_scene | PackedScene | Player.tscn loaded |
| stop_n_go_winner_id | int | Winner's peer ID (-1 if none) |
| _unstuck_cooldown_remaining | float | Unstuck button cooldown |
| touch_controls | Node | TouchControls autoload ref |
**Signals:**
- (none declared; uses method-based event routing)
**Public Functions (selected key ones):**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Initializes ENet multiplayer, spawns players, starts goals cycle, sets up HUD |
| `_process` | `func _process(delta: float) -> void` | void | Unstuck cooldown tick |
| `_input` | `func _input(event: InputEvent) -> void` | void | ESC pause, F9 debug floor check |
| `initialize_game` | -- | void | Create EnhancedGridMap, spawn player scene instances |
| `spawn_player` | -- | Node | Instantiate player.tscn, position, set authority |
| `_spawn_local_player` | -- | void | Create local player node |
| `add_bot_players_if_needed` | -- | void | Fill remaining slots with bot players |
| `display_message` | `func display_message(message: String, type: int) -> void` | void (RPC) | Broadcast message to local player's UI |
| `request_leaderboard_sync` | `func request_leaderboard_sync() -> void` | void (RPC) | Client requests leaderboard from server |
| `sync_leaderboard_data` | `func sync_leaderboard_data(player_data: Array) -> void` | void (RPC authority) | Receive + render leaderboard |
| `_update_leaderboard_display` | internal | void | Local leaderboard refresh |
| `_render_leaderboard_entries` | internal | void | Populate leaderboard entries |
| `_get_ordinal` | `func _get_ordinal(n: int) -> String` | String | "1st", "2nd", "3rd", etc. |
| `can_rpc` | `func can_rpc() -> bool` | bool | Check multiplayer peer state |
| `check_multiplayer` | `func check_multiplayer() -> bool` | bool | Safety check for peer access |
| `_toggle_pause_menu` | internal | void | Show/hide pause overlay |
| `_on_resume_pressed` | -- | void | Close pause menu |
| `_on_how_to_play_pressed` | -- | void | Open help panel |
| `_on_settings_pressed` | -- | void | Open settings dynamically |
| `_on_quit_match_pressed` | -- | void | Leave match, return to lobby |
| `_on_unstuck_pressed` | -- | void | Teleport local player to safe position |
| `_find_safe_spawn_position` | internal | Vector2i | Scan grid for safe walkable cell |
| `_on_back_to_menu_pressed` | -- | void | Cleanup and transition to lobby |
| `_cleanup_multiplayer` | -- | void | NakamaManager.cleanup() wrapper |
| `_deferred_init_leaderboard` | internal | void | Delayed leaderboard init (1.5s) |
| `_on_rematch_pressed` | -- | void | Request rematch vote |
| `check_all_floors` | `func check_all_floors() -> void` | void | Debug F9: scan missing floor tiles |
| `update_visual_position` | on player | void | Snap player to grid-aligned world position |
| `grid_to_world` | on player | Vector3 | Convert grid Vector2i to world Vector3 |
**RPCs (network-synced functions):**
| Function | RPC Mode | Description |
|---|---|---|
| `request_leaderboard_sync` | any_peer | Client requests data from server |
| `sync_leaderboard_data` | authority, call_local | Server sends leaderboard to client |
| `display_message` | authority, call_local | Broadcast message to player UI |
| `sync_position` (on player) | any_peer, call_local | Sync grid position |
| `sync_grid_item` (on player) | any_peer, call_local | Sync grid cell item |
| `sync_goals` (on player) | any_peer, call_local | Sync active goals |
| `sync_rotation` (on player) | any_peer, call_local | Sync character rotation |
| `sync_bump` (on player) | any_peer, call_local, unreliable | Visual bump animation |
| `sync_knock_tekton` (on player) | any_peer, call_local, reliable | Knock tekton |
| `sync_grab_tekton` (on player) | any_peer, call_local, reliable | Grab roaming tekton |
| `sync_throw_tekton` (on player) | any_peer, call_local, reliable | Throw tekton |
| `sync_drop_tekton` (on player) | any_peer, call_local, reliable | Drop tekton |
| `set_spawn_position` (on player) | any_peer, call_local, reliable | Random spawn position |
| `complete_race` (on player) | any_peer, call_local, reliable | Player finished race |
| `force_action_state_none` (on player) | any_peer, call_local, reliable | Reset UI action state |
| `request_server_grab` (on player) | any_peer, reliable | Server-authoritative grab |
| `request_server_put` (on player) | any_peer, reliable | Server-authoritative put |
| `notify_spawn_selected` (on player) | any_peer, reliable | Occupancy sync for spawn |
| `trigger_screen_shake` | -- | Camera shake RPC |
**Dependencies:** NakamaManager, LobbyManager, GameStateManager, PlayerMovementManager, PlayerActionManager, GoalsCycleManager, StopNGoManager, GauntletManager, PortalModeManager, PlayerRaceManager, PlayerboardManager, UIManager, SfxManager, MusicManager, NotificationManager, ScreenShake, CameraContextManager, TouchControls.
**Depended by:** (this is the root game scene, depends on everything).
[Back to top](#top)
### 11.2 player.gd
[Back to top](#top)
**File:** `/home/dev/tekton/scenes/player.gd` (2751 lines)
**Extends:** CharacterBody3D (assumed from Node3D methods)
**Scene:** player.tscn
The player character controller. Handles movement, action execution (grab/put/arrange), tekton interaction (carry/snatch/throw/knock), grid positioning, bot AI, visual synchronization, and playerboard management delegation.
**Key Properties:**
| Name | Type | Default | Description |
|---|---|---|---|
| current_position | Vector2i | Vector2i(0, 0) | Grid-aligned position |
| cell_size | Vector3 | (1.0, 1.0, 1.0) | Grid cell dimensions |
| cell_offset | Vector3 | Vector3.ZERO | Visual position offset |
| is_player_moving | bool | false | Movement tween active |
| is_carrying_tekton | bool | false | Holding roaming tekton |
| carried_tekton | Node3D | null | Reference to carried tekton |
| is_charged_strike | bool | false | Charged attack mode |
| is_frozen | bool | false | Stun/freeze state |
| is_stop_frozen | bool | false | Stop n Go freeze |
| is_invisible | bool | false | Ghost mode |
| is_bot | bool | false | Bot AI flag |
| display_name | String | "" | Player display name |
| score | int | 0 | Match score |
| action_points | int | 1 | Actions per turn |
| playerboard | Array | [-1, -1, ...] | Item slot board |
| goals | Array | [] | Active goals |
| enhanced_gridmap | Node | null | Grid reference |
| anim_player | AnimationPlayer | null | Character animations |
| movement_manager | PlayerMovementManager | ref | Movement delegation |
| action_manager | PlayerActionManager | ref | Action delegation |
| playerboard_manager | PlayerboardManager | ref | Board delegation |
| race_manager | PlayerRaceManager | ref | Race delegate |
| powerup_manager | PowerupManager | ref | Boost/charge delegate |
**Signals:**
| Signal | Params | Description |
|---|---|---|
| `position_changed` | none | Player grid position changed |
**Public Functions (selected key ones):**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Init references, connect signals, set initial position |
| `_physics_process` | `func _physics_process(delta: float) -> void` | void | Movement smoothing, carry timer, unstuck timer |
| `_input` | `func _input(event: InputEvent) -> void` | void | Click-to-move on gridmap, slot clicks |
| `grid_to_world` | `func grid_to_world(pos: Vector2i) -> Vector3` | Vector3 | Convert grid to world coordinates |
| `move_to_grid_position` | `func move_to_grid_position(target: Vector2i) -> void` | void | Initiate grid movement |
| `grab_item` | `func grab_item(grid_pos: Vector2i = current_position) -> bool` | bool | Delegates to playerboard_manager.grab_item |
| `auto_put_item` | `func auto_put_item() -> bool` | bool | Delegates auto-put |
| `handle_playerboard_slot_selected` | `func handle_playerboard_slot_selected(slot_index: int) -> void` | void | Delegates to playerboard_manager |
| `handle_put_slot_selected` | `func handle_put_slot_selected(slot_index: int) -> void` | void | Delegates put slot |
| `arrange_playerboard_item` | `func arrange_playerboard_item(slot_index: int) -> void` | void | Delegates arrange |
| `_on_slot_clicked` | `func _on_slot_clicked(event: InputEvent, slot_index: int) -> void` | void | Delegates to playerboard_manager |
| `has_item_at_current_position` | `func has_item_at_current_position() -> bool` | bool | Check grid cell occupancy |
| `has_items_in_playerboard` | `func has_items_in_playerboard() -> bool` | bool | Any items in board |
| `playerboard_is_full` | `func playerboard_is_full() -> bool` | bool | All slots filled |
| `highlight_movement_range` | `func highlight_movement_range() -> void` | void | Delegates to movement_manager |
| `highlight_adjacent_cells` | `func highlight_adjacent_cells() -> void` | void | Delegates to movement_manager |
| `highlight_cells_if_authorized` | `func highlight_cells_if_authorized(cells: Array, item_id: int) -> void` | void | Delegates to action_manager |
| `clear_highlights` | `func clear_highlights() -> void` | void | Clear grid highlights |
| `rotate_towards_target` | `func rotate_towards_target(target_pos: Vector2i) -> void` | void | Delegates to movement_manager |
| `select_playerboard_slot` | `func select_playerboard_slot(slot_index: int) -> void` | void | Delegates to playerboard_manager |
| `deselect_playerboard_slot` | `func deselect_playerboard_slot() -> void` | void | Clear selection |
| `target_playerboard_slot` | `func target_playerboard_slot(slot_index: int) -> void` | void | Target slot for move |
| `untarget_playerboard_slot` | `func untarget_playerboard_slot() -> void` | void | Clear target |
| `can_move_to_target_playerboard_slot` | `func can_move_to_target_playerboard_slot() -> bool` | bool | Target slot validity |
| `update_visual_position` | `func update_visual_position() -> void` | void | Snap to grid |
| `grab_tekton` | `func grab_tekton() -> void` | void | Tekton interaction: snatch or grab |
| `snatch_tekton` | `func snatch_tekton(target_carrier: Node3D) -> void` | void | Steal tekton from carrier |
| `throw_tekton` | `func throw_tekton() -> void` | void | Throw tekton in facing direction |
| `drop_tekton` | `func drop_tekton() -> void` | void | Drop tekton at current position |
| `enter_charged_strike` | `func enter_charged_strike() -> void` | void | Activate charged attack mode |
| `knock_tekton` | `func knock_tekton() -> void` | void | Special attack on nearby tekton |
| `update_active_player_indicator` | `func update_active_player_indicator() -> void` | void | Refresh visual state |
| `is_finish_position` | `func is_finish_position(pos: Vector2i) -> bool` | bool | Check if pos is a finish line |
| `_after_action_completed` | internal | void | Post-action: cycle goals, check win |
| `consume_action_points` | `func consume_action_points(points: int) -> void` | void | Deduct AP |
| `display_message` | on player | void | Show notification to this player |
| `apply_stagger` | `func apply_stagger(duration: float) -> void` | void | Stun for duration |
**RPCs (network-synced functions on player.gd):**
| Function | RPC Mode | Description |
|---|---|---|
| `sync_position` | any_peer, call_local | Sync current grid position |
| `sync_rotation` | any_peer, call_local | Sync Y rotation |
| `sync_grid_item` | any_peer, call_local | Sync grid cell item change |
| `sync_goals` | any_peer, call_local | Sync active goal set |
| `sync_second_lap_goals` | any_peer, call_local | Sync lap 2 goals |
| `sync_grab_tekton` | any_peer, call_local, reliable | Grab tekton network sync |
| `sync_snatch_tekton` | any_peer, call_local, reliable | Tekton theft sync |
| `sync_throw_tekton` | any_peer, call_local, reliable | Throw tekton sync |
| `sync_drop_tekton` | any_peer, call_local, reliable | Drop tekton sync |
| `sync_bump` | any_peer, call_local, unreliable | Visual bump animation |
| `sync_knock_tekton` | any_peer, call_local, reliable | Knock attack sync |
| `set_spawn_position` | any_peer, call_local, reliable | Random spawn position |
| `complete_race` | any_peer, call_local, reliable | Race completion |
| `force_action_state_none` | any_peer, call_local, reliable | Reset UI action state |
| `request_server_grab` | any_peer, reliable | Server-auth grab request |
| `request_server_put` | any_peer, reliable | Server-auth put request |
| `notify_spawn_selected` | any_peer, reliable | Spawn occupancy sync |
| `trigger_screen_shake` | (authority) | Screen shake RPC |
| `bot_grab_item` | any_peer, call_local | Bot grab sync |
| `bot_put_item` | any_peer, call_local | Bot put sync |
| `bot_arrange_item` | any_peer, call_local | Bot arrange sync |
**Dependencies:** PlayerMovementManager, PlayerInputManager, PlayerActionManager, PlayerboardManager, PowerupManager, PlayerRaceManager, GoalsCycleManager, SfxManager, NotificationManager, EnhancedGridMap (scene node), LobbyManager.
**Depended by:** main.gd (spawned per player).
[Back to top](#top)
### 11.3 lobby.gd
[Back to top](#top)
**File:** `/home/dev/tekton/scenes/lobby.gd` (583 lines)
**Extends:** Control
**Scene:** lobby.tscn
The lobby/home screen controller. Manages main menu, room creation/joining, player slots, server selection, character selection, settings, mail, chat, social panel, and 3D character preview.
**Key Properties:**
| Name | Type | Description |
|---|---|---|
| chat | LobbyChat | Chat helper instance |
| main_menu | LobbyMainMenu | Main menu helper |
| room_list_helper | LobbyRoomList | Room list helper |
| room_helper | LobbyRoom | Room/lobby helper |
| character_textures | Dictionary | {char_name: Texture2D} |
| profile_panel_instance | Control | Dynamic profile panel |
| shop_panel_instance | Control | Dynamic shop panel |
| daily_reward_panel_instance | Control | Daily reward panel |
| leaderboard_panel_instance | Control | Leaderboard panel |
| _mailbox_panel_instance | Control | Mail panel |
| social_panel_instance | Control | Social panel |
| _local_player_rank | int | Cached rank |
| _bot_names | Dictionary | Slot index -> bot name |
| _room_mode_filter | String | Room list filter |
| _is_hosting | bool | Re-entry guard |
**UI Node References (onready):**
| Variable | Node Path | Type |
|---|---|---|
| main_menu_panel | $MainMenuPanel | Control |
| main_title | %Title | Label |
| username_label | %Username | Label |
| create_room_btn | %CreateRoomBtn | Button |
| browse_rooms_btn | %BrowseRoomsBtn | Button |
| tutorial_btn | %TutorialBtn | Button |
| main_menu_profile_btn | %MainProfileBtn | Button |
| avatar_display | %AvatarDisplay | TextureRect |
| lobby_settings_btn | %SettingsBtn | Button |
| quit_btn | %QuitBtn | Button |
| character_root | %CharacterRoot | Node3D |
| anim_player | %AnimationPlayer | AnimationPlayer |
| gold_label | %GoldLabel | Label |
| star_label | %StarLabel | Label |
| server_option | %ServerOption | OptionButton |
| server_ip_input | %ServerIPInput | LineEdit |
| leaderboard_btn | %LeaderboardBtn | Button |
| shop_btn | %CartBtn | Button |
| top_right_profile_btn | %ProfileBtn | Button |
| mailbox_btn | %MailboxBtn | Button |
| mail_badge | %MailBadge | Label |
| banner1_btn | %Banner1 | Button |
| ticket_btn | %TicketBtn | Button |
| room_list_panel | %RoomListPanel | Control |
| room_list | %RoomList | ItemList |
| match_id_input | %MatchIdInput | LineEdit |
| refresh_btn | %RefreshBtn | Button |
| join_btn | %JoinBtn | Button |
| back_btn | %RoomListCloseBtn | Button |
| lobby_panel | $LobbyPanel | Control |
| host_banner | $LobbyPanel/HostBanner | Panel |
| match_id_display | $LobbyPanel/TopBar/... | Label |
| copy_id_btn | $LobbyPanel/TopBar/... | Button |
| duration_option | $LobbyPanel/TopBar/... | OptionButton |
| random_spawn_check | `" " ` | CheckButton |
| enable_timer_check | `" " ` | CheckButton |
| scarcity_option | `" " ` | OptionButton |
| game_mode_option | `" " ` | OptionButton |
| players_container | $LobbyPanel/PlayersContainer | Control |
| area_selector | $LobbyPanel/AreaSelector | Control |
| leave_btn | $LobbyPanel/BottomBar/LeaveBtn | Button |
| ready_btn | $LobbyPanel/BottomBar/ReadyBtn | Button |
| start_game_btn | $LobbyPanel/BottomBar/StartGameBtn | Button |
| connection_status | $StatusBar/ConnectionStatus | Label |
| chat_display | %RichTextLabel | RichTextLabel |
| chat_input | %ChatInput | LineEdit |
| chat_send_btn | %SendBtn | Button |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_ready` | auto-called | void | Initialize all helpers, load textures, setup UI, connect signals |
| `_setup_3d_preview` | `func _setup_3d_preview() -> void` | void | Swap character model in SubViewport |
| `_load_character_textures` | `func _load_character_textures() -> void` | void | Load preview textures |
| `_on_server_option_selected` | `func _on_server_option_selected(index: int) -> void` | void | Handle server type dropdown |
| `_on_server_ip_submitted` | `func _on_server_ip_submitted(new_text: String) -> void` | void | Handle IP input |
| `_setup_game_modes` | `func _setup_game_modes() -> void` | void | Populate game mode dropdown |
| `_setup_player_slots` | `func _setup_player_slots() -> void` | void | Collect player slot nodes |
| `_connect_slot_signals` | `func _connect_slot_signals(slot: Control, i: int)` | void | Wire character nav buttons |
| `_show_panel` | `func _show_panel(panel_name: String) -> void` | void | Toggle main_menu/room_list/lobby panels |
| `_update_settings_visibility` | `func _update_settings_visibility() -> void` | void | Show/hide settings by mode and host status |
| `_create_custom_settings_ui` | `func _create_custom_settings_ui() -> void` | void | Build SNG/Tekton Doors settings dynamically |
| `_sync_room_profile_card` | `func _sync_room_profile_card() -> void` | void | Refresh username, score, rank, avatar, currency |
| `_apply_loadout_character` | `func _apply_loadout_character() -> void` | void | Apply saved character to LobbyManager |
| `admin_wipe_chat` | `func admin_wipe_chat() -> void` | void (async) | Admin: clear global chat |
| `admin_purge_chat` | `func admin_purge_chat(max_age_days: int) -> int` | int (async) | Admin: purge old messages |
**Dependencies:** AuthManager, NakamaManager, LobbyManager, UserProfileManager, SkinManager, MusicManager, FriendManager, MailManager, BackendService.
**Depended by:** (root lobby scene; no dependents).
[Back to top](#top)
### 11.4 animation.gd
[Back to top](#top)
**File:** `/home/dev/tekton/scenes/animation.gd` (41 lines)
**Extends:** Control
**Scene:** (embedded in main.tscn for Stop n Go UI)
Stop n Go phase animation player. Controls ready-go countdown, stop phase overlay, safe zone, and go animation sequences.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `play_ready_go` | `func play_ready_go() -> void` | void | Play ready-set-go sequence |
| `play_stop_phase` | `func play_stop_phase() -> void` | void | Play STOP overlay |
| `play_safe_zone_appear` | `func play_safe_zone_appear() -> void` | void | Show safe zone indicator |
| `stop_phase_anim_play` | `func stop_phase_anim_play() -> void` | void | Play stop phase spritesheet |
| `stop_phase_anim_stop` | `func stop_phase_anim_stop() -> void` | void | Stop phase animation |
| `play_countdown_30s` | `func play_countdown_30s() -> void` | void | 30-second countdown |
| `play_countdown_15s` | `func play_countdown_15s() -> void` | void | 15-second countdown |
| `play_go_animation` | `func play_go_animation() -> void` | void | GO animation |
| `play_go_finish_animation` | `func play_go_finish_animation() -> void` | void | Finish line animation |
**Dependencies:** AnimatedSprite2D, AnimationPlayer (scene nodes).
**Depended by:** StopNGoManager, main.gd.
[Back to top](#top)
## 12. UI Helper Classes (RefCounted)
[Back to top](#top)
All UI helper classes are RefCounted objects instantiated by lobby.gd in _ready(). They do NOT extend Node -- they are lightweight event wiring and state management objects.
### 12.1 LobbyMainMenu
[Back to top](#top)
```gdscript
class_name LobbyMainMenu extends RefCounted
```
**File:** `/home/dev/tekton/scenes/ui/lobby_main_menu.gd` (338 lines)
Event wiring for main menu buttons. Connects all lobby button signals to handler methods.
**Constructor:** `func _init(p_lobby: Control)` -- Stores lobby ref, connects 15+ button signals.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `on_tutorial_pressed` | `func on_tutorial_pressed() -> void` | void | Set name, apply loadout, call LobbyManager.start_tutorial |
| `on_create_room_pressed` | `func on_create_room_pressed() -> void` | void | Show room list panel, create tab |
| `host_room` | `func host_room(game_mode: String) -> void` | void | Guarded double-click, set name/mode, create Nakama or LAN room |
| `on_browse_rooms_pressed` | `func on_browse_rooms_pressed() -> void` | void | Show room list, browse tab, refresh |
| `on_profile_btn_pressed` | `func on_profile_btn_pressed() -> void` | void | Instantiate and show profile_panel.tscn |
| `on_mailbox_pressed` | `func on_mailbox_pressed() -> void` | void | Instantiate and show mailbox_panel.tscn |
| `on_settings_pressed` | `func on_settings_pressed() -> void` | void | Instantiate and show settings_menu.tscn |
| `restore_after_settings` | `func restore_after_settings() -> void` | void | Restore lobby/main_menu panel visibility |
| `on_shop_pressed` | `func on_shop_pressed() -> void` | void | Instantiate and show shop_panel.tscn |
| `on_banner1_pressed` | `func on_banner1_pressed() -> void` | void | Instantiate and show gacha_panel.tscn |
| `on_leaderboard_pressed` | `func on_leaderboard_pressed() -> void` | void | Show leaderboard_panel.tscn |
| `on_ticket_pressed` | `func on_ticket_pressed() -> void` | void | Show daily_reward_panel.tscn |
| `on_social_pressed` | `func on_social_pressed() -> void` | void | Show social_panel.tscn, hide main menu UI |
| `on_logout_pressed` | `func on_logout_pressed() -> void` | void | AuthManager.logout() -> login screen |
| `on_quit_pressed` | `func on_quit_pressed() -> void` | void | get_tree().quit() |
| `go_to_login` | `func go_to_login() -> void` | void | Change scene to login_screen.tscn |
**Dependencies:** AuthManager, LobbyManager, UserProfileManager, NakamaManager, BackendService.
**Depended by:** lobby.gd.
[Back to top](#top)
### 12.2 LobbyRoom
[Back to top](#top)
```gdscript
class_name LobbyRoom extends RefCounted
```
**File:** `/home/dev/tekton/scenes/ui/lobby_room.gd` (432 lines)
Room/lobby panel event wiring. Handles ready/start/leave buttons, player slot rendering, character navigation, game mode/duration/scarcity settings, friend invites, and lobby invitation popup.
**Constructor:** `func _init(p_lobby: Control)` -- Stores lobby ref, connects 20+ signals.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_on_ready_toggled` | `func _on_ready_toggled(is_ready: bool) -> void` | void | Toggle ready state |
| `_on_start_game_pressed` | `func _on_start_game_pressed() -> void` | void | Host starts game |
| `_on_leave_pressed` | `func _on_leave_pressed() -> void` | void | Leave room, release bot names |
| `_on_copy_id_pressed` | `func _on_copy_id_pressed() -> void` | void | Copy match ID to clipboard |
| `_on_duration_selected` | `func _on_duration_selected(index: int) -> void` | void | Host sets match duration |
| `_on_random_spawn_toggled` | `func _on_random_spawn_toggled(toggled_on: bool) -> void` | void | Toggle random spawn |
| `_on_enable_timer_toggled` | `func _on_enable_timer_toggled(toggled_on: bool) -> void` | void | Toggle cycle timer |
| `_on_scarcity_selected` | `func _on_scarcity_selected(index: int) -> void` | void | Host sets scarcity |
| `_on_scarcity_mode_changed` | `func _on_scarcity_mode_changed(mode: String) -> void` | void | UI update for scarcity |
| `_on_game_mode_selected` | `func _on_game_mode_selected(index: int) -> void` | void | Host sets game mode |
| `_on_game_mode_changed` | `func _on_game_mode_changed(mode: String) -> void` | void | UI update for game mode |
| `_on_sng_update` | `func _on_sng_update(_val: int = 0) -> void` | void | Sync SNG setting UI |
| `_on_doors_update` | `func _on_doors_update(_val: int = 0) -> void` | void | Sync Doors setting UI |
| `_on_room_joined` | `func _on_room_joined(room_data: Dictionary) -> void` | void | Switch to lobby panel, populate settings |
| `_on_room_left` | `func _on_room_left() -> void` | void | Return to main menu |
| `_on_host_disconnected` | `func _on_host_disconnected() -> void` | void | Show disconnect message |
| `_on_player_joined` | `func _on_player_joined(player_data: Dictionary) -> void` | void | Update slots + status |
| `_on_player_left` | `func _on_player_left(_player_id: int) -> void` | void | Update slots |
| `_on_ready_state_changed` | `func _on_ready_state_changed(_player_id: int, _is_ready: bool) -> void` | void | Update slot visuals |
| `_on_all_players_ready` | `func _on_all_players_ready() -> void` | void | Enable start button |
| `_on_game_starting` | `func _on_game_starting() -> void` | void | Transition to main.tscn |
| `_update_player_slots` | `func _update_player_slots() -> void` | void | Render all 8 player slots (players + bot slots) |
| `_update_status` | `func _update_status() -> void` | void | Show ready count |
| `_on_add_friend_pressed` | `func _on_add_friend_pressed(nakama_id: String) -> void` | void (async) | Add friend by Nakama ID |
| `on_invite_friends_pressed` | `func on_invite_friends_pressed() -> void` | void | Open invite dialog |
| `_on_lobby_invite_received` | `func _on_lobby_invite_received(from_user_id: String, from_name: String, match_id: String) -> void` | void | Show invite popup |
| `_on_invite_accepted` | `func _on_invite_accepted() -> void` | void | Join invited match |
**Dependencies:** LobbyManager, FriendManager, NakamaManager, NameGenerator, UserProfileManager.
**Depended by:** lobby.gd.
[Back to top](#top)
### 12.3 LobbyRoomList
[Back to top](#top)
```gdscript
class_name LobbyRoomList extends RefCounted
```
**File:** `/home/dev/tekton/scenes/ui/lobby_room_list.gd` (155 lines)
Room list panel event wiring. Handles room list refresh, selection, join, and back navigation.
**Constructor:** `func _init(p_lobby: Control)` -- Stores lobby ref, connects signals.
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `_on_refresh_pressed` | `func _on_refresh_pressed() -> void` | void | Clear + refresh room list |
| `_on_room_selected` | `func _on_room_selected(index: int) -> void` | void | Copy room match_id/IP to input |
| `_on_room_activated` | `func _on_room_activated(index: int) -> void` | void | Select + auto-join |
| `_on_join_pressed` | `func _on_join_pressed() -> void` | void | Validate input, set name, join room (LAN or Nakama) |
| `_on_back_pressed` | `func _on_back_pressed() -> void` | void | Return to main menu |
| `on_room_list_updated` | `func on_room_list_updated(rooms: Array) -> void` | void | Render room rows, apply mode filter |
**Dependencies:** LobbyManager, AuthManager, UserProfileManager.
**Depended by:** lobby.gd.
[Back to top](#top)
### 12.4 LobbyChat
[Back to top](#top)
```gdscript
class_name LobbyChat extends RefCounted
```
**File:** `/home/dev/tekton/scenes/ui/lobby_chat.gd` (373 lines)
Global and direct message chat system. Handles Nakama socket channel chat, DM tabs, friend suggestions, and admin chat commands.
**Constants:** `GLOBAL_CHAT_ROOM = "social_global"`
**Properties:**
| Name | Type | Description |
|---|---|---|
| _chat_channel | NakamaChannel | Current chat channel |
| _chat_messages | Array | [{sender, content, ts, date}] |
| _active_chat_context | String | "global" or user_id |
| _dm_tabs | Dictionary | user_id -> HBoxContainer (tab UI) |
| _dm_messages | Dictionary | user_id -> Array of messages |
| _chat_config | Dictionary | {prefix, max_messages, max_age_days} |
**Public Functions:**
| Function | Signature | Return | Description |
|---|---|---|---|
| `join_global_chat` | `func join_global_chat() -> void` | void (async) | Join social_global channel, fetch history, inject prefix |
| `switch_chat_tab` | `func switch_chat_tab(context_id: String) -> void` | void | Switch between global and DM tabs |
| `_on_chat_send_pressed` | `func _on_chat_send_pressed() -> void` | void (async) | Send message: @username for DM, /clear for admin |
| `on_lobby_dm_received` | `func on_lobby_dm_received(from_user_id: String, from_name: String, message: String) -> void` | void | Incoming DM handler |
| `leave_global_chat` | `func leave_global_chat() -> void` | void (async) | Disconnect and leave channel |
**Internal Functions:** `_add_chat_message`, `_send_dm_message`, `_open_dm_tab`, `_create_dm_tab`, `_close_dm_tab`, `_inject_local_message`, `_trim_old_messages`, `_refresh_chat_display`, `_format_nakama_time`, `_get_local_time`, `_on_chat_input_changed`, `_on_friend_suggest_activated`, `_setup_friend_suggest_ui`.
**Dependencies:** NakamaManager, BackendService, FriendManager, AdminManager, UserProfileManager.
**Depended by:** lobby.gd.
[Back to top](#top)
## 13. Dependency Graph
[Back to top](#top)
### 13.1 Manager Autoload Dependencies
[Back to top](#top)
ASCII diagram showing which autoloads reference others:
```
NakamaManager (no deps on other managers -- pure Nakama SDK)
|
+-- BackendService
| +-- SteamworksManager (child node, not autoload)
|
+-- AuthManager
| +-- NakamaManager
| +-- BackendService
|
+-- SessionManager
| +-- NakamaManager
|
+-- LobbyManager
| +-- NakamaManager
| +-- GameStateManager
|
+-- GameStateManager (no deps)
|
+-- PlayerManager (no deps -- data only)
|
+-- EventBus (no deps -- pure observer)
|
+-- UserProfileManager
| +-- NakamaManager
| +-- BackendService
| +-- EventBus
|
+-- FriendManager
| +-- BackendService
| +-- NakamaManager
|
+-- MailManager
| +-- BackendService
|
+-- GachaManager
| +-- BackendService
| +-- UserProfileManager
| +-- EventBus
|
+-- DailyRewardManager
| +-- BackendService
|
+-- AdminManager
| +-- BackendService
| +-- NakamaManager
|
+-- SkinManager
| +-- UserProfileManager
|
+-- ShopManager
| +-- BackendService (thin)
|
+-- PlayerInputManager
| +-- TouchControls
|
+-- PlayerMovementManager
| +-- ObstacleManager
| +-- SpecialTilesManager
| +-- EnhancedGridMap (scene)
|
+-- PlayerActionManager
| +-- PlayerboardManager
| +-- PlayerInputManager
| +-- GoalsCycleManager
|
+-- PlayerboardManager
| +-- GoalsCycleManager
| +-- GoalManager
|
+-- PlayerRaceManager
| +-- GoalsCycleManager
|
+-- GoalsCycleManager
| +-- GoalManager
| +-- TurnManager
| +-- Timer (scene)
|
+-- StopNGoManager
| +-- TurnManager
| +-- GoalManager
| +-- GoalsCycleManager
| +-- animation.gd (scene)
|
+-- GauntletManager
| +-- TurnManager
| +-- EnhancedGridMap
|
+-- PortalModeManager
| +-- SpecialTilesManager
| +-- EnhancedGridMap
|
+-- SpecialTilesManager
| +-- ObstacleManager
| +-- EnhancedGridMap
|
+-- ObstacleManager
| +-- EnhancedGridMap
|
+-- StaticTektonManager
| +-- EnhancedGridMap
| +-- ObstacleManager
|
+-- PowerupManager (no deps)
|
+-- UIManager (no deps -- dynamic UI)
|
+-- SettingsManager (no deps -- ConfigFile)
|
+-- GameUpdateManager (HTTPRequest -- no manager deps)
|
+-- TutorialManager
| +-- TutorialOverlay
|
+-- TutorialOverlay
| +-- TutorialManager
| +-- UIManager
|
+-- MusicManager (no deps)
+-- SfxManager (no deps)
+-- ScreenShake (no deps)
+-- NotificationManager (no deps)
+-- CameraContextManager (no deps)
+-- TouchControls (no deps)
+-- JoinManager (no deps -- stub)
```
[Back to top](#top)
### 13.2 Cross-Manager Signal Wiring
[Back to top](#top)
Key signal connections between managers and scene scripts:
```
NakamaManager.match_joined -> LobbyManager._on_match_joined
NakamaManager.match_join_error -> lobby.gd (clears _is_hosting)
NakamaManager.connection_failed -> lobby.gd (clears _is_hosting)
LobbyManager.room_joined -> LobbyRoom._on_room_joined
LobbyManager.room_left -> LobbyRoom._on_room_left
LobbyManager.host_disconnected -> LobbyRoom._on_host_disconnected
LobbyManager.player_joined -> LobbyRoom._on_player_joined
LobbyManager.player_left -> LobbyRoom._on_player_left
LobbyManager.ready_state_changed -> LobbyRoom._on_ready_state_changed
LobbyManager.all_players_ready -> LobbyRoom._on_all_players_ready
LobbyManager.game_starting -> LobbyRoom._on_game_starting
LobbyManager.game_mode_changed -> LobbyRoom._on_game_mode_changed
LobbyManager.room_list_updated -> LobbyRoomList.on_room_list_updated
LobbyManager.character_changed -> LobbyRoom._on_character_changed
LobbyManager.rematch_votes_updated -> main.gd (update rematch button)
FriendManager.dm_message_received -> LobbyChat.on_lobby_dm_received
FriendManager.lobby_invite_received -> LobbyRoom._on_lobby_invite_received
MailManager.unread_count_changed -> lobby.gd (update badge)
UserProfileManager.profile_loaded -> lobby.gd (_sync_room_profile_card)
UserProfileManager.profile_updated -> lobby.gd (_sync_room_profile_card)
AuthManager.logged_out -> LobbyMainMenu.go_to_login
```
[Back to top](#top)
## 14. Scene Node Trees
[Back to top](#top)
### 14.1 main.tscn
[Back to top](#top)
```
Main (Node) -- attached: main.gd
+-- EnhancedGridMap (GridMap / custom EnhancedGridMap node)
+-- PlayerSpawnPoints (Node3D)
+-- HUD (CanvasLayer)
| +-- LeaderboardPanel (Panel)
| | +-- MarginContainer/VBox
| | +-- Entry1-8 (HBoxContainer with RankLabel/NameLabel/ScoreLabel)
| +-- NotificationOverlay (Control)
| +-- ActionButtons (Control)
+-- PauseMenu (Panel)
| +-- Panel/VBox/ResumeBtn, HowToPlayBtn, SettingsBtn, UnstuckBtn, QuitMatchBtn
+-- HowToPlayPanel (Panel)
+-- StopNGoUI (Control) -- attached: animation.gd
| +-- StopPhase/AnimatedSprite2D
| +-- AnimationPlayer
| +-- CountDown/CountDownAnimation (AnimatedSprite2D)
| +-- GoFinish/GoAnimation2D (AnimatedSprite2D)
+-- Camera3D
+-- WorldEnvironment
+-- Player instances (added dynamically by main.gd)
+-- Player (CharacterBody3D) -- attached: player.gd
+-- MeshInstance3D (visual)
+-- CollisionShape3D
+-- PlayerboardUI (Control overlay)
+-- AnimationPlayer
```
[Back to top](#top)
### 14.2 player.tscn
[Back to top](#top)
```
Player (CharacterBody3D) -- attached: player.gd
+-- MeshInstance3D (character model)
+-- CollisionShape3D
+-- PlayerboardUI (Control)
| +-- Slot0-9 (Panel/TextureRect)
+-- AnimationPlayer
+-- (tektons picked up become children at runtime)
```
[Back to top](#top)
### 14.3 lobby.tscn
[Back to top](#top)
```
Lobby (Control) -- attached: lobby.gd
+-- StatusBar (HBoxContainer)
| +-- ConnectionStatus (Label)
+-- MainMenuPanel (Panel)
| +-- Title/Username/Subtitle/Buttons (CreateRoom, BrowseRooms, Tutorial, etc.)
| +-- CharacterRoot (SubViewportContainer > SubViewport > Node3D)
| | +-- Oldpop, Masbro, Gatot, Bob (character meshes, hidden by default)
| +-- %AnimationPlayer
| +-- CurrencyLabels (GoldLabel, StarLabel)
| +-- ServerOption / ServerIPInput
| +-- LeaderboardBtn, CartBtn, ProfileBtn, MailboxBtn, Banner1Btn, TicketBtn
+-- RoomListPanel (Control)
| +-- RoomListTabs (TabContainer)
| +-- RoomTab, PlayTab
| +-- MatchIdInput, RefreshBtn, JoinBtn, BackBtn
| +-- RoomList (ItemList)
| +-- ItemTemplate (hidden)
| +-- ProfileCard (PlayerUsername, PlayerScore, Rank, Avatar)
+-- LobbyPanel (Panel)
| +-- RoomNameHeader
| +-- HostBanner
| +-- TopBar/SettingsSection (Duration, Spawn, Timer, Scarcity, GameMode options)
| +-- AreaSelector
| +-- PlayersContainer (slots 1-4)
| +-- PlayersContainer2 (slots 5-8)
| +-- BottomBar (LeaveBtn, ReadyBtn, StartGameBtn, InviteBtn)
| +-- StatusLabel
+-- ChatPanel (Panel)
| +-- RichTextLabel
| +-- ChatInput (LineEdit)
| +-- SendBtn
| +-- ChatTabsContainer (GlobalChatTabBtn + DM tabs)
| +-- FriendSuggestPanel (hidden)
+-- (dynamic instances: MailboxPanel, ShopPanel, GachaPanel, ProfilePanel, etc.)
```
[Back to top](#top)
+392
View File
@@ -0,0 +1,392 @@
# Tekton Armageddon - Server Architecture
<a id="top"></a>
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<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](#top)
---
## Module Architecture
```mermaid
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](#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<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](#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<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](#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<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](#top)
+604
View File
@@ -0,0 +1,604 @@
# Game Modes
<a id="top"></a>
[Back to Home](./Home)
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [GameMode Enum &amp; ModeConfig](#gamemode-enum--modeconfig)
- [Session Flow (all modes)](#session-flow-all-modes)
- [Core Managers (shared)](#core-managers-shared)
- [Freemode](#freemode)
- [Stop n Go](#stop-n-go)
- [Tekton Doors (Portal)](#tekton-doors-portal)
- [Candy Pump Survival (Gauntlet)](#candy-pump-survival-gauntlet)
- [Scoring &amp; Leaderboard](#scoring--leaderboard)
- [Glossary](#glossary)
- [File Index](#file-index)
[Back to top](#top)
## Overview
Four game modes, each implementing the same core loop — players navigate a grid, collect tiles (Heart / Diamond / Star / Coin), match them to goals on a virtual 5x5 playerboard, and compete for score before the match timer expires.
| Mode | Enum Value | Display Name | Play Area | Gimmick |
|------|-----------|-------------|-----------|---------|
| Freemode | `FREEMODE = 0` | "Freemode" | Variable | No restrictions, just goals + timer |
| Stop n Go | `STOP_N_GO = 1` | "Stop n Go" | 23x12 | GO/STOP phases, safe zones, scatter penalty |
| Tekton Doors | `TEKTON_DOORS = 2` | "Tekton Doors" | 14x14 | 4 rooms, portal doors swap connections every 15s |
| Candy Pump Survival | `GAUNTLET = 3` | "Candy Pump Survival" | 20x20 | Ground growth — candy slowly fills the arena |
[Back to top](#top)
## Architecture
```
GameMode (enum/RefCounted)
├── .from_string() / .mode_to_string() / .is_restricted() / .get_all_modes()
├── ModeConfig (RefCounted)
│ └── SCHEMA with defaults, type-checking, min/max for each mode
├── GoalsCycleManager (Node, autoload?)
│ ├── 30s cycle timer
│ ├── Match timer (configurable 60-600s)
│ ├── Score tracking (player_scores, player_goal_counts)
│ ├── Goal completion: _process_goal_completion()
│ └── RPC sync: sync_player_score, sync_goal_count, sync_timer
├── GoalManager (Node)
│ ├── initialize_random_goals() — generates 9-slot goal patterns
│ ├── Speed tracking (completion_times, boost_multiplier)
│ └── generate_preset_goals() / get_goals_for_player()
├── PlayerRaceManager (per-player)
│ ├── goals: Array[int] (9 slots)
│ ├── playerboard: Array[int] (25 slots, 5x5)
│ ├── check_pattern_match() — core matching logic
│ └── DEPRECATED lap/finish-line stubs
├── PlayerboardManager (per-player)
│ ├── grab_item() / auto_put_item() / arrange operations
│ ├── _execute_grab() — server-authoritative grab validation
│ ├── HIDDEN_SLOTS (12 of 25 cells blocked)
│ ├── bot_try_grab_item() — AI grab logic
│ └── _check_and_refill_grid_if_needed() — scarcity refill
├── TurnManager (shared)
│ ├── next_turn() / end_current_turn()
│ └── turn_based_mode toggle
└── Mode-specific managers
├── StopNGoManager — phase transitions, safe zones, mission HUD
├── PortalModeManager — room partitions, portal doors, swap timer
└── GauntletManager — ground growth, phases, bubbles, smack
```
All mode managers live under `/root/Main` and connect to `GoalsCycleManager` signals for score / goal tracking.
[Back to top](#top)
## GameMode Enum &amp; ModeConfig
**File:** `scripts/game_mode.gd` (41 lines)
```
enum Mode { FREEMODE = 0, STOP_N_GO = 1, TEKTON_DOORS = 2, GAUNTLET = 3 }
```
| Static Method | Returns | Description |
|--------------|---------|-------------|
| `from_string(mode: String)` | `Mode` | Converts "Freemode"/"Stop n Go"/"Tekton Doors"/"Candy Pump Survival" to enum |
| `mode_to_string(mode: Mode)` | `String` | Reverse of from_string |
| `is_restricted(mode: Mode)` | `bool` | true for STOP_N_GO, TEKTON_DOORS, GAUNTLET |
| `get_all_modes()` | `Array[String]` | Returns display names |
**File:** `scripts/mode_config.gd` (109 lines)
SCHEMA defines per-mode settings with type, default, min, max, allowed values:
| Mode | Settings |
|------|---------|
| Freemode | match_duration (180s default), randomize_spawn (bool), enable_cycle_timer (bool), scarcity_mode (Normal/Aggressive/Chaos) |
| Stop n Go | match_duration, sng_go_duration (20s), sng_stop_duration (4s), sng_required_goals (8) |
| Tekton Doors | match_duration, doors_swap_time (15s), doors_refresh_time (25s), doors_required_goals (8) |
| Gauntlet | match_duration, gauntlet_growth_interval (3.0s), gauntlet_cells_per_tick (phase dict) |
| Method | Args | Returns |
|--------|------|---------|
| `get_defaults(mode)` | String | Dictionary of defaults for that mode |
| `validate_setting(mode, key, value)` | String, String, Variant | `{"valid": bool, "error": String}` |
| `validate_config(mode, config)` | String, Dictionary | `{"valid": bool, "errors": Array}` |
| `get_mode_settings(mode)` | String | Array of setting keys |
| `get_setting_schema(mode, key)` | String, String | Dictionary with type/default/min/max/allowed |
[Back to top](#top)
## Session Flow (all modes)
1. **Lobby** — players join, select mode + settings
2. **Game start**`main.gd` calls `_setup_host_game()` which:
- Creates arena (gridmap resize + clear)
- Spawns mission tiles on Layer 1
- Calls mode manager's `start_game_mode()`
3. **Countdown** — brief timer then match begins
4. **Match active**`GoalsCycleManager.start_match()` runs match timer + cycle timer
5. **Per-cycle** (30s):
- Players grab tiles from grid → place on 5x5 playerboard
- Match 3x3 pattern against 9-slot goals
- Complete goals → B1000 score + new goals + tiles randomize around player
- Cycle ends → board cleared, unmatched tiles scored at 10/tile match
6. **Match ends** — final leaderboard sync
[Back to top](#top)
## Core Managers (shared)
### GoalManager (`scripts/managers/goal_manager.gd`, 108 lines)
Generates 9-slot goal arrays using tile IDs 7-10 (Heart=7, Diamond=8, Star=9, Coin=10) with -1 for no-goal slots (~3 nulls per set).
| Function | Returns | Description |
|----------|---------|-------------|
| `initialize_random_goals(size, min_value, max_value, null_count)` | `Array` | Random goals with controlled null distribution |
| `generate_preset_goals(count)` | `Array` | Pre-generates N goal sets for all players |
| `get_goals_for_player(player_index)` | `Array` | Returns goals for a specific player slot |
| `mark_goal_start(player_id)` | void | Records timestamp for speed tracking |
| `mark_goal_complete(player_id)` | void | Records completion duration |
| `get_player_average_time(player_id)` | `float` | Average completion speed |
| `get_global_average_time()` | `float` | Average across all players |
| `get_boost_multiplier(player_id)` | `float` | 0.8-1.5x fill rate based on speed vs average |
| `reset()` | void | Clears all state |
### GoalsCycleManager (`scripts/managers/goals_cycle_manager.gd`, 520 lines)
Central scoring and timer system. Emits signals consumed by mode managers, UI, and leaderboard.
| Signal | Payload |
|--------|---------|
| `cycle_started()` | — |
| `cycle_ended()` | — |
| `timer_updated(time_remaining)` | float |
| `score_updated(peer_id, new_score)` | int, int |
| `goal_count_updated(peer_id, count)` | int, int |
| `leaderboard_updated(sorted_scores)` | Array |
| `match_started()` | — |
| `match_ended()` | — |
| `global_timer_updated(time_remaining)` | float |
| Function | Description |
|----------|-------------|
| `start_match(duration_seconds, start_cycles)` | Begins match timer, optionally starts first 30s cycle |
| `_on_match_end()` | Processes final scores, syncs to clients |
| `start_cycle()` | Begin 30s cycle, emit `cycle_started` |
| `on_goal_completed(player, time_remaining)` | Entry point — routes to server or optimistic local path |
| `_process_goal_completion(player, time_remaining)` | Server: award B1000 + time bonus, regen goals, randomize tiles |
| `regenerate_goals_for_player(player)` | Generate new 9-slot goal set, sync via RPC |
| `_randomize_tiles_around_player(player)` | Randomize 3x3 area around player on the grid |
| `_process_cycle_end_for_all_players()` | Clear all boards, convert matches to B10/tile |
| `add_score(peer_id, amount)` | Server: add arbitrary score points |
| `_update_leaderboard()` | Sort by score descending (or with SNG winner override) |
### PlayerRaceManager (`scripts/managers/player_race_manager.gd`, 133 lines)
State holder per player. Core logic is `check_pattern_match()`.
| Function | Description |
|----------|-------------|
| `check_pattern_match()` | Returns true if any 3x3 sub-grid of 5x5 board matches 3x3 goals |
| `check_3x3_section(board, goals, start_row, start_col)` | Checks single 3x3 section |
| `_normalize_tile(tile)` | Converts holo tiles 11-14 → 7-10 for match comparison |
| Remaining functions | DEPRECATED lap/finish line stubs |
**Playerboard Layout:** 5x5 (indices 0-24). 13 cells are HIDDEN_SLOTS (cannot hold tiles). Only 12 usable slots arranged in an L-shape matching the HUD.
### PlayerboardManager (`scripts/managers/playerboard_manager.gd`, 793 lines)
Handles grab, put, arrange operations with optimistic local updates + server-authoritative validation.
| Function | Description |
|----------|-------------|
| `grab_item(grid_position)` | Grab tile from grid → place on board (auto-arrange) or consume as power-up |
| `_execute_grab(grid_pos, cell, item_id, expected_slot)` | Server-side validation + state update + sync |
| `_force_sync_to_client(cell, server_item)` | Revert client when server rejects grab |
| `auto_put_item()` | AI/bot: find best tile to remove from board |
| `find_best_goal_slot_for_item(item)` | Auto-arrange into best matching slot |
| `bot_try_grab_item()` | AI grab logic |
| `_check_and_refill_grid_if_needed(gridmap)` | Refill floor 1 via ScarcityController when empty |
### TurnManager (`scripts/managers/turn_manager.gd`, 27 lines)
| Function | Description |
|----------|-------------|
| `next_turn(players)` | Advance turn index, emit `turn_changed` |
| `end_current_turn()` | Emit `turn_ended` |
| `reset_turn()` / `reset()` | Clear state |
[Back to top](#top)
## Freemode
**No dedicated manager.** Freemode relies entirely on the shared core managers (GoalsCycleManager, PlayerboardManager, etc.) with no mode-specific restrictions or gimmicks. Arena size is configurable via LobbyManager settings.
**Settings effects:**
- `enable_cycle_timer` false → cycle never expires (board never auto-clears)
- `scarcity_mode` → controls tile refill aggression
- `randomize_spawn` → players start at random positions
[Back to top](#top)
## Stop n Go
**Manager:** `StopNGoManager` (`scripts/managers/stop_n_go_manager.gd`, 1107 lines)
A 23x12 arena with two alternating phases:
### Phase System
| Phase | Duration (default) | Behaviour |
|-------|-------------------|-----------|
| GO | 20s | Players move freely, collect tiles, complete goals |
| STOP | 4s | Players frozen if outside safe zone → tiles scattered |
When STOP begins:
1. 3 dynamic safe zones spawn randomly (green tiles)
2. All players outside safe zone get `_scatter_player_tiles()` — board tiles scattered on grid
3. Power-up tiles (Speed=11, Ghost=14) spawn at 5 permanent locations
4. Mission requirement: complete 8 goals before reaching finish (x=22)
When GO begins:
1. Dynamic safe zones cleared
2. All STOP freeze effects removed via `sync_stop_freeze(false)`
### Tile IDs
| ID | Meaning |
|----|---------|
| 0 | Walkable floor |
| 2 | Safe zone (green) |
| 3 | Start/Finish line |
| 4 | Wall/obstacle |
| 15 | Lightning stone (decorative ancient rock) |
| 16 | Safe zone wall |
### Arena
22x10 walkable area with two interior rooms with entrances:
- Room 1: (7,6) to (11,9) — 5x4 area with 4 door entrances
- Room 2: (15,1) to (19,5) — 5x5 area with 4 door entrances
### HUD
- Center-bottom mission label: "GOALS (X/8)" or "ALL GOALS COMPLETE! REACH THE FINISH!"
- Traffic light stop timer (3 segments): all empty during GO, fills red during STOP phase
- Last 3 seconds of GO phase: segments light up one-by-one (countdown)
- VFX: `vfx_manager.play_go_animation()` / `play_stop_phase()`
### RPCs
| RPC | Direction | Description |
|-----|-----------|-------------|
| `sync_phase(phase_name, duration)` | Authority → all | Broadcast GO/STOP phase change |
| `sync_arena_setup()` | Authority → remote | Sync 23x12 grid dimensions + obstacles |
| `sync_all_safe_zones_vfx()` | Authority → all | Trigger safe zone visual effects |
### Key Functions (server-only unless noted)
| Function | Description |
|----------|-------------|
| `start_game_mode()` | Server: setup arena, assign missions, start GO phase |
| `_start_phase(phase)` | Transition GO↔STOP, penalize players outside safe zone |
| `_setup_arena()` | Build 23x12 with obstacles + rooms |
| `_spawn_mission_tiles()` | Heart(7)/Diamond(8)/Star(9)/Coin(10) at 60% density |
| `_spawn_powerup_tiles()` | Speed(11)+Ghost(14) at 5 permanent locations |
| `_assign_missions()` | NO-OP (mission = achievement: collect 8 goals) |
| `activate_client_side()` | Client: show HUD, connect to GoalsCycleManager signals |
| `rotate_players_to_start()` | Force all players to face East (PI/2) |
| `can_rpc()` | Check multiplayer peer is connected |
[Back to top](#top)
## Tekton Doors (Portal)
**Manager:** `PortalModeManager` (`scripts/managers/portal_mode_manager.gd`, 585 lines)
**Actor:** `PortalDoor` (`scripts/portal_door.gd`, 136 lines)
A 14x14 grid divided into 4 rooms (7x7 each) by cross-shaped wall partitions. Players move between rooms via portal doors that swap connections every 15 seconds.
### Room Layout
```
Room 0 (NW) | Room 1 (NE)
x: 0-6 | x: 7-13
z: 0-6 | z: 0-6
--------------+--------------
Room 2 (SW) | Room 3 (SE)
x: 0-6 | x: 7-13
z: 7-13 | z: 7-13
```
Central divider: columns 6,7 and rows 6,7 are walls (tile ID 4).
### Portal System
- 10 doors total (2 base per room + 2 randomly placed extras)
- Every 15s (`doors_swap_time`): `_randomize_connections()` shuffles pairings
- Each pair gets a color from `PORTAL_COLORS` (Cyan/Magenta/Red/Green/Orange)
- Validation ensures no pair connects doors in the same room
- 200ms anti-jitter cooldown per player in `handle_portal_interaction()`
### PortalDoor (actor)
| Property/Method | Description |
|----------------|-------------|
| `room_id` | Which room this door belongs to |
| `door_id` | Unique door index |
| `target_door_id` | Connected door (set by PortalModeManager) |
| `portal_color` | Color (set triggers `set_portal_color`) |
| `detection_area` | Area3D — body_entered triggers portal |
| `_on_body_entered(body)` | 200ms cooldown, emit `player_entered_portal` |
| `spawn_offset` | Vector2i meta — nudge spawn position into room |
| `_adjust_indicator_position()` | Move GroundIndicator toward room interior |
### Finish Room
- At 30s remaining on match timer, reveal `_spawn_finish_room()`
- Random 3x3 area converted to finish tiles (ID 3) in one room
- Player must be standing on finish tile AND have `doors_required_goals` complete
### Tile Refill
- Every 25s (`tile_refresh_time`): `_refresh_tiles()` refills Floor 1 at 60% density
- Uses `ScarcityModel.get_tile_weights()` for weighted random selection
- Avoids spawning under portal doors
### HUD
- Center-bottom: "GOALS (X/8)" or "ALL GOALS COMPLETE! FIND THE FINISH ROOM!"
- Message broadcasts: "PORTALS SWITCHED!", "TILES REPLENISHED!"
- Warning: "A 3x3 Finish Zone has appeared in Room N!"
### RPCs
| RPC | Direction | Description |
|-----|-----------|-------------|
| `sync_portal_data(data)` | Authority → local | Sync connections + colors to all clients |
| `sync_portal_configs(door_configs)` | Via main | Broadcast door positions/rotations |
### Key Functions
| Function | Description |
|----------|-------------|
| `initialize(p_main, p_gridmap)` | Create swap timer + tile refresh timer, connect signals |
| `start_game_mode()` | Setup arena, randomize connections, start timers |
| `setup_arena_locally()` | Resize to 14x14, build room walls, spawn portal doors |
| `_randomize_connections()` | Shuffle door pairings, assign colors, validate same-room rule |
| `handle_portal_interaction(player, door)` | Teleport player to connected door + offset |
| `_spawn_finish_room()` | Convert random 3x3 area to finish tiles |
| `check_win_condition(player_id, pos)` | Check finish tile + mission complete |
| `_refresh_tiles()` | Refill floor 1 items with scarcity weights |
| `sync_to_client(peer_id)` | Sync portal connections to late-joining client |
| `get_spawn_points()` | Returns 4 spawn positions (one per room quadrant) |
[Back to top](#top)
## Candy Pump Survival (Gauntlet)
**Manager:** `GauntletManager` (`scripts/managers/gauntlet_manager.gd`, 1825 lines)
A 20x20 arena where sticky candy (pink) slowly grows from the edges inward over 3 phases. Players must navigate shrinking safe zones, avoid sticky tiles, and use the "Smack" ability to temporarily clear candy.
### Phase System
| Phase | Start | Duration | Cell Growth (per tick) | Description |
|-------|-------|----------|----------------------|-------------|
| OPEN_ARENA (0) | 0s | 60s | 4-6 | "Outer Pressure" — candy pushes from perimeter |
| ROUTE_PRESSURE (1) | 60s | 60s | 6-8 | "Middle Pressure" — corridors tighten |
| SURVIVAL_ENDGAME (2) | 120s | 60s | 8-10 | "Inner Survival" — center fills in |
Each phase transition shrinks the arena bounds by removing outer layers (via `_shrink_arena()`).
### Growth Algorithm
Each tick (every `growth_interval` = 3s):
1. **Detect movement buffers** — identify critical corridor cells (#083)
2. **Generate candidates** — all SAFE cells scored by formula:
```
CandidateScore = LayerPriority + StickyNeighbor + InwardPressure
+ PlayerPressure + ClusterGrowth + CampingPressure
+ RandomNoise(-20..+20) + MovementBuffer + PathSafety + Repetition
```
3. **Weighted selection** — pick `_cells_this_tick()` cells via roulette wheel
4. **Path safety check** — `_apply_path_safety()` ensures no player gets fully trapped
5. **Telegraph** — amber warning overlay appears for 1s (cells still passable)
6. **Apply** — cells convert to permanent STICKY (pink overlay on Layer 2)
### Scoring Components
| Score Component | Range | Description |
|----------------|-------|-------------|
| `_score_layer_priority` | -40..+60 | Phase weight by ring (outer/middle/inner) |
| `_score_sticky_neighbor` | 0..+64 | +8 per adjacent sticky cell (cap +64) |
| `_score_inward_pressure` | 0..+30 | Push inward, scales with phase |
| `_score_player_pressure` | -50..+20 | 2-4 cells away +20; under player -50 (+10 in final 30s) |
| `_score_cluster_growth` | 0..+25 | +15 expansion, +25 bridge between clusters |
| `_score_camping_pressure` | 0..+60 | Per-region: >5s +20, >8s +40, >10s +60 |
| `_score_movement_buffer` | -40..0 | Hidden corridor buffers + player proximity floor |
| `_score_path_safety` | -100..0 | Soft penalty if selection would strand a player |
| `_score_repetition` | -30..0 | Penalty for cells near last tick's selection |
### Cell States
| State | Meaning | Passable? |
|-------|---------|-----------|
| SAFE | Normal floor | Yes |
| TELEGRAPHED | Amber warning (1s) | Yes |
| STICKY | Permanent candy overlay | No (slows) |
| BUBBLE_GROWING | Candy bubble expanding | No |
| BLOCKED | NPC zone / permanent obstacle | No |
### Candy Bubble System (#082)
Anti-camping hazard: grows 1x1 → 3x3 sticky area.
| Phase | Max Bubbles |
|-------|-------------|
| OPEN_ARENA | 0 (disabled) |
| ROUTE_PRESSURE | 2 |
| SURVIVAL_ENDGAME | 3 |
| Property | Value |
|----------|-------|
| Grow duration | 2.75s |
| Explosion radius | 1 (3x3) |
| Recent memory | 4 positions |
| Anti-stack radius | 3 (no bubbles within 3 of recent) |
### Camping Detection (#073)
Players tracked in 4x4 regions. Time accumulates while player stays in same region, resets on region change. Drives camping pressure in candidate scoring.
### Movement Buffers (#083)
Hidden per-cell penalties on critical corridor cells (chokepoints where removing the cell would isolate part of the arena). Decay over time and phase transitions so arena can still close in.
### Smack Mechanic
| Property | Value |
|----------|-------|
| Cooldown | 8s |
| Charge window | 3s |
| Effect | Clears nearby sticky? (consumes charge) |
Per-player cooldown/charge tracked in `smack_cooldowns` / `smack_charged` Dictionaries. Pink modulate during charge window, white on cooldown.
### Arena NPC
Candy Pump NPC at center (9,9) in a 3x3 blocked zone. Visual-only in v2 (projectile logic removed). Scattered projectiles still spawned for visual effect during telegraph phase.
### Slow-Mo
- Triggered conditionally, duration 4s
- `Engine.time_scale = 0.25` (1/4 speed)
- Restored to 1.0 in `_exit_tree()`
### Spawn Points
| Player Count | Positions |
|-------------|-----------|
| 4 | 4 corners: (1,1), (18,1), (1,18), (18,18) |
| 5-6 | 4 corners + top-mid (10,1) + bottom-mid (10,18) |
| 7-8 | 4 corners + all 4 mid-edges |
### RPCs
| RPC | Direction | Description |
|-----|-----------|-------------|
| `sync_phase(phase_index, phase_name)` | Authority → local | Phase change broadcast |
| `sync_arena_setup()` | Authority → remote | Arena dimensions + layout |
| `sync_growth_telegraph(cells)` | Authority → local | Show amber warning on selected cells |
| `sync_growth_apply(cells)` | Authority → local | Convert telegraphed to sticky |
| `consume_smack(pid)` | Any peer → local | Smack consumption + animation |
| `sync_stop_freeze` | (inherited from player.gd) | Freeze/unfreeze player |
### Key Functions
| Function | Description |
|----------|-------------|
| `initialize(main, grid)` | Connect to GoalsCycleManager |
| `start_game_mode()` | Activate client side, start OPEN_ARENA phase |
| `_setup_arena()` | Build 20x20, spawn Candy Pump NPC |
| `_process_growth_tick()` | One growth cycle: score → select → telegraph → apply |
| `_generate_candidates()` | Score all SAFE cells |
| `_calculate_candidate_score(pos, player_cells)` | Full formula with 10 components |
| `_select_cells_weighted(candidates, count)` | Roulette-wheel selection |
| `_apply_path_safety(selected)` | Filter: ensure no player stranded |
| `_try_spawn_bubble()` | Anti-camping bubble spawn attempt |
| `_update_camp_tracking(delta)` | Per-player region residency timer |
| `_detect_movement_buffers()` | Identify critical corridor chokepoints |
| `_shrink_arena()` | Remove outer arena layers on phase change |
| `_spawn_mission_tiles()` | Heart/Diamond/Star/Coin at 60% density |
| `get_spawn_points(player_count)` | Return spawn positions by player count |
| `has_smack_charged(pid)` / `consume_smack(pid)` | Smack mechanic |
| `_spawn_telegraph_highlight(pos)` | Amber glow visual (2-stage: build-up + flash) |
| `_spawn_impact_particles(targets)` | Candy splash particles on sticky impact |
| `_check_all_players_trapped()` | Re-evaluate sticky traps after growth apply |
[Back to top](#top)
## Scoring &amp; Leaderboard
| Action | Points |
|--------|--------|
| Complete goal pattern (match 3x3) | B1000 + time bonus |
| Tile match at cycle end (per tile) | B10 |
| Time bonus formula | `int(time_remaining * TIME_BONUS_MULTIPLIER)` — currently 0 (flat 1000) |
Leaderboard sorted descending. Stop n Go special case: winner (first to reach finish) placed at top regardless of score.
Leaderboard signal payload:
```
[{"peer_id": int, "score": int}, ...]
```
[Back to top](#top)
## Glossary
| Term | Definition |
|------|------------|
| Goal | 3x3 pattern (9 slots, some -1 for null) player must match on their playerboard |
| Playerboard | 5x5 virtual grid (12 usable slots, 13 hidden) per player |
| Tile | Grid item on Floor 1: Heart(7), Diamond(8), Star(9), Coin(10) |
| Holo Tile | Power-up tiles: Speed(11), Ghost(14) — consumed on pickup, not placed on board |
| Cycle | 30-second scoring round; ends with board clear + point conversion |
| Sticky | Permanent pink overlay on Gauntlet floor cells — blocks/slows movement |
| Telegraph | Amber 1-second warning before a cell becomes sticky |
| Safe Zone | Green tiles in Stop n Go STOP phase — only safe tile type |
| Portal | Colored door connecting rooms in Tekton Doors |
| Scarcity | Tile refill model controlling spawn weights based on mode config |
| Smack | Gauntlet ability: clear nearby sticky (8s cooldown, 3s charge window) |
| Camping | Player staying in same 4x4 region >5s, attracts growth pressure |
| Movement Buffer | Hidden chokepoint corridor that growth algorithm avoids sealing early |
| Chebyshev Distance | `max(|x1-x2|, |y1-y2|)` — used for all proximity calculations |
[Back to top](#top)
## File Index
| File | Lines | Role |
|------|-------|------|
| `scripts/game_mode.gd` | 41 | Mode enum + helper functions |
| `scripts/mode_config.gd` | 109 | Schema-driven per-mode settings |
| `scripts/managers/goal_manager.gd` | 108 | Goal generation + speed tracking |
| `scripts/managers/goals_cycle_manager.gd` | 520 | Timer, scoring, cycle control |
| `scripts/managers/player_race_manager.gd` | 133 | Per-player state: goals, board, pattern matching |
| `scripts/managers/playerboard_manager.gd` | 793 | Grab/put/arrange operations |
| `scripts/managers/turn_manager.gd` | 27 | Turn-based flow |
| `scripts/managers/stop_n_go_manager.gd` | 1107 | Stop n Go phase system, safe zones, HUD |
| `scripts/managers/portal_mode_manager.gd` | 585 | Tekton Doors room layout, portals, tiles |
| `scripts/managers/gauntlet_manager.gd` | 1825 | Candy Pump Survival growth, phases, smack |
| `scripts/portal_door.gd` | 136 | PortalDoor actor — detection, teleport, visuals |
| `scripts/managers/goals_cycle_manager.gd` | (shared) | Also referenced by gauntlet signal connections |
| `scripts/managers/camera_context_manager.gd` | ... | Camera mode changes per game mode? |
| `scripts/managers/player_movement_manager.gd` | ... | Movement restrictions per mode |
[Back to top](#top)
+16
View File
@@ -0,0 +1,16 @@
# Tekton Dash Armageddon
<a id="top"></a>
- [Game Modes](./Game-Modes.-) — Full per-mode reference: Stop n Go, Tekton Doors, Candy Pump Survival, Freemode
- [Architecture - Client](./Architecture-Client) — Godot client code structure, managers, scenes, player controller
- [Architecture - Server](./Architecture-Server) — Nakama Lua backend topology, auth flow, wallet economy, admin roles
- [Nakama Server API](./Nakama-Server-API) — Full per-function RPC reference with params, returns, errors
- [Patch Release Workflow](./Patch-Release-Workflow.-) — Hot patch and binary release CI/CD pipelines
- [Skin Creation Workflow](./Skin-Creation-Workflow.-) — Skin material authoring, catalog registration, gacha prizes
- [Nakama Deployment](./Nakama-Deployment.-) — Push Lua updates to Nakama server
- [SSH Setup — Linux](./SSH-Setup-Linux)
- [SSH Setup — macOS](./SSH-Setup-macOS)
- [SSH Setup — Windows](./SSH-Setup-Windows)
[Back to top](#top)
+322
View File
@@ -0,0 +1,322 @@
# Patch & Release Workflow
Complete guide for shipping updates to Tekton players — hot patches (`.pck`) for content changes and full binary releases for engine/platform changes.
---
## Overview
Two automated CI pipelines handle all distribution:
| Pipeline | Trigger | Output | Delivery |
|---|---|---|---|
| **Deploy Patch** (`deploy_patch.yml`) | Manual workflow dispatch | `patch.pck` + `version.json``patches` branch | Gitea raw endpoint |
| **Release** (`ci.yml`) | Git tag `v*` push | Windows/Linux/macOS `.zip` → Gitea Release | git.klud.top releases |
---
## Infrastructure
### Gitea instance
- **URL:** https://git.klud.top
- **API:** http://52.74.133.55:3000/api/v1
- **Runner:** Local Docker container (`gitea-runner`) via `docker-compose`
- **Cache volume:** `/home/dev/godot-cache``/cache` (rw) inside runner containers
- **Secret:** `TEKTON_RELEASE_TOKEN` — Token from user `adtpdn` with repo write access
### Patch serving
Patches served directly from Gitea's built-in raw file endpoint — no external CDN:
- Manifest: `https://git.klud.top/danchie/tekton/raw/branch/patches/version.json`
- PCK: `https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck`
Old `raw.klud.top` (gitea-pages container) retired — Gitea raw endpoint is faster, simpler, and always available.
### Release page
- **URL:** https://git.klud.top/danchie/tekton/releases
- Assets auto-uploaded by CI on tag push
---
## Part 1: Hot Patch (content-only updates)
Use when: script changes, UI tweaks, balance patches, asset replacements, config changes.
### Step-by-step
**1. Write changelog**
Edit `CHANGELOG_DRAFT.md` — add player-facing notes under `## [NEXT]`:
```markdown
## [NEXT]
- Fixed playerboard desync in multiplayer.
- Adjusted Gauntlet difficulty scaling.
```
If `[NEXT]` is missing, add the header. Format is markdown list items without leading dash (the tool strips it). Each line becomes a bullet on the patch notes page.
**2. Commit to `experimental`**
```bash
git add CHANGELOG_DRAFT.md
git commit -m "docs: patch notes for next release"
git push origin experimental
```
**3. Trigger patch deploy workflow**
Navigate to the Actions tab:
```
https://git.klud.top/danchie/tekton/actions
```
Click **Deploy Patch****Run workflow**:
| Field | Example |
|---|---|
| **Patch version** | `2.4.3` |
| **Release notes** | `fix: multiplayer desync, gauntlet balance` |
OR via API:
```bash
curl -X POST "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/workflows/deploy_patch.yml/dispatches" \
-H "Authorization: token $TEKTON_RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ref":"experimental","inputs":{"version":"2.4.3","notes":"fix: multiplayer desync, gauntlet balance"}}'
```
### What the CI does (deploy_patch.yml)
1. **Checkout**`git clone --depth 1` from `experimental` branch (shallow = fast).
2. **Setup Godot** — Uses cached `/cache/godot_4.7` binary. Downloads only if missing (140MB, cached forever).
3. **Generate version.json** — Runs `tools/generate_version_json.py --skip-changelog`. Reads version from `project.godot`, bumps patch number, writes `assets/data/version.json` with the new release entry including `pck_url` pointing to Gitea raw endpoint.
4. **Export patch PCK**`godot --headless --export-pack "Windows Desktop" build/patch.pck`. No export templates needed — `--export-pack` only packs resources, not binaries. Output ~10-15MB.
5. **Push to patches branch** — Force-pushes `patch.pck` + `version.json` to the `patches` branch of the repo.
### Verification
```bash
# Check manifest
curl -s "https://git.klud.top/danchie/tekton/raw/branch/patches/version.json"
# Expected: latest_version matches your patch number
# Check pck exists
curl -s -o /dev/null -w "%{http_code} %{size_download}B" \
"https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck"
# Expected: HTTP 200, size ~10-15MB
```
### How players receive patches
1. Game boots → `GameUpdateManager` fetches `version.json` from Gitea raw endpoint.
2. Compares `latest_version` against local version.
3. If remote is newer → downloads `patch.pck` to `user://patch.pck`.
4. Mounts with `ProjectSettings.load_resource_pack("user://patch.pck")`.
5. All files in `patch.pck` override base `res://` files in memory.
6. No files are overwritten on disk (safe rollback by deleting `patch.pck`).
---
## Part 2: Full Binary Release (platform updates)
Use when: engine upgrade, native plugin change, export template update, platform-specific build fix, or any change that needs a new `.exe`/`.app`.
### Step-by-step
**1. Ensure changelog is written**
Same as patch step 1 — `CHANGELOG_DRAFT.md` must have `## [NEXT]` entries. The CI auto-extracts them for the release body.
**2. Commit and tag**
```bash
# Commit all changes
git add -A
git commit -m "chore: bump to v2.4.3"
# Push to experimental
git push origin experimental
# Create and push tag
git tag v2.4.3 experimental
git push origin v2.4.3
```
**IMPORTANT:** Tag must match `v` + version format (e.g. `v2.4.3`). The CI is triggered by `v*` tags.
### What the CI does (ci.yml)
1. **Install tools**`apt-get install curl unzip zip` (zip was missing in early runs — make sure it's present).
2. **Checkout** — Full clone from tag (shallow not used — needs full history for changelog extraction, though `--depth 1` works too).
3. **Setup Godot with templates** — Caches both Godot binary (140MB) and export templates (1.3GB) in `/cache/`. Templates downloaded once per runner lifetime.
4. **Export 3 platforms:**
- **Windows** — `godot --headless --export-release "Windows Desktop"` → zipped with `zip`.
- **Linux/X11** — Same pattern.
- **macOS** — Export to `.zip` directly (Godot's macOS export produces a zip).
- **Note:** Steam DLLs copied into Windows build from `addons/godotsteam/`.
- **Note:** `|| true` on export commands masks Godot errors (e.g. GodotSteam plugin warnings). Real failures (missing `zip`) will surface.
5. **Extract changelog** — Parses `CHANGELOG_DRAFT.md` for the `## [version]` section matching the tag. Writes to `$CHANGELOG_BODY` env var.
6. **Create/Update Gitea Release** — Checks if release exists for tag. Creates new one with changelog as body if missing. Updates draft release if re-run.
7. **Upload assets** — Each `.zip` uploaded as release asset via multipart POST.
8. **Publish** — Sets `draft:false` to make release public.
### Verification
Check the release page:
```
https://git.klud.top/danchie/tekton/releases/tag/v2.4.3
```
Expected: 3 assets (Windows, Linux, macOS) with correct sizes, changelog body populated, release marked as published (not draft).
### Cleaning duplicate assets
If a tag was force-pushed, multiple CI runs may upload duplicate assets to the same release:
```bash
# List assets
curl "https://git.klud.top/api/v1/repos/danchie/tekton/releases/tags/v2.4.3" \
-H "Authorization: token $TEKTON_RELEASE_TOKEN" | jq '.assets[] | "\(.id): \(.name) \(.size/1024/1024)MiB"'
# Delete old duplicates (keep latest 3: Windows, Linux, macOS)
RELEASE_ID=<id>
curl -X DELETE "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets/$ASSET_ID" \
-H "Authorization: token $TEKTON_RELEASE_TOKEN"
```
### Cancelling stuck or duplicate runs
Gitea API cannot cancel in-progress runs. Wait for completion, then delete:
```bash
# List runs for a tag
curl "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/runs?page=1&limit=10" \
-H "Authorization: token $TEKTON_RELEASE_TOKEN" | jq '.workflow_runs[] | "\(.id): \(.status) \(.conclusion) \(.display_title)"'
# Delete completed run
curl -X DELETE "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/runs/$RUN_ID" \
-H "Authorization: token $TEKTON_RELEASE_TOKEN"
```
---
## Part 3: Agent-Automated Release
Agent (Hermes) can execute the full release flow from a single user request:
### Scenario: "Ship v2.4.3"
Agent actions:
1. Read `CHANGELOG_DRAFT.md` — verify `[NEXT]` has entries.
2. Check `project.godot` current version.
3. Commit changelog to `experimental`.
4. Create tag `v2.4.3` → push to trigger `ci.yml`.
5. Wait for CI completion (poll every 30s, up to 30 min).
6. If CI fails:
- Read job logs for failure reason.
- Fix the workflow file, commit, force-push tag.
- Clean up duplicate assets after re-run.
7. Verify release page has 3 assets with correct sizes.
8. If patch deploy also needed:
- Trigger `deploy_patch.yml` dispatch.
- Verify `patch.pck` is served and version.json updated.
### Scenario: "Quick hot patch"
Agent actions:
1. Check if `[NEXT]` has entries in `CHANGELOG_DRAFT.md`.
2. If empty, ask user for changelog notes.
3. Commit `CHANGELOG_DRAFT.md` to `experimental`.
4. Dispatch `deploy_patch.yml` workflow.
5. Verify patch files on Gitea raw endpoint.
---
## Troubleshooting
### `zip: command not found` in CI
Root cause: `ubuntu-latest` container doesn't have `zip` pre-installed. The install step must include `zip`:
```yaml
- name: Install tools
run: apt-get update -qq && apt-get install -y -qq curl unzip zip
```
### Godot export fails silently (`|| true`)
The `|| true` on export commands means a failed Godot export still shows step as success. Check:
- Is `godot_4.7` cached at `/cache/`?
- Does the export preset name match exactly? E.g. `"Windows Desktop"` must match `export_presets.cfg`.
- Is `addons/godotsteam/libgodotsteam*` present? Missing DLLs cause Godot to exit 1.
### Runner container can't clone repo
Runner uses HTTP auth with `god` username and `TEKTON_RELEASE_TOKEN` as password. If token is revoked:
1. Generate new token from Gitea → Settings → Applications.
2. Update secret `TEKTON_RELEASE_TOKEN` in repo Settings → Actions → Secrets.
3. Restart runner: `docker compose -f /home/dev/gitea/docker-compose.yml restart runner`.
### Runner shows "permission denied" for Docker socket
User `dev` doesn't have Docker socket access. Commands that touch Docker must be run via `sudo` or by the root user on the VPS. The local agent can only:
- Restart runner via systemd: `systemctl --user restart docker-runner` (if running as user service).
- No Docker CLI commands from agent terminal.
### Release has duplicate assets
Each CI run uploads assets as new entries. To clean:
- Get release ID from API.
- Delete old asset IDs keeping only latest (highest IDs) for each platform.
- Use jq or manual curl loop (see "Cleaning duplicate assets" above).
### Tag force-push creates redundant CI runs
Each push to a tag triggers `ci.yml`. Force-pushing a tag to a new commit creates another run:
- Previous runs keep running (can't cancel via API).
- Wait for all to finish, then delete stale ones.
- The last run to publish sets the release state.
Best practice: Delete old release before force-pushing tag, or at minimum delete stale completed runs after.
### Patch manifest not updating
`generate_version_json.py --skip-changelog` only bumps version and writes `version.json`. If the version didn't change (e.g. `--skip-changelog` with no `[NEXT]` entries), the script exits with code 0 but doesn't write anything. Verify `assets/data/version.json` has the new version after CI run.
### `gitea-pages` (raw.klud.top) returns 404
gitea-pages container uses a Gitea token to read files. If the token is dead:
- Switch to Gitea native raw endpoint: `https://git.klud.top/danchie/tekton/raw/branch/patches/...`
- Update `MANIFEST_URL` in `generate_version_json.py` and `VERSION_MANIFEST_URL` in `game_update_manager.gd`.
- Retire gitea-pages container entirely (not needed, Gitea has built-in raw serving).
---
## File Reference
| File | Purpose |
|---|---|
| `.gitea/workflows/deploy_patch.yml` | Patch deploy CI — generates pck + pushes to patches branch |
| `.gitea/workflows/ci.yml` | Full binary release CI — exports 3 platforms + creates release |
| `tools/generate_version_json.py` | Version bumping + changelog → version.json conversion |
| `CHANGELOG_DRAFT.md` | Human-readable changelog draft (source of truth for release notes) |
| `assets/data/version.json` | Machine-readable manifest served to players (auto-generated) |
| `scripts/managers/game_update_manager.gd` | Client-side update checker (reads version.json → downloads patch.pck) |
| `project.godot` | Godot project file (config/version = source of truth for version number) |
| `export_presets.cfg` | Export configuration for all platforms |
| `/home/dev/gitea/docker-compose.yml` | Runner container composition (cache volume mount: `/home/dev/godot-cache:/cache`) |
---
## Key Gotchas
- **`zip` must be in install step** — missing zip kills Windows/Linux export. Added in run 141 — do not remove.
- **Tag format is `vX.Y.Z`** — `ci.yml` trigger is `v*`. A tag without `v` prefix won't build.
- **Force-push tag = new CI run** — Always expect a new run on force-push. Old run keeps running.
- **Changelog extracted from tag version** — `## [X.Y.Z]` section in `CHANGELOG_DRAFT.md`. If section doesn't exist, release body is empty.
- **Patch deploy skips changelog clearing** — `--skip-changelog` means `version.json` is written but `CHANGELOG_DRAFT.md` is NOT modified. Only the full `ci.yml` pipeline clears it.
- **Cache is per-runner-host, not per-run** — Godot binary (140MB) and templates (1.3GB) download once on fresh runner container, then persist via `/cache` volume. Running `docker compose down` + `up` reuses cache if volume isn't deleted.
- **`|| true` masks Godot export errors** — If export fails silently, check the `2>&1 | tail -5` output in CI logs. Error messages like "Cannot call method 'queue_free' on a null value" from GodotSteam are non-fatal (cosmetic plugin warnings).
+257
View File
@@ -0,0 +1,257 @@
# Skin Creation Workflow
<a id="top"></a>
How to author a new character skin, register it in the game's shop/gacha catalog, and ship it to players.
---
## Overview
Each skin is defined in two places that must stay in sync:
| Layer | File | What it stores |
|---|---|---|
| **Client (visual)** | `scripts/managers/skin_manager.gd` | Mesh slots, material paths, override/overlay mode |
| **Server (shop)** | `server/nakama/lua/economy.lua` | Item ID, name, category, price (gold/star) |
The item `id` in `economy.lua` must match the dictionary key in `skin_manager.gd` `SKIN_CATALOG` exactly -- the Godot client looks up the item ID from the wallet/inventory and applies the matching skin data.
[Back to top](#top)
---
## Step 1: Create the Skin Material
Open the **Skin Shader Generator** at `res://scenes/tools/skin_shader_generator.tscn`.
1. Run the scene (F6).
2. Import your base albedo and mask textures (PNG with color/alpha channels).
3. Use the UI to visualize UV overlays and adjust color channels (Red, Green, Blue, Alpha).
4. Export the configured material as a `.tres` file into `assets/materials/skins/` or a subfolder:
- `assets/characters/skins/hat/`
- `assets/characters/skins/clothing/`
- `assets/characters/skins/gloves/`
**Material path conventions (Oldpop character):**
| Category | Example path |
|---|---|
| hat | `res://assets/characters/skins/hat/oldpop_mat_hat_blue.tres` |
| costume/clothing | `res://assets/characters/skins/clothing/oldpop_mat_cloth_red_pant.tres` |
| glove | `res://assets/characters/skins/gloves/oldpop_mat_gloves_blue.tres` |
| accessory | `res://assets/characters/skins/accessory/` |
[Back to top](#top)
---
## Step 2: Register the Skin in SkinManager (Client)
Open `res://scripts/managers/skin_manager.gd` and add a new entry inside `SKIN_CATALOG` (between `[BEGIN_SKIN_CATALOG]` and `[END_SKIN_CATALOG]` markers).
### Entry format
```gdscript
"item_id": {
"category": "head", # head | costume | glove | accessory
"character": "Oldpop", # node name under CharacterRoot
"slots": [
{
"mesh": "oldpop-hat1", # MeshInstance3D child name
"mode": "override", # "override" | "overlay"
"material": "res://path/to/material.tres"
},
]
}
```
### Slot modes
- **`override`** -- `set_surface_override_material(0, mat)`. Replaces the base material entirely. Preserves the outline shader (`next_pass`) automatically.
- **`overlay`** -- `material_overlay = mat`. Transparent layer on top of the base material. Good for costume/pant patterns.
### Multi-slot skins (costume example)
Costumes typically touch 3 meshes:
```gdscript
"oldpop-grey-pant": {
"category": "costume",
"character": "Oldpop",
"slots": [
{ "mesh": "oldpop-body", "mode": "overlay", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
{ "mesh": "oldpop-bottom1", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
{ "mesh": "oldpop-bottom2", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" },
]
},
```
### Tips
- Leave `"material"` as `""` if the `.tres` file is not ready yet. The slot is skipped gracefully.
- Use the **Skin Catalog Editor** (`res://scenes/tools/skin_catalog_editor.tscn`) to avoid manual edits. Click **Save & Generate** to rewrite both `skin_manager.gd` and `economy.lua`.
[Back to top](#top)
---
## Step 3: Register the Skin in Economy (Server)
Open `server/nakama/lua/economy.lua` and add a new entry to `SHOP_CATALOG_DEFS`.
### Catalog entry format
```lua
{ id = "oldpop-blue-hat", name = "Oldpop Blue Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" },
```
| Field | Type | Description |
|---|---|---|
| `id` | string | Must match the `SKIN_CATALOG` key in `skin_manager.gd` exactly |
| `name` | string | Display name shown in shop |
| `category` | string | `head` / `costume` / `glove` / `accessory` |
| `gold` | number | Gold coin price (0 = not sold for gold) |
| `star` | number | Star gem price (0 = not sold for stars) |
| `rarity` | string | `"Common"` / `"Uncommon"` / `"Rare"` -- cosmetic label only |
| `character` | string | Character this skin belongs to (e.g. `"Oldpop"`) |
### Existing catalog (12 items)
```
oldpop-blue-hat head 100 gold Common Oldpop
oldpop-green-hat head 100 gold Common Oldpop
oldpop-red-hat head 100 gold Common Oldpop
oldpop-yellow-hat head 100 gold Common Oldpop
oldpop-og-pant costume 0 gold Common Oldpop (free)
oldpop-grey-pant costume 150 gold Common Oldpop
oldpop-red-pant costume 150 gold Common Oldpop
oldpop-yellow-pant costume 150 gold Common Oldpop
oldpop-blue-gloves glove 75 gold Common Oldpop
oldpop-green-gloves glove 75 gold Common Oldpop
oldpop-red-gloves glove 75 gold Common Oldpop
oldpop-yellow-gloves glove 75 gold Common Oldpop
```
[Back to top](#top)
---
## Step 4 (Optional): Add Skin as Gacha Prize
Gacha-only skins are registered in `server/nakama/lua/gacha.lua` inside `GACHA_DATA.real_prize_catalog`.
### Existing gacha skins (4 items)
```lua
skin_gacha_rainbow_suit = { name = "Rainbow Suit", category = "costume", rarity = "real_prize", character = "" }
skin_gacha_dragon_hat = { name = "Dragon Hat", category = "head", rarity = "real_prize", character = "" }
skin_gacha_phantom_gloves = { name = "Phantom Gloves", category = "glove", rarity = "real_prize", character = "" }
skin_gacha_neon_acc = { name = "Neon Accessory", category = "accessory", rarity = "real_prize", character = "" }
```
Gacha skins also need a `skin_manager.gd` entry (same step 2 format) and a `skin_catalog_editor` entry so `SkinManager.apply_loadout()` can render them. The server catalog is optional -- gacha skins are not sold in the shop directly.
The gacha pulls these IDs from `GACHA_DATA.pools.real_prize`, so add your item_id there too.
```lua
pools = {
common = {"frag_common"},
uncommon = {"frag_uncommon"},
rare = {"frag_rare"},
real_prize = {
"skin_gacha_rainbow_suit",
"skin_gacha_dragon_hat",
"skin_gacha_phantom_gloves",
"skin_gacha_neon_acc",
-- add new skin here
}
},
```
[Back to top](#top)
---
## Step 5: Deploy
### Hot patch (content only) -- recommended for skins
1. Commit changes to `experimental` branch:
- `scripts/managers/skin_manager.gd`
- `server/nakama/lua/economy.lua` (if shop item)
- `server/nakama/lua/gacha.lua` (if gacha prize)
- Material `.tres` files
2. Push to `experimental`.
3. Trigger `deploy_patch.yml` via Gitea UI workflow dispatch.
- CI runs `--export-pack` to build `patch.pck`.
- CI force-pushes `patch.pck` + `version.json` to `patches` branch.
- Existing players auto-download on next boot via `GameUpdateManager`.
### Full binary release (if engine/templates changed)
Tag a version (e.g. `v2.5.0`) and push. CI builds all platform binaries and uploads to the release.
[Back to top](#top)
---
## Full Flow Diagram
```
┌──────────────────────┐
│ 1. Create Material │
│ skin_shader_generator │
│ ────────────────── │
│ Export .tres file │
└────────┬─────────────┘
v
┌──────────────────────────────┐
│ 2. Register in SkinManager │
│ skin_manager.gd │
│ ────────────────── │
│ Add SKIN_CATALOG entry │
│ (mesh slots + material) │
└────────┬─────────────────────┘
v
┌──────────────────────────────┐
│ 3. Register in Economy │
│ economy.lua │
│ ────────────────── │
│ Add SHOP_CATALOG_DEFS │
│ (price, name, category) │
└────────┬─────────────────────┘
v (optional)
┌──────────────────────────────┐
│ 4. Gacha Prize │
│ gacha.lua │
│ ────────────────── │
│ real_prize_catalog + pools │
└────────┬─────────────────────┘
v
┌──────────────────────┐
│ 5. Git Push & CI │
│ deploy_patch.yml │
│ ────────────────── │
│ patch.pck → players │
└──────────────────────┘
```
[Back to top](#top)
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Skin visible in editor but not in-game | Material path wrong or `.tres` not exported | Verify `res://` path in SKIN_CATALOG, run `--export-pack` |
| Skin purchase fails: "NotEnoughFunds" | Wallet balance insufficient | Check gold/star prices in economy.lua |
| Skin visible on all characters | `character` field wrong | Set correct character node name |
| Skin purchase fails: "ItemNotFound" | Item ID not in SHOP_CATALOG_DEFS | Add entry matching SKIN_CATALOG key |
| Player downloads patch but skin missing | econmy.lua change didn't reach server | Nakama hot-reload: restart Nakama container or wait for next restart |
| Outline shader lost on skin | next_pass not preserved | SkinManager preserves it automatically -- verify with latest `skin_manager.gd` |
[Back to top](#top)