Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ gateway/gateway-stderr.log
dhat-heap.json
__pycache__/

# cargo-mutants report output (#380). Regenerated on every run; the CI job uploads it as an
# artifact instead. `.cargo/mutants.toml` is the configuration and IS checked in.
# cargo-mutants report output. Regenerated on every run, so it is never committed. This repository
# carries no mutants configuration file; the maintainers run cargo-mutants from their own repository.
/mutants.out/
/mutants.out.old/
.scratch/
Expand Down
2 changes: 1 addition & 1 deletion .wire-harness-rev
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# any other 5875 core?" unanswerable.
#
# ONE line: `<tag> <full commit sha>`, and BOTH halves are load-bearing.
# * the TAG is what `scripts/wire-harness.sh` checks out — a release, not a branch. There is
# * the TAG is what `lyracore dev smoke` checks out — a release, not a branch. There is
# deliberately no way to say "main" here.
# * the SHA is what it then VERIFIES the checkout resolved to. A git tag is a mutable ref; a
# re-pointed tag would otherwise change what this checkout builds without changing this file,
Expand Down
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Changelog

All notable changes to LyraCore are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) — with the caveat that a `0.x` alpha
makes no compatibility promise at all: schemas, environment variables, CLI flags, and database
names may change between pre-releases without a deprecation period.
This file records release-level changes only. It is **not** a running log of every merge, and it is
not maintained as one: day-to-day changes are the
[merged pull requests](https://github.com/LyraCoreProject/LyraCore/pulls?q=is%3Apr+is%3Amerged),
which are the honest history. Read this file for what a release means, and the PR list for what
happened.

Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html), with the caveat that a
`0.x` alpha makes no compatibility promise at all: schemas, environment variables, CLI flags and
database names may change between pre-releases without a deprecation period.

This file starts at the first public release. The work before it happened in a private repository
and is not reconstructed here.
Expand Down
34 changes: 10 additions & 24 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,26 @@ members = [
"gateway",
"importer",
]
# NOT a member any more (#246): the headless build-5875 wire client lives in its own repository,
# LyraCoreProject/wire-harness, and is consumed as a PINNED RELEASE — see `.wire-harness-rev`,
# `scripts/wire-harness.sh` and `adapters/lyracore/run-suite.sh`. It was never a server crate: it
# is a server-AGNOSTIC protocol tool, and a workspace membership is exactly the coupling that made
# "does this run against any other 5875 core?" unanswerable.
# The headless build-5875 wire client is deliberately not a member. It lives in its own repository,
# LyraCoreProject/wire-harness, and is consumed as a pinned release; `.wire-harness-rev` holds the
# pin and `lyracore dev smoke` resolves it. It is a server-agnostic protocol tool, and a workspace
# membership was the coupling that made "does this run against any other 5875 core?" unanswerable.

# The SpacetimeDB module compiles to wasm and pulls its own (large) dependency tree;
# keep workspace-wide settings minimal so the module and the native gateway can diverge.

