Chickens Must Die.Technical docs / Godot client

Godot 4 / Source index

Client Code Reference

A file-by-file guide to the Godot client: configuration, networking, screen controllers, replicated entities, player components, UI, the Tiled map pipeline and scene resources.

GDScript + TypeScriptCode and scene catalogSource-reviewed scope

1. Reading this reference

This is a file-by-file reference for the application-owned GDScript and TypeScript code. Vendored addon internals are intentionally summarized only where the application calls them. Paths are relative to the repository root.

Status labels have the same meaning as in CLIENT_ARCHITECTURE.md: Implemented, Partial, and Not implemented describe the current source, not a runtime test result.

2. Global configuration and services

core/globals/Config.gd

Process-wide constants:

Symbol Current value Use
VERSION "1.0.0" Main-menu version label and log reports.
CLIENT_ENV DEVELOPMENT Selects endpoint, log behavior, and environment label.
GRID_SIZE 16 One grid step in the optional grid movement mode.
PLAYER_BASE_SPEED 100 Pixels per second for client-side simulation.
MOVEMENT_TYPE FREE Chooses free or grid local simulation.
LOG_API_URL https://chickensmustdie.com/api/client-logs Destination for critical reports.

ClientEnvEnum contains PRODUCTION and DEVELOPMENT. MovementType contains GRID and FREE.

core/globals/NetConfig.gd

Network and smoothing configuration:

Symbol Current value
Development endpoint http://172.22.111.106:8080
Production endpoint https://chickensmustdie.com
LOCAL_INTERPOLATION_SPEED 18.0
REMOTE_INTERPOLATION_SPEED 12.0
INPUT_SEND_INTERVAL 0.05 seconds
RECONCILIATION_ENABLED false
RECONCILIATION_INTERVAL 3.0 seconds
RECONCILIATION_DURATION 0.3 seconds
RECONCILIATION_IDLE_DELAY 0.15 seconds
RECONCILIATION_MIN_DISTANCE 0.5 pixels
RECONCILIATION_HARD_SNAP_DISTANCE 32.0 pixels

Debug state/message logging is disabled by default and additionally guarded by OS.is_debug_build(). State logging extracts the players key through should_log_state(); message logging uses should_log_message().

core/globals/NetworkManager.gd

Owns the Colyseus connection.

Connection state

enum ConnectionState {
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
    RECONNECTING,
    LEAVING,
}

Read-only computed properties:

  • is_in_room is true only in CONNECTED.
  • is_reconnecting is true only in RECONNECTING.

Public signals

Signal Emitted when
joined_room room.joined completes and callback adapter is created.
reconnecting(code, reason) The room emits dropped, unless an intentional leave is active.
reconnected The room emits reconnected.
game_session_ended Room state is cleaned up after a completed/locally detected leave.
connection_error(code, message) Join creation fails or the room reports a connection error/close during join.
server_config_message_received(payload) game_config message arrives. No current consumer.
server_tick_sync_message_received(payload) tick_sync message arrives. No current consumer.
server_chat_message_received(payload) chat_message arrives.
server_chat_error_received(payload) chat_error arrives.
server_player_eaten_message_received(payload) player_eaten arrives.

Public methods

  • join_game(nickname, color) guards for DISCONNECTED, calls join_or_create("my_room", options), configures reconnection, and connects room signals.
  • leave_game() accepts CONNECTED or RECONNECTING, disables SDK reconnection, calls room.leave(), and starts a 1.5-second verification. If disconnected, timeout completes local cleanup. If still connected, it retains the room, restores CONNECTED and reconnection options, logs a critical error and permits another leave attempt.
  • send_message(message_type, payload) sends only while is_in_room.
  • send_chat_message(message) wraps send_message("chat_send", message) and is used by the chat controller.

Join options are exactly:

{
    "nickname": nickname,
    "playerColor": color,
}

