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
@@ -43,6 +43,48 @@ class ScreenDesignsTests(unittest.TestCase):
|
||||
self.assertEqual(image.size,
|
||||
(screen_designs.WIDTH, screen_designs.HEIGHT))
|
||||
|
||||
def test_catalogue_covers_the_shipped_screens(self):
|
||||
names = [screen["name"] for screen in screen_designs.SCREENS]
|
||||
required = [
|
||||
"venue_overview", "targeting_npc", "access_inspection",
|
||||
"radio_report", "late_night_avenue", "night_flashlight",
|
||||
"radial_menu", "shift_review", "start_menu", "pause_overlay",
|
||||
"settings_panel", "emergency_radio", "wand_screening",
|
||||
"joke_shop_queue", "studio_arrival", "crowd_panic",
|
||||
"responder_arrival", "body_camera_timeline", "escalation_hud",
|
||||
]
|
||||
for name in required:
|
||||
self.assertIn(name, names)
|
||||
self.assertGreaterEqual(len(screen_designs.SCREENS), len(required))
|
||||
for screen in screen_designs.SCREENS:
|
||||
self.assertTrue(screen["title"].strip())
|
||||
|
||||
def test_list_command_prints_the_catalogue(self):
|
||||
import contextlib
|
||||
import io
|
||||
buffer = io.StringIO()
|
||||
with contextlib.redirect_stdout(buffer):
|
||||
code = screen_designs.main(["list"])
|
||||
self.assertEqual(code, 0)
|
||||
output = buffer.getvalue()
|
||||
self.assertIn("escalation_hud", output)
|
||||
self.assertIn("wand_screening", output)
|
||||
self.assertIn("Total:", output)
|
||||
|
||||
def test_judge_dry_run_builds_requests_without_network(self):
|
||||
import contextlib
|
||||
import io
|
||||
if not screen_designs.SCREENSHOTS_DIR.is_dir():
|
||||
self.skipTest("no screenshots checked out")
|
||||
buffer = io.StringIO()
|
||||
with contextlib.redirect_stdout(buffer):
|
||||
code = screen_designs.main(["judge", "--dry-run"])
|
||||
# A dry run must not call the API and must not crash on missing verdicts.
|
||||
self.assertEqual(code, 0)
|
||||
output = buffer.getvalue()
|
||||
self.assertIn("dry run", output)
|
||||
self.assertIn("Summary:", output)
|
||||
|
||||
def test_compose_prompt_appends_kind_specific_style(self):
|
||||
scene = {"kind": "scene", "prompt": "a street"}
|
||||
ui = {"kind": "ui", "prompt": "a panel"}
|
||||
@@ -108,7 +150,8 @@ class VisionJudgeTests(unittest.TestCase):
|
||||
parts = body["messages"][0]["content"]
|
||||
self.assertEqual(parts[0]["type"], "text")
|
||||
images = [p for p in parts if p["type"] == "image_url"]
|
||||
self.assertEqual(len(images), 2, "design and capture are both attached")
|
||||
self.assertEqual(
|
||||
len(images), 2, "design and capture are both attached")
|
||||
for image in images:
|
||||
self.assertTrue(image["image_url"]["url"].startswith(
|
||||
"data:image/png;base64,"))
|
||||
@@ -117,7 +160,7 @@ class VisionJudgeTests(unittest.TestCase):
|
||||
|
||||
def test_parse_plain_json_verdict(self):
|
||||
payload = {"choices": [{"message": {"content":
|
||||
'{"match": true, "confidence": 0.9, "differences": ["style"]}'}}]}
|
||||
'{"match": true, "confidence": 0.9, "differences": ["style"]}'}}]}
|
||||
verdict = screen_designs.parse_judge_response(payload)
|
||||
self.assertTrue(verdict["match"])
|
||||
self.assertAlmostEqual(verdict["confidence"], 0.9)
|
||||
@@ -125,7 +168,7 @@ class VisionJudgeTests(unittest.TestCase):
|
||||
|
||||
def test_parse_fenced_json_verdict(self):
|
||||
payload = {"choices": [{"message": {"content":
|
||||
'Sure:\n```json\n{"match": false, "confidence": 0.7}\n```'}}]}
|
||||
'Sure:\n```json\n{"match": false, "confidence": 0.7}\n```'}}]}
|
||||
verdict = screen_designs.parse_judge_response(payload)
|
||||
self.assertFalse(verdict["match"])
|
||||
self.assertAlmostEqual(verdict["confidence"], 0.7)
|
||||
|
||||
Reference in new issue
Block a user