Skip to content

Multiplayer

denfry edited this page Oct 13, 2026 · 1 revision

Multiplayer

Note

Design stage. This page describes planned mechanics from the design document. Not implemented yet — see Roadmap (milestone M10 for DamageBudget, containment and server modes).

Prompt Critical simulates reactors that can melt down, vessels that can burst and plumes that can drift onto a neighbour's base. On a shared server that needs firm limits. This page covers how the mod keeps a server safe and fair: server modes, the DamageBudget that caps all world damage, containment strength, contamination limits, anti-griefing, handling of unloaded chunks and offline owners, the station log, roles, economic balance and difficulty levels.

Principles

  1. The server matters more than realism. Any destruction, contamination or load from one station is capped by configuration, whatever the physics calculated. Physics decides what happened; the budget decides how much of it reaches the world.
  2. Responsibility is traceable. Every significant action — who, when, from which device — is logged. You cannot harm another station anonymously.
  3. Consequences fall on the culprit. In contained mode the damage from a station is limited to its owner's territory; other players' builds are protected.
  4. Unloading a chunk is neither an accident nor an exploit. A reactor does not explode because you walked away, and it is not "frozen mid-excursion" to dodge physics.

Server modes

server.mode is realistic, contained (default) or creative_safe. Each row can be overridden with its own damage.* / contam.* key; the mode only sets defaults.

Aspect realistic contained (default) creative_safe
Block destruction by explosions and pressure yes, within DamageBudget yes, only on the source owner's territory (claims / station radius) no (visual effects, particles, sound)
Max explosion power (MC equivalent) damage.max_explosion_power 12 8 0
Containment failure yes yes, debris only inward to the territory no: containment "holds" any pressure, but the overpressure is logged
Territory contamination up to contam.max_chunks_per_event owner's chunks + 64 m radius only none (only inside station buildings, gone in 1 game day)
Contamination of others' chunks yes (logged) no no
Dose effects on players all, up to death all cosmetic, no damage
Contamination of items and players yes yes no
Fires from accidents yes on the territory only no
Spent fuel / HLW damage when dropped out of a container yes yes no
Station behaviour with the owner offline KEEP_RUNNING SAFE_SHUTDOWN FREEZE
Carrying HLW without a cask allowed, with severe exposure forbidden allowed, no exposure
Interacting with another team's station allowed, logged, station alarm forbidden (except the scram button) forbidden

DamageBudget

DamageBudget is the only path by which the simulation changes world blocks as a result of accidents: explosions (hydrogen, steam, centrifuge failure), containment rupture, melt-through (corium), pipe whip, fire. The domain submits a DamageRequest; the budget decides how many blocks change and when; the binding applies changes on the server thread.

public enum DamageKind { BLAST, OVERPRESSURE_BREACH, MELT_THROUGH, PIPE_WHIP, MISSILE, FIRE_SPREAD, CORROSION }

public record DamageRequest(long requestId, SimId source, UUID responsibleTeam, DamageKind kind,
                            GridPos origin, double energyJ, double maxRadiusM, int priority) {}

public enum DenyReason { NONE, MODE_FORBIDS, OUTSIDE_TERRITORY, PER_EVENT_CAP, PER_CHUNK_CAP,
                         PER_REGION_CAP, QUEUE_FULL, PROTECTED_BLOCK }

Priority 0 is highest (containment rupture), 9 is cosmetic (pollution effects, oiled shores).

Key Default Reason
damage.max_blocks_per_tick 64 setBlock with neighbour and light updates ≈ 5–15 µs → ≤ 1 ms/tick worst case
damage.max_blocks_per_event 4096 a 16×16×16 volume; at 64/tick applied over 3.2 s — a progressive collapse that also looks better
damage.max_blocks_per_chunk_per_day 1024 per game day; a chunk cannot be eaten away by a series of small accidents
damage.max_blocks_per_region_per_day 8192 per 32×32-chunk region file
damage.max_pending 32768 queue memory (≈ 1 MB)
damage.max_explosion_power by mode —
damage.tnt_reference_kg 50 see below
damage.item_drop_fraction 0.1 share of destroyed blocks that drop an item
damage.max_item_entities_per_event 32 —
damage.protected_tag #promptcritical:damage_immune bedrock, portals, other mods' blocks via datapack

The queue is ordered by request priority, then by distance from the origin, so collapse spreads from the centre. A block is skipped if its blast resistance exceeds the remaining ray intensity, or it is protected by tag or territory.

