Implement control pass: focus cone, action wheel, named inputs, boot fixes
Full build / Import + headless tests (push) Successful in 22s
Full build / Full desktop build + optional release (push) Skipped

- Venue-correct access artifacts (AccessLoadout) and screened-aware verify
- Cone focus on NPC heads with billboarded ring + display name (ADR-0008)
- Action wheel with de-escalation-first slices (ADR-0009); Esc/RMB cancel
- Procedural NPC face/hair/neck/shoes (ADR-0010); named input actions
- Fix GameManager parse error (radial typed as ActionRadial)
- Fix first-person camera height and guard spawning inside the building
- Move building volume back so the door/awning/marquee are visible
- Pull the event scheduler once per simulated second; cap live NPCs
- Regenerate README screenshots; update TODO/DONE/MEMORY/README; 77 tests

Adds src/test/smoke_focus_radial.gd for the windowed focus/radial smoke.
This commit is contained in:
zwitschi committed 2026-10-31 15:58:01 +01:00
1 parent 69484866db
commit f11b441c07
28 files changed
+1433 -249

No files matched your search

+22 -5
View File
@@ -393,6 +393,8 @@ static func joke_shop() -> VenueScenario:
"guard_post": Vector3(0, 0, -3),
"entrance": Vector3(0, 0, 1.0),
"door_area": Vector3(0, 0, 3),
"steps_end": Vector3(0, 0, 3),
"sidewalk": Vector3(0, 0, 9),
"queue_area": Vector3(0, 0, 9),
}
scenario.door_staff_lines = {
@@ -433,7 +435,7 @@ static func joke_shop() -> VenueScenario:
scenario.event_pool = [
_joke_guest("guest_valid", true, true, Vector2(0, 900), 0.30),
_joke_guest("guest_no_ticket", false, true, Vector2(0, 900), 0.18),
_joke_guest("guest_expired_id", true, true, Vector2(0, 900), 0.12),
_joke_guest("guest_expired_id", true, true, Vector2(0, 900), 0.12, {"id_status": Rules.ArtifactStatus.EXPIRED}),
_joke_guest("guest_no_id", true, false, Vector2(0, 900), 0.12),
_joke_staff_entry(Vector2(0, 900), 0.25),
_joke_routine("performer_show", Rules.NpcType.PERFORMER, "perform", Vector2(300, 800), 0.15),
@@ -475,6 +477,8 @@ static func tv_studio() -> VenueScenario:
"guard_post": Vector3(0, 0, -3),
"entrance": Vector3(0, 0, 1.0),
"stage_door": Vector3(6, 0, -2),
"steps_end": Vector3(0, 0, 3),
"sidewalk": Vector3(0, 0, 9),
"queue_area": Vector3(0, 0, 9),
}
@@ -492,9 +496,9 @@ static func tv_studio() -> VenueScenario:
])
scenario.event_pool = [
_studio_routine("audience_ticket", Rules.NpcType.AUTHORIZED_GUEST, "enter", Vector2(0, 900), 0.35),
_studio_guest("audience_ticket", {"has_ticket": true}, Vector2(0, 900), 0.35),
_studio_routine("protest_outside", Rules.NpcType.PROTESTER, "protest", Vector2(300, 900), 0.18),
_studio_routine("celebrity_arrival", Rules.NpcType.AUTHORIZED_GUEST, "arrive", Vector2(300, 700), 0.20),
_studio_guest("celebrity_arrival", {"has_guest_list": true}, Vector2(300, 700), 0.20, "arrive"),
_studio_routine("crew_shift", Rules.NpcType.AUTHORIZED_STAFF, "enter", Vector2(0, 700), 0.30),
_studio_routine("tourist_photo", Rules.NpcType.TOURIST, "photo", Vector2(0, 900), 0.35),
_studio_routine("confused_enter", Rules.NpcType.CONFUSED_VISITOR, "enter", Vector2(0, 900), 0.25),
@@ -509,6 +513,7 @@ static func tv_studio() -> VenueScenario:
"time_range_s": Vector2(620, 780),
"probability": 0.55,
"base_weight": 1.0,
"override_access": {"wristband_status": Rules.ArtifactStatus.COUNTERFEIT, "credential_status": Rules.ArtifactStatus.COUNTERFEIT},
},
{
"id": "studio_follow_through",
@@ -516,6 +521,7 @@ static func tv_studio() -> VenueScenario:
"kind": "scripted",
"npc_type": Rules.NpcType.TRESPASSER,
"intent": "follow_authorized",
"override_access": {"has_ticket": false, "has_wristband": false, "has_credential": false, "has_guest_list": false},
"time_range_s": Vector2(700, 860),
"probability": 0.45,
"base_weight": 1.0,
@@ -549,12 +555,23 @@ static func _building_routine(id: String, npc_type: int, intent: String, time_ra
"time_range_s": time_range, "probability": prob, "base_weight": 1.0,
}
static func _joke_guest(id: String, has_ticket: bool, has_id: bool, time_range: Vector2, prob: float) -> Dictionary:
static func _joke_guest(id: String, has_ticket: bool, has_id: bool, time_range: Vector2, prob: float, extra: Dictionary = {}) -> Dictionary:
var override := {"has_ticket": has_ticket, "has_id": has_id}
for key in extra.keys():
override[key] = extra[key]
return {
"id": id, "phase": _phase_for_range(time_range, 900.0),
"kind": "routine", "npc_type": Rules.NpcType.AUTHORIZED_GUEST, "intent": "enter",
"time_range_s": time_range, "probability": prob, "base_weight": 1.0,
"override_access": {"has_ticket": has_ticket, "has_id": has_id},
"override_access": override,
}
static func _studio_guest(id: String, override_access: Dictionary, time_range: Vector2, prob: float, intent: String = "enter") -> Dictionary:
return {
"id": id, "phase": _phase_for_range(time_range, 900.0),
"kind": "routine", "npc_type": Rules.NpcType.AUTHORIZED_GUEST, "intent": intent,
"time_range_s": time_range, "probability": prob, "base_weight": 1.0,
"override_access": override_access,
}
static func _joke_staff_entry(time_range: Vector2, prob: float) -> Dictionary: