Add the non-blocking prompt card, safe compliant visitor, action-driven advancement through the real wheel/inspection/warning/radio panels, persisted completion/skip, a Settings replay action and localized tutorial strings. Scripted tests/captures bypass the tutorial unless --tutorial is passed. Add a windowed smoke covering the full flow. Headless 237/237, Python 94/94, tutorial smoke passes.
105 lines
3.6 KiB
GDScript
105 lines
3.6 KiB
GDScript
extends SceneTree
|
|
## Windowed tutorial smoke: start the Hollywood shift with the tutorial enabled,
|
|
## complete all eight beats through the real action methods, verify persistence,
|
|
## then replay and skip it. Use an isolated user-data dir:
|
|
## XDG_DATA_HOME=/tmp/gigline-tutorial godot --path . --script res://src/test/smoke_tutorial.gd -- --tutorial
|
|
|
|
const Rules := preload("res://src/core/rules.gd")
|
|
const MAX_FRAMES := 900
|
|
var gm: Node = null
|
|
var frames := 0
|
|
var started := false
|
|
var failures: Array[String] = []
|
|
var step := 0
|
|
|
|
func _initialize() -> void:
|
|
root.size = Vector2i(960, 540)
|
|
root.add_child(load("res://scenes/main.tscn").instantiate())
|
|
|
|
func _process(_delta: float) -> bool:
|
|
frames += 1
|
|
if gm == null:
|
|
gm = root.find_child("GameManager", true, false)
|
|
return false
|
|
if frames > MAX_FRAMES:
|
|
_fail("timeout")
|
|
return true
|
|
if not started:
|
|
# Ignore any saved completion from a previous local run.
|
|
gm.player_settings["tutorial_completed"] = false
|
|
gm.player_settings["tutorial_skipped"] = false
|
|
gm.call("_on_venue_selected", "hollywood_building")
|
|
started = true
|
|
return false
|
|
step += 1
|
|
match step:
|
|
5:
|
|
_check(gm.tutorial_active, "tutorial starts")
|
|
_check(gm.tutorial != null and gm.tutorial.current()["id"] == "arrive",
|
|
"starts at the arrival beat")
|
|
_check(gm.tutorial_overlay != null and gm.tutorial_overlay.visible,
|
|
"prompt card is visible")
|
|
_check(gm.npcs.size() == 1, "only the safe visitor is present")
|
|
gm.guard.global_position += Vector3(0, 0, 1.0)
|
|
gm.guard.rotation.y += 0.2
|
|
gm.call("_tutorial_tick")
|
|
_check(gm.tutorial.current()["id"] == "focus", "movement/look advances")
|
|
10:
|
|
gm.call("_cycle_nearby_target", 1)
|
|
_check(gm.tutorial.current()["id"] == "wheel", "target cycling advances")
|
|
gm.call("_open_radial")
|
|
_check(gm.tutorial.current()["id"] == "inspect", "wheel opens")
|
|
gm.call("_cancel_radial")
|
|
15:
|
|
gm.call("_toggle_inspection")
|
|
_check(gm.tutorial.current()["id"] == "direction", "inspection advances")
|
|
gm.call("_toggle_inspection")
|
|
gm.call("_apply_interaction", Rules.Level.SIMPLE_DETERRENCE, "polite direction")
|
|
_check(gm.tutorial.current()["id"] == "warning", "polite direction advances")
|
|
20:
|
|
gm.call("_apply_interaction", Rules.Level.FORMAL_WARNING, "formal warning")
|
|
_check(gm.tutorial.current()["id"] == "radio", "warning/body camera advances")
|
|
gm.call("_on_radio_submitted", {
|
|
"npc_id": "tutorial_visitor",
|
|
"location": "entrance",
|
|
"description": "Visitor at the door",
|
|
"behavior": "calm",
|
|
"weapons_seen": false,
|
|
"danger_level": 0,
|
|
}, false)
|
|
_check(gm.tutorial.current()["id"] == "review", "radio advances")
|
|
25:
|
|
gm.call("_on_shift_ended", gm.coordinator.build_review())
|
|
_check(gm.tutorial != null and gm.tutorial.is_complete(), "review completes tutorial")
|
|
_check(not gm.tutorial_active, "tutorial deactivates")
|
|
_check(bool(gm.player_settings.get("tutorial_completed", false)),
|
|
"completion is persisted in settings")
|
|
gm.call("_on_replay_tutorial")
|
|
30:
|
|
_check(gm.tutorial_active and gm.tutorial.current()["id"] == "arrive",
|
|
"replay starts a fresh tutorial")
|
|
gm.call("_skip_tutorial")
|
|
_check(bool(gm.player_settings.get("tutorial_skipped", false)),
|
|
"skip is persisted separately")
|
|
_finish()
|
|
return false
|
|
|
|
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: tutorial prompts, visitor, completion, replay and skip")
|
|
quit(0)
|
|
else:
|
|
for message in failures:
|
|
printerr("SMOKE FAIL: " + message)
|
|
quit(1)
|