Skip to content

feat(godot): 3D client with smooth facing, capsule animals, water drought fade & bird roosting#88

Merged
biosynthart merged 25 commits into
hellolifeforms:mainfrom
biosynthart:fix/godot-client-sync
Jun 21, 2026
Merged

feat(godot): 3D client with smooth facing, capsule animals, water drought fade & bird roosting#88
biosynthart merged 25 commits into
hellolifeforms:mainfrom
biosynthart:fix/godot-client-sync

Conversation

@biosynthart

@biosynthart biosynthart commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

First PR for the Līlā Godot 4.x 3D Client — a native desktop visualizer for the ecosystem simulation that complements the existing browser-based client.

Genesis

The Godot client started as an isometric 2D renderer and evolved through several milestones:

  • Initial port — 3D world view with orbit camera, WebSocket protocol mirroring the browser client
  • Renderer — MultiMeshInstance3D primitive-based rendering with browser-harmonized color palette
  • Client-side agency — 60 Hz behavior engine (fleeing, foraging, hunting, mating, drinking, pollinating) with reconciliation against server reference positions
  • Ground voxels — 32x32 moisture grid with water source overlays
  • Particle system — spawn effects for consumption, pollination, and death events

This PR adds the latest visual polish and behavioral refinements built on top of that foundation.

Changes in this PR

  • Smooth facing direction — Animals and insects lerp their facing angle (TURN_SPEED=8.0 rad/s) instead of snapping instantly when changing direction
  • Capsule mesh for animals — Replaced box mesh with horizontal CapsuleMesh (radius=0.55, height=2.5) for a deer-like body shape
  • Water drought fade — Ground voxels use max_radius for water blend falloff so drought visually fades the water footprint instead of just shrinking it
  • Bird roosting — Birds seek nearest tree when IDLE or RESTING, then hover nearby with a gentle wobble (mirrors server-side roost_affinity logic)

Architecture

The Godot client follows the same intent-based model as the browser:

  • Server (Python worker) — authoritative simulation, streams tick packets at 0.5 Hz
  • Client (Godot) — receives tick packets, runs 60 Hz local agency, sends heartbeats back
  • Reconciliation — client nudges entities toward server reference positions via a gravity well + queued position smoothing

Files changed

  • client/godot/scripts/agency.gd — smooth facing interpolation, bird roosting behavior
  • client/godot/scripts/renderer.gd — capsule mesh, water blend fade
  • client/godot/scripts/constants.gd — TURN_SPEED constant
  • client/godot/scripts/autoloads/world_model.gd — max_radius tracking for water sources

Commits ()

6a14ef3 Add bird roosting behavior — seek trees when IDLE or RESTING
462f1f8 Thin animal capsule radius from 1.0 to 0.55
4c09325 Fade water footprint with drought instead of just shrinking
601b730 Fix animal capsule orientation — use X-tilt instead of Z-tilt
380e993 Remove unsupported ring_count on CapsuleMesh (Godot 4.7)
891f876 Swap animal deer voxel from box to horizontal capsule
681ac2d Smooth entity facing direction with angle lerp instead of instant snap
05f3bb5 fix(godot-client): port ConeMesh/SphereMesh to Godot 4.7 API and tune sizes
9f0fa76 godot: place entities on ground surface + fruiting wildflower spheres
77d0727 fix: log() calls need single-argument formatting
6bea2bc feat: add LilaConstants.log() with HH:MM:SS timestamps
63961d1 fix: WebSocket stability and ground memory leak
cdd05a5 fix: minor compatibility fixes
26d3248 godot: voxel ground with water integration
e9255ee feat(godot): primitive-based 3D entity renderer with browser color harmony
ce7512f docs: add Godot Client architecture and implementation details
cfef168 feat(godot): migrate from isometric 2D to 3D world view
120d5c4 fix(godot): fix entity jitter, marching, and reconciliation drift

