-- ── module-uo · schema fragment ──────────────────────────────────────────── -- -- Replayed by core's ensureSchema() immediately after core's own schema.sql, -- statement by statement, split the same way (docs/website/MODULE_API.md §2.6). -- It inherits core's rules because it goes through core's splitter: idempotent -- CREATE/ALTER only, no DROP, and no `--` inside a string literal. -- -- SPIKE SCOPE: the eight spawn-atlas tables, lifted verbatim out of -- server/db/schema.sql. Phase 3 brings the other nineteen. -- -- These names are NOT `uo_`-prefixed, which the contract otherwise requires of a -- module's tables. module-uo is grandfathered by an explicit allowlist in the -- loader: renaming twenty-seven live tables is a data migration this workstream -- deliberately does not do, and the prefix rule holds for every module written -- after this one. -- ── Spawn atlas (Protocol 3.0 Part C) ─────────────────────────────────────── -- Static shard CONTENT, not live shard state: what spawns where, which regions -- and landmarks exist, and which champion altars are configured. Nothing here -- comes from the sidecar — it is imported from a committed artifact built off a -- ServUO tree by `npm run atlas:build` (see docs/website/SPAWN_ATLAS.md), so -- these tables stay populated whether the shard is up or not. -- -- Every table is import-owned: `npm run atlas:import` TRUNCATEs and reloads them -- in one transaction. Nothing else may write here, and nothing else may hold a -- foreign key to them. No FKs at all, consistent with every other shard_* table. -- One row per spawnable type, aggregated across the world. `total` is the sum of -- each type's own MX across every point that spawns it (how many exist at once); -- `facets` is a per-facet point count, so the facet filter and "where does this -- live" both answer without touching shard_spawn_points. CREATE TABLE IF NOT EXISTS shard_spawn_creatures ( slug VARCHAR(120) NOT NULL PRIMARY KEY, -- slugified class name; the /atlas/:slug key name VARCHAR(120) NOT NULL, -- display spelling chosen by the build total INT NOT NULL DEFAULT 0, points INT NOT NULL DEFAULT 0, facets JSON NULL, -- { "Felucca": 171, "Trammel": 160, ... } -- Operator-supplied artwork, always NULL on a fresh import. The repo ships no -- creature art: sprites live in the operator's own client .mul/.uop files and -- are theirs to extract and place under uploads/atlas/. The UI renders without -- art when this is NULL, which is the normal case. art VARCHAR(255) NULL, -- Plain INDEX, deliberately NOT FULLTEXT: ~800 rows makes a LIKE scan free, -- and FULLTEXT's min-token-length would break searches for names like "orc". INDEX idx_shard_spawn_creatures_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- One row per spawner. `region`/`landmark` are the resolved place name — the -- point-in-rect transform that turns "5411,1234" into "Despise" — and `label` is -- the resolved display string (region, else landmark, else 'Wilderness'). CREATE TABLE IF NOT EXISTS shard_spawn_points ( id INT AUTO_INCREMENT PRIMARY KEY, facet VARCHAR(40) NOT NULL, name VARCHAR(120) NULL, -- the ServUO spawner's own name x INT NOT NULL, y INT NOT NULL, width INT NOT NULL DEFAULT 0, height INT NOT NULL DEFAULT 0, spawn_range INT NOT NULL DEFAULT 0, -- `range` is reserved in MariaDB max_count INT NOT NULL DEFAULT 0, min_delay INT NOT NULL DEFAULT 0, max_delay INT NOT NULL DEFAULT 0, tod_start INT NOT NULL DEFAULT 0, -- meaningless unless tod_mode <> 0 tod_end INT NOT NULL DEFAULT 0, tod_mode INT NOT NULL DEFAULT 0, region VARCHAR(120) NULL, landmark VARCHAR(120) NULL, label VARCHAR(120) NOT NULL DEFAULT 'Wilderness', INDEX idx_shard_spawn_points_facet (facet), INDEX idx_shard_spawn_points_label (label) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- The many-to-many between the two above: one spawner commonly carries several -- types (a single Trammel point spawns six), each with its own max. This is how -- /atlas/creatures/:slug finds the places a creature appears. CREATE TABLE IF NOT EXISTS shard_spawn_point_types ( point_id INT NOT NULL, slug VARCHAR(120) NOT NULL, -- → shard_spawn_creatures.slug (no FK) max_count INT NOT NULL DEFAULT 1, PRIMARY KEY (point_id, slug), INDEX idx_shard_spawn_point_types_slug (slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Named regions from Data/Regions.xml, flattened out of their nesting. `rects` -- holds the region's rectangles; `priority` and rect area are what resolved each -- spawn point at build time, kept here so the admin drift check can re-derive. CREATE TABLE IF NOT EXISTS shard_regions ( id INT AUTO_INCREMENT PRIMARY KEY, facet VARCHAR(40) NOT NULL, name VARCHAR(120) NOT NULL, type VARCHAR(80) NULL, -- ServUO region class priority INT NOT NULL DEFAULT 0, parent VARCHAR(120) NULL, -- enclosing named region, if any rects JSON NULL, INDEX idx_shard_regions_facet (facet), INDEX idx_shard_regions_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Points of interest from Data/Locations/*.xml. `grp` is the innermost enclosing -- parent ("Covetous"), which is the label worth showing — "Covetous" reads -- better than the individual marker "Level 1". (`group` is reserved in SQL.) CREATE TABLE IF NOT EXISTS shard_landmarks ( id INT AUTO_INCREMENT PRIMARY KEY, facet VARCHAR(40) NOT NULL, name VARCHAR(120) NOT NULL, grp VARCHAR(120) NULL, x INT NOT NULL, y INT NOT NULL, z INT NOT NULL DEFAULT 0, INDEX idx_shard_landmarks_facet (facet), INDEX idx_shard_landmarks_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Configured champion altars from Config/ChampionSpawns.xml. This is static -- roster data ("there is an Unholy Terror altar in Deceit") and is distinct from -- the live champ.update feed in shard_champs ("it is on level 3 right now"). CREATE TABLE IF NOT EXISTS shard_champion_spawns ( slug VARCHAR(160) NOT NULL PRIMARY KEY, -- facet-name, e.g. "felucca-deceit" name VARCHAR(120) NOT NULL, grp VARCHAR(80) NULL, -- spawn group; one active per group type VARCHAR(80) NULL, -- '' when randomised per activation random_type TINYINT(1) NOT NULL DEFAULT 0, facet VARCHAR(40) NOT NULL, x INT NOT NULL, y INT NOT NULL, z INT NOT NULL DEFAULT 0, radius INT NOT NULL DEFAULT 0, label VARCHAR(120) NULL, -- resolved place name INDEX idx_shard_champion_spawns_facet (facet) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Singleton (id = 1) describing the artifact currently loaded: when it was -- built, its counts, and a sha256 per ServUO source file. The admin drift check -- compares this against db/data/spawnAtlas.meta.json to report when the database -- is behind the committed artifact. CREATE TABLE IF NOT EXISTS shard_atlas_meta ( id TINYINT NOT NULL PRIMARY KEY DEFAULT 1, payload JSON NOT NULL, imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT chk_shard_atlas_meta_singleton CHECK (id = 1) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Singleton (id = 1) holding an atlas refresh that was parsed but deliberately -- NOT applied, because it would remove a facet the site currently serves. -- -- Losing a facet is the signature of a half-copied or mid-update ServUO tree as -- much as of a real map change, and boot cannot tell the two apart — so the -- refresh is staged here for a human instead of being applied. Startup is never -- blocked by it: the site comes up serving the atlas it already had. -- -- Only the DECISION is stored, not the parsed world: `payload` holds the source -- hashes and the facet diff (a few KB), and approving re-parses the tree. That -- keeps a multi-megabyte blob out of the database and guarantees the applied -- atlas matches the tree as it is at approval time, not as it was at boot. -- -- `rejected` is remembered against those exact source hashes so a declined -- refresh does not re-prompt on every restart; changing the tree changes the -- hashes and asks again. CREATE TABLE IF NOT EXISTS shard_atlas_pending ( id TINYINT NOT NULL PRIMARY KEY DEFAULT 1, status ENUM('pending','rejected') NOT NULL DEFAULT 'pending', payload JSON NOT NULL, -- source hashes + facet diff detected_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT chk_shard_atlas_pending_singleton CHECK (id = 1) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;