Author the missing scenario content from the PLAN lists
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Successful in 3m26s

Joke Shop
- Reservation list without ID (guest list valid, ID missing) — denied.
- Borrowed/mismatched ID (ID valid but mismatched) — denied.
- Guest who refuses the wand screen: new `refuses_screening` event flag, copied
  onto NPCController, handled in _toggle_wand_screening (logs wand.refused, records
  a wand_refused event, small prop hit, panel stays closed).
- Late arrival during the performance (ID + ticket, "arrive" intent).

Studio
- Filming-boundary event and a later repeat, both using the new `film_boundary`
  intent which maps to `steps_end` so the influencer stands past the posted line.
- Celebrity arrival now pulls in a small crowd via a new `extra_spawns` event field
  (two tourists + an influencer), spawned through the normal path.

Building
- Filming-boundary event whose hint points at the body camera and request-reason
  verbs, plus an unclear-intent event that hints at asking for a reason.

Engine support
- Events may carry `hint` (string key logged on spawn), `extra_spawns`, and
  `refuses_screening`; 9 new localized hint strings.

Tests/docs
- Headless suite 139 -> 149 (test_scenario_content.gd) and a new
  smoke_scenario_content.gd that spawns the refusal and celebrity events through the
  real signal path.
- README, TODO section moved to DONE, MEMORY. Version 0.2.13.
This commit is contained in:
zwitschi committed 2026-10-31 21:55:24 +01:00
1 parent 922a990c7a
commit a2a0ded527
12 files changed
+426 -18

No files matched your search

+91
View File
@@ -0,0 +1,91 @@
extends SceneTree
## Scenario-content smoke: forces the authored Joke Shop refusal event and the
## Studio celebrity arrival through the real spawn path, then checks the extra
## bystanders, the hint log, and that a screening refusal is handled.
##
## Usage:
## godot --headless --path . --script res://src/test/smoke_scenario_content.gd
const MAX_FRAMES := 900
var frame := 0
var gm: Node = null
var started := false
var step := 0
var failures: Array[String] = []
func _initialize() -> void:
root.size = Vector2i(1280, 720)
root.add_child(load("res://scenes/main.tscn").instantiate())
func _process(_delta: float) -> bool:
frame += 1
if gm == null:
gm = root.find_child("GameManager", true, false)
return false
if not started:
gm.call("_on_venue_selected", "joke_shop")
started = true
return false
if frame > MAX_FRAMES:
_fail("timeout")
return true
if frame < 120:
return false
match step:
0:
# Force the refusal event through the normal spawn signal.
var event := _find_event(gm.selected_scenario.event_pool, "joke_refuses_wand")
_check(not event.is_empty(), "the refusal event exists in the Joke Shop pool")
var guest: Object = gm.selected_scenario.profile_for(int(event.get("npc_type", 0)))
gm.coordinator.npc_spawn_requested.emit(guest, "enter", "smoke_refuser", event)
_check(gm.npcs.size() > 0, "the refusing guest spawned")
gm.call("_on_guard_target", gm.npcs[gm.npcs.size() - 1])
var controller: Object = gm.npcs[gm.npcs.size() - 1].get_npc_controller()
_check(controller.refuses_screening, "the refusal flag reached the controller")
gm.call("_toggle_wand_screening")
_check(not gm.wand_open, "screening does not open for someone who refuses")
step = 1
1:
# Studio: celebrity arrival should bring extra bystanders.
gm.call("_on_venue_selected", "tv_studio")
step = 2
2:
if frame % 20 != 0:
return false
var event := _find_event(gm.selected_scenario.event_pool, "studio_celebrity_arrival")
var before: int = gm.npcs.size()
var guest: Object = gm.selected_scenario.profile_for(int(event.get("npc_type", 0)))
gm.coordinator.npc_spawn_requested.emit(guest, "arrive", "smoke_celeb", event)
var added: int = gm.npcs.size() - before
_check(added >= 4, "celebrity arrival spawns the guest plus bystanders (%d)" % added)
step = 3
3:
_finish()
return true
return false
func _find_event(pool: Array, id: String) -> Dictionary:
for event in pool:
if str((event as Dictionary).get("id", "")) == id:
return event
return {}
func _check(condition: bool, message: String) -> void:
if condition:
print("ok: " + message)
else:
_fail(message)
func _fail(message: String) -> void:
failures.append(message)
_finish()
func _finish() -> void:
if failures.is_empty():
print("SMOKE OK: authored scenario content")
quit(0)
else:
for message in failures:
printerr("SMOKE FAIL: " + message)
quit(1)