[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
# Amended twice, both by empirical build attempts rather than by rule:
# - #231 moved this from 1.80 to 1.85: SpacetimeDB 2.5.0's crates declare `edition = "2024"`,
# which needs Cargo >=1.85 just to PARSE their manifests, so 1.80 was never buildable.
# - #229's CI work found 1.85 *itself* still isn't buildable: `spacetimedb`/`spacetimedb-sdk`
# 2.5.0 (and several of their own transitive deps — icu_*, darling, image, wow-mpq/-adt/-blp/
# -m2/-wmo) each declare a `rust-version` field of their own, and the highest of those is
# `spacetimedb`/`spacetimedb-sdk`'s **1.93.0** — `cargo +1.85.0 check --workspace` fails
# outright with "rustc 1.85.0 is not supported" for exactly that reason, before a single line
# of this repo's own code is even considered.
# - re-checked at the 2.5.0 -> 2.7.1 bump: every `spacetimedb-*` crate still declares
# `rust-version = "1.93.0"` and `edition = "2024"`, so the floor below did not move.
# Net result: today's REAL floor is 1.93.0, identical to the pinned dev toolchain below — there is
# no daylight between "MSRV" and "the toolchain everyone already builds with" to test separately.
# CI's `msrv-consistency` job (#229) asserts these two numbers stay equal instead of pretending a
# lower, unbuildable one is real; if a future SpacetimeDB release relaxes its own `rust-version`,
# lowering this one again needs the same empirical check, not an assumption.
# Two facts set this floor, both re-checked at the 2.7.1 bump: every `spacetimedb-*` crate declares
# `rust-version = "1.93.0"`, and they declare `edition = "2024"`, which Cargo below 1.85 cannot even
# parse. So the real MSRV equals the toolchain pinned in `rust-toolchain.toml` and there is nothing
# lower to test. Keep the two numbers equal. Lowering this needs a build attempt, not an assumption.
rust-version = "1.93.0"
repository = "https://github.com/LyraCoreProject/LyraCore"
homepage = "https://github.com/LyraCoreProject/LyraCore"
# Every crate in this workspace is INTERNAL to the LyraCore server. None of them is a library
# anyone is meant to depend on from crates.io, and #239's alpha explicitly does not publish
# either repository — so the whole workspace is publish-gated here rather than crate by crate.
# Every crate here is internal to the LyraCore server. None is a library anyone should depend on from
# crates.io, so the whole workspace is publish-gated once rather than crate by crate.
publish = false

[workspace.dependencies]
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Point your client's `realmlist.wtf` at `127.0.0.1` and play.

## Can I use my existing client?

A client never speaks to SpacetimeDB — it speaks SRP6 and WoW opcodes — so the gateway is translator and every mutation the world can undergo is a reducer call. The
realm is sharded across several databases running the *same* wasm; `dev up` brings up a sharded
fixture with a live seam you can walk across mid-session.
Yes. An unmodified 1.12.1 client, build 5875, is the only client this project supports. Nothing is
patched, no launcher is replaced, and no addon is required. Point its `realmlist.wtf` at your realm
and log in. Other 1.12.x builds fail at the logon challenge or mid-handshake.


## Importing data
Expand All @@ -46,6 +46,13 @@ The import tool pulls game data *you* supply: cmangos' public

## Architecture at a glance

A client never speaks to SpacetimeDB. It speaks SRP6 and WoW opcodes, so the gateway is the
translator and every mutation the world can undergo is a reducer call. The realm is sharded across
several databases that all run the *same* wasm. `dev up` brings up that fixture: Eastern Kingdoms on
`lyracore`, Kalimdor on `lyracore-kalimdor`, dungeons on `lyracore-instances`, and accounts and
sessions on `lyracore-realm`. Crossing between World Shards is an escrowed character transfer, not a
walk. `dev up --single` brings up `lyracore` alone.

```
unmodified 1.12.1 clients (build 5875)
│ raw TCP · SRP6 · header-encrypted opcodes
Expand Down
85 changes: 54 additions & 31 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LyraCore architecture

**Status:** current — verified against the tree on 2026-08-04. Every claim below cites the file
**Status:** current — verified against the tree on 2026-09-03. Every claim below cites the file
that makes it true; where an older document disagreed with the code, the code won.

**Authority note:** [`danger-zones.md`](./danger-zones.md) is authoritative over this document and
Expand Down Expand Up @@ -68,15 +68,15 @@ routing fact; module game logic never reads one, and an architecture test fails

### 2.1 The module owns

- **Every durable table** — 176 `#[table]` declarations (167 in the core module, plus 9
contributed by in-tree extension packages), of which 109 are `public` and 67 private. Full
inventory is covered in depth in the maintainers' internal docs; §4 below is the summary.
- **Every state transition** — 240 `#[reducer]` functions in the core module (116 of them in a
default build, the rest behind the `debug_reducers` feature), plus more from any installed
extension package.
- **All periodic work** — 12 scheduled tables drive combat swings, creature AI, aura ticks, ground
areas, instance reaping, transfer reaping, and event GC. Nothing on a gateway timer decides
gameplay.
- **Every durable table** — every `#[table]` declaration lives in `module/src/**`, plus any an
installed extension package contributes. §4 and [`schema.md`](./schema.md) §2 hold the summary;
`grep -rn '^#\[table(' module/src --include='*.rs' | wc -l` is the count that cannot go stale.
- **Every state transition** — every `#[reducer]` function, likewise:
`grep -rn '^#\[reducer' module/src --include='*.rs' | wc -l`. Roughly a third of them are behind
the `debug_reducers` feature and absent from a plain build (§8).
- **All periodic work** — scheduled tables drive combat swings, creature AI, aura ticks, ground
areas, motion republishing, weather, and every reaper.
[`schema.md`](./schema.md) §6 lists them all. Nothing on a gateway timer decides gameplay.
- **All authorization of writes** — a reducer derives its actor from `ctx.sender`.

### 2.2 The gateway owns
Expand Down Expand Up @@ -268,15 +268,17 @@ connectivity.
Full inventory and the load-bearing row shapes are covered in depth in the maintainers' internal
docs. The summary:

