Skip to content

Reload should resume same player using stable ID + two-layer recovery model #61

@odama626

Description

@odama626

Summary

Reloading currently creates a new player/invite instead of resuming the same player. We also need a durable recovery story so a player can reload and continue as the same player with their board state intact.

This work should introduce a stable player identity and formalize a two-layer recovery model that supports current behavior and future privacy-focused constraints (e.g., not transmitting full card identity when it should be hidden).

Current behavior

  • Reload results in a new ephemeral identity
  • Player gets added as a new invite / duplicate presence entry
  • Player’s local state may be lost or partially reset

Expected behavior

  • Reload resumes the same player identity (no duplicate invites)
  • Player can recover their game state
  • Recovery model must support future privacy choices around what data is shared over the network

Design requirement: Two-layer recovery model (intentional, future-proof)

We should treat state as having two categories, regardless of how it’s currently implemented:

Layer 1: Shared / network-recoverable state

State that can be reconstructed from shared sources (e.g., Yjs doc + append-only action log + awareness).

Examples:

  • Public/shared zones and objects
  • Positions/transforms that are intended to be consistent for everyone
  • Any information that is safe to replicate to all peers

Important: This layer may include full card identity today, but the model must not require that. We want the freedom to reduce shared data in the future (e.g., “only share card details when public”).

Layer 2: Owner-local / private state

State that is:

  • specific to the owning player/device
  • not required (or not safe) to share with other peers
  • necessary for the owner to fully restore their personal view

Examples:

  • Per-player private zone details (hand/library identities, local-only zones)
  • UI-only state (arrangement preferences, selection, filters)
  • Potential future: mapping from opaque IDs → actual card identity for private objects

This layer must be persisted locally (localStorage/IndexedDB) so that reload can restore it.


Proposed changes

1) Stable client/player identity

  • Generate a stable clientId once and persist in localStorage
  • Use clientId as the canonical key for “this is the same player” across reloads
  • On rejoin, dedupe/resume instead of creating a new invite/player entry

Suggested key:

  • arcanetable:clientId

2) Recovery flow on load

On joining a table:

  1. Shared recovery: sync/replay shared sources to reconstruct shared state
  2. Local recovery: load the owner-local state keyed by (tableId, clientId) and apply it

Suggested keys:

  • arcanetable:table:<tableId>:client:<clientId>:localState

3) Do not block future privacy changes

Implementation must not assume the shared log always contains full card identity. If today’s log includes full identity, local recovery can be minimal initially — but the architecture should allow:

  • card instances represented by opaque IDs
  • identity resolution for private objects to come from Layer 2 in the future
  • explicit “reveal” semantics if/when you decide to limit what’s shared

Acceptance criteria

  • Reload does not create a new player/invite
  • Presence/player list remains stable across reloads (deduped by clientId)
  • Player recovers shared game state after reload
  • Player recovers owner-local state after reload when local persistence exists
  • If owner-local state is missing, game still loads (graceful fallback)
  • Architecture supports (does not prevent) future “share less until public” privacy model

Notes / follow-ups

  • Multi-tab behavior (same clientId) should not create duplicate players
  • Consider an optional “Reset local identity/state” button for debugging
  • Potential future enhancement: encrypted export/import of owner-local state for cross-device recovery

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions