-- ── 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;