- WebSocket protocol identical to browser/Python clients
- Intent-based agency: 60 Hz local behavior (flee→drink→mate→forage→hunt→pollinate→wander)
- Heartbeat sender: 1 Hz position/event reporting upstream
- Position reconciliation: gravity well + staggered sync + _ack queue
- Isometric voxel renderer: colored blocks with depth sorting, moisture grid, animated water
- HUD: stats panel, event log, rain button (R key)
- Autoloads: WS (WebSocket + HTTP), World (entity registry + spatial queries)
- Simulation layer is renderer-independent for future 3D swap
…rrors

- Load world.json from local resources instead of HTTP (websockets
  server passthrough returns empty body to Godot HTTPRequest)
- Send world definition immediately on WebSocket open (was waiting for
  session_started which creates a chicken-and-egg)
- Fix PackedStringArray → Array[String] for pop_front()
- Fix hash_val.abs() → absi(hash_val)
- Fix HORIZONTAL_ALIGNMENT.CENTER → integer 1
- Fix C-style for loop → while loop
- Fix particles.gd class_name + inner class parse error
- Add _is_connecting flag to prevent concurrent WS reconnection
- Add bbcode_enabled to event log RichTextLabel
- Fix death particle position (lookup entity before removal)
Godot's JSON.stringify converts integers to floats, which broke
range(dx) in voxel_manager. Coerce dimensions to int at parse time.
Godot JSON.stringify sends floats; randint needs ints.
- Right/middle mouse drag to pan, scroll wheel to zoom (0.2x-4x)
- Fix isometric ground and entity rendering to scale with zoom
- Add guard for degenerate polygons when zoomed out
- Remove duplicate _world_center variable
Three bugs causing entities to jiggle in place and march off-grid:

1. Gravity well missing `* delta` multiplication — nudge was applied
   at full strength every frame (60x too strong), causing violent
   overshoot/oscillation around ref_position. Now matches Python/JS:
   nudge = GRAVITY_WELL_FACTOR * sync_speed * delta, with 0.2 unit
   proximity threshold to skip when already close.

2. Wander target regenerated every frame — \_evaluate_wandering() picked
   a new random target each frame instead of reusing an existing one
   until reached. This caused jitter as the entity chased a moving
   target. Now tracks has_target/last_action_type to persist wander
   targets, mirroring Python/browser behavior.

3. Reconciliation missing \_lastReconciledTick — used (tick % 4) ==
   sync_phase instead of tracking ticks since last reconcile. Added
   queue pruning, last_reconciled_tick tracking, and proper stagger
   logic to match Python/browser reconciliation.

Also added telemetry logging (every 10 ticks) printing entity
divergence stats for debugging.
- Add orbit camera (LMB orbit, RMB pan, scroll zoom)
- Add directional + ambient 3D lighting
- Ground mesh, entity MultiMesh, particle MultiMesh nodes
- Add help label with control hints to HUD
- Switch main.gd from Node2D to Node3D with @onready bindings
New section documenting the 3D Godot 4.x client:

- Project scaffolding, file layout, autoloads (WS + World)
- Node tree: Node3D + Camera3D + MultiMesh entities/particles + HUD
- WebSocket layer: connect, dispatch, session_started, tick_packet
- World model: WorldEntity fields, spatial queries, sync personality
- Client-side agency: 60 Hz behavior priority chain, wander target
  persistence, gravity well, interaction triggers
- Reconciliation: staggered sync, queue management, last_reconciled_tick
- Heartbeat sender: 1 Hz upstream position/event reporting
- Rendering: ground mesh (SurfaceTool), entity cubes, particles
- Orbit camera: spherical coordinate orbit, pan, zoom
- Constants module: shared with browser/Python clients
- Telemetry logging: divergence stats every 10 ticks
- Sync bugs fixed: gravity well delta, wander reuse, last_reconciled_tick
- Current limitations: no skeletal animation, no water rendering
…rmony

Replace single BoxMesh-per-entity with per-type composite meshes built
from SurfaceTool primitives (cylinder, sphere, cone, planes). Each
entity type gets its own MultiMeshInstance3D with a unique shape:

