Files
gamedev-the-steward/tests/emergent_jajce_slice_test.gd
2026-08-12 21:15:58 +02:00

218 lines
8.1 KiB
GDScript

extends SceneTree
var failures: Array[String] = []
var manager: Node
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
manager = load("res://simulation/SimulationManager.gd").new()
root.add_child(manager)
var speaker: SimNPC = manager.npcs[0]
speaker.position = Vector3.ZERO
speaker.hunger = 85.0
for npc in manager.npcs:
if npc != speaker:
npc.position = Vector3(100.0 + npc.id, 0.0, 0.0)
_set_pantry_amount(1.0)
_check(manager.get_quest_journal_entries().is_empty(), "Journal starts empty")
_check(
is_equal_approx(
manager.economy.withdraw_to_inventory(speaker, SimulationIds.RESOURCE_FOOD, 1.0), 1.0
),
"A real last-food withdrawal creates the evidence"
)
var situations: Array[SituationStateRecord] = manager.get_active_situations()
_check(situations.size() == 1, "The exact shortage evidence opens one world situation")
if situations.is_empty():
_finish()
return
var situation := situations[0]
_check(
manager.event_knowledge_system.knows_event(speaker.id, situation.get_trigger_event_id()),
"The performing NPC knows the trigger fact"
)
_check(
not manager.event_knowledge_system.knows_event(
SimulationIds.PLAYER_ACTOR_ID, situation.get_trigger_event_id()
),
"The player does not know the situation before conversation"
)
_check(manager.get_quest_journal_entries().is_empty(), "Unknown situations stay out of journal")
manager.speed_index = 5
var conversation_id: StringName = manager.begin_player_conversation(speaker.id)
_check(not conversation_id.is_empty(), "The informed NPC can begin generated dialogue")
_check(
manager.speed_index == 2 and manager.is_dialogue_active(), "Dialogue runs simulation at 1x"
)
_check(
manager.event_knowledge_system.knows_event(
SimulationIds.PLAYER_ACTOR_ID, situation.get_trigger_event_id()
),
"Speaking communicates the exact trigger fact to the player"
)
_check(manager.get_quest_journal_entries().size() == 1, "Discovery projects into the journal")
var turn: ConversationTurn = manager.conversation_service.get_turn(conversation_id)
_check(turn != null and _has_intent(turn, ConversationIntentIds.OFFER_HELP), "Help is semantic")
_check(_has_intent(turn, ConversationIntentIds.DECLINE), "Declining remains available")
_check(
_has_intent(turn, ConversationIntentIds.ASK_WHO_ELSE), "Alternative helpers can be asked"
)
var offer := _option_for_intent(turn, ConversationIntentIds.OFFER_HELP)
var offered: ConversationSelectionResult = manager.select_player_conversation_option(
conversation_id, offer.get_option_id(), turn.get_revision()
)
_check(offered.was_accepted(), "Offering help advances the deterministic conversation")
_check(manager.get_active_commitments().size() == 1, "Offering help creates one commitment")
turn = offered.get_turn()
var accept := _option_for_intent(turn, ConversationIntentIds.ACCEPT)
var accepted: ConversationSelectionResult = manager.select_player_conversation_option(
conversation_id, accept.get_option_id(), turn.get_revision()
)
_check(accepted.was_accepted(), "Accepting the offer records a semantic act")
_check(manager.get_active_commitments().size() == 1, "NPC acknowledgement cannot duplicate it")
_check(
(
manager.get_quest_journal_entries()[0].get_selected_alternative_id()
== SituationSystem.ALTERNATIVE_RESTOCK
),
"Acceptance selects a real resolution alternative without copying progress"
)
var saved: String = manager.serialize_state()
var canonical_checksum: String = JSON.stringify(JSON.parse_string(saved)).sha256_text()
manager.end_player_conversation(conversation_id)
_check(manager.speed_index == 5, "Closing dialogue restores the prior speed")
var restored: Node = load("res://simulation/SimulationManager.gd").new()
root.add_child(restored)
_check(restored.restore_state_from_json(saved), "Mid-commitment save reloads")
var restored_serialized: String = restored.serialize_state()
var restored_checksum: String = (
JSON.stringify(JSON.parse_string(restored_serialized)).sha256_text()
)
if restored_checksum != canonical_checksum:
var saved_data: Dictionary = JSON.parse_string(saved)
var restored_data: Dictionary = JSON.parse_string(restored_serialized)
print("[TEST] checksum mismatch sections: ", _different_keys(saved_data, restored_data))
_check(restored_checksum == canonical_checksum, "Mid-commitment continuation is deterministic")
_check(restored.get_active_commitments().size() == 1, "The social commitment persists")
restored.player_system.add_carried(SimulationIds.RESOURCE_FOOD, 1.0)
_check(
is_equal_approx(restored.deposit_player_inventory(SimulationIds.RESOURCE_FOOD), 1.0),
"The player resolves the situation with an ordinary real deposit"
)
var restored_situation: SituationStateRecord = restored.situation_system.get_by_id(
situation.get_situation_id()
)
var restored_commitment: CommitmentStateRecord = restored.commitment_system.get_all_sorted()[0]
_check(
restored_situation.get_status() == SituationStateRecord.STATUS_RESOLVED,
"The exact deposit event resolves the world situation"
)
_check(
(
restored_commitment.get_status() == CommitmentStateRecord.STATUS_FULFILLED
and (
restored_commitment.get_cause_event_id()
== restored_situation.get_resolution_event_id()
)
),
"That same event fulfills the commitment exactly once"
)
_check(
(
restored.get_quest_journal_entries()[0].get_status()
== QuestJournalEntryStateRecord.STATUS_COMPLETED
),
"The journal projects the closed world state"
)
_check(
restored.conversation_history.size() >= 3,
"Semantic conversation acts and topics persist, never rendered prose"
)
var fulfilled_fact: EconomicEventRecord = restored.commitment_lifecycle.get_fact(
restored.event_log,
CommitmentLifecycleService.EVENT_FULFILLED,
restored_commitment.get_commitment_id()
)
_check(fulfilled_fact != null, "Fulfillment records a durable social outcome fact")
var player_relationship: RelationshipStateRecord = (
restored.relationship_system.get_relationship(speaker.id, SimulationIds.PLAYER_ACTOR_ID)
)
_check(
(
player_relationship != null
and (
player_relationship.get_last_trust_cause_event_id()
== int(fulfilled_fact.data["event_id"])
)
),
"The exact outcome fact causes the relationship consequence"
)
_check(
SimulationStateRecord.from_json(restored.serialize_state()) != null,
"Fulfilled commitment facts and relationship causes remain schema-valid"
)
var later_conversation: StringName = restored.begin_player_conversation(speaker.id)
var later_turn: ConversationTurn = restored.conversation_service.get_turn(later_conversation)
_check(
later_turn != null and later_turn.get_act().get_intent_id() == ConversationIntentIds.THANK,
"Later dialogue reflects the fulfilled commitment"
)
_check(
(
StringName("outcome_event_%d" % restored_situation.get_resolution_event_id())
in later_turn.get_reason_trace()
),
"Later dialogue traces the exact real deposit event"
)
restored.end_player_conversation(later_conversation)
restored.free()
manager.free()
_finish()
func _set_pantry_amount(amount: float) -> void:
var pantry: StorageStateRecord = manager.get_pantry()
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
pantry.deposit(SimulationIds.RESOURCE_FOOD, amount)
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
func _option_for_intent(turn: ConversationTurn, intent_id: StringName) -> ConversationOption:
for option in turn.get_options():
if option.get_intent_id() == intent_id:
return option
return null
func _has_intent(turn: ConversationTurn, intent_id: StringName) -> bool:
return _option_for_intent(turn, intent_id) != null
func _check(condition: bool, message: String) -> void:
if condition:
return
failures.append(message)
push_error("[TEST] " + message)
func _different_keys(left: Dictionary, right: Dictionary) -> Array[String]:
var differences: Array[String] = []
for key in left:
if not right.has(key) or JSON.stringify(left[key]) != JSON.stringify(right[key]):
differences.append(String(key))
return differences
func _finish() -> void:
if failures.is_empty():
print("[TEST] Emergent Jajce slice passed")
quit(0)
else:
quit(1)