Reconnection is configured for eight retries, 250 ms initial/minimum delay, 3,000 ms maximum delay, and a ten-message SDK queue. The application-level send guard does not submit messages while RECONNECTING, so the SDK queue is not used by current gameplay sends during that state.

On join-time room.error, the code invokes _cleanup_room() before emitting connection_error; retry is therefore possible. Cleanup disconnects the manager's old room signals. Errors outside CONNECTING are reported without changing the state. Missing GDExtension or callback adapter produces an error instead of entering gameplay with an unusable client.

core/globals/GameLogger.gd

Provides debug, info, success, warn, error, and critical methods. Every entry includes:

timestamp, level, message, version, environment, source, context

In development, entries are printed, emitted through log_added, and retained in a maximum 100-entry history. get_history() returns a deep duplicate; clear_history() empties it.

Only critical() calls _enqueue_report(). Critical reports are POSTed as JSON through a runtime-created HTTPRequest with a six-second timeout. The reporter limits itself to five reports per 60 seconds, holds at most 20 queued entries, and replaces context when the serialized body exceeds 8,192 characters. A failed report is logged in development and discarded; there is no retry or disk spool.

network_state(callable) and network_message(type, payload) implement interval/type filtering from NetConfig.

core/globals/SceneManager.gd

Defines paths for the game, main menu, join, splash, local player, and remote player scenes.

  • load_scene(path) performs an immediate change_scene_to_file, reports errors with push_error, and returns the Error code.
  • load_game() requires a room and callback adapter, loads game.tscn, and leaves the session if loading fails. The join screen schedules this method with call_deferred().
  • load_scene_with_swipe(path, direction) guards re-entry with is_transitioning, animates the global swipe overlay, changes scene, waits one process frame, and removes the overlay. This method is implemented but unused.
  • _on_game_sessions_ended() returns to the main menu.

core/globals/GlobalEffects.gd

Creates a CanvasLayer at layer 100 and two runtime ColorRect overlays.

  • fade_out(duration) fades black to alpha 1 and blocks mouse input.
  • fade_in(duration) fades to alpha 0, hides the overlay, and restores ignored mouse input.
  • swipe_in(direction, duration) brings a dark panel from left or right and triggers a damped impact.
  • swipe_out(direction, duration) sends it offscreen and resets offsets.

Fade is used by the splash-to-menu transition. Swipe is available through SceneManager but currently unused. Effect waits also finish when a tween is killed; an interrupted exit does not hide a newer overlay. A newer direct scene request supersedes a pending swipe scene change.

core/globals/SettingsManager.gd

Owns normalized master_volume, music_volume, and sfx_volume, each defaulting to 1.0.

At startup it reads user://settings.cfg, then applies values to the Master, Music, and SFX buses. Setters clamp to [0, 1]; values at or below 0.001 mute the bus, while positive values unmute it and apply linear_to_db(value).

save_settings() writes only the three volume values. Mute remains runtime-only; set_master_muted(), is_master_muted() and master_mute_changed keep the button synchronized with Master slider changes. Invalid/non-finite stored values fall back to full volume.

core/globals/MusicManager.gd and .tscn

The track dictionary currently contains only:

&"menu": preload("res://assets/music/time_for_adventure.mp3")

Two AudioStreamPlayer nodes on the Music bus enable crossfading. play_track() prevents restarting the current playing track, alternates players, starts the new track at -40 dB, and fades both in parallel. stop_music() fades and stops both players, including an interrupted crossfade. A non-positive stop duration stops both immediately.

No scene stops menu music when leaving the menu, so it continues through join and gameplay unless another caller changes it.

core/globals/UIAudioManager.gd and .tscn

Contains one AudioStreamPlayer with click1.wav, -15 dB volume, and the SFX bus. play_click(start_from) is used by menu, join, settings, and character-selection buttons.

3. Screen controllers

core/screens/menu_screen/main_menu_screen.gd

On ready:

  • starts the menu track;
  • centers pivots for the OPTIONS button and logo;
  • displays Version 1.0.0.