- **176 tables**, `game_`-prefixed for core and `pkg_<name>_`-prefixed for packages. External gtker
crates keep their `wow_` names and are never renamed.
- **109 public / 67 private.** `public` means "subscribable by a client connection". Private tables
- **238 tables on 2026-09-03**, `game_`-prefixed for core and `pkg_<name>_`-prefixed for packages.
External gtker crates keep their `wow_` names and are never renamed. Recount with
`grep -rn '^#\[table(' module/src --include='*.rs' | wc -l`; the per-domain breakdown is
[`schema.md`](./schema.md) §2.
- **122 public / 116 private.** `public` means "subscribable by a client connection". Private tables
(`game_account`, `game_session`, `game_operator`, every region/transfer/instance/realm-core table)
are readable only over the owner token.
- **16 `#[client_visibility_filter]` RLS filters**, every one of the same shape
`SELECT * FROM <table> WHERE <identity column> = :sender`, scoping owner-private rows
(`game_character`, `game_item_instance`, `game_player_spell`, …) and recipient-addressed events
(`game_group_event`, `game_whisper_event`, `game_xp_event`, …).
- **No `#[client_visibility_filter]` RLS filters.** The sixteen owner-scoped filters this document
used to list went out in commit `7fda35a` (2026-08-11), with the per-player client connections
they were the only thing that applied to. §5.4 and [`schema.md`](./schema.md) §4 give the
consequences.
- **Spatial partitioning is baked into the rows.** `game_world_entity` carries `grid_x`/`grid_y`
cell columns and indexes `(map_id, instance_id, grid_x, grid_y)`; seven event/motion tables carry
the identical 4-column grid key so an AOI box query is a range scan.
Expand Down Expand Up @@ -372,9 +374,10 @@ broadcast tier.
operator-gated (`gw_*`, `require_operator`), and the sender-path player reducers are deleted
(#483) — but **the model's safety still rests on `:3000` being unreachable**, and nothing in
this repository enforces that today.
- **RLS does not gate delivery.** The gateway reads every table through the owner token and gates
visibility with its own predicates (§5.3); the deployed `client_visibility_filter` rules only
ever applied to client-identity subscriptions, which no longer exist.
- **RLS does not gate delivery, and there is no RLS left.** The gateway reads every table through
the owner token and gates visibility with its own predicates (§5.3). The
`client_visibility_filter` rules only ever applied to client-identity subscriptions; those
subscriptions are gone, and commit `7fda35a` (2026-08-11) deleted the rules with them.
- **RLS validation is partial, and knowing which half matters.** Preflight's RLS-filter validation
step checks every filter against the generated bindings and **fails** if a filter names an
unknown table or column — so an identifier typo no longer survives to production. What preflight
Expand Down Expand Up @@ -538,15 +541,17 @@ table outside a shrinking whitelist; every character-keyed table must carry
Each has a companion ratchet test that fails when its whitelist names something that no
longer needs to be there.

**Debug reducers are compiled out by default.** `module/Cargo.toml` declares
`debug_reducers = []` with no `default`, and `module/src/debug/` (#386 split the former single
`debug.rs` into a directory along its section banners) is `#![cfg(feature =
"debug_reducers")]` in its entirety. A debug build adds 124 reducers (109 in `debug/`, 15 more
gated individually elsewhere — one `#[cfg(feature = "debug_reducers")]` per function — across ten
other files, including the not-obviously-named `set_guid_floor` in `auth.rs`; `grep -rn
'cfg(feature = "debug_reducers")' module/src` finds every one of them. A production build must be a
plain build; the deploy wrapper `lyracore publish` enables the feature deliberately because the
local headless-client tests need it.
**Debug reducers are compiled out by default.** `module/Cargo.toml` declares `debug_reducers = []`
with no `default`, and `module/src/debug/` is `#![cfg(feature = "debug_reducers")]` in its entirety.
Enabling the feature adds 127 reducers: 112 in `debug/`, and 15 more gated one function at a time
across ten other files, including the not-obviously-named `set_guid_floor` in `auth.rs`.
`grep -rn 'cfg(feature = "debug_reducers")' module/src` finds every one of them.

What a real deploy actually publishes is not decided here.
[`danger-zones.md`](./danger-zones.md) §3 is authoritative, and today it publishes **with**
`--features=debug_reducers` and runs the unoptimized `./target/debug/lyracore-gateway` binary on
every realm, production included. That is an accepted posture of the alpha, which the README's
warning banner states plainly, not a contradiction to resolve in this document.

---

Expand All @@ -560,6 +565,7 @@ local headless-client tests need it.
| [`../CONTEXT.md`](../CONTEXT.md) | The glossary. The words this document and the code are supposed to use, and the words to avoid. |
| [`danger-zones.md`](./danger-zones.md) | **Authoritative.** Traps, tooling gotchas, and the exact deploy/verify procedure. Read before any engine change. |
| [`schema.md`](./schema.md) | The table-level data model. |
| [`package-api.md`](./package-api.md) | The Package API, version 1: what a Package's Rust half may call, and what core promises about it. |
| [`region-sharding.md`](./region-sharding.md) | Retired (#471): the removed region tier's design — seam menus, assignments, view merge — kept for reference. |

### Operating and building
Expand All @@ -569,7 +575,24 @@ local headless-client tests need it.
| [`quickstart.md`](./quickstart.md) | The shortest path from a clean checkout to a running realm. |
| [`development-cli.md`](./development-cli.md) | The `./lyracore` CLI: the pinned shim, and the build, preflight, publish and local-stack commands. |
| [`data-ingestion.md`](./data-ingestion.md) | Where vanilla content comes from and the licensing firewall. |
| [`aura-capacity-verification.md`](./aura-capacity-verification.md) | The live-stack procedure proving the 32-buff/16-debuff cap end to end: refusal, untouched survivors, the overflow log line, and the wire-level `SMSG_SPELL_FAILURE` relay. |
| [`mount-verification.md`](./mount-verification.md) | The land-mount fixture ids, the attended procedure, and the Headless Client scenario for the next pinned release. |

### Verification write-ups

Each of these records one behaviour proven against a live stack, with the fixture, the exact
procedure, and what the result was. They are reproducible recipes, not status reports. §8 above
explains why two rungs of the ladder are written down instead of automated.

| Document | What it verifies |
|---|---|
| [`aura-capacity-verification.md`](./aura-capacity-verification.md) | The 32-buff/16-debuff cap end to end: refusal, untouched survivors, the overflow log line, and the wire-level `SMSG_SPELL_FAILURE` relay. |
| [`aura-stacking-probes.md`](./aura-stacking-probes.md) | The stacking-family decision on real `game_aura` rows, as an operator sees it. |
| [`cc-diminishing-returns-probe.md`](./cc-diminishing-returns-probe.md) | Crowd-control diminishing returns, whose persisted state and removal-time window only a live database shows. |
| [`vmap-rollout.md`](./vmap-rollout.md) | Exact collision on both populated World Shards, and that the Instance Pool receives no open-world vmap generation. |
| [`mount-verification.md`](./mount-verification.md) | The land-mount fixture ids, the attended procedure, and the Headless Client scenario. |
| [`taxi-flight-verification.md`](./taxi-flight-verification.md) | The direct-route flight baseline, and the cancel path when catalogue geometry mutates mid-flight. |
| [`movement-batch-acceptance.md`](./movement-batch-acceptance.md) | The steady-heartbeat batching path under a load driver, on a `disposable:` realm only. Script in `scripts/`. |
| [`auction-house-client-check.md`](./auction-house-client-check.md) | The auction house against a real 5875 client. Status: outstanding, needs a human. |
| [`duel-client-check.md`](./duel-client-check.md) | Duel visuals against a real 5875 client, which the automated tests cannot see. Status: outstanding. |
| [`hunter-pet-live-check.md`](./hunter-pet-live-check.md) | Taming, pet bars and pet lifecycle against a live development realm and a real client. |

**The work queue is GitHub Issues**, which is the single source of truth for what is open.
6 changes: 3 additions & 3 deletions docs/aura-stacking-probes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Run these probes on a development database only. Every step writes gameplay stat
- The module must be published with debug reducers. `./lyracore publish` bakes them in; a bare
`spacetime publish` does not. Follow [`danger-zones.md`](./danger-zones.md) §3 for the deploy, and
never pass `-c`.
- `scripts/publish-module.sh` calls `debug_repair_after_publish` after every publish. That reducer
re-seeds the stacking families and the probe fixture, because `init` does not re-run on an
auto-migrating publish. Call it by hand if you published another way:
- Call `debug_repair_after_publish` by hand after every publish. Nothing runs it for you. That
reducer re-seeds the stacking families and the probe fixture, because `init` does not re-run on an
auto-migrating publish, and without it the probes read as "family missing":

```bash
spacetime call -s local lyracore debug_repair_after_publish
Expand Down
Loading