September 12, 2026 at 12:00:00 AM UTC
Liminalia source modularisation — round 2
Continued the one-type-per-file and concern-based split pass on Liminalia. The first round handled the obvious multi-type files and the largest services. This round targeted every remaining file over ~170 lines, splitting by concern where the split was real.
What was split
SimulationWorld (326 → 218 + 134): tick engine (accumulator, fixed-tick loop, service pipeline, difficulty watch) moved to SimulationWorldTick.cs. Core state (services, properties, collections, constructor, event raising) stays in SimulationWorld.cs.
HealthService (289 → 132 + 137 + 54): susceptibility accumulation and falling ill stay in HealthService. Doctor dispatch and on-scene treatment moved to HealthDispatch. Fatal untreated illness moved to HealthFatality.
LabourService (244 → 90 + 92 + 154): civic institution staffing (hospitals, fire stations, police stations, schools posting openings) moved to LabourCivicStaffing. The hiring pass (collecting openings, finding nearest qualified candidate, hiring) moved to LabourHiring.
CitizenMovement (243 → 109 + 174): route planning (RequestMove, return-safe routing, re-planning) stays in CitizenMovement. Traversal (walking routes edge by edge, tolls, fold crossings, arrival) moved to CitizenTraversal.
PartnershipService (217 → 153 + 97): cohabitation formalisation stays. Births (chance per simulated year, child creation, household registration) moved to PartnershipBirths.
WorldCommands (193 → deleted, split into two): construction commands (roads, buildings, demolitions, spatial folds) moved to WorldConstruction. Policy commands (income tax, fold tolls, edge tolls, UBI) moved to WorldPolicy.
CrimeCommitting (289 → 95 + 212): the CommitCrimes orchestration loop stays. Individual crime acts (theft, burglary, arson, vandalism, victim finding) moved to CrimeActs.
CrimePressure (178 → 81 + 97): economic pressure accumulation (debt, unemployment, hunger, poverty → crime pressure, deterrence) stays. Hatred accumulation between rivals and murder escalation moved to CrimeHatred.
SaveSerializer (255 → 67 + 228): the Restore method moved to SaveSerializerRead, leaving Snapshot as the sole entry point in SaveSerializer.
What was not split
Files left intact because the split would be artificial:
WorldBuilder.cs(196) — linear test fixture, one methodRouteFinder.cs(179) — single Dijkstra implementation, methods tightly coupledGoalManager.cs(167) — Tick and EvaluateGoals share the same stack stateSimulationHost.cs(159) — thin Godot bridge wrappersEducationService.cs(157) — cohesive Tick loop with supporting helpersFireService.cs(172) — ignition and evacuation are the same concernCrimeDispatch.cs(172) — dispatch and resolution are tightly coupled
Results
162 source files (was ~90 before round 1), 63 test files. Largest file is 228 lines (was 753). All 367 tests pass across repeated runs.
all entries