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.

MarkerMeaning in this diagram
⌨ N13Input / interaction source
▤ N11Surface / view component
▣ N05Actor / statechart
◆ N16Native resource / engine
◷ N09Timer / clock source
≋ N24Queue / log / pubsub resource
↦ P04Push event
○ P01Value / immutable snapshot
◎ P24Inspection / evidence
⇆ P02Request / response
◷ P15Clock / tick / timer
→ H02Value flow
⇆ H05Request / reply binding
◷ H31Clock / 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

RelationPatternParticipants, preserving role positions
commandH02 — Value flow {#h02--value-flow}provider[0]: input.command; consumer[0]: room.command
predictH02 — Value flow {#h02--value-flow}provider[0]: input.intent; consumer[0]: prediction.intent
simulateH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: physics.step; consumer[0]: room.physics
tickH02 — Value flow {#h02--value-flow}provider[0]: clock.tick; consumer[0]: room.tick
snapshotH02 — Value flow {#h02--value-flow}provider[0]: room.snapshot; consumer[0]: prediction.authoritative
logH02 — Value flow {#h02--value-flow}provider[0]: room.event; consumer[0]: log.event
alignmentH31 — 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.

MarkerMeaning in this diagram
⌕ N22Query / materialized view
▣ N05Actor / statechart
▰ N21Database / state authority
▤ N11Surface / view component
▧ N35Artifact / release manifest
⇆ P02Request / response
⇄ P07Duplex session
◎ P24Inspection / evidence
⇆ H05Request / reply binding
⌬ H06Multiparty session
⇧ H35State migration
⏻ H29Activation 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

RelationPatternParticipants, preserving role positions
aH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: a.session; consumer[0]: session.a
bH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: b.session; consumer[0]: session.b
leaseAH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: authority.leaseA; consumer[0]: a.authority
leaseBH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: authority.leaseB; consumer[0]: b.authority
sessionH06 — Multiparty session {#h06--multiparty-session}provider[0]: session.session; consumer[0]: client.session
handoffH35 — State migration {#h35--state-migration}old[0]: a.entity; new[1]: b.entity; fence[2]: authority.fence
rulesH29 — 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)
    )
}