mirror of
https://github.com/ratspeak/lrgp-rs
synced 2026-08-12 18:07:21 -04:00
No description
Port the Chess implementation from the Ratspeak local source of truth: UCI-only wire format, board state replayed locally via cozy-chess, two-side validation, terminal reasons coded as 2-3 chars (cm, sm, ins, 3fr, 50m, rsn, agr). Drop pre-release legacy code: rlap.v1 / ratspeak.game protocol-marker recognition, LegacyNoNonce dedup verdict, optional-nonce handling. The nonce is now required at the wire boundary, simplifying dedup to Fresh|Replay. Rename GameManifest -> AppManifest (matches Ratspeak naming) and add default snapshot_session / rollback_session methods to GameApp so apps that need transactional rollback (chess) opt in by overriding. Also adds the test-helpers feature gate exposing per-app force_coin hooks for deterministic test-vector generation. |
||
|---|---|---|
| examples | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| SPEC.md | ||
LRGP-rs
Rust implementation of the Lightweight Reticulum Gaming Protocol (LRGP) — a compact, session-based protocol for multiplayer games over LXMF / Reticulum mesh networks.
LRGP enables turn-based and real-time multiplayer games to run over LoRa radios, WiFi, TCP, and any other medium Reticulum supports. Game moves are encoded as tiny msgpack envelopes that fit in a single encrypted packet — no link setup needed.
Features
- Compact wire format — msgpack with single-character keys, ~60 bytes per game move
- Game session state machine — challenge → accept → play → win/draw/resign lifecycle
GameApptrait — implement this trait to create any gameLrgpRouter— register games, dispatch moves, manage manifestsLrgpStore— SQLite persistence for game sessions and move history- Transport bridge — zero-copy conversion between LRGP envelopes and LXMF fields
- Backward compatible — recognizes legacy
rlap.v1messages on inbound
Quick Start
use lrgp::apps::tictactoe::TicTacToeApp;
use lrgp::router::LrgpRouter;
let router = LrgpRouter::new();
router.register(Box::new(TicTacToeApp::new()));
// List available games
for game in router.list_apps() {
println!("{} v{} — {}", game.app_id, game.version, game.display_name);
}
Architecture
src/
constants.rs # Protocol constants, game session types, wire keys
errors.rs # LrgpError hierarchy
envelope.rs # Pack/unpack/validate LRGP envelopes (msgpack)
session.rs # Game session state machine and lifecycle
app_base.rs # GameApp trait + GameManifest
router.rs # Game registry and move dispatch
store.rs # SQLite persistence (game_sessions, game_actions)
transport.rs # LXMF ↔ LRGP bridge (pure data, no I/O)
apps/
tictactoe.rs # Built-in Tic-Tac-Toe game
Building a Game
Implement the GameApp trait:
use lrgp::app_base::{GameApp, GameManifest, IncomingResult, OutgoingResult};
struct MyGame;
impl GameApp for MyGame {
fn app_id(&self) -> &str { "mygame" }
fn version(&self) -> u32 { 1 }
fn manifest(&self) -> GameManifest { /* ... */ }
fn handle_incoming(&self, /* ... */) -> IncomingResult { /* ... */ }
fn handle_outgoing(&self, /* ... */) -> OutgoingResult { /* ... */ }
fn validate_action(&self, /* ... */) -> (bool, Option<String>) { /* ... */ }
fn get_session_state(&self, /* ... */) -> HashMap<String, JsonValue> { /* ... */ }
fn render_fallback(&self, /* ... */) -> String { /* ... */ }
}
Wire Format
Every game move fits in a single LXMF OPPORTUNISTIC packet (≤295 bytes total):
fields[0xFB] = "lrgp.v1" # protocol marker
fields[0xFD] = { # envelope (≤200 bytes)
"a": "ttt.1", # game_id.version
"c": "move", # command
"s": "a1b2c3d4e5f6g7h8", # session_id
"p": {"i": 4, "b": "____X____", ...}, # payload
}
Non-LRGP clients see human-readable fallback text (e.g., "[LRGP TTT] Move 3").
Protocol Spec
See SPEC.md for the full protocol specification.
See Also
- lrgp-py — Python implementation (wire-compatible)
License
MIT — see LICENSE.