class_name CanonLog extends RefCounted ## The §11 canon log: compact structured state code maintains and injects into ## every AI call. This container owns the container-level invariants; leaf rows ## own theirs. AI prose never writes here directly — mutators are called by code. const SCHEMA_VERSION := 1 const MAX_RECENT_EVENTS := 5 var schema_version: int = SCHEMA_VERSION var player: LogPlayer var location: LogLocation var party: Array[PartyMember] = [] var recent_events: Array[String] = [] var established_facts: Array[String] = [] var active_quests: Array[Quest] = [] var humiliations: Array[Humiliation] = [] func _init() -> void: player = LogPlayer.new() location = LogLocation.new() # ── maintenance mutators (charter §11 maintenance table) ───────────────────── func push_event(line: String) -> void: recent_events.append(line) while recent_events.size() > MAX_RECENT_EVENTS: recent_events.pop_front() func add_fact(text: String) -> void: if text not in established_facts: established_facts.append(text) func add_humiliation(id: String, text: String, weight: int, turn: int) -> void: humiliations.append(Humiliation.new(id, text, weight, turn)) func set_location(id: String, name: String) -> void: location = LogLocation.new(id, name) func party_member(id: String) -> PartyMember: for m in party: if m.id == id: return m return null func adjust_disposition(id: String, delta: int) -> bool: var m := party_member(id) if m == null: return false m.set_disposition(m.disposition + delta) return true func has_quest(id: String) -> bool: for q in active_quests: if q.id == id: return true return false func add_quest(id: String, name: String, objective: String) -> bool: if has_quest(id): return false active_quests.append(Quest.new(id, name, "active", objective)) return true func set_quest_status(id: String, status: String) -> bool: for q in active_quests: if q.id == id: return q.set_status(status) return false # ── serialisation ──────────────────────────────────────────────────────────── func to_dict() -> Dictionary: return { "schema_version": schema_version, "player": player.to_dict(), "location": location.to_dict(), "party": party.map(func(m): return m.to_dict()), "recent_events": recent_events.duplicate(), "established_facts": established_facts.duplicate(), "active_quests": active_quests.map(func(q): return q.to_dict()), "humiliations": humiliations.map(func(h): return h.to_dict()), } static func from_dict(d: Dictionary) -> CanonLog: var v := int(d.get("schema_version", -1)) if v != SCHEMA_VERSION: push_error("unsupported canon_log schema_version: %s" % v) return null var log := CanonLog.new() log.player = LogPlayer.from_dict(d.get("player", {})) log.location = LogLocation.from_dict(d.get("location", {})) for m in d.get("party", []): log.party.append(PartyMember.from_dict(m)) for e in d.get("recent_events", []): log.push_event(e) for f in d.get("established_facts", []): log.add_fact(f) for q in d.get("active_quests", []): log.active_quests.append(Quest.from_dict(q)) for h in d.get("humiliations", []): log.humiliations.append(Humiliation.from_dict(h)) return log