Implement TODO backlog: LATE phase fix, protester, CALL_SOMEONE, door lock, flashlight, persistence, navmesh, JSON localization, and release tooling
CI / headless-tests (push) Successful in 55s

- Fix LATE shift phase (start 700s) so the violent-threat event is reachable
- Add PROTESTER archetype (rules, profile, colors, events in public venues)
- Add CALL_SOMEONE reaction (FSM -> ARGUING, influencer/trespasser/protester weights)
- Door lock: DoorState core class, L binding, red/green visual indicator,
  locked doors thwart FORCE_ENTRY
- Flashlight: G binding, SpotLight3D, perception radius bonus at evening/late
- Persistence: save shift results via Save autoload, history + best score on
  review screen, round-trip test
- Navigation: flat NavigationRegion3D navmesh, NavigationAgent3D path following
  with direct-movement fallback; steps made visual-only so NPCs reach entrance
- Localization: strings moved to content/strings.en.json (loader keeps
  lookup/fmt signatures; coverage test over all keys)
- Distribution: export_presets.cfg (Linux/Windows/macOS), tools/build_releases.sh,
  docs/RELEASING.md, prominent README Downloads section
- Remove stale signal_bus.gd (invalid static-signal experiment)
- Tests: 53 passing across 9 suites
This commit is contained in:
zwitschi committed 2026-10-31 12:51:13 +01:00
1 parent c808165302
commit 1e972a7490
27 files changed
+1044 -340

No files matched your search

+17 -1
View File
@@ -44,7 +44,7 @@ func _init() -> void:
quit_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
center.add_child(quit_btn)
func show_review(review: Dictionary, scenario: Resource) -> void:
func show_review(review: Dictionary, scenario: Resource, history: Array = []) -> void:
var reputation: Dictionary = review.get("reputation", {})
var incidents: Dictionary = review.get("incidents", {})
var crowd: Dictionary = review.get("crowd", {})
@@ -55,9 +55,25 @@ func show_review(review: Dictionary, scenario: Resource) -> void:
if inc.get("outcome", "") in ["peaceful", "left"]:
peaceful += 1
# Persisted-shift history (Save autoload): record count + best rating.
var history_line := ""
if history.size() > 0:
var best := -1.0
var past_grades: Array[String] = []
for entry in history:
var rep: Dictionary = entry.get("reputation", {})
var rating_v: float = float(rep.get("rating", 0.0))
best = maxf(best, rating_v)
var g: String = rep.get("grade", "?")
if not past_grades.has(g):
past_grades.append(g)
history_line = "Shifts on record: %d · best score: %.0f · grades seen: %s" % [
history.size(), best, ", ".join(past_grades)]
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)],
history_line,
"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)],