jonnify·codemojex /codemojex/road-to-gameisland/the-crossing

Codemojex · C9.1

The crossing.

One scored attempt leaves the React island, crosses to the Elixir engine, and returns as fresh state. Along the way the branded id travels the whole distance — a JOB minted on submit, a GES written by the scorer, the PLR lane it rode, the GAM it belongs to. The secret and other players’ guesses never cross.

Grounding — liveview_boot/effector-pipeline.md · interactions.md §2 · game.ex (Codemojex.Guesses.submit/3 · ScoreWorker) · channel/model.ts.

The round-trip — a branded JOB rides the pipeline; select a stage
submit island bridge JOB · lane EchoMQ · PLR the scorer GES · board fan-out log + pubsub push game:update the model $props · React JOB
submit → GameEdge calls bridge.pushEvent("submit_guess", { emojis }) and clears its picks — the only client-owned state. The boot forwards it to the LiveView socket.
§1

The submit path

game.ex · effector-pipeline

On the server, Codemojex.submit/3 validates the guess (open, not expired, codes valid for the set), overlays the player’s locks, charges the wallet, then enqueues a branded JOB on the player’s PLR lane. The host never scores — the enqueue is the whole write; the reply is {:noreply}.

echo/…/codemojex/game.ex · Codemojex.Guesses.submit/3 source
# validate · Locks.merge · Wallet.charge_guess, then:
job = EchoData.BrandedId.generate!("JOB")
payload = :erlang.term_to_binary({:guess, game, player, guess})
Lanes.enqueue(Bus.conn(), @queue, player, job, payload)   # @queue "cm", lane = PLR

The ScoreWorker consumer claims the job, scores against the cached immutable secret, writes a GES guess and records the board, then fans the result out twice — onto EchoMQ’s retained log (durable, replayable) and a Phoenix.PubSub broadcast on "game:" <> gam (ephemeral, live). A blind golden game skips both: its first public signal is the one fat revealed frame at close.

effector-pipeline.md · stages 5–8 (the return path) source
# GameLive re-reads the privacy-safe view and pushes full state:
push_event(socket, "game:update", game_props)
# boot GameIsland hook → handle.update(props) →
model.propsReceived(props)      # $props updates; $view + $leaderboard recompute
§2

The identity travels, the payload doesn’t

view.ex · the gate

The crossing carries identities and messages about identities — never object graphs, never the secret. Codemojex.View.game_view/1 is the privacy gate: it builds a player-facing view that never selects the secret and, for a blind golden game, withholds the per-guess scores and the leaderboard until close. Select each side.

Across the View gate — what crosses, what stays sealed
View gate crosses → the island GAM · PLR JOB · GES game:update · full props stays sealed → the core the secret · six codes other players’ guesses blind scores (until close)
the identity → the branded ids and the full-state game:update props cross the gate to seed the island’s $props. The id is the key on every frame — time-ordered, coordination-free, unchanged across the boundary.

This is the BCS law on the wire: the only values that cross a boundary are identities, and messages about identities. A dropped frame is corrected by the next full game:update; a reconnect re-reads from scratch — the client can never drift from the server’s view.

§3

References

sources · related

Sources

Related