253 lines
9.4 KiB
GDScript
253 lines
9.4 KiB
GDScript
extends GutTest
|
|
|
|
const CommitmentLifecycleService := preload(
|
|
"res://simulation/situations/CommitmentLifecycleService.gd"
|
|
)
|
|
const SimulationEventLogScript := preload("res://simulation/events/SimulationEventLog.gd")
|
|
const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd")
|
|
|
|
|
|
func test_debtor_fulfillment_records_exact_facts_and_idempotent_consequences() -> void:
|
|
var context := _open_commitment(SimulationIds.PLAYER_ACTOR_ID, 4, 20)
|
|
var lifecycle := CommitmentLifecycleService.new()
|
|
var acceptance := lifecycle.record_acceptance(
|
|
context.commitment, context.event_log, context.relationships
|
|
)
|
|
assert_not_null(acceptance)
|
|
assert_eq(StringName(acceptance.data["event_type"]), CommitmentLifecycleService.EVENT_ACCEPTED)
|
|
assert_eq(int(acceptance.data["commitment_id"]), context.commitment.get_commitment_id())
|
|
var relationship: RelationshipStateRecord = context.relationships.get_relationship(
|
|
4, SimulationIds.PLAYER_ACTOR_ID
|
|
)
|
|
assert_not_null(relationship)
|
|
assert_almost_eq(
|
|
relationship.get_obligation(), CommitmentLifecycleService.ACCEPTED_OBLIGATION, 0.0001
|
|
)
|
|
assert_eq(relationship.get_last_obligation_cause_event_id(), int(acceptance.data["event_id"]))
|
|
|
|
var resolution := _record_supply(context.event_log, 12, SimulationIds.PLAYER_ACTOR_ID)
|
|
var closed := lifecycle.maintain_and_record(
|
|
12,
|
|
context.commitments,
|
|
context.situations,
|
|
context.event_log,
|
|
_pantry_facts(1.0),
|
|
context.relationships
|
|
)
|
|
assert_eq(closed, [context.commitment])
|
|
assert_eq(context.commitment.get_status(), CommitmentStateRecord.STATUS_FULFILLED)
|
|
assert_eq(context.commitment.get_cause_event_id(), int(resolution.data["event_id"]))
|
|
var fulfilled := lifecycle.get_fact(
|
|
context.event_log,
|
|
CommitmentLifecycleService.EVENT_FULFILLED,
|
|
context.commitment.get_commitment_id()
|
|
)
|
|
assert_not_null(fulfilled)
|
|
assert_eq(int(fulfilled.data["cause_event_id"]), int(resolution.data["event_id"]))
|
|
assert_almost_eq(relationship.get_obligation(), 0.0, 0.0001)
|
|
assert_almost_eq(
|
|
relationship.get_trust(),
|
|
RelationshipStateRecord.NEUTRAL_TRUST + CommitmentLifecycleService.FULFILLED_TRUST,
|
|
0.0001
|
|
)
|
|
assert_eq(relationship.get_last_trust_cause_event_id(), int(fulfilled.data["event_id"]))
|
|
assert_eq(relationship.get_last_obligation_cause_event_id(), int(fulfilled.data["event_id"]))
|
|
|
|
var event_count: int = context.event_log.events.size()
|
|
var relationship_before := relationship.to_dictionary()
|
|
assert_same(
|
|
lifecycle.record_outcome(context.commitment, context.event_log, context.relationships),
|
|
fulfilled
|
|
)
|
|
assert_true(
|
|
(
|
|
lifecycle
|
|
. maintain_and_record(
|
|
13,
|
|
context.commitments,
|
|
context.situations,
|
|
context.event_log,
|
|
_pantry_facts(1.0),
|
|
context.relationships
|
|
)
|
|
. is_empty()
|
|
)
|
|
)
|
|
assert_eq(context.event_log.events.size(), event_count)
|
|
assert_eq(relationship.to_dictionary(), relationship_before)
|
|
_assert_fact_round_trip(context.event_log, fulfilled)
|
|
|
|
|
|
func test_deadline_breaks_at_due_tick_and_applies_one_exact_penalty() -> void:
|
|
var context := _open_commitment(2, 3, 12)
|
|
var lifecycle := CommitmentLifecycleService.new()
|
|
lifecycle.record_acceptance(context.commitment, context.event_log, context.relationships)
|
|
var relationship: RelationshipStateRecord = context.relationships.get_relationship(3, 2)
|
|
var closed := lifecycle.maintain_and_record(
|
|
12,
|
|
context.commitments,
|
|
context.situations,
|
|
context.event_log,
|
|
_pantry_facts(0.0),
|
|
context.relationships
|
|
)
|
|
|
|
assert_eq(closed, [context.commitment])
|
|
assert_eq(context.commitment.get_status(), CommitmentStateRecord.STATUS_BROKEN)
|
|
assert_eq(context.commitment.get_close_reason(), SocialCommitmentSystem.REASON_DEADLINE_MISSED)
|
|
var broken := lifecycle.get_fact(
|
|
context.event_log,
|
|
CommitmentLifecycleService.EVENT_BROKEN,
|
|
context.commitment.get_commitment_id()
|
|
)
|
|
assert_not_null(broken)
|
|
assert_almost_eq(relationship.get_obligation(), 0.0, 0.0001)
|
|
assert_almost_eq(
|
|
relationship.get_trust(),
|
|
RelationshipStateRecord.NEUTRAL_TRUST + CommitmentLifecycleService.BROKEN_TRUST,
|
|
0.0001
|
|
)
|
|
assert_almost_eq(
|
|
relationship.get_hostility(), CommitmentLifecycleService.BROKEN_HOSTILITY, 0.0001
|
|
)
|
|
for dimension in [
|
|
RelationshipStateRecord.DIMENSION_OBLIGATION,
|
|
RelationshipStateRecord.DIMENSION_TRUST,
|
|
RelationshipStateRecord.DIMENSION_HOSTILITY,
|
|
]:
|
|
assert_eq(relationship.get_last_cause_event_id(dimension), int(broken.data["event_id"]))
|
|
var before := relationship.to_dictionary()
|
|
assert_same(
|
|
lifecycle.record_outcome(context.commitment, context.event_log, context.relationships),
|
|
broken
|
|
)
|
|
assert_eq(relationship.to_dictionary(), before)
|
|
|
|
|
|
func test_other_npc_resolution_supersedes_without_trust_or_hostility_penalty() -> void:
|
|
var context := _open_commitment(2, 4, 20)
|
|
var lifecycle := CommitmentLifecycleService.new()
|
|
lifecycle.record_acceptance(context.commitment, context.event_log, context.relationships)
|
|
var relationship: RelationshipStateRecord = context.relationships.get_relationship(4, 2)
|
|
var resolution := _record_supply(context.event_log, 12, 3)
|
|
var closed := lifecycle.maintain_and_record(
|
|
12,
|
|
context.commitments,
|
|
context.situations,
|
|
context.event_log,
|
|
_pantry_facts(1.0),
|
|
context.relationships
|
|
)
|
|
|
|
assert_eq(closed, [context.commitment])
|
|
assert_eq(context.commitment.get_status(), CommitmentStateRecord.STATUS_SUPERSEDED)
|
|
assert_eq(
|
|
context.commitment.get_close_reason(), SocialCommitmentSystem.REASON_FACT_RESOLVED_BY_OTHER
|
|
)
|
|
assert_eq(context.commitment.get_cause_event_id(), int(resolution.data["event_id"]))
|
|
var superseded := lifecycle.get_fact(
|
|
context.event_log,
|
|
CommitmentLifecycleService.EVENT_SUPERSEDED,
|
|
context.commitment.get_commitment_id()
|
|
)
|
|
assert_not_null(superseded)
|
|
assert_almost_eq(relationship.get_obligation(), 0.0, 0.0001)
|
|
assert_almost_eq(relationship.get_trust(), RelationshipStateRecord.NEUTRAL_TRUST, 0.0001)
|
|
assert_almost_eq(relationship.get_hostility(), 0.0, 0.0001)
|
|
assert_eq(relationship.get_last_obligation_cause_event_id(), int(superseded.data["event_id"]))
|
|
assert_eq(relationship.get_last_trust_cause_event_id(), RelationshipStateRecord.NO_CAUSE_EVENT)
|
|
assert_eq(
|
|
relationship.get_last_hostility_cause_event_id(), RelationshipStateRecord.NO_CAUSE_EVENT
|
|
)
|
|
|
|
|
|
func test_released_commitment_records_fact_and_clears_obligation_without_penalty() -> void:
|
|
var context := _open_commitment(2, 4, 20)
|
|
var lifecycle := CommitmentLifecycleService.new()
|
|
var relationship: RelationshipStateRecord = context.relationships.get_or_create_relationship(
|
|
4, 2
|
|
)
|
|
assert_almost_eq(relationship.adjust_obligation(0.9, -1), 0.9, 0.0001)
|
|
var acceptance := lifecycle.record_acceptance(
|
|
context.commitment, context.event_log, context.relationships
|
|
)
|
|
assert_almost_eq(float(acceptance.data["obligation_delta"]), 0.1, 0.0001)
|
|
assert_almost_eq(relationship.get_obligation(), 1.0, 0.0001)
|
|
assert_true(context.commitments.release(0, 11, &"mutual_release"))
|
|
var released := lifecycle.record_outcome(
|
|
context.commitment, context.event_log, context.relationships
|
|
)
|
|
|
|
assert_not_null(released)
|
|
assert_eq(StringName(released.data["event_type"]), CommitmentLifecycleService.EVENT_RELEASED)
|
|
assert_almost_eq(relationship.get_obligation(), 0.9, 0.0001)
|
|
assert_almost_eq(relationship.get_trust(), RelationshipStateRecord.NEUTRAL_TRUST, 0.0001)
|
|
assert_almost_eq(relationship.get_hostility(), 0.0, 0.0001)
|
|
|
|
|
|
func _open_commitment(debtor_id: int, creditor_id: int, deadline_tick: int) -> Dictionary:
|
|
var event_log := SimulationEventLogScript.new()
|
|
var trigger := event_log.record_economic(
|
|
10,
|
|
&"storage_withdrawn",
|
|
creditor_id,
|
|
&"village_pantry",
|
|
StringName("npc_inventory_%d" % creditor_id),
|
|
&"food",
|
|
1.0
|
|
)
|
|
var situations := SituationSystem.create_default()
|
|
situations.consider_event(
|
|
event_log.get_world_event(int(trigger.data["event_id"])), 10, _pantry_facts(0.0)
|
|
)
|
|
var situation := situations.get_by_id(0)
|
|
var commitments := SocialCommitmentSystem.new()
|
|
var debtor_type := (
|
|
WorldEventRecord.ENTITY_TYPE_PLAYER
|
|
if debtor_id == SimulationIds.PLAYER_ACTOR_ID
|
|
else WorldEventRecord.ENTITY_TYPE_NPC
|
|
)
|
|
var commitment := commitments.create_for_alternative(
|
|
situation,
|
|
situations.get_definition(situation.get_definition_id()),
|
|
SituationSystem.ALTERNATIVE_RESTOCK,
|
|
WorldEntityRef.create(debtor_type, StringName(str(debtor_id))),
|
|
WorldEntityRef.create(WorldEventRecord.ENTITY_TYPE_NPC, StringName(str(creditor_id))),
|
|
10,
|
|
deadline_tick
|
|
)
|
|
return {
|
|
"commitment": commitment,
|
|
"commitments": commitments,
|
|
"event_log": event_log,
|
|
"relationships": RelationshipSystemScript.new(),
|
|
"situations": situations,
|
|
}
|
|
|
|
|
|
func _record_supply(event_log: RefCounted, tick: int, actor_id: int) -> EconomicEventRecord:
|
|
var source_id := (
|
|
&"player_inventory"
|
|
if actor_id == SimulationIds.PLAYER_ACTOR_ID
|
|
else StringName("npc_inventory_%d" % actor_id)
|
|
)
|
|
return event_log.record_economic(
|
|
tick, &"storage_deposited", actor_id, source_id, &"village_pantry", &"food", 1.0
|
|
)
|
|
|
|
|
|
func _pantry_facts(amount: float) -> Dictionary:
|
|
return {"pantry.food": amount, "target_id": "village_pantry"}
|
|
|
|
|
|
func _assert_fact_round_trip(event_log: RefCounted, expected: EconomicEventRecord) -> void:
|
|
var records: Array[EconomicEventRecord] = []
|
|
for event in event_log.events:
|
|
records.append(EconomicEventRecord.from_dictionary(event.to_dictionary()))
|
|
var restored := SimulationEventLogScript.new()
|
|
restored.restore(records, event_log.next_event_id)
|
|
var actual := restored.get_by_id(int(expected.data["event_id"]))
|
|
assert_not_null(actual)
|
|
assert_eq(actual.to_dictionary(), expected.to_dictionary())
|
|
assert_not_null(restored.get_world_event(int(expected.data["event_id"])))
|