Chickens Must Die.Technical docs / Godot client

Godot 4 / developer guide

Client Development & Testing

Set up and extend the Godot client, compile the Tiled map, diagnose networking, and test the game. This guide distinguishes what is in the repository from work that still needs implementation or verification.

Godot 4.7 feature versionCompatibility rendererColyseus GDExtension

1. Repository prerequisites

The client project declares Godot feature version 4.7 and uses the Compatibility renderer. Use a Godot build capable of opening that project format and loading the included native Colyseus GDExtension.

The repository already contains:

  • the Godot project and imported-source assets;
  • Colyseus native libraries under addons/colyseus/bin/;
  • the Damped Oscillator addon;
  • Web and Windows Desktop export presets;
  • source and compiled forms of one Tiled map.

It does not contain:

  • the Colyseus backend;
  • a package manifest or pinned TypeScript toolchain for the map compiler;
  • automated tests;
  • CI configuration;
  • configured export presets for Linux, macOS, Android, or iOS.

The project can display menu/join/splash UI without a backend, but its normal gameplay transition requires a successful room join.

2. Initial setup

  1. Open project.godot in the appropriate Godot editor.

  2. Confirm the Colyseus SDK and Damped Oscillator plugins are enabled. They are listed in project.godot.

  3. Provide a compatible backend implementing the contract in Network Protocol.

  4. Set the intended environment and endpoint in core/globals/Config.gd and core/globals/NetConfig.gd.

  5. Confirm the backend exposes a room named my_room.

  6. Use the project main scene to begin at the main menu.

Current checked-in network selection:

Config.CLIENT_ENV = DEVELOPMENT
Development URL    = http://172.22.111.106:8080
Production URL     = https://chickensmustdie.com

Do not assume the development address is reachable from another machine, container, phone, or browser. It is a hard-coded LAN/private address in the current source.

3. Configuration guide

General gameplay

Edit core/globals/Config.gd:

Setting Effect
VERSION UI version and log report version.
CLIENT_ENV Endpoint branch and development log behavior.
GRID_SIZE Step size for grid simulation only.
PLAYER_BASE_SPEED Local simulation speed when prediction/reconciliation is enabled.
MOVEMENT_TYPE FREE or GRID for local simulation.
LOG_API_URL Critical-report HTTP endpoint.

Network tuning

Edit core/globals/NetConfig.gd:

  • LOCAL_INTERPOLATION_SPEED and REMOTE_INTERPOLATION_SPEED control exponential visual convergence.
  • INPUT_SEND_INTERVAL controls heartbeat spacing; direction changes still send immediately.
  • RECONCILIATION_ENABLED switches the local player from interpolation to local simulation plus idle correction.
  • reconciliation interval, duration, idle delay, minimum error, and hard-snap distance tune that partial feature.
  • debug state/message flags and intervals control filtered development logs.

Changing movement or reconciliation constants without matching backend behavior can increase visible correction and collision disagreement.

Camera and mass

Mass formulas are constants in actors/player/base_player.gd. Camera mass and zoom thresholds are exported values on the local Camera2D scene. The source contains a TODO to obtain camera/gameplay mass configuration from the server, but no consumer exists today.

Audio

default_bus_layout.tres defines Music and SFX under Master. Settings persist to user://settings.cfg, not the repository. To make a sound respond to the SFX slider, its AudioStreamPlayer.bus must explicitly be SFX; the current eating and splash players do not do this.

4. Development conventions visible in the code

  • Global services are autoloads; scene-specific orchestration remains on scene roots.
  • Shared player behavior is split into typed class_name components.
  • Network lifecycle is exposed through signals instead of direct UI references.
  • Replicated entities are stored in dictionaries keyed by server IDs.
  • Full server collections are treated as authoritative and reconciled against local nodes.
  • Scene node references use typed @onready fields and unique-name %Node lookup where configured.
  • Runtime tuning is concentrated in Config, NetConfig, exported camera fields, and a small number of player constants.
  • Animation names follow <skin>_<state>.
  • Logs should go through GameLogger when they represent application/network events.

There is no enforced formatter, linter configuration, static-analysis script, or CI gate in the repository. Existing code style is not completely uniform, so follow the surrounding file and preserve typed signatures where practical.

5. Common extension recipes

Add a chicken skin

  1. Add its sprite sheet to assets/sprites/characters/chickens/.

  2. Add walk, stand, and idle animations to both base_player.tscn and the join preview's SpriteFrames.

  3. Use exact names <new_key>_walk, <new_key>_stand, and <new_key>_idle.

  4. Add the same key and display name at matching indexes in CharacterPreview.CHARACTER_KEYS and CHARACTER_NAMES.

  5. Ensure the backend accepts the join value and republishes it as playerColor.

  6. Test preview cycling plus local and remote rendering.