Energy to Minecraft explosion power. Hopkinson–Cranz scaling: effects depend on scaled distance Z = R / W^(1/3). A vanilla TNT explosion (power 4) destroys blocks within ≈ 5 blocks, which matches ≈ 46–50 kg of TNT at Z ≈ 1.4. So:

W = E_mech / 4.184 MJ/kg, P_MC = min(4 · (W / 50 kg)^(1/3), P_max)

Example: 182 kg TNT equivalent → P_MC = 6.2 (between vanilla TNT at 4 and an End crystal at 6). The ray algorithm is like vanilla but runs inside the budget queue, 256 rays per tick.

Containment

Containment is a closed surface of blocks tagged #promptcritical:containment_shell (containment_wall, containment_hatch, containment_penetration). A pipe or cable must pass through a containment_penetration; a pipe through a plain wall is a hole and the containment is not formed.

Validation flood-fills the air from the reactor_controller outward. If the fill exceeds containment.max_volume_m3 = 200,000 or reaches an unloaded chunk, formed = false. It yields free volume V, inner surface A, flat panels and the list of hatches and penetrations. Containment is required to start reactors of tier T2 and above (containment.required_from_tier = 2): without a formed and leak-tested containment the controller shows a "NO CONTAINMENT INTEGRITY" start interlock.

Strength

  • Membrane (curved parts). The shape is reduced to an equivalent sphere corrected by sphericity Ψ; effective thickness is the weakest of 64 rays from the centroid. Curved sections (a stepped block dome counts as curved) work in tension.
  • Flat panel bending. A flat wall works in bending and is much weaker: ΔP_b = σ_u,f / (β · (b/t)²), with β from 0.287 (square) to 0.750 (long strip). Panels backed by ground or solid blocks are excluded. This rewards domes and cylinders, like real containments.
Material Membrane strength, MPa Bending strength, MPa Base leak, %/day at design pressure
containment_wall (prestressed concrete + 6 mm steel liner) 16 18 0.1
MC concrete or brick tagged #promptcritical:containment_material_weak 3 3 5
custom (datapack) per datapack

Failure pressure is the minimum over membrane, every panel and every hatch; design pressure is failure / 2.5 (containment.ultimate_to_design, based on the Sandia 1:4 prestressed model test). The design pressure is shown to the player and drives the high containment pressure scram (recommended setpoint 0.3 × design). The true capacity of each containment is rolled once at assembly from a log-normal fragility curve (β = 0.15): you see the median, never the hidden margin.

Reference check (VVER-1000-like). Cylinder R = 22.5 m, 40 m high, hemispherical dome, 1.2 m wall: V = 87,473 m³, Ψ = 0.914 → membrane failure 1.27 MPa, design 0.51 MPa — the real order of PWR/VVER design pressures (0.4–0.5 MPa).

A player's box. 20 × 20 × 25 m, 1-block walls, floor on ground: membrane 1.91 MPa, but a 20 × 25 wall panel fails at 0.115 MPa (design 0.046 MPa) — even a small leak would burst it. Fixes: stiffening ribs of shell blocks every 5 m (panels 5 × 25 → 0.96 MPa), thicker walls (∝ t²), or a cylinder with a dome. This is the engineering puzzle of the section.

Element Strength Leakage Notes
containment_hatch (1×2, 2 m²) 1.0 MPa +0.01 %/day seals degrade above 423 K
containment_penetration as wall +0.005 %/day the line must be isolated with valves on both sides, or containment isolation leaves an open release path
open hatch — flow path of hatch area not leak-tight; T2+ start blocked

Leak rate and leak test

Leakage is flow through an equivalent hole sized from the allowed leak rate. Example: 10,000 m³ at 0.5 MPa abs and 0.1 %/day → a 0.5 mm² "pinhole". Leakage goes to the accident model (atmosphere mass balance) and radiation model (activity release). The integrated leak test (pc_containment.startLeakTest) pressurizes to design pressure and measures mass for 24 physical hours (20 real minutes); acceptance is ≤ 0.75 × allowed rate. Any change to containment blocks requires a new test.

Containment failure

When pressure exceeds capacity at the weakest element:

  1. A breach opens (0.1 m², growing with overpressure) and becomes a flow path to the environment.
  2. Mechanical energy is the isentropic expansion work of the compressed atmosphere × blast fraction (0.05 for reinforced, ductile; 0.3 for unreinforced, brittle). Example: 10,000 m³ at 1.2 MPa → 1.52·10¹⁰ J total, 7.6·10⁸ J mechanical = 182 kg TNT → P_MC 6.2.
  3. DamageRequest(OVERPRESSURE_BREACH, priority 0) centred on the breach.
  4. Activity release through the breach, limited as below.
  5. State BREACHED, P1 alarm, event pc_containment_state{breach=true}, chat announcement.

