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

+77
View File
@@ -0,0 +1,77 @@
class_name ShiftReview
extends CanvasLayer
## End-of-shift review screen (PLAN.md: Evidence and Body Camera, Shift Review).
signal restart_requested()
var report_label: Label
func _init() -> void:
name = "ShiftReview"
layer = 30
var dim := ColorRect.new()
dim.color = Color(0.04, 0.05, 0.08, 0.92)
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(dim)
var center := VBoxContainer.new()
center.set_anchors_preset(Control.PRESET_CENTER)
center.alignment = BoxContainer.ALIGNMENT_CENTER
center.add_theme_constant_override("separation", 12)
add_child(center)
var title := Label.new()
title.text = "Shift Review"
title.add_theme_font_size_override("font_size", 28)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
center.add_child(title)
report_label = Label.new()
report_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
report_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
report_label.custom_minimum_size = Vector2(720, 0)
center.add_child(report_label)
var restart := Button.new()
restart.text = "Run another shift"
restart.pressed.connect(func(): restart_requested.emit())
restart.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
center.add_child(restart)
var quit_btn := Button.new()
quit_btn.text = "Quit"
quit_btn.pressed.connect(func(): get_tree().quit())
quit_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
center.add_child(quit_btn)
func show_review(review: Dictionary, scenario: Resource) -> void:
var reputation: Dictionary = review.get("reputation", {})
var incidents: Dictionary = review.get("incidents", {})
var crowd: Dictionary = review.get("crowd", {})
var radio: Dictionary = review.get("radio", {})
var incident_list: Array = incidents.get("incidents", [])
var peaceful := 0
for inc in incident_list:
if inc.get("outcome", "") in ["peaceful", "left"]:
peaceful += 1
var lines: Array[String] = [
"Venue: %s" % (scenario.display_name if scenario else review.get("scenario_id", "")),
"Grade: %s Score: %.0f/100" % [reputation.get("grade", "?"), reputation.get("rating", 0.0)],
"Incidents handled: %d (peaceful %d)" % [incident_list.size(), peaceful],
"NPCs encountered: %d" % review.get("spawned_count", 0),
"Radio battery: %.0f%% emergency cooldown: %.0fs" % [radio.get("battery", 0.0), radio.get("emergency_cooldown_s", 0.0)],
"Crowd mood: %.0f%% (panic: %s)" % [crowd.get("mood", 0.0) * 100.0, str(crowd.get("panic", false))],
"",
"Highlights:",
]
var mods: Array = reputation.get("modifiers", [])
var shown := 0
for mod in mods:
var delta: float = float(mod.get("delta", 0.0))
lines.append(" %s (%+.0f) %s" % [mod.get("category", ""), delta, mod.get("reason", "")])
shown += 1
if shown >= 12:
break
report_label.text = "\n".join(lines)
visible = true