Phase 11 — Day/night cycle + Lighting (taken before Phase 9 per recommendation)

Three gdscript-refactor agents in parallel; Opus integrated and verified
the day-night transition + torch lighting via MCP runtime + screenshot.

Clock autoload (Agent A, autoload/clock.gd, ~138 lines):
- TICKS_PER_DAY = 4800 → 4 min/day at 1× / 48 s at Fast / 20 s at Ultra
- TICKS_PER_HOUR = 200 (so 60 min × ~3 ticks per minute)
- 4-phase day: night → dawn (5–7) → day (7–19) → dusk (19–22) → night
- darkness_factor() returns 0..1 with linear ramps across dawn/dusk
- phase_changed signal fires on phase transitions
- save_dict / apply_dict for save round-trip
- Boots at Day 1, 06:00 (mid-dawn for atmospheric start)
- Registered in project.godot autoload list (Opus)

Top-bar clock UI (Agent A):
- ClockLabel added to top_bar.tscn (center-anchored at ±80 px)
- _on_clock_refresh in top_bar.gd; early-out string compare to skip text
  assignments when unchanged (cheap per-tick)

Torch entity + lights registry (Agent B, scenes/entities/torch.{gd,tscn} +
workbench.gd + world.gd, ~210 lines):
- class Torch: buildable furniture, BUILD_TICKS=30, LIGHT_RADIUS=6
- Procedural radial gradient texture (64×64) generated at runtime with
  smoothstep falloff → no PNG dependency
- PointLight2D child with the gradient texture, warm fire tint, energy 1.2
- is_on / get_light_tile / get_light_radius duck-typed interface; same
  shape exposed by Workbench when label_text='Hearth' (HEARTH_LIGHT_RADIUS=5)
- World.light_sources registry + register/unregister + is_tile_lit(tile)
  (Manhattan distance, no occlusion — Phase 13 may add wall-occlusion)

CanvasModulate darkness + in_darkness thought (Agent C, ~30 lines mod +
new factory):
- DarkOverlay CanvasModulate node added to world.tscn (first child of
  World root so it tints all sibling layers + entities)
- world.gd._update_dark_overlay lerps DAY_TINT (white) ↔ NIGHT_TINT
  (0.20, 0.22, 0.40 deep cool blue) by Clock.darkness_factor() each tick
- ThoughtCatalog.in_darkness(): persistent, -3 mood, fires when
  darkness > 0.3 AND World.is_tile_lit(pawn.tile) is false
- Pawn._process_thoughts syncs in_darkness alongside hungry/tired

Opus integration:
- project.godot: Clock autoload registered
- world.tscn: DarkOverlay CanvasModulate node, plus the agent additions
- Demo seed: 2 torches inside cabin at (46, 26) + (49, 26), pre-built
- MCP-driven runtime test verified day→night transition + lighting
  effects:
  - Noon: world bright green, torches barely visible (over-bright at noon
    is minor polish — Phase 17 may scale torch energy by darkness)
  - Midnight: world deep blue/green tinted, torches cast yellow halos,
    Hearth ember glows orange, cabin interior warmly lit, exterior dark
- top_bar clock label updates each sim tick (early-out on no-change)

Phase 11 followups for later phases:
- Torch energy should scale with darkness — visible halos at noon are
  silly. Phase 17 will likely tie PointLight2D.energy to clamp(darkness,
  0.2, 1.0) so they're invisible at midday
- Wall-occlusion for light_map — Phase 13's room-detection BFS could
  treat completed wall tiles as occluders so light doesn't bleed through
- 'In darkness' thought currently treats ALL unlit cells as darkness;
  Phase 13's roof flag could differentiate 'indoors-dark' (different
  thought) from 'outdoors-dark'
- Light source visibility through CanvasModulate works correctly thanks
  to PointLight2D's additive blend mode

Acceptance — MCP-verified via play_scene + get_game_screenshot:
-  Day → Dusk → Night cycle visible (Clock.current_phase emits events)
-  CanvasModulate tints world deep blue at night
-  Torches cast visible yellow halos via PointLight2D additive blend
-  Hearth opts-in as a light source via label_text='Hearth' check
-  Top-bar clock shows 'Day N, HH:MM' format and updates each tick
-  in_darkness thought wires through _process_thoughts (would fire if
  a pawn were standing in an unlit night tile — demo didn't capture this
  specifically but the code path is verified)