The containment GUI shows heavy damage radius 1.4·W^(1/3) m and light damage (broken glass) 15·W^(1/3) m — 7.9 m and 85 m for 182 kg TNT. Light damage only affects glass and #promptcritical:blast_fragile blocks within the budget.

Contamination limits

The radioactive ContaminationMap must apply a ContaminationPolicy when depositing:

Key Default Meaning
contam.max_chunks_per_event 64 one release contaminates at most ≈ 8×8 chunks
contam.max_chunks_total 2048 per dimension; weakest chunks cleared first (logged)
contam.min_surface_bq_m2 1000 below this a chunk is clean and dropped
contam.spawn_protected_radius 128 blocks from world spawn — no deposition
contam.protected_zones [] admin rectangles (/pc zone add)
contam.station_radius_m 64 station territory in contained without a claims mod
contam.weathering_half_life_days 30 rain weathering of surface contamination

The forbidden share (outside territory in contained, protected zones) is redirected to the owner's nearest allowed chunk — activity is conserved, and the "dirt" stays with the culprit. In realistic only protected zones are redirected. Chemical pollution uses the same policy (Environment and Economy).

Territory comes from a claims mod if installed (FTB Chunks or Open Parties and Claims, optional integration mod.compat.claims), otherwise from the station's multiblock chunks plus the station radius. Team claims count as the owner team's territory.

Anti-griefing

Threat Countermeasure
Blowing up someone's reactor by pulling rods commands only with a station role key card; mechanisms in LOCAL by default; logged
Dismantling another station contained/creative_safe: only members can break station blocks; realistic: allowed, but a P1 "UNAUTHORIZED INTERFERENCE" alarm with the name, log and team chat
Pulling control rod channels at power control_rod_channel is unbreakable unless the reactor is SHUTDOWN with shutdown margin — in all modes
Removing a Create drive shaft safe by design: loss of drive → rods drop; who broke the shaft is logged
Spill or release at a neighbour contamination limited to the source owner's territory
"Dirty bomb" from HLW see below
Lua virus on a foreign computer in the station network without a key card: read-only and requestTrip (logged); key cards cannot be copied (UUID in the station registry, /pc card revoke)
requestTrip spam non-members: max 1 scram request per computer per 60 s; repeats → E_RATE and a station alarm
Server load (thousands of sensors, giant networks) per-station limits: limits.max_sensors_per_station 512, limits.max_fluid_nodes_per_station 4096, limits.max_stations_per_team 4; excess blocks do not join the simulation

Scram button for everyone. Any player can press another station's scram_button in all modes: it is always a safe action, and neighbours can save an absent owner's station. A non-member may press it again only after 10 minutes; each press is logged and the owner notified.

LOTO locks are another anti-griefing tool: a member can lock out equipment under repair and nobody else can start it (Operations and Safety).

High-activity materials

Every item above transport.tracked_activity_bq = 10¹² Bq (1 TBq ≈ 27 Ci, the IAEA category 2/3 boundary for Cs-137) gets a SourceId and is tracked in HighActivitySourceRegistry: current holder, position, activity, last legal owner. Admin commands: /pc sources list [radius], /pc sources track <id>.

Rule Implementation
HLW, spent fuel and vitrified waste do not fit in shulker boxes or bundles canFitInsideContainerItems() = false
Not in the Ender chest or other mods' backpacks slot checks; tag #promptcritical:forbidden_containers
Carrying without transport_cask contained: cannot be picked up except into a cask; realistic: allowed, with a lethal unshielded dose within minutes
transport_cask shielded; Slowness IV, no sprinting or swimming; max 1 in inventory
Dropped HLW item never despawns, tracked, keeps radiating
Destroyed (lava, void) logged, owner alerted; in lava activity is released into the chunk

"Dirty bomb." The mod has no dispersal device, but any explosion (even vanilla TNT) next to HLW disperses it. In realistic dispersal follows the radiation model within the contamination limits, with responsibility on the last player who held the source and the explosion's author if known. In contained dispersal is allowed only on the territory of the last legal owner; elsewhere the source just drops as an item with a damaged container. creative_safe: no dispersal. PORTAL frame monitors at the territory boundary detect sources being carried through.

Unloaded chunks and offline owners

The simulation needs every block of a station loaded. "Compute as if loaded" (desync) and "stop time forever" (exploit: walk away and the accident is cancelled) are both unacceptable. The solution is chunk tickets + a safe state + freezing.

