PURPOSE

Pure functions for mod-grid placement validation and stat aggregation. Layer between mod-template data and any caller (UI, store, or future server-side validation) that needs to ask “does this mod fit here?” or “what is the total stat bonus from these mods?“. No store reads, no side effects — all inputs are passed in explicitly. Supports the v5.64+ global 4×4 Mod Grid with Survivor.io-style merge, where instances carry { uid, templateId, rarity } and stats are computed as template.stats × RARITY_MULTIPLIER.

OWNS

  • PlacedMod interface — extends ModInstance with col / row grid coordinates for UI consumption.
  • canPlace — bounds + overlap check for a candidate template at a target cell, honoring per-template cell masks (L/T/S/Z shapes have empty corners) and an optional ignoreUid to allow self-replacement.
  • computeOccupancy — produces a gridRows × gridCols boolean matrix marking which cells are filled by any placed mod.
  • computeEquippedStatBonus — sums rarity-multiplied stat blocks across an array of equipped instances, returning a ModStatBlock.
  • statsForInstance — returns the rarity-multiplied stat block for a single instance (used for tooltip deltas).
  • Internal fillSet helper that builds the "col,row" string set of cells a template occupies at a given origin.

READS FROM

  • ../data/mod-templates for the types ModStatBlock, ModTemplate, ModInstance, ModRarity and the value RARITY_MULTIPLIER.
  • Caller-supplied Record<string, ModTemplate> template lookup — the service never imports a template registry directly.
  • Caller-supplied placed: PlacedMod[] arrays, mod-instance arrays, and grid dimension arguments.

PUSHES TO

Nothing. Pure functions only — outputs are returned as new objects (Set<string>, boolean[][], ModStatBlock). No global state mutation, no events, no logs.

DOES NOT

  • Does not read from modGridStore or any Zustand store.
  • Does not import the template registry; callers pass the templates record so the service stays reusable for server-side validation.
  • Does not mutate its input arrays or the supplied templates record.
  • Does not handle drag/drop, animation, snapping, or any UI concern.
  • Does not persist or load snapshots — that responsibility lives in modGridStore.
  • Does not compute rotation — template.cells is consumed as-is.

Signals

None. The module is signal-free: callers invoke functions and use the returned values directly.

Entry points

  • canPlace(placed, templates, template, col, row, gridCols, gridRows, ignoreUid?) — validates a prospective placement.
  • computeOccupancy(placed, templates, gridCols, gridRows) — builds a boolean grid for UI highlighting.
  • computeEquippedStatBonus(mods, templates) — total stat bonus from a set of equipped mods.
  • statsForInstance(mod, templates) — per-instance rarity-multiplied stats.
  • Re-exports the types ModStatKey, ModInstance, ModRarity for downstream convenience.

Pattern notes

  • Two divergent missing-template policies coexist by design: canPlace and computeOccupancy and statsForInstance throw on missing templates because they describe an in-progress state that must be coherent; computeEquippedStatBonus silently skips unknown templateIds to tolerate inventories carrying mods whose templates were deleted in a prior patch, matching modGridStore::loadFromSnapshot.
  • Cell masks are the authoritative shape source. Bounds and overlap checks iterate template.cells and skip falsy entries so non-rectangular shapes (L/T/S/Z) correctly leave their empty corners free.
  • Occupancy uses [row][col] indexing while placement and fill helpers consume (col, row) arguments — the asymmetry is preserved at the boundary; callers reading the matrix must use grid[row][col].
  • Stat aggregation iterates Object.entries(tpl.stats) and guards typeof v === 'number', defending against future non-numeric stat fields.
  • ignoreUid on canPlace exists to let the caller validate a “move” by ignoring the mover’s current footprint.
  • The fillSet cell key is the literal string ${col},${row} — keep that format if any caller ever needs to match against it externally.