v0.3.0: rendering pass, generated props, selection model and Web build
Rendering and presentation - Shared surface-material module (src/game/materials.gd) with per-surface roughness/metallic/uv profiles; ACES tonemapping, glow, fog and phase-driven sky energy. WorldBuilder rebuilt on those profiles with facade windows that light up, door trim/handles, curb, lane dashes, bollards, bench, bin and planter foliage. - NPC silhouettes: per-role clothing palettes, caps/hoods/backpacks/sunglasses, hands and shoes, arm/leg walk swing. - Six generated low-poly GLB props (bollards, bench, bin, planter, newspaper box, hydrant) built by tools/model_pipeline.py, loaded by WorldBuilder with a procedural fallback; ADR-0012. Selection and movement - Focus ranking by view angle then distance (NpcFocus.rank_indices); Tab and the mouse wheel cycle nearby people initially, right click clears focus with a re-acquire grace, HUD shows the candidate count, Shift walks at half speed. ADR-0011. Windowed smoke extended to cover wheel cycling and clearing. QA tooling - screen_designs.py catalogue grown to 19 screens (8 scene, 11 UI) plus a `list` subcommand; draft generation keeps existing AI references unless --force. Web export - New Web preset using the non-threaded Godot template (static hosting, no COOP/COEP). tools/build_releases.sh exports build/web and packages build/step-line-web-<ver>.zip; publish_release.sh uploads it with the desktop artifacts. test_web_export.gd covers the preset and release wiring. Verification - Headless suite 177 -> 181, Python tooling 53 tests, all eight smokes pass (including the windowed focus/wheel smoke). Web bundle serves index.html, index.wasm (35 MB) and index.pck (30 MB) with HTTP 200 from a static server. - Version 0.3.0; README, PLAN, RELEASING, TODO/DONE and MEMORY updated.
This commit is contained in:
1 parent
f763cff744
commit
afa1d6beec
48 files changed
+2602
-147
No files matched your search
@@ -0,0 +1,77 @@
|
||||
extends RefCounted
|
||||
## Headless tests for the generated prop models (tools/model_pipeline.py) and
|
||||
## their placement in the procedural world.
|
||||
|
||||
const WorldBuilder := preload("res://src/game/world_builder.gd")
|
||||
const MODEL_DIR := "res://assets/models/generated/"
|
||||
|
||||
func test_prop_glb_files_exist() -> bool:
|
||||
var missing: Array[String] = []
|
||||
for prop in WorldBuilder.MODEL_PROPS:
|
||||
if not FileAccess.file_exists(MODEL_DIR + str(prop) + ".glb"):
|
||||
missing.append(str(prop))
|
||||
return TestUtils.check(missing.is_empty(), "all prop models built (missing: %s)" % str(missing))
|
||||
|
||||
func test_prop_glb_files_are_imported_scenes() -> bool:
|
||||
## `load()` only returns a PackedScene once Godot has imported the `.glb`;
|
||||
## a null here means the checkout is missing the import or the model is invalid.
|
||||
var problems: Array[String] = []
|
||||
for prop in WorldBuilder.MODEL_PROPS:
|
||||
var path := MODEL_DIR + str(prop) + ".glb"
|
||||
var packed := ResourceLoader.load(path) as PackedScene
|
||||
if packed == null:
|
||||
problems.append("%s not imported as a scene" % prop)
|
||||
elif not FileAccess.file_exists(path + ".import"):
|
||||
problems.append("%s has no .import file" % prop)
|
||||
return TestUtils.check(problems.is_empty(), "prop models import as scenes (%s)" % str(problems))
|
||||
|
||||
func test_prop_scenes_instantiate_with_meshes() -> bool:
|
||||
var problems: Array[String] = []
|
||||
for prop in WorldBuilder.MODEL_PROPS:
|
||||
var packed := ResourceLoader.load(MODEL_DIR + str(prop) + ".glb") as PackedScene
|
||||
if packed == null:
|
||||
problems.append("%s: no scene" % prop)
|
||||
continue
|
||||
var instance := packed.instantiate()
|
||||
var meshes := instance.find_children("*", "MeshInstance3D", true, false)
|
||||
if meshes.is_empty():
|
||||
problems.append("%s: no mesh instances" % prop)
|
||||
instance.free()
|
||||
return TestUtils.check(problems.is_empty(), "every prop scene has geometry (%s)" % str(problems))
|
||||
|
||||
func test_world_places_generated_props() -> bool:
|
||||
var root := Node3D.new()
|
||||
var builder := WorldBuilder.new(root)
|
||||
builder.build()
|
||||
var detail := root.get_node_or_null("StreetDetail")
|
||||
var a := TestUtils.check(detail != null, "StreetDetail exists")
|
||||
var placed: int = detail.get_child_count() if detail else 0
|
||||
var b := TestUtils.check(placed >= 10, "street furniture placed (%d)" % placed)
|
||||
var c := TestUtils.check(builder.loaded_models.size() >= WorldBuilder.MODEL_PROPS.size(),
|
||||
"generated models loaded instead of fallbacks")
|
||||
var d := TestUtils.check(builder.fallback_props.is_empty(),
|
||||
"no procedural fallback used (fallbacks: %s)" % str(builder.fallback_props))
|
||||
root.free()
|
||||
return a and b and c and d
|
||||
|
||||
func test_world_facade_windows_light_at_night() -> bool:
|
||||
var root := Node3D.new()
|
||||
var builder := WorldBuilder.new(root)
|
||||
builder.build()
|
||||
var a := TestUtils.check(builder._window_mats.size() >= 8, "window panes built")
|
||||
builder.set_night_mode(true)
|
||||
var lit: bool = builder._window_mats[0].emission_energy_multiplier > 0.0
|
||||
builder.set_night_mode(false)
|
||||
var dark: bool = is_zero_approx(builder._window_mats[0].emission_energy_multiplier)
|
||||
root.free()
|
||||
return a and TestUtils.check(lit, "windows light up at night") and TestUtils.check(dark, "windows are dark by day")
|
||||
|
||||
func test_fallback_prop_is_usable() -> bool:
|
||||
var root := Node3D.new()
|
||||
var builder := WorldBuilder.new(root)
|
||||
var fallback := builder._fallback_prop("bench")
|
||||
var meshes := fallback.find_children("*", "MeshInstance3D", true, false)
|
||||
var ok := TestUtils.check(meshes.size() >= 3, "fallback bench has geometry")
|
||||
fallback.free()
|
||||
root.free()
|
||||
return ok
|
||||
Reference in new issue
Block a user