Delegation report this phase:
- Agent A: Clock autoload + 4-phase day cycle + top-bar UI extension
- Agent B: Torch entity + PointLight2D + procedural radial texture +
  Workbench Hearth opt-in + World.light_sources registry
- Agent C: CanvasModulate world.tscn node + day/night colour lerp +
  in_darkness ThoughtCatalog entry + Pawn persistent thought sync
- Opus: Clock autoload registration in project.godot + 2 torches in
  demo seed + MCP runtime verification at midnight vs noon

~75% of Phase 11 GDScript was subagent-authored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-11 15:54:15 +01:00
parent 43e52ffe75
commit a1e5b38dd6
16 changed files with 606 additions and 10 deletions

View file

@ -29,6 +29,7 @@ const WORKBENCH_SCENE: PackedScene = preload("res://scenes/entities/workbench.ts
const CROP_SCENE: PackedScene = preload("res://scenes/entities/crop.tscn")
const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
const BED_SCENE: PackedScene = preload("res://scenes/entities/bed.tscn")
const TORCH_SCENE: PackedScene = preload("res://scenes/entities/torch.tscn")
# 3 starting pawns — Phase 2 demo. Phase 7+ replaces this with map-gen + name table.
const SAMPLE_PAWNS: Array[Dictionary] = [
@ -51,6 +52,12 @@ const SAMPLE_ROCKS: Array[Vector2i] = [
# HaulingProvider re-flow cadence — every 5 sim seconds at 1× (100 ticks).
const HAUL_SWEEP_INTERVAL_TICKS: int = 100
# Phase 11 — global darkness tint. Day = white, night = deep cool blue.
# Driven by Clock.darkness_factor() (0..1) each sim tick.
const NIGHT_TINT: Color = Color(0.20, 0.22, 0.40, 1.0)
const DAY_TINT: Color = Color(1.0, 1.0, 1.0, 1.0)
@onready var dark_overlay: CanvasModulate = $DarkOverlay
@onready var terrain_layer: TileMapLayer = $Terrain
@onready var floor_layer: TileMapLayer = $Floor
@onready var wall_layer: TileMapLayer = $Wall
@ -395,6 +402,19 @@ func _seed_phase5_demo_buildings() -> void:
bed.on_build_tick()
Audit.log("world", "phase 8 demo: %d beds pre-built inside cabin" % bed_tiles.size())
# Phase 11 demo — 2 torches inside cabin (north-east + south-west corners
# of interior) so the indoor area stays lit at night. Combined with the
# Hearth's light radius=5, the cabin interior should be mostly bright
# while the outdoors goes deep-blue tinted.
var torch_tiles: Array[Vector2i] = [Vector2i(46, 26), Vector2i(49, 26)]
for tt in torch_tiles:
var torch: Torch = TORCH_SCENE.instantiate()
add_child(torch)
torch.setup(tt)
while torch.is_buildable():
torch.on_build_tick()
Audit.log("world", "phase 11 demo: %d torches pre-built inside cabin" % torch_tiles.size())
func _spawn_sample_stockpiles() -> void:
# Two zones for the Phase 4 acceptance demo:
@ -463,11 +483,20 @@ func _on_designation_cleared(cell: Vector2i) -> void:
# ── periodic re-flow (the "wood floats up" cascade) ─────────────────────────
func _on_sim_tick_world_sweep(tick_n: int) -> void:
_update_dark_overlay()
if tick_n % HAUL_SWEEP_INTERVAL_TICKS != 0:
return
hauling_provider.sweep_for_better_destinations()
# Phase 11 — interpolate CanvasModulate between DAY_TINT and NIGHT_TINT based
# on Clock.darkness_factor() (0 = full day, 1 = full night).
# Called every sim tick; Color.lerp is a handful of float ops — negligible cost.
func _update_dark_overlay() -> void:
var f := Clock.darkness_factor()
dark_overlay.color = DAY_TINT.lerp(NIGHT_TINT, f)
# ── spike: AStarGrid2D query timing at 80² ──────────────────────────────────
func _run_pathfinder_spike() -> void:

View file

@ -19,6 +19,9 @@
y_sort_enabled = true
script = ExtResource("1_world")
[node name="DarkOverlay" type="CanvasModulate" parent="."]
color = Color(1, 1, 1, 1)
[node name="Terrain" type="TileMapLayer" parent="."]
z_index = 0