The card hid Start now until the roster filled or the deadline elapsed, preventing groups from starting early with fewer players. Expose the existing start action for every unfinished caller-owned call, including scheduled calls before check-in. Keep check-in controls and launch only after core acceptance. Add rendered-card regressions for six-of-eight players, solo and scheduled starts, creator authority, terminal states, and unavailable local games. Use installed React packages for these tests without rewriting the app lockfile, and document the caller's control over start time. Test Plan: - `just frontend-test` -- passed, 107 tests including 8 rendered-card tests - `just test` -- passed, 797 Rust tests - `just clippy` -- passed with warnings denied - `just fmt` -- passed - `deno task build` -- passed TypeScript and Vite production build - `git diff --cached --check` -- passed
331 lines
14 KiB
Markdown
331 lines
14 KiB
Markdown
# lanspread
|
|
|
|
Peer-to-peer game library sharing for LAN parties. Peers discover each other on
|
|
the local network via mDNS, exchange library metadata over QUIC, and let users
|
|
browse and download games from each other. Ships as a Tauri desktop app.
|
|
|
|
## Peer identity and protocol status
|
|
|
|
Each desktop installation creates and reuses one Ed25519 identity in its Tauri
|
|
app-data directory. Its `PeerId` is lowercase unpadded RFC 4648 base32 of BLAKE3
|
|
over the certificate's complete canonical DER SubjectPublicKeyInfo; certificates
|
|
and private keys are never distributed from the repository. Outbound QUIC
|
|
connections authenticate the responder against the exact expected `PeerId`. Wire
|
|
protocol 8 (`lanspread/8`) is the only supported protocol. Peers exchange
|
|
responder-owned, revisioned snapshots over identity-pinned endpoints; file and
|
|
streamed-install requests name the catalog's exact `ContentId`. There are no
|
|
legacy wire fallbacks or repository-shared identity keys.
|
|
|
|
## First-time setup
|
|
|
|
Install Rust, Deno, and `just` first, then bootstrap the project:
|
|
|
|
```bash
|
|
just setup
|
|
```
|
|
|
|
That installs the Tauri CLI and the locked frontend dependencies. All routine
|
|
repository work should go through `just`; the recipes carry the catalog mode and
|
|
Tauri resource configuration needed to keep fixture authority out of production
|
|
builds.
|
|
|
|
## Daily development
|
|
|
|
Run the desktop app with the already-generated production catalog:
|
|
|
|
```bash
|
|
just run
|
|
```
|
|
|
|
Plain `just` (or the `j` shell alias) does the same. It checks the existing
|
|
catalog and starts the launcher; no source package directory or environment
|
|
variables are needed. Select the folder containing your games in the launcher.
|
|
|
|
To test directly with a local game folder and a separate catalog:
|
|
|
|
```bash
|
|
just run-local /srv/lanspread/test-games
|
|
```
|
|
|
|
This selects that game folder and generates or reuses test authority under
|
|
`crates/lanspread-tauri-deno-ts/src-tauri/local-catalog/`, with its own cache in
|
|
`.lanspread/catalog-cache/local.json`. Missing catalog games and differing
|
|
`version.ini` values are allowed; directory names must still match known game
|
|
IDs. The console identifies this as local test mode. Its opt-in is checked at
|
|
build time and cannot enable local authority in a production build. The local
|
|
launcher uses separate test settings and a window title marked `LOCAL TEST`.
|
|
|
|
Build a launcher from the already-published production catalog without creating
|
|
an installer:
|
|
|
|
```bash
|
|
just build
|
|
```
|
|
|
|
Generate the production catalog from a package tree when necessary, then build
|
|
the same no-bundle launcher:
|
|
|
|
```bash
|
|
just build-production /srv/lanspread/packages
|
|
```
|
|
|
|
To regenerate or reuse production authority from a package tree and then run:
|
|
|
|
```bash
|
|
just run-production /srv/lanspread/packages
|
|
```
|
|
|
|
The fixture-only paths remain explicit:
|
|
|
|
```bash
|
|
just run-fixture
|
|
just build-fixture
|
|
```
|
|
|
|
Run the standard checks before committing:
|
|
|
|
```bash
|
|
just fmt
|
|
just clippy
|
|
just test
|
|
just frontend-test
|
|
```
|
|
|
|
Frontend tests render components with the React packages installed by
|
|
`just setup`. Their separate Deno configuration uses those installed packages
|
|
without rewriting the application's dependency lockfile.
|
|
|
|
See [launcher shutdown ownership](crates/lanspread-tauri-deno-ts/SHUTDOWN.md)
|
|
for the cancellation/drain sequence and a native close-button regression probe.
|
|
|
|
`just build` always uses the production resource map and production profile; use
|
|
`just build-fixture` when a fixture build is intended. Tauri's build script
|
|
rejects fixture and local resources unless their explicit development opt-in is
|
|
present, and the production profile rejects both modes. Before Tauri copies
|
|
catalog resources, the build script clears the generated `game.db` and
|
|
`manifests/` in the active Cargo profile directory. This keeps
|
|
fixture/production switches and reduced production catalogs free of stale
|
|
manifests. Cargo also watches these generated copies, so deleting them causes
|
|
the next build to recreate them even when the source catalog is unchanged.
|
|
|
|
## Catalog checksums and production manifests
|
|
|
|
Lanspread does not trust file descriptions received from peers. The release
|
|
catalog contains the expected bytes for each included game version. The catalog
|
|
publisher derives that authority from the canonical game packages and writes:
|
|
|
|
- one `<game_id>.json` manifest containing every ordinary file's size, full-file
|
|
BLAKE3, and BLAKE3 for each 128 MiB transfer chunk;
|
|
- BLAKE3 and size for every file obtained by extracting the root-level `.eti`
|
|
archives, which authorizes Stream Install output; and
|
|
- `catalog-content-index-v1.jsonl`, which binds every catalog game/version to
|
|
its complete manifest `ContentId` and Stream Install capability.
|
|
|
|
The `ContentId` is a digest of the canonical manifest transcript, not merely a
|
|
hash of one archive. Never calculate or edit these values by hand.
|
|
|
|
### Prepare the package tree
|
|
|
|
Place the canonical packages under one directory, with each direct child named
|
|
exactly like the `game_id` in the production `game.db`:
|
|
|
|
```text
|
|
/srv/lanspread/packages/
|
|
├── alienswarm/
|
|
│ ├── version.ini
|
|
│ ├── alienswarm.eti
|
|
│ └── ...
|
|
├── bf1942/
|
|
│ ├── version.ini
|
|
│ ├── bf1942.eti
|
|
│ └── ...
|
|
└── ...
|
|
```
|
|
|
|
Each `version.ini` must match that game's `game_version` in `game.db`. Package
|
|
entries must be regular files/directories with canonical portable names; links,
|
|
reparse points, special files, aliases, unsafe archive output, or changing bytes
|
|
make generation fail closed.
|
|
|
|
The default publisher uses the checked-in Linux `unrar` sidecar. Select another
|
|
trusted executable when generating on a different supported host:
|
|
|
|
```bash
|
|
LANSPREAD_UNRAR=/absolute/path/to/unrar \
|
|
just catalog-generate-production /srv/lanspread/packages
|
|
```
|
|
|
|
### Generate and verify checksums
|
|
|
|
Generate the complete production catalog:
|
|
|
|
```bash
|
|
just catalog-generate-production /srv/lanspread/packages
|
|
```
|
|
|
|
The default remains strict: every game in the checked-in source `game.db` must
|
|
have a package directory. To build a reduced catalog from only the package
|
|
directories currently available, pass the explicit optional mode:
|
|
|
|
```bash
|
|
just catalog-generate-production /srv/lanspread/packages allow-missing-games
|
|
```
|
|
|
|
`allow-missing-games` omits absent package directories and reports their count;
|
|
it still rejects a package whose `version.ini` differs from the source catalog.
|
|
Version mismatches have their own independent opt-in:
|
|
|
|
```bash
|
|
just catalog-generate-production /srv/lanspread/packages allow-version-mismatch
|
|
```
|
|
|
|
That mode reports mismatched game IDs and uses each package's actual
|
|
`version.ini` value in the generated database copy, while still requiring every
|
|
package directory. Combine both options for an older, incomplete package corpus:
|
|
|
|
```bash
|
|
just catalog-generate-production /srv/lanspread/packages \
|
|
allow-missing-games allow-version-mismatch
|
|
```
|
|
|
|
Invalid directories and unreadable or non-UTF-8 version files always fail. The
|
|
generator copies and filters `game.db` to exactly the included games, then
|
|
generates and validates their complete manifest/index set. At least one matching
|
|
source-catalog game must be present. The checked-in full database is never
|
|
modified; the coherent generated output is installed under
|
|
`crates/lanspread-tauri-deno-ts/src-tauri/production-catalog/`.
|
|
|
|
The mode can also be used while running or building real data:
|
|
|
|
```bash
|
|
just run-production /srv/lanspread/packages \
|
|
allow-missing-games allow-version-mismatch
|
|
just build-production /srv/lanspread/packages \
|
|
allow-missing-games allow-version-mismatch
|
|
```
|
|
|
|
The recipe keeps a local metadata-only stamp in
|
|
`.lanspread/catalog-cache/production.json`. It compares the package tree's
|
|
paths, entry types, sizes, and nanosecond mtimes, plus the source and generated
|
|
catalog databases, generation mode, and `unrar` metadata. If those values and
|
|
the published output still match, the expensive package hashing and extraction
|
|
step is skipped. This is only a performance cache: `catalog-check-production`
|
|
and every production build still validate the catalog authority. Force a refresh
|
|
when needed:
|
|
|
|
```bash
|
|
LANSPREAD_CATALOG_FORCE=1 just catalog-generate-production /srv/lanspread/packages
|
|
```
|
|
|
|
The publisher first checks every included package's `version.ini` against the
|
|
generated `game.db`, then independently reads and hashes each package twice
|
|
before publication. It creates the complete database, manifest, and index set in
|
|
staging and validates it with the application loader before atomically replacing
|
|
the previous production catalog directory.
|
|
|
|
Verify the published database/index/manifest set without rereading the package
|
|
corpus:
|
|
|
|
```bash
|
|
just catalog-check-production
|
|
```
|
|
|
|
This is a packaging integrity check, not a substitute for retaining the
|
|
canonical packages and the successful generation log as provenance. The output
|
|
prints the `game_id`, `game_version`, and `content_id` for every checked game.
|
|
|
|
### Regenerate one changed game
|
|
|
|
After a complete manifest set exists, update one game without rehashing every
|
|
package:
|
|
|
|
```bash
|
|
just catalog-generate-production-game /srv/lanspread/packages alienswarm
|
|
just catalog-check-production
|
|
```
|
|
|
|
Incremental generation refuses to bootstrap a catalog. Under its durable
|
|
publication marker it revalidates every unselected manifest/index identity,
|
|
rebuilds the complete generated index, and then publishes the selected game. Use
|
|
the main generation command for a new catalog or any intentionally broad catalog
|
|
change.
|
|
|
|
### Build a production bundle
|
|
|
|
Only after the production check succeeds, create the installer/bundle:
|
|
|
|
```bash
|
|
just bundle
|
|
```
|
|
|
|
`just bundle` repeats the production catalog gate before Tauri packaging. It
|
|
never falls back to fixture manifests. This checkout does not include generated
|
|
production authority, so the command intentionally fails until an external
|
|
package corpus has been processed into
|
|
`crates/lanspread-tauri-deno-ts/src-tauri/production-catalog/`.
|
|
|
|
## Fixture catalogs and peer-CLI acceptance
|
|
|
|
The committed fixture profiles are test authority only:
|
|
|
|
```bash
|
|
just fixture-catalog-check # explicit fixture GUI/CLI profile
|
|
just fixture-catalogs-check # default + solid + multi + unknown profiles
|
|
just fixture-catalogs # explicitly regenerate all committed profiles
|
|
```
|
|
|
|
The solid, multi-archive, and unknown-game profiles intentionally describe
|
|
different package bytes, so the peer-CLI matrix checks all of them before
|
|
building its Docker image. `just run-fixture` uses the default fixture profile.
|
|
|
|
Build or run the JSONL peer harness:
|
|
|
|
```bash
|
|
just peer-cli-build
|
|
just peer-cli-image
|
|
just peer-cli-tests S1 S2 S3
|
|
just peer-cli-run alpha
|
|
```
|
|
|
|
Run the entire S1-S49 acceptance matrix by omitting scenario arguments:
|
|
|
|
```bash
|
|
just peer-cli-tests
|
|
```
|
|
|
|
For an interactive three-peer session, start these in separate terminals:
|
|
|
|
```bash
|
|
just peer-cli-alpha
|
|
just peer-cli-bravo
|
|
just peer-cli-charlie
|
|
```
|
|
|
|
## Important just commands
|
|
|
|
| Command | Purpose |
|
|
| --------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
| `just setup` | Install the Tauri CLI and frontend dependencies. |
|
|
| `just` / `just run` | Check the existing production catalog and run the GUI. |
|
|
| `just run-local GAMES_DIR` | Generate/reuse separate local test authority and run with that folder. |
|
|
| `just run-production PACKAGES_DIR [MODE...]` | Generate/reuse production authority and run the GUI. |
|
|
| `just run-fixture` | Check the default fixture catalog and run the GUI. |
|
|
| `just build` | Validate production authority and build without bundling. |
|
|
| `just build-production PACKAGES_DIR [MODE]` | Generate/reuse production authority and build without bundling. |
|
|
| `just build-fixture` | Check the default fixture catalog and build without bundling. |
|
|
| `just bundle` | Check production authority and build production bundles. |
|
|
| `just catalog-generate-production DIR [MODE...]` | Publish packages with explicit missing/version mismatch opt-ins. |
|
|
| `just catalog-generate-production-game DIR GAME_ID` | Safely regenerate one game in an already complete catalog. |
|
|
| `just catalog-check-production` | Validate all production database/index/manifest artifacts. |
|
|
| `just fixture-catalog-check` | Validate the default development fixture catalog. |
|
|
| `just fixture-catalogs-check` | Validate every peer-CLI fixture profile. |
|
|
| `just fixture-catalogs` | Regenerate all committed fixture profiles from their packages. |
|
|
| `just fmt` | Format Rust, TOML, Markdown, and the Justfile. |
|
|
| `just clippy` | Lint every Rust workspace target with warnings denied. |
|
|
| `just test` | Run every Rust workspace test target. |
|
|
| `just frontend-test` | Run frontend reducer, lifecycle, and rendered component tests. |
|
|
| `just peer-cli-build` | Build the JSONL peer harness. |
|
|
| `just peer-cli-image` | Check fixture authority and build the harness Docker image. |
|
|
| `just peer-cli-tests [SCENARIO ...]` | Run selected scenarios, or the complete matrix when no IDs are supplied. |
|
|
| `just peer-cli-run NAME` | Start one persistent-state interactive harness peer. |
|