Skip to content

Atlas home · Diagrams · Audit · Runtime · Persistence & services

Runtime and gameplay architecture audit

Commit: 5bf351248de6050876fe5b53f91209a970cc5668 (Civ13/Civ13 master snapshot supplied for this audit). Read-only static analysis. No server, compiler, live client, deployment, publication, or paid service was used. All source links pin this commit. AGENTS.md was read; its operational guidance is not treated as proof of runtime behavior.

Scope and interpretation

This report covers startup, process scheduling, round phases, human movement/input, item combat/projectiles, crafting, faction research, simple-animal AI, and delayed-action ownership. Mermaid files describe component, class, sequence, and state abstractions. They are not a claim to enumerate every concrete execution trace. Random choices, arbitrary player inputs, map-specific overrides, spawned callbacks, configuration recipes and unlimited repeated rounds make that set unbounded. The accompanying inventory covers the full tracked DM tree.

Evidence labels used below:

1. Startup and scheduling ownership

Pre-map /datum/global_init/New() creates game ID, reference registries, configuration and chemistry definitions, then deletes itself. Native map construction occurs before /world/New(). World startup records compiled dimensions and detects persistence recovery before yielding, runs configuration post-load and startup hooks, builds materials and the shared research registry, creates /processScheduler, and schedules setup. The process scheduler is set up before setup_everything() initializes occupations, movable atoms, areas, access-list files, recipe files and dictionary data. These are interleaved cooperative operations, not an atomic initialization transaction. code/world.dm:27-44 code/world.dm:81-123 code/setup.dm:1-28 code/setup.dm:83-120

/process/ticker/setup() instantiates the gameticker and schedules pregame plus server watchdog, status-file, messaging and persistence loops. /world/start_serverswap_loop starts or restarts the legacy scheduler and separately creates/starts the movement machine. These background loops exist in addition to process objects. The persistence loop schedules saving and maintenance reboot; the messaging loop polls file-based external input. code/processes/ticker.dm:6-28 code/world.dm:348-399 code/world.dm:400-502

The scheduler discovers all compiled /process subtypes, constructs each, calls setup, and tracks name, priority and queue membership. In this revision is_subsystem_member defaults FALSE; no production assignment overriding it was found. The SSUtility, SSCombat, etc. headings in process_list are comments, not actual parent subsystems. deferSetupFor(/process/ticker) records a list but the shown setup() enumeration does not consult that list. Therefore the startup diagram does not claim ticker setup is deferred until all other processes finish. code/processes/__processScheduler.dm:74-98 code/processes/_process.dm:40-49 code/processes/__processes.dm:1-49

Scheduler execution contract

  1. start() installs a new run token, sets interval from world.tick_lag, orders processes by priority and spawns the main loop. A replacement start retires the prior sleeping scheduler invocation through token inequality.
  2. Each cycle checks running processes, queues enabled idle processes whose game-state and interval gates pass, then relays eligible queued work within a tick-usage envelope.
  3. relayProcess() has waitfor = FALSE; it initializes a missing work snapshot and calls the process wrapper. started() marks running, records timestamps and invokes onStart; process() invokes fire unless paused; finished() returns idle and keeps only unfinished nonempty budget snapshots.
  4. Ordinary priorities copy registered lists and remove visited entries. VERY_HIGH aliases the live list and skips both removal and the ordinary per-process tick check. These macros affect movement/projectile/throwing semantics and must be represented explicitly in performance reasoning.
  5. Hung states escalate by elapsed time, then the scheduler constructs a replacement of the old runtime type, copies selected configuration/state, replaces owner maps/list entries and kills the old instance. Disabled/paused/state-gated are distinct from queue/running status.