- Deer (ANIMAL): cylinder body + hemispherical head + cone snout + legs
- Songbird (BIRD): capsule body + nose cone + tail cone + wing planes
- Butterfly (INSECT): small body + 4 wing planes (upper/lower pairs)
- Oak (TREE): cylinder trunk + sphere canopy
- Grass (PLANT): 3 angled blade planes
- Wildflower (PLANT): stem cylinder + bloom sphere
- Mushroom (MICROORGANISM): stalk + flat-topped cap

State-aware sizing: trees/plants scale by growth, mushrooms by activity,
insects float with sine oscillation, birds fly higher. Dormant entities
are darkened 55%, wilted plants shift to brown.

Harmonize all type/species/particle colors with the browser renderer
palette (constants.js). Water sources rendered as flat cylinders with
animated alpha pulse.

Scene restructuring:
- Add Renderer node (composite mesh builder)
- Replace single Entities MultiMeshInstance3D with per-type instances
- Add WaterSources Node3D parent
- Dark background matching browser (#0f100f)
- Fix ground triangle winding (second triangle was CW, normal pointed down)
- Replace planar grid with 1x1x1 BoxMesh voxels via MultiMesh
- Merge water sources into voxel grid (blend cells toward water color)
- Remove circular water mesh rendering (cylinder + standard material)
- Drop separate WaterSources scene node
- world_model: use explicit type list instead of PackedStringArray lookup
- engine.py: include type/species in entity update packets
- ws_client: add 100ms poll timer (decoupled from render loop) so
  server pings are serviced even when FPS drops from voxel rebuild
- ws_client: persist world definition as raw string, resends on reconnect
- ws_client: create fresh WebSocketPeer on reconnect to clear stale state
- renderer: build ground MultiMesh once in setup, only update colors each frame
  (was allocating new BoxMesh+MultiMesh every frame, causing memory pressure)
- Add log() helper to LilaConstants
- Replace print() calls in ws_client.gd and main.gd
… sizes

- Replace removed ConeMesh with CylinderMesh (top_radius=0)
- Drop sphere.rings (not settable in 4.7)
- Remove ring_count from CylinderMesh (not settable in 4.7)
- Lower fruiting wildflower size multiplier from 2.0 to 1.3
- Remove class_name Renderer (not needed, scene wires it directly)
- Drop static from renderer methods (called via instance in main.gd)
- Add TURN_SPEED constant (8.0 rad/s) in constants.gd
- _move_toward(): lerp facing_angle toward target direction with wrapf
  to handle -π/π boundary correctly
- _execute_reconcile(): also smooth facing angle during reconciliation
  spiral movement (was not updating facing_angle at all)
- Uses clampf on wrapped angle diff for frame-rate-independent turn speed
- CapsuleMesh (radius=1.0, height=2.5) for a deer-like body shape
- Tilt capsule 90° around Z in transform so it lies horizontal
  (default CapsuleMesh is vertical like a standing pill)
- Combine Y-facing rotation with Z-tilt via Basis multiplication,
  same pattern as bird cone tilt
X-tilt lays capsule horizontal so long axis aligns with travel
direction; Z-tilt was spinning around the capsule's own long axis
- Derive max_radius on client (radius / water_level) to know original
  footprint size
- Blend falloff now uses max_radius so cells keep a water tint that
  fades with level rather than suddenly losing coverage
Deer look less fat with narrower capsule body
- Birds in IDLE/RESTING now seek nearest tree instead of wandering randomly
- When near a tree, birds hover nearby with gentle sinusoidal wobble
- Mirrors server-side roosting logic from movement_actors.py
@biosynthart biosynthart changed the title Godot client sync: smooth facing, capsule animals, water fade & bird roosting feat(godot): 3D client with smooth facing, capsule animals, water drought fade & bird roosting Jun 21, 2026
Mirrors server Python header style:
  - līlā project banner
  - Copyright 2025 BioSynthArt Studios LLC
  - Apache 2.0 license line
  - File path and purpose description
Mirrors server header style:
  - līlā project banner
  - Copyright 2025 BioSynthArt Studios LLC
  - Apache 2.0 license line
  - File path and purpose description
@biosynthart biosynthart merged commit bf11123 into hellolifeforms:main Jun 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant