Multiplayer games — worked graph designs
These designs describe intended system behavior and qualification requirements. Inspect the system atlas for named ports and relation participants.
Multiplayer: authoritative simulation and client prediction
How are predicted client state and authoritative state reconciled?
Inspect this design · Definition JSON · revision 355611235428
Diagram legend. Frames are named graph regions. Left/right pins show required/offered participation, not a universal schedule. Filled pins are connected; hollow pins are unbound. A junction card preserves one relation's role slots.
| Marker | Meaning in this diagram |
|---|---|
| ⌨ N13 | Input / interaction source |
| ▤ N11 | Surface / view component |
| ▣ N05 | Actor / statechart |
| ◆ N16 | Native resource / engine |
| ◷ N09 | Timer / clock source |
| ≋ N24 | Queue / log / pubsub resource |
| ↦ P04 | Push event |
| ○ P01 | Value / immutable snapshot |
| ◎ P24 | Inspection / evidence |
| ⇆ P02 | Request / response |
| ◷ P15 | Clock / tick / timer |
| → H02 | Value flow |
| ⇆ H05 | Request / reply binding |
| ◷ H31 | Clock / time alignment |
Logic and ownership
Kotlin implements prediction, sequence-number filtering and reconciliation; the server alone admits simulation-changing commands.
Failure and qualification
- Clients propose input, not authoritative position or inventory.
- Deterministic replay requires qualified physics, seed and floating-point behavior for the target.
- Latency, loss and malicious commands are explicit tests rather than properties of the drawing.
Hyperedge roles
| Relation | Pattern | Participants, preserving role positions |
|---|---|---|
| command | H02 — Value flow {#h02--value-flow} | provider[0]: input.command; consumer[0]: room.command |
| predict | H02 — Value flow {#h02--value-flow} | provider[0]: input.intent; consumer[0]: prediction.intent |
| simulate | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: physics.step; consumer[0]: room.physics |
| tick | H02 — Value flow {#h02--value-flow} | provider[0]: clock.tick; consumer[0]: room.tick |
| snapshot | H02 — Value flow {#h02--value-flow} | provider[0]: room.snapshot; consumer[0]: prediction.authoritative |
| log | H02 — Value flow {#h02--value-flow} | provider[0]: room.event; consumer[0]: log.event |
| alignment | H31 — Clock / time alignment {#h31--clock--time-alignment} | clock[0]: clock.clock; simulation[1]: room.position; client[2]: prediction.ack |
Kotlin common declaration
Import dev.shibasis.reaktor.portgraph.definition.* for this structural declaration. Behavior stays in ordinary Kotlin and binds through qualified runtime ports and adapters. Declaring a graph does not activate a system.
graphDefinition("game-authoritative", "355611235428e4fe1e1e808a16adc40d5b30dceb22333075b468d67392e037ba") {
region("client", "Game clients")
region("server", "Authoritative game host")
node("input", "Player input", "N13", "client", ports = listOf(
PortDefinition("command", "P04", "PlayerCommandV1", PortPolarity.Offer),
PortDefinition("intent", "P04", "InputIntentV1", PortPolarity.Offer)
))
node("prediction", "Prediction / rendering", "N11", "client", ports = listOf(
PortDefinition("intent", "P04", "InputIntentV1", PortPolarity.Require),
PortDefinition("authoritative", "P01", "GameSnapshotV1", PortPolarity.Require),
PortDefinition("ack", "P24", "AckPositionV1", PortPolarity.Neutral)
))
node("room", "Authoritative room actor", "N05", "server", ports = listOf(
PortDefinition("command", "P04", "PlayerCommandV1", PortPolarity.Require),
PortDefinition("physics", "P02", "SimulationStepV1", PortPolarity.Require),
PortDefinition("tick", "P15", "TickV1", PortPolarity.Require),
PortDefinition("snapshot", "P01", "GameSnapshotV1", PortPolarity.Offer),
PortDefinition("event", "P04", "MatchEventV1", PortPolarity.Offer),
PortDefinition("position", "P24", "TickPositionV1", PortPolarity.Neutral)
))
node("physics", "Simulation kernel", "N16", "server", ports = listOf(
PortDefinition("step", "P02", "SimulationStepV1", PortPolarity.Offer)
))
node("clock", "Server tick clock", "N09", "server", ports = listOf(
PortDefinition("tick", "P15", "TickV1", PortPolarity.Offer),
PortDefinition("clock", "P24", "ClockRefV1", PortPolarity.Neutral)
))
node("log", "Match event log", "N24", "server", ports = listOf(
PortDefinition("event", "P04", "MatchEventV1", PortPolarity.Require)
))
relation("command", "H02",
Incidence("input", "command", "provider", 0),
Incidence("room", "command", "consumer", 0)
)
relation("predict", "H02",
Incidence("input", "intent", "provider", 0),
Incidence("prediction", "intent", "consumer", 0)
)
relation("simulate", "H05",
Incidence("physics", "step", "provider", 0),
Incidence("room", "physics", "consumer", 0)
)
relation("tick", "H02",
Incidence("clock", "tick", "provider", 0),
Incidence("room", "tick", "consumer", 0)
)
relation("snapshot", "H02",
Incidence("room", "snapshot", "provider", 0),
Incidence("prediction", "authoritative", "consumer", 0)
)
relation("log", "H02",
Incidence("room", "event", "provider", 0),
Incidence("log", "event", "consumer", 0)
)
relation("alignment", "H31",
Incidence("clock", "clock", "clock", 0),
Incidence("room", "position", "simulation", 1),
Incidence("prediction", "ack", "client", 2)
)
}
Multiplayer: world partitions, sessions and entity handoff
What fences an entity when a player crosses a world partition?
Inspect this design · Definition JSON · revision ece94168bc9d
Diagram legend. Frames are named graph regions. Left/right pins show required/offered participation, not a universal schedule. Filled pins are connected; hollow pins are unbound. A junction card preserves one relation's role slots.
| Marker | Meaning in this diagram |
|---|---|
| ⌕ N22 | Query / materialized view |
| ▣ N05 | Actor / statechart |
| ▰ N21 | Database / state authority |
| ▤ N11 | Surface / view component |
| ▧ N35 | Artifact / release manifest |
| ⇆ P02 | Request / response |
| ⇄ P07 | Duplex session |
| ◎ P24 | Inspection / evidence |
| ⇆ H05 | Request / reply binding |
| ⌬ H06 | Multiparty session |
| ⇧ H35 | State migration |
| ⏻ H29 | Activation cohort |
Logic and ownership
Common Kotlin rules operate over one cell activation; migration reconstructs the entity under a new fenced owner.
Failure and qualification
- Partition overlap requires one authority for writes to an entity.
- Rule updates need compatible session/state protocols while versions coexist.
- World streaming and entity ownership have different lifetimes.
Hyperedge roles
| Relation | Pattern | Participants, preserving role positions |
|---|---|---|
| a | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: a.session; consumer[0]: session.a |
| b | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: b.session; consumer[0]: session.b |
| leaseA | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: authority.leaseA; consumer[0]: a.authority |
| leaseB | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: authority.leaseB; consumer[0]: b.authority |
| session | H06 — Multiparty session {#h06--multiparty-session} | provider[0]: session.session; consumer[0]: client.session |
| handoff | H35 — State migration {#h35--state-migration} | old[0]: a.entity; new[1]: b.entity; fence[2]: authority.fence |
| rules | H29 — Activation cohort {#h29--activation-cohort} | bundle[0]: build.revision; cell[1]: a.rules; cell[2]: b.rules |
Kotlin common declaration
Import dev.shibasis.reaktor.portgraph.definition.* for this structural declaration. Behavior stays in ordinary Kotlin and binds through qualified runtime ports and adapters. Declaring a graph does not activate a system.
graphDefinition("game-world", "ece94168bc9d90e637feb129a3664e527792b19d2c30cef5f09737c4a7111c2b") {
region("client", "Game clients")
region("server", "Authoritative game host")
node("session", "Player session router", "N22", "server", ports = listOf(
PortDefinition("a", "P02", "WorldSessionV1", PortPolarity.Require),
PortDefinition("b", "P02", "WorldSessionV1", PortPolarity.Require),
PortDefinition("session", "P07", "PlayerSessionV1", PortPolarity.Offer)
))
node("a", "World cell A", "N05", "server", ports = listOf(
PortDefinition("session", "P02", "WorldSessionV1", PortPolarity.Offer),
PortDefinition("authority", "P02", "EntityLeaseV1", PortPolarity.Require),
PortDefinition("entity", "P24", "EntityEpochV1", PortPolarity.Neutral),
PortDefinition("rules", "P24", "ActivationRefV1", PortPolarity.Neutral)
))
node("b", "World cell B", "N05", "server", ports = listOf(
PortDefinition("session", "P02", "WorldSessionV1", PortPolarity.Offer),
PortDefinition("authority", "P02", "EntityLeaseV1", PortPolarity.Require),
PortDefinition("entity", "P24", "EntityEpochV1", PortPolarity.Neutral),
PortDefinition("rules", "P24", "ActivationRefV1", PortPolarity.Neutral)
))
node("authority", "Entity lease authority", "N21", "server", ports = listOf(
PortDefinition("leaseA", "P02", "EntityLeaseV1", PortPolarity.Offer),
PortDefinition("leaseB", "P02", "EntityLeaseV1", PortPolarity.Offer),
PortDefinition("fence", "P24", "FenceRefV1", PortPolarity.Neutral)
))
node("client", "Player Surface", "N11", "client", ports = listOf(
PortDefinition("session", "P07", "PlayerSessionV1", PortPolarity.Require)
))
node("build", "World rules bundle", "N35", "server", ports = listOf(
PortDefinition("revision", "P24", "RevisionRefV1", PortPolarity.Neutral)
))
relation("a", "H05",
Incidence("a", "session", "provider", 0),
Incidence("session", "a", "consumer", 0)
)
relation("b", "H05",
Incidence("b", "session", "provider", 0),
Incidence("session", "b", "consumer", 0)
)
relation("leaseA", "H05",
Incidence("authority", "leaseA", "provider", 0),
Incidence("a", "authority", "consumer", 0)
)
relation("leaseB", "H05",
Incidence("authority", "leaseB", "provider", 0),
Incidence("b", "authority", "consumer", 0)
)
relation("session", "H06",
Incidence("session", "session", "provider", 0),
Incidence("client", "session", "consumer", 0)
)
relation("handoff", "H35",
Incidence("a", "entity", "old", 0),
Incidence("b", "entity", "new", 1),
Incidence("authority", "fence", "fence", 2)
)
relation("rules", "H29",
Incidence("build", "revision", "bundle", 0),
Incidence("a", "rules", "cell", 1),
Incidence("b", "rules", "cell", 2)
)
}