[code/processes/__processScheduler.dm:100-209](https://github.com/Civ13/Civ13/blob/5bf351248de6050876fe5b53f91209a970cc5668/code/processes/__processScheduler.dm#L100-L209) [code/processes/__processScheduler.dm:243-306](https://github.com/Civ13/Civ13/blob/5bf351248de6050876fe5b53f91209a970cc5668/code/processes/__processScheduler.dm#L243-L306) [code/processes/_processScheduler.dm:399-407](https://github.com/Civ13/Civ13/blob/5bf351248de6050876fe5b53f91209a970cc5668/code/processes/__processScheduler.dm#L399-L407) code/processes/_process.dm:134-159 code/processes/_process.dm:182-273 code/processes/_process.dm:314-348 [code/processes/defines.dm:28-33](https://github.com/Civ13/Civ13/blob/5bf351248de6050876fe5b53f91209a970cc5668/code/processes/_defines.dm#L28-L33)

The source catalogue contains 37 directly included setup declarations under code/processes/; it intentionally does not claim this is the entire compiled /process subtype universe. See process_catalog.md/json. Major dispatches are mob -> Life (1 second), object -> process_interval (2 seconds), map -> tick (2 seconds), ticker -> process (2 seconds), research benches -> analysis_tick (10 seconds), and projectile -> process (configured 0.03 second). These are ideal schedule expressions, not measured frequency or guaranteed wall-clock latency. code/processes/mob.dm:3-53 code/processes/obj.dm:1-27 code/processes/map.dm:3-12 code/processes/ticker.dm:6-45 code/processes/research_tree.dm:9-29 code/processes/projectile.dm:3-40

Other timing mechanisms

2. Round lifecycle

The live gameticker state variable begins PREGAME. pregame() waits on persistence startup hold and exits if the round was restored. It counts down a 90-second lobby under round_progressing, with map voting able to wait independently; zero advances to SETTING_UP. setup() retries or returns to PREGAME when persistence/save/admission gates do not permit a fresh round. Success sets PLAYING, resets occupations, creates ready characters, collects minds, equips roles, starts time-of-day logic and invokes roundstart hooks. Restored rounds bypass fresh character/equipment/map setup. code/game/gameticker.dm:6-35 code/game/gameticker.dm:45-157

Map tick checks faction boundaries, elimination conditions, map-specific win conditions and events; the base win-condition implementation sets ticker.finished. Ticker does not run every native tick: the process interval is two seconds. For normal rounds its process transitions PLAYING -> FINISHED and spawns delayed roundend/reboot handling; admin delay_end, epoch-swap state and persistence rechecks can alter that path. A persistence-active round clears finished and stays PLAYING. World reboot separately checks save/restart ownership and startup holds before native restart or OpenDream supervisor behavior. code/game/objects/map_metadata/_map_metadata.dm:477-522 code/game/objects/map_metadata/_map_metadata.dm:684-735 code/game/gameticker.dm:193-245 code/world.dm:235-277

02_round_lifecycle.mmd is the gameticker abstraction, not a lifecycle diagram for every map metadata subtype. Map-specific state variables and hooks are dynamic extensions. In particular, a map’s game_over, victory timer and ticker.finished are not universally synonymous.

3. Input, movement and collision

Input has native cardinal/diagonal handlers and held-direction start/stop verbs. The delayed mob-construction callback adds a non-lobby client to movementMachine_clients; the movement loop selects cardinal or combined diagonal intent, checks movement ownership/delay, then asynchronously calls client.Move. It removes the client while movement is busy and re-adds after the current delay. The loop requests a 0.01-second interval but this is not evidence BYOND delivers 100 Hz simulation. code/game/mob/mob.dm:30-44 code/game/mob/mob_movement.dm:97-145 code/game/mob/mob_movement.dm:977-1195 code/movementMachine.dm:24-98 code/movementMachine.dm:102-126

client.Move is a large policy junction: canmove/moving/delay checks, map boundaries, prone/lying actions, equipment, stamina, pulling, riding/vehicles and movement timing. It eventually uses polymorphic mob movement. Living Move coordinates buckle/drone/restraint/pull behavior, movable Move owns the native offset seam, and BYOND collision invokes Bump. Human Bump tries the shared mining operation before inherited living Bump’s deferred pushing. code/game/mob/mob_movement.dm:193-283 code/game/mob/mob_movement.dm:284-340 code/game/mob/mob_movement.dm:650-700 code/game/mob/living/living.dm:502-560 code/game/mob/living/carbon/carbon.dm:392-405 code/game/mob/living/living.dm:45-80

The human physical sub-tile adapter is compiled but human_subtile_enabled defaults FALSE. It has restricted shape/direction/state eligibility and runtime ownership/capsule machinery. The movement diagram treats its enabled path as opt-in, not normal production behavior. Its presence does not establish real-client timing acceptance. civ13.dme:230 code/game/mob/living/carbon/human/subtile_movement.dm:1-74 code/game/mob/living/carbon/human/subtile_movement.dm:230-246

Shared delayed-action boundary

do_after locks ordinary repeated actions, captures actor location, active hand and target location, shows progress, then sleeps and revalidates incapacity/movement/hand/target. It clears the lock and progress at completion. Optional mining context claims and validates actor+turf ownership; gameplay bridge context is conditional on CIV13_GAMEPLAY_BRIDGE and must not be flattened into the default signature. code/_helpers/mobs.dm:190-280

Click and bump mining reserve actor/turf before a spawned worker, record exact tool, origin and physical step offsets, and revalidate before committing. try_bump_mining -> start_pickaxe_mining -> pickaxe_mining_action.perform -> do_after(mining_context=...) is a distinct ownership-sensitive path, not just an inherited generic attack call. code/game/turfs/floor_attackby.dm:677-817

4. Combat and polymorphic item interaction

atom.Click -> usr.ClickOn is the common mouse entry. ClickOn first checks target and next-click deadline, then routes modifiers, special equipment/vehicle branches, active-hand/self interactions, adjacent versus ranged interaction, or unarmed action. Do not interpret every click as an attack. code/_onclick/click.dm:7-74 code/_onclick/click.dm:198-287

Item dispatch order is explicit: resolve turf attack first when appropriate; then target attackby; then item attack_obj for objects. A truthy resolved result suppresses ordinary afterattack paths. Living target attackby can route surgery; otherwise item attack applies eligibility, stamina, logging, cooldown and animation, asks the target to resolve the hit, and invokes apply_hit_effect -> hit_with_weapon for a resolved hit. Target/item subclass overrides determine actual armor, hit-zone and special-effect branches. code/_onclick/item_attack.dm:22-74 code/_onclick/item_attack.dm:92-121

Gun afterattack routes aiming/PreFire or Fire; Fire uses weapon-specific ammunition acquisition and calls projectile processing/launch. Projectiles register and travel through their own process queue; process, handleTurf, attack_mob, do_bullet_act and target bullet_act decide collision, miss, penetration and damage. process/projectile.reset_current_list limits the current list to 500 and deletes the excess; this is explicit overload behavior, not proof of a measured problem. code/modules/projectiles/gun.dm:138-193 code/modules/projectiles/gun.dm:276-375 code/modules/projectiles/gun.dm:460-524 code/modules/projectiles/projectile.dm:175-239 code/modules/projectiles/projectile.dm:308-443 code/modules/projectiles/projectile.dm:472-728 code/modules/projectiles/projectile.dm:732-821 code/processes/projectile.dm:34-40

04_combat_dispatch.mmd shows the default melee and gun paths. Alternative overrides (explosions, thrown weapons, spells, surgery, vehicles, special weapon actions) remain extensions, not omissions claimed unreachable.

5. Crafting and research

Recipes have both code-defined variants and config-driven records. Setup reads config/crafting/material_recipes* lines containing 13 comma-separated fields into global/faction lists. material.get_recipes_civs chooses race/faction and map/civilization research values. Generation resolves result path strings with text2path, gates by research-node completion when mapped, otherwise legacy three-axis research thresholds, and applies era/material/feature checks before creating stack-recipe datums. This is a data-to-type construction boundary. code/setup.dm:83-107 code/game/objects/items/stacks/civ_recipes.dm:19-144

The stack menu Topic path validates actor state and held source, resolves recipe/sublist/multiplier, and uses H.can_build_recipe to serialize the operation. produce_recipe is a large content-specific transaction: feature gates, subtype-specific prompts and temporary constructions, derived time, do_after, post-wait special revalidation, debit with use(required,H), dynamic new recipe.result_type and post-construction configuration. Craft cancellation and payment semantics are branch-specific. In particular, wood-handle crafting keeps the exact handle/source through the wait, revalidates both, and removes the actual equipment slot only after successful main stack debit. Do not generalize that transaction guarantee to every recipe branch without a separate branch audit. code/game/objects/items/stacks/stack.dm:298-335 code/game/objects/items/stacks/stack.dm:1805-1830 code/game/objects/items/stacks/stack.dm:1898-1944 code/game/objects/items/stacks/stack.dm:2355-2437

The new research architecture separates shared immutable-ish node definitions (research_nodes, recipe requirement index) from mutable map-owned faction progress. Benches register themselves globally, own faction/tier/assignment/queue, and operate under leader/director/researcher permissions. The process iterates benches and calls analysis_tick; the bench advances its queue, checks mode, tier, prerequisites, already-done state and an online authorized researcher, then credits progress and refreshes viewers. code/modules/research_tree/research_node.dm:13-74 code/modules/research_tree/research_node.dm:159-234 code/modules/research_tree/research_bench.dm:23-94 code/modules/research_tree/research_bench.dm:105-207 code/modules/research_tree/research_faction.dm:43-71 code/modules/research_tree/research_faction.dm:200-232

add_research_ticks moves an entry into IN_PROGRESS and completes ANALYSIS when cost is reached; PROTOTYPE needs both accumulated study and a submitted prototype. Books can complete through the explicit book route; notes and reverse engineering add progress; coin inputs upgrade benches. complete_node marks faction DONE and prototype era capstones can increase the world’s ordinal age. Global era progression does not imply every faction researched every node: is_node_done has explicit baseline-era semantics. code/modules/research_tree/research_faction.dm:114-190 code/modules/research_tree/research_faction.dm:214-232 code/modules/research_tree/research_bench.dm:585-740

The generated 08_research_prerequisites.mmd covers 93 direct node constructor registrations in the selected registry file, with 99 declared prerequisite edges. research_nodes.json preserves costs, tiers, modes and line numbers; research_graph_checks.json checks unresolved prerequisite IDs and duplicate IDs. It is a declared dependency graph, not proof that every research route is dynamically reachable in every map. Recipe-to-node mapping and baseline grants are separate from prerequisite edges.

Research storage comments describe faction progress as round-scoped and bench variables as saver-owned. This report does not infer current serialization solely from those comments; persistence schema/import coverage belongs to the persistence audit.

6. Life and AI

process/mob dispatches Life() over living_mob_list and catches per-object exceptions. Human Life combines physiology, body/visual state, nutrition, stamina, environmental/health logic and content-specific rules; survival idle multiplier separately uses input inactivity plus action/movement clocks. Simple animal Life handles status, starvation, following and then handle_ai. code/processes/mob.dm:12-53 code/game/mob/living/carbon/human/human_life.dm:40-180 code/game/mob/living/simple_animal/simple_animal.dm:124-175

Simple AI returns for unconscious/dead, client-owned, anchored or empty-server cases, and for various movement restrictions. Idle hunger can preempt general behavior. Behavior strings select scared/wander/hunt/defends/hostile; attack/alert runs more urgently while idle is throttled. Tactical handling adds IDLE/ALERT/ATTACK/TIRED stance logic, perception, faction eligibility, target loss, pursuit, surroundings destruction and attack. Hostile mode can transition directly from IDLE to ATTACK, so ALERT is not a mandatory intermediate state. code/game/mob/living/simple_animal/simple_ai.dm:3-121 code/game/mob/living/simple_animal/hostile/tactical_ai.dm:131-265 code/game/mob/living/simple_animal/hostile/tactical_ai.dm:266-438

Cover and friendly-fire helpers apply to human NPCs; arbitrary animal subtypes can override target and attack procedures. Dogs additionally have a dedicated fast follow process. Pathfinding/native walking can continue between Life dispatches, so AI is not a single closed four-state automaton. 07_animal_ai.mmd explicitly labels the simplified combat stance graph and behavior dispatch. code/game/mob/living/simple_animal/hostile/tactical_ai.dm:4-127 code/processes/dog.dm:3-46

7. Review concerns and concrete follow-on verification

These are evidence-supported review targets, not asserted bugs:

  1. Mixed scheduler ownership and stale comments. Included MC/timer infrastructure, legacy process comments naming non-existent grouping owners, deferred-setup record without observed use, spawned loops and native walking create competing mental models. Verify actual startup with a fixture trace of process creation, ticker creation, loop starts and readiness. Do not assume declared controller files are active. Sources: startup/scheduler sections above.
  2. Budget fairness depends on list shape and macros. Ordinary loops retain work snapshots, VERY_HIGH uses live lists, and research fire iterates a fresh bench copy with a tick check rather than the same current-list resume protocol. Inspect large-list progress and mutation behavior with native tests before claiming fairness/starvation. [code/processes/__defines.dm:28-33](https://github.com/Civ13/Civ13/blob/5bf351248de6050876fe5b53f91209a970cc5668/code/processes/__defines.dm#L28-L33) code/processes/research_tree.dm:16-28 code/processes/_process.dm:147-159
  3. Movement recovery ownership deserves a targeted test. Legacy scheduler start has a run token; movementMachine.start simply spawns another process loop, and watchdog recovery calls start on a stale last_run. The architecture exposes different restart contracts. Whether duplicate live movement loops occur requires a controlled stale/watchdog timing test, not source-only assertion. code/processes/__processScheduler.dm:100-131 code/movementMachine.dm:24-31 code/world.dm:466-500
  4. Crafting is a content-heavy transaction hub. produce_recipe spans hundreds of branches and yields at input/do_after; consumption, object creation and cancellation need per-family conservation tests. Recent handle/drill/electronics guards are explicit examples, not blanket coverage. code/game/objects/items/stacks/stack.dm:298-335 code/game/objects/items/stacks/stack.dm:1805-1944
  5. Research comment drift. Bench analysis comments describe prototype submission as a shortcut rather than mandatory; executable add_research_ticks caps study until the prototype is submitted. UI/examine text should be checked against executable completion gates. code/modules/research_tree/research_bench.dm:66-73 code/modules/research_tree/research_faction.dm:181-189
  6. Cross-cutting mutable globals. Map, ticker, job master, process-list singleton, registries and actor variables are shared across input, periodic updates and deferred work. Save/import, round transitions and object deletion must preserve reciprocal ownership and avoid treating process-local deadlines as durable state. This report establishes the boundary, not persistence correctness.
  7. Dynamic dispatch limits static coverage. Hooks use text-to-path and call; scheduled tasks use dynamic procedure/arguments; crafting resolves data strings to constructors; every Life, attackby, Move, map tick and object process_interval may resolve a subtype implementation. A sound complete may-call graph needs DM type/inheritance resolution and config/map variants, plus explicit unresolved edges. code/hooks.dm:50-64 code/processes/scheduler.dm:108-132 code/game/objects/items/stacks/civ_recipes.dm:114-144

Deliverables and checks

Mermaid is editable diagram source. Eight equivalent focused PlantUML sources are included for rendering during packaging. No runtime or performance validation is claimed. The source checkout remained unmodified.

Atlas home · Diagrams · Audit · Runtime · Persistence & services