If any animation is missing, the runtime controller warns and keeps the previous visual state.

Add a server message

Keep transport routing in NetworkManager and feature behavior in a consumer:

# NetworkManager.gd
signal server_example_message_received(payload: Variant)
func _route_server_message(message_type: Variant, payload: Variant) -> void:
    match str(message_type):
        "example":
            server_example_message_received.emit(payload)

Then connect from the relevant scene/controller in _ready(). Avoid making NetworkManager depend on a node that exists only in one screen. Define payload requirements in the protocol documentation and handle absent/invalid fields deliberately.

Add an outgoing message

Call NetworkManager.send_message(type, payload). It already guards for the CONNECTED state. Decide explicitly whether the message may be lost during reconnection: current sends are discarded rather than queued by the application.

Add a replicated collection

Follow the PlayerManager or SeedManager pattern:

  1. create a small entity scene with setup(state) and apply_server_state(state);

  2. preload it in a manager;

  3. store instances by stable server ID;

  4. spawn missing IDs;

  5. update existing IDs;

  6. queue and erase stale IDs;

  7. call the manager from Game._on_state_changed();

  8. add Colyseus collection callbacks only if immediate add/remove behavior is useful.

Full snapshots should remain authoritative unless the protocol is deliberately changed.

Add a music track

Add a preload to MusicManager.TRACKS:

const TRACKS: Dictionary = {
    &"menu": preload("res://assets/music/time_for_adventure.mp3"),
    &"game": preload("res://assets/music/new_track.ogg"),
}

Call MusicManager.play_track(&"game") from the owning screen lifecycle and decide when to stop or restore it. Both internal players already use the Music bus.

Add an SFX sound

Create or configure an AudioStreamPlayer and set bus = &"SFX" in the scene if it must follow the SFX slider. The current eating sound is a useful structural example but is routed to Master, not SFX.

Add a screen

  1. Create the .tscn and controller.

  2. Add its path to SceneManager if it is a first-class route.

  3. Choose direct load_scene() or the currently-unused load_scene_with_swipe().

  4. Connect transitions once, either in the scene or code.

  5. Verify interaction with the persistent GlobalUi layer and its settings overlay.

Complete leaderboard or credits

The menu buttons already exist, but they have no signal connections or handlers. A real implementation must add the destination UI/data source and explicitly wire the buttons. Their presence alone is not a feature stub with behavior.

6. Map workflow

Files

File Purpose
world/map/map.json Source export from Tiled.
world/map/compile-map-client.ts Validator/compiler.
world/map/map.client.json Checked-in runtime format.
world/map/map_loader.gd Godot runtime builder.
world/world.tscn Map-loader host scene.

Current artifact

Metric Value
Tiled version 1.11.2
Tiled format 1.10
Orientation Orthogonal
Finite Yes
Grid 50 x 38
Tile size 16 x 16 px
World size 800 x 608 px
Atlas tilesets 20
Render layers Ground, Objects, Buildings
Non-empty cells 1,689 + 367 + 56 = 2,112
Collision rectangles 9

Compiler constraints

Use finite orthogonal tile maps, atlas tilesets, and complete tile-layer arrays. Put collision rectangles on an object layer named Collision (case-insensitive). Collision objects must be unrotated rectangles with positive dimensions. Ellipses, points, polygons, polylines, infinite chunks, and collection-of-images tilesets are unsupported.

All tileset images are rewritten to:

res://assets/sprites/village/<source image filename>

The repository has no package.json or pinned runner. With a Node version that supports TypeScript type stripping, the intended command shape is:

node --experimental-strip-types world/map/compile-map-client.ts world/map/map.json world/map/map.client.json

A configured tsx, ts-node, or tsc workflow can also execute/compile the file. Pin one before relying on it in CI.

Updating the map

  1. Edit/export the finite Tiled JSON.

  2. Keep referenced atlas image filenames available under assets/sprites/village/.

  3. Compile to world/map/map.client.json.

  4. Review the compiler summary and diff the generated JSON.

  5. Confirm the output remains version 1 and contains expected dimensions/layers/collisions.

  6. Update the local camera limits in local_player.tscn if world dimensions change.

  7. Update the backend's collision and boundary model to the same geometry.

  8. Manually verify rendering order, transform flags, obstacles, boundaries, and server spawn points.

