Implement prototype: venue-driven simulation core, scenarios, tests, and playable entrance scene
CI / headless-tests (push) Canceled after 49s
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:
1 parent
e23f284ad4
commit
439e0a4281
45 files changed
+4368
-7
No files matched your search
@@ -0,0 +1,68 @@
|
||||
class_name EventScheduler
|
||||
extends RefCounted
|
||||
|
||||
const Rules := preload("res://src/core/rules.gd")
|
||||
const VenueScenario := preload("res://src/core/venue_scenario.gd")
|
||||
## Schedules routine traffic and scripted events for a shift according to the
|
||||
## scenario's event pool (ADR-0001, Shift Structure).
|
||||
|
||||
signal event_ready(event_data: Dictionary)
|
||||
|
||||
var rng := RandomNumberGenerator.new()
|
||||
var _scenario: VenueScenario = null
|
||||
var _weighted_pool: Array[Dictionary] = []
|
||||
|
||||
func _init() -> void:
|
||||
pass
|
||||
|
||||
func setup(scenario: VenueScenario, seed_value: int = 0) -> void:
|
||||
_scenario = scenario
|
||||
if seed_value != 0:
|
||||
rng.seed = seed_value
|
||||
_weighted_pool = []
|
||||
for event in scenario.event_pool:
|
||||
if event.get("enabled", true):
|
||||
_weighted_pool.append(event)
|
||||
|
||||
## Pull events due for the current elapsed time and phase.
|
||||
## Returns Array[Dictionary] of event data to spawn.
|
||||
func pull_due(elapsed_s: float, phase: int) -> Array[Dictionary]:
|
||||
var due: Array[Dictionary] = []
|
||||
if _scenario == null:
|
||||
return due
|
||||
for event in _weighted_pool:
|
||||
var time_range: Vector2 = event.get("time_range_s", Vector2(0, _scenario.shift_duration_s))
|
||||
if elapsed_s < time_range.x or elapsed_s > time_range.y:
|
||||
continue
|
||||
if event.get("phase", -1) != phase:
|
||||
continue
|
||||
var prob: float = event.get("probability", 0.0)
|
||||
var base: float = event.get("base_weight", 1.0)
|
||||
var spawn_rate: float = event.get("spawn_rate", 0.0)
|
||||
if spawn_rate > 0.0:
|
||||
# Rate-based spawning: emit at most one per second window.
|
||||
if rng.randf() < spawn_rate:
|
||||
if rng.randf() < prob:
|
||||
due.append(event.duplicate())
|
||||
else:
|
||||
if rng.randf() < prob * base:
|
||||
due.append(event.duplicate())
|
||||
return due
|
||||
|
||||
func fallback_routine_traffic(phase: int) -> Array[Dictionary]:
|
||||
## Cheap routine traffic so the street never feels empty.
|
||||
var out: Array[Dictionary] = []
|
||||
var pool := _routine_pool(phase)
|
||||
if pool.is_empty():
|
||||
return out
|
||||
for i in 3:
|
||||
if rng.randf() < 0.35:
|
||||
out.append(pool[rng.randi_range(0, pool.size() - 1)].duplicate())
|
||||
return out
|
||||
|
||||
func _routine_pool(phase: int) -> Array:
|
||||
var out: Array = []
|
||||
for event in _weighted_pool:
|
||||
if event.get("kind", "routine") == "routine" and event.get("phase", -1) == phase:
|
||||
out.append(event)
|
||||
return out
|
||||
Reference in new issue
Block a user