Handlers:

  • START loads the join scene;
  • OPTIONS plays a click, springs the button scale, and opens the menu-local settings panel;
  • EXIT calls get_tree().quit();
  • logo hover springs its scale;
  • left-clicking the logo loads the splash scene.

The LEADERBOARD and CREDITS buttons have no scene connections and no script handlers.

core/screens/join_screen/join_screen.gd

Coordinates nickname entry, selected skin, and network lifecycle.

join() trims the nickname, rejects empty input and non-DISCONNECTED state, then calls NetworkManager.join_game(). Nickname, selection, JOIN and BACK are disabled during the request because the SDK wrapper exposes no matchmaking cancellation API. joined_room schedules gameplay loading. connection_error restores the form only after the manager returns to DISCONNECTED. BACK returns to the menu when disconnected.

The nickname control has no client-side max_length in its scene. Character, length, uniqueness, and moderation validation are backend responsibilities.

core/screens/join_screen/character_preview.gd (CharacterPreview)

Defines four selectable keys and display names:

Key Display name
chicken_1 White Chicken
chicken_2 Black Chicken
chicken_3 Dark Brown Chicken
chicken_4 Light Brown Chicken

The preview starts at index 0 and cycles STAND -> IDLE -> WALK -> STAND with default durations of one, three, and three seconds respectively. Previous/next selection wraps around. Animation names are assembled as <key>_<walk|stand|idle> and checked before playback.

The serialized scene initially contains a different animation/label, but _ready() calls show_current_character(), making White Chicken and chicken_1_stand the effective initial state.

core/screens/splash_screen/splash_screen.gd

Plays the chickens-text AnimationPlayer sequence. When it finishes, the controller fades to black, loads the main menu, then fades in. The explosion_sprite field is declared but not referenced by the script; the scene animation itself triggers the explosion and fireworks.

The animation lasts about 2.03 seconds and sequences four splash sounds plus three logo words, an explosion, and fireworks.

4. Gameplay composition and replicated entity managers

game/game.gd

Requires a room and callback adapter; otherwise it schedules a return to the menu. On ready it:

  1. initializes PlayerManager with NetworkManager.callbacks and room.get_session_id();
  2. connects to room.state_changed;
  3. defers one initial _on_state_changed() call.

Each state application reads a dictionary and dispatches collections with safe empty defaults:

player_manager.apply_server_state(state.get("players", {}))
seed_manager.reconcile(state.get("seeds", {}))

The scene holds its own room reference, ignores stale/disconnected state and non-dictionary collections, reapplies the snapshot on reconnect, and disconnects its subscriptions on exit.

game/managers/player_manager.gd (PlayerManager)

Maintains players: Dictionary, keyed by session ID.

initialize() registers Colyseus on_add and on_remove callbacks for players, retaining their handles for removal on reinitialization or tree exit. _on_player_add() instantiates the local scene when the ID equals the room session ID, otherwise the remote scene, adds it to the tree, stores it, and calls setup(). _on_player_remove() queues the node for deletion and erases its dictionary entry.

apply_server_state() updates existing nodes, creates any missing node, and removes local entries absent from the server snapshot. This makes full state changes authoritative even if a collection callback is missed or arrives in a different order.

game/managers/seed_manager.gd (SeedManager)

Maintains seeds: Dictionary, keyed by seed ID. reconcile() performs a complete diff:

  • instantiate actors/seed/seed.tscn for new IDs;
  • call apply_server_state() for existing IDs;
  • queue and erase IDs no longer present.

game/managers/seed_manager.tscn is an empty node without the script and is not referenced. The active gameplay scene attaches seed_manager.gd directly to its own SeedManager node.

game/chat/chat_controller.gd

Bridges network signals and ChatPanel:

  • chat_message -> display_message(payload);
  • chat_error -> display_error(payload);
  • message_send_requested(text) -> send_chat_message({"text": text}).