The compiler creates directories and overwrites its output path. Keep the output target explicit.

7. Logging and network diagnostics

Development logs

GameLogger includes source location and retains 100 entries when Config.CLIENT_ENV is DEVELOPMENT. Use get_history() only for diagnostic UI/tools; it returns a deep copy.

State logging

Enable in NetConfig.gd:

const DEBUG_STATE_LOG_ENABLED := true
const DEBUG_STATE_INTERVAL_MS := 5000
const DEBUG_STATE_LOG_KEY := "players"

It works only in a debug build. An empty key logs the complete state; a non-empty missing key emits a warning.

Message logging

Enable DEBUG_MESSAGE_LOG_ENABLED. Set DEBUG_MESSAGE_TYPE to an exact type string to filter, or leave it empty for all messages. DEBUG_MESSAGE_INTERVAL_MS throttles globally, not separately per message type.

Diagnostic order

For a failed join or missing gameplay state, check:

  1. Config.CLIENT_ENV and the selected endpoint;

  2. whether my_room exists;

  3. join option names nickname and playerColor;

  4. transition DISCONNECTED -> CONNECTING -> CONNECTED;

  5. joined_room and creation of NetworkManager.callbacks;

  6. players and seeds collection shapes;

  7. local session ID matching the player collection key;

  8. state/message debug output;

  9. missing animation warnings or malformed numeric fields.

For movement disagreement, additionally compare backend speed, diagonal normalization, map geometry, world bounds, and update rate. Remember that default local behavior is interpolation, not local simulation.

Critical reporting

Only GameLogger.critical() sends HTTP. Review privacy and authentication requirements before adding user data to context; the current report is JSON sent to Config.LOG_API_URL with no application-level authorization header.

8. Testing strategy

Current automated-test status

No application test files, test framework configuration, or CI workflow were found. The map compiler exports a pure compileClientMap() function, which is suitable for unit tests, but none are included.

Until automation is added, changes require structured manual regression. Source review alone cannot establish that a native GDExtension, backend contract, renderer, or export target works at runtime.

Minimum offline UI smoke test

This subset does not require a backend:

  • main menu loads and displays Version 1.0.0;
  • menu music starts once and does not restart on a repeated same-track call;
  • parallax follows the mouse without exposing background edges;
  • OPTIONS/global settings open and close;
  • all three sliders change their intended buses;
  • closing settings persists values across application restart;
  • mute icon and Master bus agree at startup and after toggling;
  • cursor swaps while the left button is held;
  • START opens join; BACK returns to menu;
  • all four skins show the correct name and cycle stand/idle/walk;
  • empty/whitespace nickname is rejected;
  • logo click plays the splash and returns to menu;
  • EXIT closes the application on desktop.

Also verify the known settings limitation: direct mute is not persisted and can be cleared by applying a positive Master slider value.

Connected single-client test

  • valid join loads game.tscn;
  • invalid/rejected join shows an error and allows immediate retry;
  • local session creates LocalPlayer, not RemotePlayer;
  • initial nickname, skin, position, and mass match server state;
  • movement sends immediately on direction changes and continues heartbeats;
  • default local movement follows server positions smoothly;
  • map collisions/bounds are enforced by server positions;
  • seeds spawn, move, and disappear from state;
  • mass changes update radius, sprite, label, and later camera zoom;
  • chat send waits for server broadcast and displays local time;
  • chat error shows for three seconds;
  • local predator player_eaten plays randomized-pitch crunch;
  • Leave game returns to the main menu and hides the leave button.

Test a non-base initial mass specifically; the current camera may remain at zoom 3 until the next mass change.

Two-client synchronization test

  • each client identifies itself by its own session ID;
  • the other session uses RemotePlayer;
  • joins and disconnects create/remove nodes once without duplicates;
  • remote motion is smooth at the configured interpolation speed;
  • hard position changes snap at the 100-pixel threshold;
  • mass/skin/name updates render consistently for both clients;
  • seed removal is visible to both;
  • chat order, timestamp, nickname, and text are consistent;
  • eating audio plays only for the client whose session ID is the predator.

Reconnection test

  • interrupt transport while connected;
  • confirm RECONNECTING is reached and sends are suppressed;
  • confirm gameplay remains visible and note the lack of status UI;
  • restore transport within the retry budget and verify CONNECTED plus fresh state;
  • exhaust retries and verify whether the SDK emits left and returns the client to menu;
  • test intentional leave during unstable transport;
  • test the 1.5-second leave verification branch.

Because there is no reconnect UI, observe logs and connection state directly during this test.