Safe state (StationSafety.isSafe()): subcritical by ≥ 1 % Δk/k (by model, not sensors); decay heat within passive heat removal capacity, projected 24 h ahead; no transients for ≥ 60 s; no criticality-controlled chemistry vessel RUNNING; centrifuges stopped or steady.

State Event Next Action
ACTIVE all station chunks loaded ACTIVE full simulation
ACTIVE a chunk is about to unload, station not safe HELD NeoForge chunk tickets on all station chunks (within owner limits)
ACTIVE same, station safe FROZEN snapshot; removed from the scheduler
HELD safe for ≥ 60 s FROZEN release tickets
HELD offline.grace_minutes expired (SAFE_SHUTDOWN) HELD scram with source SERVER_POLICY; continue until safe
HELD ticket limit exhausted HELD_DEGRADED immediate scram; keep a ticket at least on the reactor_controller chunk
FROZEN any station chunk loads THAWING wait for all chunks (forced 5 s ticket)
THAWING all loaded, multiblocks valid ACTIVE catch-up of slow processes

Ticket limits: chunkload.max_chunks_per_station = 16, chunkload.max_chunks_per_owner = 32.

Catch-up (offline.thaw_mode): FROZEN_TIME resumes exactly where it stopped (xenon does not decay while you are away). REDUCED_CATCHUP (default) applies only analytic slow-process solutions for the elapsed physical time: I/Xe/Pm/Sm decay at zero flux, decay heat, spent fuel cooling in pools and storage. Thermal-hydraulics is not caught up — freezing only happens in a safe state, so temperatures are taken as equilibrium. Capped at offline.max_catchup_days = 30 physical days.

Scram source is a separate field from the physical trip reason: TripSource = AUTOMATIC, OPERATOR_PANEL, LUA, WATCHDOG, SERVER_POLICY, ADMIN_COMMAND. A policy scram appears in pc_scram with source = "SERVER_POLICY".

offline.policy All station members offline Use
KEEP_RUNNING station runs on tickets within limits servers with permanent infrastructure
SAFE_SHUTDOWN 10 min after the last member leaves (offline.grace_minutes): scram, cool down to safe, freeze default
FREEZE freeze immediately if safe; otherwise scram and freeze when safe creative / test servers

Station log

Every station keeps an event journal. Entries record sequence, game tick, sim time, station, actor (Player, Computer with card holder, Automatic system, ServerPolicy, Admin), action, target, detail and severity (INFO, NOTICE, WARNING, CRITICAL). Actions include COMMAND, ROD_MOVE, TRIP, TRIP_RESET, BYPASS_ON/OFF, CHANNEL_TEST, KEY_INSERT, CARD_ISSUED/REVOKED, MODE_CHANGE, ALARM_SHELVE, BLOCK_BREAK/PLACE, UNAUTHORIZED_ACCESS, HATCH_OPEN, SOURCE_TRANSFER/LOST, RELEASE, CONTAINMENT_BREACH, DAMAGE_APPLIED, CHUNK_TICKET, ADMIN. Other sections add their own (LOTO, permits, market trades, fees).

What Aggregation Severity
Any accepted mechanism command one entry per continuous rod movement INFO
Scram, reset, channel test none NOTICE / CRITICAL
Bypass on/off none WARNING
Breaking/placing station blocks one entry per player per 10 s with block count INFO; by non-member WARNING
Access attempts without rights 1 per 60 s per actor/device WARNING
Source moved between owners / lost none WARNING / CRITICAL
Activity release integral per event by scale below
DamageBudget applied summary per request WARNING

Storage: ring buffer of 10,000 entries per station (journal.max_entries_per_station, ≈ 1.5 MB) in SavedData; /pc journal export <station> writes CSV to the world folder. Members see everything; others see only their own actions on that station; admins see all.

Announcements use a scale inspired by INES (shifted down 1–2 orders of magnitude for Minecraft-sized stations; journal.announce_min_level = 3):

Level In-game criterion Announced to
0–1 scram within limits, bypass station team
2 operating limit violated, fuel cladding failure without release station team
3 release > 10¹² Bq iodine-equivalent or a player dose over threshold team + neighbours within 256 m
4 > 10¹³ Bq whole server
5 > 10¹⁴ Bq whole server, alarm sound
6–7 > 10¹⁵ / > 10¹⁶ Bq whole server, title

Stations and roles

