diff --git a/Cargo.lock b/Cargo.lock
index de6765c..8992cbe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -189,7 +189,7 @@ dependencies = [
[[package]]
name = "backbone-lib"
-version = "0.2.11"
+version = "0.2.12"
dependencies = [
"bytes",
"ewebsock",
@@ -2649,7 +2649,7 @@ dependencies = [
[[package]]
name = "protocol"
-version = "0.2.11"
+version = "0.2.12"
dependencies = [
"serde",
]
@@ -2883,7 +2883,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "relay-server"
-version = "0.2.11"
+version = "0.2.12"
dependencies = [
"argon2",
"axum",
@@ -3893,7 +3893,7 @@ dependencies = [
[[package]]
name = "trictrac-store"
-version = "0.2.11"
+version = "0.2.12"
dependencies = [
"anyhow",
"base64 0.21.7",
@@ -3906,7 +3906,7 @@ dependencies = [
[[package]]
name = "trictrac-web"
-version = "0.2.11"
+version = "0.2.12"
dependencies = [
"backbone-lib",
"futures",
diff --git a/README.md b/README.md
index ca4c0de..f9485c7 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,6 @@
This is a game of [Trictrac](https://en.wikipedia.org/wiki/Trictrac) rust implementation.
-The project is still on its early stages.
-
## Usage
Install [devenv](https://devenv.sh/getting-started/), start a devenv shell `devenv shell`, and run the following commands.
@@ -17,118 +15,18 @@ just run-relay # listens on :8080
just dev
```
-Open two browser windows at `http://127.0.0.1:9091`. In one, create a room; in the other, join with the same room name.
-
-Playing with the cli against the 'random' bot: `cargo run --bin=client_cli -- --bot random`
-
-## Roadmap
-
-- [x] rules
-- [x] command line interface
-- [x] basic bot (random play)
-- [ ] web client (in progress)
-- [ ] network game (in progress)
-- [ ] AI bot
+Open a browser window at `http://127.0.0.1:9091`. You can play against a very basic bot, or invite an other player to connect at the same address.
## Code structure
- game rules and game state are implemented in the _store/_ folder.
+- a server for the network game is implemented in _server/relay-server_, which uses _server/protocol_
+- the web client is in _clients/web_, it connects to the server using the _clients/backbone-lib_ library
- the command-line application is implemented in _clients/cli/_; it allows you to play against a bot, or to have two bots play against each other
-- the bots algorithms and the training of their models are implemented in the _bot/_ and _spiel_bot_ folders.
+- the bots algorithms and the training of their models are implemented in the _bot/_ and _spiel_bot_ folders. This is a work in progress, they are not performant at all.
-### _store_ package
+## Inspirations
-The game state is defined by the `GameState` struct in _store/src/game.rs_. The `to_string_id()` method allows this state to be encoded compactly in a string (without the played moves history). For a more readable textual representation, the `fmt::Display` trait is implemented.
+The multiplayer game architecture, implemented in packages _clients/backbone-lib_, _clients/web/game_, _server/protocol_, _server/relay-server_ is a Leptos-optimized adaptation of the macroquad-based [Carbonfreezer/multiplayer](https://github.com/Carbonfreezer/multiplayer) project. It is a multiplayer game system in Rust targeting browser-based board games compiled as WASM. The original project used Macroquad with a polling-based transport layer; this version replaces that with an async session API built for [Leptos](https://leptos.dev/).
-### _clients/cli_ package
-
-`clients/cli/src/game_runner.rs` contains the logic to make two bots play against each other.
-
-### _bot_ package
-
-- `bot/src/strategy/default.rs` contains the code for a basic bot strategy: it determines the list of valid moves (using the `get_possible_moves_sequences` method of `store::MoveRules`) and simply executes the first move in the list.
-- `bot/src/strategy/dqnburn.rs` is another bot strategy that uses a reinforcement learning trained model with the DQN algorithm via the burn library ().
-- `bot/scripts/trains.sh` allows you to train agents using different algorithms (DQN, PPO, SAC).
-
-### multiplayer game
-
-Packages "clients/backbone-lib", "clients/web/game", "server/protocol", "server/relay-server" are a Leptos-optimized adaptation of the macroquad-based [Carbonfreezer/multiplayer](https://github.com/Carbonfreezer/multiplayer) project. It is a multiplayer game system in Rust targeting browser-based board games compiled as WASM. The original project used Macroquad with a polling-based transport layer; this version replaces that with an async session API built for [Leptos](https://leptos.dev/).
-
-The system consists of:
-
-- A **relay server** (Axum/Tokio) that routes messages between players and manages rooms, without knowing anything about game rules.
-- A **backbone library** that handles WebSocket connection, handshake, and message routing, exposing an async API to the game frontend.
-- Game-specific **backend logic** implementing the `BackEndArchitecture` trait, which runs only on the hosting client.
-- A **Leptos frontend** that connects to a session and reacts to state updates.
-
-There is no dedicated game server. One of the players acts as the host: their browser runs the game backend locally. The relay server only forwards messages — it never touches game state.
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ Host Client │
-│ ┌─────────────┐ ┌──────────────────┐ ┌────────────┐ │
-│ │ Leptos UI │◄──►│ GameSession │◄──►│ Backend │ │
-│ └─────────────┘ └────────┬─────────┘ └────────────┘ │
-└───────────────────────────── │ ────────────────────────────┘
- │ WebSocket
- ┌──────▼──────┐
- │ Relay Server│
- └──────┬──────┘
- │ WebSocket
-┌───────────────────────────────│────────────────────────────┐
-│ ┌─────────────┐ ┌─────────▼────────┐ │
-│ │ Leptos UI │◄──►│ GameSession │ (no backend) │
-│ └─────────────┘ └──────────────────┘ │
-│ Remote Client │
-└─────────────────────────────────────────────────────────────┘
-```
-
-#### Data flow
-
-- **Actions** (e.g. "place stone at B3") flow from the UI to the host backend via `GameSession::send_action()`.
-- **State updates** flow back as `ViewStateUpdate::Full` (full snapshot, on join or reset) or `ViewStateUpdate::Incremental` (delta, for animations).
-- **Timers** are managed by the host's background task (wall-clock, no polling required from the game).
-
-#### backbone-lib session API
-
-The key design choice: `backbone-lib` owns a background async task per session. The Leptos app never drives a loop — it just awaits on events.
-
-#### Workspace
-
-**server/protocol**
-
-Shared message-type constants and the `JoinRequest` struct used during the WebSocket handshake.
-
-**server/relay-server**
-
-Listens on port 8080. Loads `GameConfig.json` on startup to know which games exist and their player limits:
-
-```json
-[{ "name": "trictrac", "max_players": 10 }]
-```
-
-Games can be added at runtime via the `/reload` endpoint. `/enlist` lists active rooms. A watchdog cleans up inactive rooms every 20 minutes.
-
-For production, put it behind a reverse proxy with SSL (the browser requires `wss://` on HTTPS pages). Example Caddy config:
-
-```
-your-domain.com {
- handle_path /api/* {
- reverse_proxy localhost:8080
- }
- file_server
-}
-```
-
-**clients/backbone-lib**
-
-Modules:
-
-| Module | Purpose |
-| ---------- | ---------------------------------------------------------------------------------------------------------- |
-| `session` | `GameSession`, `connect()`, `SessionEvent`, `RoomConfig` |
-| `host` | Background async task for the hosting client (drives `BackEndArchitecture`, manages timers) |
-| `client` | Background async task for non-hosting clients |
-| `protocol` | Wire encoding/decoding helpers (postcard + message-type bytes) |
-| `platform` | `spawn_task` / `sleep_ms` abstractions (WASM: `spawn_local` + gloo-timers; native: thread + thread::sleep) |
-| `traits` | `BackEndArchitecture`, `BackendCommand`, `ViewStateUpdate`, `SerializationCap` |
+The web client UX/UI is inspired by https://playtiao.com.
diff --git a/clients/web/src/app.rs b/clients/web/src/app.rs
index 6cfaa54..2749d9c 100644
--- a/clients/web/src/app.rs
+++ b/clients/web/src/app.rs
@@ -29,6 +29,12 @@ use trictrac_store::CheckerMove;
use std::collections::VecDeque;
+/// Newtype wrappers so context lookup can distinguish signals of the same inner type.
+#[derive(Clone, Copy)]
+pub(crate) struct AnonNickname(pub RwSignal