Optional prediction/reconciliation test

This feature is disabled by default and should be tested separately before enabling:

  • verify free movement speed and diagonal normalization against the backend;
  • verify obstacle sliding with the same collision rectangles;
  • introduce controlled latency and packet loss;
  • measure error while moving and after becoming idle;
  • verify sub-0.5-pixel error is ignored;
  • verify moderate error corrects over 0.3 seconds;
  • verify 32-pixel-or-greater error snaps;
  • confirm a correction is cancelled when movement resumes;
  • document divergence caused by the lack of input acknowledgement/replay.

Grid mode needs its own collision/corner/diagonal/high-delta test matrix because the source explicitly marks it for verification.

Map regression test

  • compare compiler output counts with expected counts;
  • verify all atlas textures load;
  • inspect Ground/Objects/Buildings order and opacity;
  • inspect flipped, vertically flipped, and transposed tiles;
  • collide with all nine rectangles from multiple directions;
  • verify all four boundary walls;
  • verify camera bounds still match the map;
  • verify backend spawn points never place a growing circle inside geometry.

Audio regression test

  • Master affects every sound;
  • Music affects the menu track;
  • SFX affects the UI click;
  • eating/splash remain controlled by Master in the current implementation;
  • zero slider value mutes and positive value unmutes;
  • crossfade does not leave both tracks audible after completion;
  • rapid track calls kill the previous fade tween safely.

9. Recommended automation targets

These are test recommendations, not implemented features:

  1. Unit-test compileClientMap() with valid layers, all GID flags, multiple tilesets, and every rejection branch.

  2. Add deterministic tests for movement direction normalization and mass formulas.

  3. Add component tests for interpolation thresholds and reconciliation timing.

  4. Add a protocol fixture test that feeds representative players, seeds, and message payloads.

  5. Add a headless scene smoke test for map loading and replicated entity reconciliation, if the native SDK can be loaded in CI.

  6. Add a two-client integration environment against the actual backend contract.

Automation should not label optional prediction or grid mode as supported until their tests pass under realistic latency and collisions.

10. Export configuration

Web/PWA

The Web preset exports to ../exports/web/index.html and currently enables:

  • extension support;
  • PWA output;
  • canvas focus on startup;
  • experimental virtual keyboard;
  • cross-origin isolation header requirement;
  • desktop and mobile VRAM compression.

Thread support is disabled in the preset. The included Colyseus Web GDExtension declares that dlink-enabled export templates are required. A production page served over HTTPS must use a compatible secure backend endpoint and hosting headers.

Windows Desktop

The Windows preset targets x86-64 and exports to ../exports/windows/chickensmustdie.zip. It does not embed the PCK, disables code signing, and leaves application icon/version/company/product metadata empty.

Before distribution, configure release metadata, signing policy, templates, and artifact packaging. Do not infer support for other desktop/mobile platforms solely from vendored Colyseus libraries.

11. Known development risks

Risk Current source behavior
Backend drift No shared schema package or contract tests are present.
Client/server simulation drift Speed, mass, camera, and collision assumptions are local constants.
Reconnect visibility Chat retains unsent input; no dedicated connection status overlay.
Leave timeout A still-connected room is retained and LEAVE can be retried; no native force-close API.
Join cancellation BACK is disabled during matchmaking; native cancellation/timeout behavior needs verification.
Invalid server values Position, mass, skin, and most payload fields are lightly validated or trusted.
Camera/map coupling Camera bounds are serialized separately from map dimensions.
Prediction naming Current reconciliation is not replay-based and is disabled.
Audio grouping Eating and splash sounds bypass SFX bus.
Settings consistency Mute and stored Master volume are separate states.
Chat growth No message-history cap within a gameplay scene.
Map build reproducibility No pinned Node/TypeScript toolchain or generated-file CI check.
Repository hygiene Temporary .tscn*.tmp files and built binaries are present; ownership/distribution should be explicit.
Licensing No top-level license or asset provenance inventory is present.

12. Source-marked incomplete areas

Only the following planned/incomplete areas are directly evidenced by active source structure or explicit comments:

  • grid simulation requires verification (movement_controller.gd TODO);
  • camera mass configuration is intended to come from the server (camera_controller.gd TODO);
  • game_config and tick_sync routing awaits consumers;
  • leaderboard and credits await handlers and content;
  • reconnect awaits user-facing state/failure handling;
  • the placeholder seed_manager.tscn is not integrated;
  • global swipe transitions await call sites.

Broader roadmap ideas are not described as client capabilities until corresponding code exists.