Repository navigation
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.
- 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.
- Responsibility is traceable. Every significant action — who, when, from which device — is logged. You cannot harm another station anonymously.
-
Consequences fall on the culprit. In
containedmode the damage from a station is limited to its owner's territory; other players' builds are protected. - 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.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 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 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.
- 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 |
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.
When pressure exceeds capacity at the weakest element:
- A breach opens (0.1 m², growing with overpressure) and becomes a flow path to the environment.
- 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.
-
DamageRequest(OVERPRESSURE_BREACH, priority 0)centred on the breach. - Activity release through the breach, limited as below.
- State
BREACHED, P1 alarm, eventpc_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.
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.
| 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).
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.
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 |
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 |
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.
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.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.
-
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
TripReasonfor administrative shutdown; the orthogonalTripSourcecovers it. - Exact FTB Chunks / Open Parties and Claims API classes for NeoForge 1.21.1 are to be determined at implementation.
- Accidents — hydrogen, steam explosions, containment atmosphere
-
Radiation Safety —
ContaminationMap, dose, zones - Operations and Safety — LOTO, permits, roles in permits
- Environment and Economy — pollution policy, market, fees
- SCADA and HMI — key cards, control room, alarms
- Create Integration — SU coupling
- Architecture
Prompt Critical · © 2026 denfry · Repository · Report an issue
Start here
Design
Nuclear
Industries
Control and automation
World and server
Development