Reconnect signals control the panel's submission flag. Text entered while reconnecting is retained instead of being cleared without a send.

5. Player classes

actors/player/base_player.gd (BasePlayer)

Replicated fields:

  • nickname;
  • session_id;
  • player_color;
  • current_mass.

setup(player_state, player_session_id) copies nickname, playerColor, x, and y, then applies skin, label, and mass once the node is ready. Because PlayerManager calls add_child() before setup(), the node is ready in the normal path.

apply_server_state() applies mass then delegates position handling to _apply_server_position(), which is a no-op hook in the base class.

Mass changes use these exact formulas:

var radius := sqrt(mass) * 2.3
var scale_factor := sqrt(mass / 5.0)
player_sprite.scale = Vector2.ONE * (0.5 * scale_factor)
player_name.position.y = 7.5 * scale_factor - 4.1

The collision shape is a per-scene CircleShape2D, so changing one player does not mutate all players. Non-positive/non-finite mass is ignored before sqrt().

actors/player/local_player.gd (LocalPlayer)

Owns input, optional local movement, input transmission, one of two server-position handling modes, the camera, and eating audio.

On every physics frame:

  1. read input unless text editing has focus;
  2. if reconciliation is enabled, simulate locally; otherwise interpolate toward the latest server target;
  3. update animation from the input direction;
  4. send changed input or heartbeat;
  5. if enabled, run reconciliation.

When player_eaten arrives, it reads predatorSessionId; only the matching local predator plays crunch.wav, with random pitch in [0.8, 1.2].

mass_changed is connected to CameraController.set_mass before super.setup() applies the initial mass. Initial and later mass changes use the same zoom calculation.

actors/player/remote_player.gd (RemotePlayer)

Always uses PositionInterpolationController at REMOTE_INTERPOLATION_SPEED. Each physics frame returns the normalized visual movement direction and feeds it to the shared animation controller.

actors/seed/seed.gd (Seed)

Sets absolute z-index 100 and applies x/y from a dictionary, defaulting absent coordinates to zero. It has no collision, pickup, animation, or local game logic.

6. Player components

actors/components/movement/movement_input.gd (MovementInput)

Returns zero while a LineEdit or TextEdit owns GUI focus. Otherwise it calls Input.get_vector() for move_left/right/up/down, then reduces each component to its sign. Keyboard input supports WASD and arrow keys. Analog magnitude is intentionally discarded if another input device is later mapped.

actors/components/movement/movement_controller.gd (MovementController)

Clamps direction components to -1, 0, or 1 and selects movement mode from Config.

Free movement normalizes diagonals, multiplies by 100 px/s and non-negative delta, then calls move_and_collide on X followed by Y. Axis separation provides sliding against rectangular obstacles.

Grid movement advances toward a persistent target one 16-pixel step at a time. It uses test_move() before beginning a step and directly updates global_position while advancing. Diagonal starts additionally require both horizontal and vertical cardinal traversals to be clear. This path is disabled and explicitly marked for verification.

actors/components/network/network_input_sender.gd (NetworkInputSender)

Tracks a monotonically increasing per-instance input_seq. It sends immediately when direction changes or when the 0.05-second heartbeat expires.

Current payload:

{
    "seq": input_seq,
    "clientTime": Time.get_ticks_msec(),
    "input": {
        "left": direction.x < 0,
        "right": direction.x > 0,
        "up": direction.y < 0,
        "down": direction.y > 0,
    }
}

clientTime is monotonic process time in milliseconds, not Unix time. The client never receives or processes an acknowledged sequence number.

actors/components/network/position_interpolation_controller.gd (PositionInterpolationController)

Stores a target, interpolation speed, and snap distance (default 100 px). update():

  • snaps at or beyond the threshold;
  • otherwise uses frame-rate-independent exponential smoothing: 1 - exp(-speed * delta);
  • snaps the final tiny residual when movement squared is at most 0.000001;
  • returns a normalized movement direction for animation.

