Implement prototype: venue-driven simulation core, scenarios, tests, and playable entrance scene
CI / headless-tests (push) Canceled after 49s

- Godot 4.3 project scaffold with first-person guard, procedural world,
  interaction HUD, access inspection, radio forms, debug overlay, shift review
- Simulation core: ShiftCoordinator, VenueScenario, EventScheduler, NPCController/FSM,
  AccessVerifier, EscalationManager, RadioSystem, IncidentRecorder,
  ReputationEvaluator, CrowdSystem, EventBus
- Three authored scenarios (Hollywood Blvd building, The Joke Shop, TV studio)
- Custom headless test runner (25 tests) replacing editor-dependent GUT,
  plus GitHub Actions CI
- Documentation workflow live: README, PLAN (tech stack), TODO/DONE, MEMORY
This commit is contained in:
zwitschi committed 2026-10-31 11:37:54 +01:00
1 parent e23f284ad4
commit 439e0a4281
45 files changed
+4368 -7

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
extends RefCounted
## Headless tests for AccessVerifier (ADR-0003, Access Policy).
const Rules := preload("res://src/core/rules.gd")
const AccessVerifier := preload("res://src/core/access_verifier.gd")
const AccessPolicy := preload("res://src/core/access_policy.gd")
const AccessArtifact := preload("res://src/core/access_artifact.gd")
var verifier := AccessVerifier.new()
func test_wristband_valid_grants() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.VALID, {"color": "red", "date": "2026-02-13", "event": "gala"}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], true, "valid wristband should grant access")
func test_wristband_expired_denies() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.EXPIRED, {}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], false, "expired wristband must deny")
func test_wristband_counterfeit_denies() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.COUNTERFEIT, {}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], false, "counterfeit wristband must deny")
func test_missing_wristband_denies() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var result := verifier.verify([], policy)
return TestUtils.check_eq(result["granted"], false, "missing wristband must deny")
func test_recognition_grants_without_wristband() -> bool:
## PLAN.md: "The guard already knows and recognizes them."
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.RECOGNITION, Rules.ArtifactStatus.VALID, {}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], true, "recognition should grant access")
func test_recognition_expired_does_not_grant() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.RECOGNITION, Rules.ArtifactStatus.MISSING, {}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], false, "missing recognition should not grant under band policy")
func test_joke_shop_id_and_ticket() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.ID: true, Rules.AccessKind.TICKET: true }
policy.minimum_age = 21
var ok: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 24}),
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
]
var denied_underage: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 19}),
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
]
var denied_no_ticket: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 24}),
]
var ok_result := verifier.verify(ok, policy)
var denied_underage_result := verifier.verify(denied_underage, policy)
var denied_no_ticket_result := verifier.verify(denied_no_ticket, policy)
var a := TestUtils.check_eq(ok_result["granted"], true, "valid ID + ticket grants")
var b := TestUtils.check_eq(denied_underage_result["granted"], false, "underage denied")
var c := TestUtils.check_eq(denied_no_ticket_result["granted"], false, "no ticket denied")
return a and b and c
func test_joke_shop_requires_wand_screen() -> bool:
var policy := AccessPolicy.new()
policy.required_artifacts = { Rules.AccessKind.ID: true, Rules.AccessKind.TICKET: true }
policy.wand_screening_required = true
var artifacts: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 25}),
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
]
var result := verifier.verify(artifacts, policy)
return TestUtils.check_eq(result["granted"], false, "unscreened guest denied when screening required")
func test_tv_studio_any_valid_grants() -> bool:
## OR-admission: any single valid artifact among several kinds grants entry.
var policy := AccessPolicy.new()
policy.required_artifacts = {
Rules.AccessKind.CREDENTIAL: true,
Rules.AccessKind.WRISTBAND: true,
Rules.AccessKind.TICKET: true,
Rules.AccessKind.GUEST_LIST: true,
}
policy.grant_if_any_required = true
var with_wristband: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.VALID, {}),
]
var with_guest_list: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.GUEST_LIST, Rules.ArtifactStatus.VALID, {}),
]
var all_fake: Array[AccessArtifact] = [
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.COUNTERFEIT, {}),
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.EXPIRED, {}),
]
var a := TestUtils.check_eq(verifier.verify(with_wristband, policy)["granted"], true, "wristband grants")
var b := TestUtils.check_eq(verifier.verify(with_guest_list, policy)["granted"], true, "guest list grants")
var c := TestUtils.check_eq(verifier.verify(all_fake, policy)["granted"], false, "all invalid denied")
return a and b and c