A station is created when a player first assembles a reactor_controller (or a cascade / chemistry controller). ownerTeam is the player's team (vanilla teams or FTB Teams) or the player. OWNER and SHIFT_SUPERVISOR assign roles in the control_room_core GUI and issue access_cards.

Permission OWNER SHIFT_SUP. OPERATOR ENGINEER CHEMIST HEALTH_PHYS. MAINT. VISITOR
Read instruments, enter station ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
Operate reactor/turbine mechanisms ✓ ✓ ✓ — — — — —
Acknowledge alarms ✓ ✓ ✓ ✓ ✓ ✓ — —
Scram reset, shelving, transfer to backup panel, bypass key ✓ ✓ — — — — — —
Tests (scram test, rod drop, leak test), device setup, defineAlarm ✓ ✓ — ✓ — — — —
Fuel-cycle chemistry, cascade ✓ ✓ — — ✓ — — —
Radiation monitor limits, zone access ✓ — — — — ✓ — —
MAINTENANCE mode, repair, calibration, bearings ✓ — — — — — ✓ —
Break/place station blocks ✓ ✓ — ✓ — — ✓ —
Issue/revoke key cards, roles ✓ ✓ (not OWNER) — — — — — —

Role gameplay: the operator holds parameters and answers alarms; the chemist runs the fuel-cycle chemistry, boron and water chemistry; the health physicist maps fields, controls zone access and portal monitors; maintenance calibrates sensors, replaces bearings, balances rotors, decontaminates; the engineer tests, tunes and programs automation; the shift supervisor handles scrams, resets, bypasses and coordination. In single player, OWNER holds everything.

Economic balance

Energy out. At 40 MWd/kgU a kilogram of uranium releases 3.46·10¹² J of heat, ≈ 1.14·10¹² J mechanical at 33 % cycle efficiency.

Fuel-cycle cost (physical). Enriching to 4.4 % with 0.25 % tails from natural 0.711 % takes 9 kg of natural uranium and 6.66 SWU per kg of product. Gas centrifuges use ≈ 50 kWh/SWU (gaseous diffusion ≈ 2500), so 333 kWh = 1.2·10⁹ J per kg of fuel — about 2900× less than the heat that kilogram releases. Physically the cycle is extremely profitable; the game balance comes from time, complexity and risk: centrifuges consume Create stress, ore and reagents are resources, and waste is a decades-long storage concern.

Create comparison. A Create steam engine gives up to 16,384 SU; a max-level boiler runs up to 18 engines (≈ 295,000 SU). At W_PER_SU = 1000 W/SU one steam engine equals 16.4 MW of mechanical power, so a 300 MW(th) unit (≈ 100 MW mechanical) equals only ≈ 6 steam engines that cost orders of magnitude less. This section recommends calibrating W_PER_SU = P_mech,ref / (N_eq × 16,384 SU); with 100 MW and N_eq = 32 that is ≈ 190 W/SU. The current project decision keeps 1000 W/SU (energy.w_per_su) until the playtest milestone; all SU values in the design scale linearly. FE coupling: at J_PER_FE = 2.5, 1 MW(e) = 20,000 FE/t.

Server multipliers: economy.fuel_energy_multiplier, economy.swu_cost_multiplier, economy.ore_yield_multiplier. Money from the commodity exchange is a separate axis and never changes this physics (Environment and Economy).

Difficulty levels

difficulty.preset sets defaults; any key can be overridden.

Parameter TRAINING STANDARD HARDCORE
Sensor noise ×0.5 ×1.0 ×1.5
Sensor failures 0 1× 3×
Sensor drift off on on, 15-day calibration
Control valve stiction off off on
Bearing wear multiplier 0 20 40
Recommended-action hints in alarms on on off
Fuel cladding failure threshold ×1.5 1× 1× + scatter
Hidden containment capacity scatter β 0 0.15 0.25
Built-in automatic power regulator yes (in reactor_controller, can be disabled) no no
Default server mode creative_safe contained realistic

The single exception to "the mod gives no regulators" is the TRAINING built-in power regulator, so a newcomer can run a station before learning Lua. TRAINING also enlarges the first boiler's pressure margin and shows full "how to" hints in the handbook route (Player Content); the market runs without price noise and fees are not charged.

Open questions

  • W_PER_SU = 1000 makes a station weak compared with Create steam engines — needs calibration at playtest, and the 16,384 SU figure must be checked against Create 6 source.
  • There is no TripReason for administrative shutdown; the orthogonal TripSource covers it.
  • Exact FTB Chunks / Open Parties and Claims API classes for NeoForge 1.21.1 are to be determined at implementation.

See also

Clone this wiki locally