It assigns global_position and does not perform collision checks.

actors/components/network/reconciliation_controller.gd (ReconciliationController)

Stores the most recent server position. When enabled, it:

  • cancels correction whenever input is non-zero;
  • waits for the three-second reconciliation interval and 0.15 seconds of idle time;
  • ignores error up to 0.5 px;
  • hard-snaps at or beyond 32 px;
  • otherwise distributes correction over 0.3 seconds using axis-separated move_and_collide().

This is a simple idle correction, not input-history reconciliation. It has no server tick mapping, sequence acknowledgement, rewind, or replay.

actors/components/animation/animation_controller.gd (PlayerAnimationController)

Builds animation names from skin_key plus walk, stand, or idle. Horizontal direction flips the sprite: right sets flip_h = true, left sets it false. Zero direction shows stand for three seconds, then idle. Missing animations generate a warning without changing to a fallback.

actors/components/camera/camera_controller.gd (CameraController)

Attached directly to the local Camera2D. set_mass() maps mass 5 -> 200 to zoom 3 -> 2, clamped at both ends. _process() exponentially smooths to the target with speed 2.

The camera scene also has bounds 0,0 to 800,608, position smoothing enabled at speed 3, and an initial position of (-3, 0). Camera mass constants are local exports and marked in source to come from the server in the future.

7. UI scripts

ui/chat/chat_panel.gd (ChatPanel)

Emits message_send_requested(message) after trimming and rejecting empty input. The scene limits the LineEdit to 200 characters. Successful local submission clears and unfocuses the input; it does not optimistically add a message.

Incoming dictionary messages instantiate chat_message.tscn, use defaults for absent fields, and coalesce scrolling into one request per frame. Errors display payload.code for three seconds using a restarted child timer, so an earlier error cannot hide a newer one. Non-dictionary payloads are ignored.

The panel starts at alpha 0.3, becomes fully opaque on focus/send/message/error, waits eight seconds, then fades back over 0.4 seconds. Escape releases chat focus. A left click outside a focused message input also releases it.

ui/chat/chat_message.gd (ChatMessage)

Displays [HH:MM], nickname, and message. sentAt is interpreted as Unix milliseconds, divided by 1,000, offset by the current system time-zone bias, and formatted with Godot's Unix-time converter.

ui/settings/settings_menu.gd (SettingsMenu)

Synchronizes three 0–100 sliders with SettingsManager on ready and every open(). Slider changes apply audio immediately. close() saves the three values and hides the overlay. BACK plays a click then closes.

LEAVE accepts connected or reconnecting sessions; it clicks, saves/closes, and calls NetworkManager.leave_game(). Session end also closes an open settings overlay.

ui/buttons/leave_button/leave_button.gd

Shows the button only while the client is connected. It reacts to joined_room and game_session_ended. Click behavior is owned by the parent SettingsMenu, which connects the button's pressed signal in code.

ui/buttons/mute_button/mute_button.gd

Reads Master mute through SettingsManager, mirrors it into a toggle button, and swaps icon/tooltip. Changes from the slider or toggle are synchronized through master_mute_changed. Toggling does not update or save master_volume.

ui/buttons/settings_button/settings_button.gd

Fetches the settings panel specifically from the GlobalUi autoload and opens it on the scene-connected pressed signal.

ui/cursor/mouse_cursor.gd

Installs the normal custom cursor at startup and swaps to a pressed texture while the left mouse button is down. The configured hotspot is (0, 0).

ui/effects/parallax_background.gd (Parallax)

Expands a TextureRect by 30 pixels on all sides, waits one layout frame, then moves it opposite to normalized mouse position. Default maximum movement is (16, 10) px and exponential follow speed is 4.

8. Map code

world/map/map_loader.gd

Loads res://world/map/map.client.json synchronously in _ready().

