Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Game rules

Game rules are typed settings that change which rules of the game run in a world: whether the sun moves, which destruction preset applies, how many creatures may wander. The engine owns them. A rule is declared once, with its type and default; a world then sets values for the whole world and, where it wants, per world space. Values persist with the world and replicate to every client.

Source: manifold_engine::world::game_rules (crates/manifold-engine/src/world/game_rules/) (declarations, scopes, validation), persistence and the guest mirror in manifold_server_state::game_rules (crates/manifold-server-state/src/game_rules.rs), the save table in manifold_saves_types::game_rules (crates/manifold-saves-types/src/game_rules.rs), the boot load in manifold_host_core::boot::seed_game_rules, and /gamerule in game_rule_commands (crates/manifold-server-state/src/console/game_rule_commands.rs).

Declaring a rule

Rules live in the manifold:game-rule registry of the data stage, so a mod declares them in data/game-rules/ (or in code with DataStage::declare_code_entry), and another mod may patch a default with an .update.ron like any other entry. The registry is loaded by both sides and covered by the handshake’s data hash.

// example: data/game-rules/max_wanderers.ron
(id: "example:max-wanderers", kind: Int(min: 0, max: 64), default: 8)
kindValueChecked
Booltrue/falsetype
Int, Int(min:, max:)integerrange
Float, Float(min:, max:)numberfinite, range
String, String(max_len:)textlength in bytes (default 256, at most 4096)
Key(registry: "<mod>:<registry>")a key of that registry, such as a preset entrykey exists (a Both rule may not name a Server registry)
Enum(["a", "b"])one of the namesmembership

Key and Enum rules hold RuleValue::Key. The engine declares manifold:do-daylight-cycle (Bool, default true) in code; a host that runs no data stage has only the engine’s rules (GameRuleDecls::builtin). Rule keys are ordinary registry keys (<mod-id>:<name>), so two mods cannot collide.

Scopes and resolution

GameRules holds explicit values at two scopes (RuleScope): World, which every space inherits, and Space(WorldSpaceId). A space’s value is its own override, else the world value, else the declared default; get_* resolves, explicit does not. Defaults are never stored, so a changed default reaches every world that never set the rule.

Every write goes through GameRules::set, which checks the value against the declaration and returns a RuleError naming the rule (unknown key with the closest declared key, wrong type, out of range, not in the registry or enum, too long); nothing changes on error. GameRuleDecls::parse reads console text by the rule’s type. reset clears an explicit value.

A saved value for a rule no declaration names (a mod that was removed) is kept and saved again, but never resolved, so re-enabling the mod restores it. A saved value that no longer fits its declaration is dropped with a warning when the world loads.

Persistence

The world’s rules load at boot, before run_on_server_world_ready and the first tick, on both hosts (seed_game_rules over the SaveStore seam), so tick 1 already runs under the saved rules. Every save writes them in the same transaction as chunks, players and metadata.

Table game_rules_v1
Keyscope: [0] for the world, [1] + the 16 bytes of the WorldSpaceId for a space
Valuepostcard GameRuleScopeRow { schema_version: 1, rules: Vec<(key, PersistedRuleValue)> }, keys sorted; PersistedRuleValue is Bool, Int, Float, String, Key in that tag order

A flush replaces the whole table; a scope with no explicit value has no row. A row with an unknown scope key, another schema version or undecodable bytes is skipped with a warning. The table is additive, so the world format version is unchanged. A world saved before the table has its rules in the legacy WorldMetaDynamic.game_rules_ron blob: when the table has no rows, the loader imports that blob into the world scope (doDaylightCycle becomes manifold:do-daylight-cycle; values equal to the default and names with no declared rule are dropped), and a flush that writes the table writes the blob as None.

Replication and mods

GameRules is an engine-baseline synced resource (manifold:GameRules), sent in the join snapshot and on change. The payload is postcard of the explicit values (StoredGameRules) for every scope; each client rebuilds GameRules over its own declarations, which the data hash guarantees match the server’s. Every client receives every space’s values: rule sets are small, public server configuration. Anything that changes rules calls mark_synced_dirty::<GameRules>().

Guests read the read-only manifold:game_rules mirror (manifold_server_api::GameRulesBridge): every declared rule’s world value and each space’s overrides, with get(space, key) resolving an override. Guests do not write it; rules change through the server’s permission-checked command surface, which reports a rejected value to its caller.

Console

/gamerule runs on the server (both hosts register it):

FormWhat
/gameruleevery rule’s world value and whether it is set or the default
/gamerule <rule>the world value, the type, the default and each space’s override
/gamerule <rule> <value> [<space>]set the world value, or a space’s override (Admin)
/gamerule reset <rule> [<space>]clear a value (Admin)

<rule> is the key, or its name after the colon when only one rule has it. <space> is here (the caller’s space) or a hex space id (0 is the default space).

Tests

  • cargo test -p manifold-engine --lib game_rules and baseline_synced: resolution across scopes, validation of every kind, console parsing, data declarations and their diagnostics, undeclared and invalid saved values, the per-space sync payload onto a client world.
  • cargo test -p manifold-saves-types --features redb --lib game_rules: the table’s rows, replacement and skipped rows.
  • cargo test -p manifold-server-state --lib game_rules and cargo test -p manifold-host-core --lib boot: save round trip, the legacy import, a world with no rules, the boot seed and /gamerule.
  • cargo test -p manifold-server --test suite persistence_e2e::game_rules_survive_restart: a real server loads world and space values and an undeclared value at boot and writes them back on shutdown; vanilla_e2e shows the flagship clock following the world rule and ignoring a space override.