Add NPC theater poses: sit, photo, film, perform
Full build / Import + headless tests (push) Successful in 29s
Full build / Full desktop build + optional release (push) Failing after 3m44s

Once an NPC reaches its intent waypoint it now does something instead of standing
in an idle T-pose:

- sit: the rig settles 0.34 m into a seat and the legs swing forward on the steps
- photo/film: influencers and tourists raise their camera prop briefly (a sine
  pulse, so it reads as taking a shot rather than a permanently raised arm)
- perform: performers sway the sign board they carry

Pose selection lives in NpcAvatar.theater_pose() (intent + arrived); the math is in
pure helpers (crouch_for_pose, prop_lift_for_pose, sway_for_pose) so it is
headless-testable. Prop/leg nodes are captured at build time and animated from
their rest offsets. Performers also get a hand-held board and post.

Tests/docs
- Headless suite 120 -> 127 (test_npc_theater.gd).
- Verified visually with a staged sit/photo/perform lineup in front of the entrance.
- README, TODO section moved to DONE, MEMORY. Version 0.2.7.
This commit is contained in:
zwitschi committed 2026-10-31 21:05:59 +01:00
1 parent 1b976bf351
commit e2c2a0bf4c
9 files changed
+225 -35

No files matched your search

+20 -11
View File
@@ -112,10 +112,10 @@ SCREENS = [
"the ring, and the HUD shows a target line with the "
"interaction hint.",
"prompt": (
"Realistic 3D first-person game scene: a pedestrian NPC walking "
"toward the camera on a city sidewalk, a thin pale-gold ring floating "
"around the NPC's head as a selection marker, a small name label "
"under the ring, minimal HUD target text at the bottom, daytime"),
"Realistic 3D first-person game scene: a friendly pedestrian "
"standing on a city sidewalk, a thin pale-gold ring floating around "
"their head as a game selection marker, a small name label under the "
"ring, minimal HUD target text at the bottom, daytime"),
"regions": [
(0.35, 0.15, 0.65, 0.60, "NPC in focus"),
(0.44, 0.10, 0.56, 0.16, "head ring"),
@@ -156,9 +156,9 @@ SCREENS = [
"whether anyone is in immediate danger, plus a send action.",
"prompt": (
"Realistic 3D game screenshot with an in-game radio dispatch panel "
"overlaid: form fields for location, description, behaviour, weapons "
"seen and immediate danger, and a Send button, dark translucent panel "
"over a softly blurred city street background"),
"overlaid: form fields for location, description, behaviour, a threat "
"assessment and whether anyone is in danger, and a Send button, dark "
"translucent panel over a softly blurred city street background"),
"regions": [
(0.30, 0.08, 0.70, 0.92, "radio panel"),
(0.34, 0.16, 0.66, 0.30, "location / description"),
@@ -407,13 +407,22 @@ def _generate_ai(screen, model, request_size):
def generate(args):
DESIGNS_DIR.mkdir(parents=True, exist_ok=True)
failed = []
for screen in SCREENS:
print(f"generating {screen['name']} ...")
if args.draft:
_draw_wireframe(screen, _design_path(screen["name"]))
else:
_generate_ai(screen, args.model, args.request_size)
try:
if args.draft:
_draw_wireframe(screen, _design_path(screen["name"]))
else:
_generate_ai(screen, args.model, args.request_size)
except SystemExit as exc:
failed.append(screen["name"])
print(f" FAILED {screen['name']}: {exc}", file=sys.stderr)
print("done")
if failed:
print(f"failed: {', '.join(failed)}", file=sys.stderr)
return 1
return 0
# ---------------------------------------------------------------------------