Ship the 0.8.0 first-hour and campaign milestone
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:
1 parent
86332cd401
commit
aa51ee331e
38 files changed
+1361
-1144
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user