The loader:

  1. checks file existence, readability, non-empty text, valid JSON, and dictionary root;
  2. validates top-level tile dimensions and tileset presence;
  3. creates a runtime TileSet and one TileSetAtlasSource per compiled tileset;
  4. creates every atlas tile from 0 to tileCount - 1;
  5. creates one TileMapLayer per compiled layer with visibility, opacity, and order/z-index;
  6. inserts cells and translates flipH, flipV, and transpose flags to Godot alternatives;
  7. creates a StaticBody2D containing compiled rectangular collisions;
  8. creates four boundary walls from worldWidth and worldHeight.

Critical early-stage failures stop _ready() before dependent stages. Missing layer/collision/boundary fields report errors inside their stage but do not roll back nodes created by previous stages. The loader does not validate the compiled schema's version field.

world/map/compile-map-client.ts

Standalone TypeScript compiler for Tiled JSON. It exports compileClientMap(map) and runs CLI main() only when invoked directly. Importing it performs no CLI file operations. Tilesets are sorted once per compilation while preserving original source IDs.

Validation supports only:

  • type: "map";
  • orthogonal, finite maps;
  • atlas tilesets (columns > 0);
  • complete finite tile-layer arrays;
  • non-rotated rectangular collision objects on object layers named collision case-insensitively.

It removes empty GIDs, decodes Tiled H/V/diagonal transform flags, resolves the applicable tileset by firstgid, converts GIDs to atlas coordinates, retains layer visibility/opacity/order, and emits world dimensions. Texture paths are rewritten to res://assets/sprites/village/<image filename>.

The compiler rejects ellipses, points, polygons, polylines, rotated rectangles, invalid rectangle dimensions, unresolved GIDs, and out-of-range local tile IDs. It does not validate collision rectangles against world bounds.

9. Scene catalog

Scene Root / role Used by
core/screens/menu_screen/main_menu_screen.tscn Control; configured main scene. Project startup.
core/screens/join_screen/join_screen.tscn Control; nickname and skin selection. Main menu START.
core/screens/splash_screen/splash_screen.tscn Node2D; animated logo intro. Main-menu logo click.
game/game.tscn Node2D; gameplay composition. Successful join.
world/world.tscn Node2D; map-loader host. game.tscn.
actors/player/base_player.tscn CharacterBody2D; shared player visuals/collider. Inherited by local/remote scenes.
actors/player/local_player.tscn Inherited local player with controls/camera/audio. PlayerManager.
actors/player/remote_player.tscn Inherited remote interpolated player. PlayerManager.
actors/seed/seed.tscn Node2D; one seed sprite. SeedManager.
game/managers/seed_manager.tscn Empty Node2D placeholder. Unused.
ui/chat/chat_panel.tscn CanvasLayer; chat input/history. game.tscn.
ui/chat/chat_message.tscn One formatted chat row. ChatPanel.
ui/settings/settings_menu.tscn Full-screen audio/leave overlay. Main menu and GlobalUi.
ui/globals/global_ui.tscn Process-wide CanvasLayer. Autoload.
ui/buttons/settings_button/settings_button.tscn Global settings icon. GlobalUi.
ui/buttons/mute_button/mute_button.tscn Global mute toggle. GlobalUi.
ui/buttons/leave_button/leave_button.tscn Conditional leave button. Settings menu.
ui/cursor/mouse_cursor.tscn Cursor texture controller. GlobalUi.
core/globals/MusicManager.tscn Two music players. Autoload.
core/globals/UIAudioManager.tscn Shared click player. Autoload.

Three temporary remote-player scene files are present in actors/player/ with .tmp suffixes. They are not referenced by current application scenes and are not part of the scene catalog.

10. Audio bus reference

default_bus_layout.tres adds two buses under Godot's implicit Master bus:

Bus Current users
Music Both MusicManager players.
SFX UiAudioManager/ClickSound.
Master Splash AudioPlayer, local EatSound, and any stream without an explicit bus.

This routing means the SFX slider does not currently affect the eating or splash sounds.