class_name SimulationScalingBenchmark extends RefCounted const SCHEMA_VERSION := 3 const WORKLOAD_ID := &"full_fidelity_emergent_headless_arrival_v3" const RECENT_FACTS_PER_NPC := 3 const STARTING_CLOCK_TICK := 50 func prepare_manager( manager: Node, population: int, history_event_count: int, seed_value: int ) -> bool: if manager == null or manager.clock == null or population <= 0 or history_event_count < 0: return false manager.set_process(false) manager.simulation_seed = seed_value manager.tick_count = history_event_count manager.clock.accumulator = 0.0 manager.clock.elapsed_ticks = STARTING_CLOCK_TICK manager.npcs.clear() manager.wander_random_sources.clear() manager.latest_decisions.clear() manager.resource_states.clear() var no_relationships: Array[RelationshipStateRecord] = [] var no_opportunities: Array[OpportunityStateRecord] = [] manager.relationship_system.restore(no_relationships) manager.opportunity_system.restore(no_opportunities, 0) var no_situations: Array[SituationStateRecord] = [] var no_journal_entries: Array[QuestJournalEntryStateRecord] = [] var no_commitments: Array[CommitmentStateRecord] = [] manager.situation_system.restore(no_situations, 0) manager.quest_journal_system.restore(no_journal_entries, 0) manager.commitment_system.restore(no_commitments, 0) manager.conversation_history.clear() manager._next_conversation_act_id = 0 for npc_id in population: var npc_random := _create_random_source(seed_value, npc_id, 0) var npc := SimNPC.new( npc_id, "Benchmark %04d" % npc_id, SimulationIds.PROFESSION_FARMER, npc_random.randf_range(1.0, 10.0), npc_random.randf_range(1.0, 10.0), npc_random ) npc.debug_logs = false npc.hunger = 35.0 + float(npc_id % 10) * 0.25 npc.energy = 90.0 npc.position = _grid_position(npc_id) npc.home_position = npc.position manager.npcs.append(npc) manager.wander_random_sources[npc_id] = _create_random_source(seed_value, npc_id, 1) manager.refresh_population_index() var no_combatants: Array[CombatantStateRecord] = [] var no_factions: Array[FactionStateRecord] = [] manager.conflict_system.restore_state_records(no_combatants, no_factions, manager.tick_count) manager.conflict_system.register_npc_combatants(manager.npcs) if not _get_combatant_coverage(manager)["valid"]: return false var pantry: StorageStateRecord = manager.get_pantry() pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD)) manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD) manager.village.update_modifiers() manager.village.update_priorities() var history := _build_history(population, history_event_count) manager.event_log.restore(history["events"], history_event_count) manager.event_knowledge_system.restore(history["knowledge"]) return true func measure_manager( manager: Node, population: int, history_event_count: int, warmup_ticks: int, measured_ticks: int ) -> Dictionary: if ( manager == null or manager.npcs.size() != population or warmup_ticks < 0 or measured_ticks <= 0 or not _get_combatant_coverage(manager)["valid"] ): return {} var warmup_arrivals := 0 for _tick in warmup_ticks: warmup_arrivals += _advance_headless_tick(manager) var warmup_coverage := _get_combatant_coverage(manager) if not warmup_coverage["valid"] or warmup_coverage["npc_combatant_count"] != population: return {} var start_json: String = manager.serialize_state() var start_event_count: int = manager.economic_events.size() var start_known_count: int = manager.event_knowledge_system.get_all_sorted().size() var start_situation_count: int = manager.situation_system.get_all_sorted().size() var start_commitment_count: int = manager.commitment_system.get_all_sorted().size() var start_tick: int = manager.tick_count var measured_arrivals := 0 var simulation_usec := 0 var arrival_usec := 0 var started_usec := Time.get_ticks_usec() for _tick in measured_ticks: var phase_started_usec := Time.get_ticks_usec() manager.simulate_tick() simulation_usec += Time.get_ticks_usec() - phase_started_usec phase_started_usec = Time.get_ticks_usec() measured_arrivals += _complete_headless_arrivals(manager) arrival_usec += Time.get_ticks_usec() - phase_started_usec var elapsed_usec := maxi(Time.get_ticks_usec() - started_usec, 1) var end_json: String = manager.serialize_state() var start_state_bytes := start_json.to_utf8_buffer().size() var end_state_bytes := end_json.to_utf8_buffer().size() var ticks_per_second := float(measured_ticks) * 1000000.0 / float(elapsed_usec) var combatant_coverage := _get_combatant_coverage(manager) if not combatant_coverage["valid"] or combatant_coverage["npc_combatant_count"] != population: return {} return { "schema_version": SCHEMA_VERSION, "simulation_state_schema_version": SimulationStateRecord.SCHEMA_VERSION, "workload_id": String(WORKLOAD_ID), "seed": manager.simulation_seed, "population": population, "history_seed_events": history_event_count, "warmup_ticks": warmup_ticks, "measured_ticks": measured_ticks, "npc_updates": population * measured_ticks, "npc_combatant_count": combatant_coverage["npc_combatant_count"], "npc_combatant_coverage_valid": combatant_coverage["valid"], "warmup_arrivals": warmup_arrivals, "arrivals_processed": measured_arrivals, "elapsed_usec": elapsed_usec, "simulation_usec": maxi(simulation_usec, 1), "arrival_usec": maxi(arrival_usec, 1), "usec_per_tick": float(elapsed_usec) / float(measured_ticks), "ticks_per_second": ticks_per_second, "tick_interval": manager.tick_interval, "realtime_factor": ticks_per_second * manager.tick_interval, "start_tick": start_tick, "end_tick": manager.tick_count, "start_state_bytes": start_state_bytes, "end_state_bytes": end_state_bytes, "state_growth_bytes": end_state_bytes - start_state_bytes, "start_event_count": start_event_count, "end_event_count": manager.economic_events.size(), "events_recorded": manager.economic_events.size() - start_event_count, "start_known_reference_count": start_known_count, "end_known_reference_count": manager.event_knowledge_system.get_all_sorted().size(), "start_situation_count": start_situation_count, "end_situation_count": manager.situation_system.get_all_sorted().size(), "start_commitment_count": start_commitment_count, "end_commitment_count": manager.commitment_system.get_all_sorted().size(), "conversation_act_count": manager.conversation_history.size(), "final_checksum": end_json.sha256_text(), } func _get_combatant_coverage(manager: Node) -> Dictionary: var expected_npcs := {} for npc in manager.npcs: expected_npcs[npc.id] = npc var covered_npc_ids := {} var npc_combatant_count := 0 var valid: bool = expected_npcs.size() == manager.npcs.size() for combatant in manager.conflict_system.get_all_combatants(): if combatant.get_kind() != SimulationIds.COMBATANT_KIND_NPC: continue npc_combatant_count += 1 var npc_id: int = combatant.get_npc_id() var npc := expected_npcs.get(npc_id) as SimNPC if ( npc == null or covered_npc_ids.has(npc_id) or combatant.get_combatant_id() != SimulationIds.npc_combatant_id(npc_id) or combatant.get_display_name() != npc.npc_name or not combatant.get_position().is_equal_approx(npc.position) ): valid = false covered_npc_ids[npc_id] = true valid = ( valid and npc_combatant_count == expected_npcs.size() and covered_npc_ids.size() == expected_npcs.size() ) return {"valid": valid, "npc_combatant_count": npc_combatant_count} func _advance_headless_tick(manager: Node) -> int: manager.simulate_tick() return _complete_headless_arrivals(manager) func _complete_headless_arrivals(manager: Node) -> int: var arrival_ids: Array[int] = [] for npc in manager.npcs: if npc.task_state == SimNPC.TASK_STATE_TRAVELING: arrival_ids.append(npc.id) for npc_id in arrival_ids: manager.notify_npc_arrived(npc_id) return arrival_ids.size() func _build_history(population: int, history_event_count: int) -> Dictionary: var events: Array[EconomicEventRecord] = [] for event_id in history_event_count: var actor_id := event_id % population events.append( EconomicEventRecord.create( event_id, SimulationIds.EVENT_STORAGE_DEPOSITED, event_id, actor_id, SimulationIds.npc_inventory_id(actor_id), SimulationIds.STORAGE_VILLAGE_PANTRY, SimulationIds.RESOURCE_FOOD, 1.0 ) ) var knowledge: Array[KnownEventStateRecord] = [] var retained_by_actor := {} for event_id in range(history_event_count - 1, -1, -1): var actor_id := event_id % population var retained_count := int(retained_by_actor.get(actor_id, 0)) if retained_count >= RECENT_FACTS_PER_NPC: continue knowledge.append( KnownEventStateRecord.create( actor_id, event_id, SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED, KnownEventStateRecord.NO_SOURCE_NPC_ID, event_id ) ) retained_by_actor[actor_id] = retained_count + 1 if knowledge.size() >= mini(history_event_count, population * RECENT_FACTS_PER_NPC): break return {"events": events, "knowledge": knowledge} func _create_random_source(seed_value: int, npc_id: int, stream_id: int) -> RandomNumberGenerator: var source := RandomNumberGenerator.new() source.seed = seed_value + (npc_id + 1) * 1000003 + stream_id * 7919 return source func _grid_position(npc_id: int) -> Vector3: var row := npc_id / 32 var column := npc_id % 32 return Vector3(float(column) * 12.0, 0.0, float(row) * 12.0)