Ship the 0.8.0 first-hour and campaign milestone
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Successful in 4m18s

Add the campaign shell, pause-safe checkpoint resume, post-shift campaign
continue, confirmed campaign reset, difficulty presets, searchable Help,
content-matrix validation, recovery events, contextual dialogue, policy/debrief
context and eligible-event previews. Extend persistence with atomic separate
files and add campaign/checkpoint/tutorial smokes. Headless 247/247, Python
94/94, tutorial/campaign/session/scenario smokes green.
This commit is contained in:
zwitschi committed 2026-11-01 21:21:43 +01:00
1 parent 86332cd401
commit aa51ee331e
38 files changed
+1361 -1144

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
class_name ScenarioValidator
extends RefCounted
## Validates authored venue data before a shift. Keeps content-matrix failures
## out of runtime: every event must reference a known profile and a valid phase,
## event ids must be unique, and the matrix must cover enough kinds/phases.
const Rules := preload("res://src/core/rules.gd")
static func validate(scenario: Resource) -> Array[String]:
var problems: Array[String] = []
if scenario == null:
return ["scenario is null"]
var profile_types := {}
for profile in scenario.npc_profiles:
if profile != null:
profile_types[int(profile.npc_type)] = true
var ids := {}
var routine := 0
var scripted := 0
var recovery := 0
var phases := {}
for event in scenario.event_pool:
if not (event is Dictionary):
problems.append("event is not a dictionary")
continue
var event_id := str(event.get("id", ""))
if event_id == "":
problems.append("event has no id")
elif ids.has(event_id):
problems.append("duplicate event id: %s" % event_id)
ids[event_id] = true
var phase := int(event.get("phase", -1))
if phase < Rules.Phase.EARLY or phase > Rules.Phase.LATE:
problems.append("%s has invalid phase %d" % [event_id, phase])
var npc_type := int(event.get("npc_type", -1))
if not profile_types.has(npc_type):
problems.append("%s references missing profile type %d" % [event_id, npc_type])
if str(event.get("intent", "")) == "":
problems.append("%s has no intent" % event_id)
var kind := str(event.get("kind", "routine"))
if kind == "routine":
routine += 1
elif kind == "scripted":
scripted += 1
elif kind == "recovery":
recovery += 1
elif kind != "routine":
problems.append("%s has unknown kind %s" % [event_id, kind])
phases[phase] = true
if scenario.event_pool.size() < 12:
problems.append("event matrix has %d events; need at least 12" % scenario.event_pool.size())
if routine < 6:
problems.append("event matrix needs at least 6 routine events")
if scripted < 2:
problems.append("event matrix needs at least 2 scripted events")
if recovery < 1:
problems.append("event matrix needs at least 1 recovery event")
if phases.size() < 2:
problems.append("event matrix must cover at least 2 shift phases")
return problems