Status: Implemented; production configuration rollout remains operator-reviewed. Date: 2026-10-21
Support exact irregular map regions while retaining simple single-tile references and all existing rectangle documents. Location checks, helper task checks, and door occupancy checks must agree on membership.
This is a cross-cutting geometry change, not a new observation authority, pathfinder, or door permission system.
Related documentation:
TopLeft and BottomRight positions.x/y point coordinates and optional region.
Map runtime gives the region precedence
when both exist.doorX/doorY position and an optional, nullable
insideRegion used for occupant-aware timed closure.core/.No new dependencies, environment variables, collections, per-tile documents, or BC packets are required.
Introduce a small core geometry module containing readonly discriminated types
for points, rectangles, and tile regions, plus a pure
containsTile(geometry, position) operation.
Use a core-owned tile position type rather than importing BcMapPosition into
core. Adapt persisted shapes at the feature boundary. BC positions can satisfy
the core coordinate contract structurally.
Keep persistence validation in the map model/repository boundary. Do not make core depend on a feature’s Zod schema or MongoDB types. Share pure shape invariants where necessary instead of maintaining a second handwritten rectangle guard in the admin service.
Point locations keep their existing fields:
{ "x": 20, "y": 10 }
Existing rectangle values in region and insideRegion remain valid:
{
"TopLeft": { "X": 20, "Y": 10 },
"BottomRight": { "X": 25, "Y": 13 }
}
Add this alternative value for those same region fields:
{
"kind": "tiles",
"rows": [
{ "y": 10, "spans": [[20, 25]] },
{ "y": 11, "spans": [[20, 25]] },
{ "y": 12, "spans": [[20, 22]] },
{ "y": 13, "spans": [[20, 22]] }
]
}
This example is an L-shaped region. Multiple spans in a row represent gaps; omitting enclosed tiles represents a hole. A one-tile region is one row with one span whose start equals its end.
The public JSON marker is kind: "tiles"; row spans are its storage encoding,
not a separate geometry type. Runtime normalization gives legacy rectangles an
internal discriminator without rewriting their stored representation.
Start with direct row/span lookup: the map is only 1,600 tiles. Do not add a global geometry cache. If profiling later justifies compiled membership sets, scope them to a configuration revision and replace them on reload.
| Surface | Required change |
|---|---|
| Map model and repository | Validate both region forms on reads and writes; preserve legacy normalization, document limits, room scoping, and reload errors. |
| Map runtime | Replace rectangle-only checks with shared containment for location transitions and door insideRegion. |
| Helper task executor | Use the same location-to-geometry conversion and core containment; preserve fresh observation and task authorization requirements. |
| Location administration | Add optional region fields to draft/read/edit contracts; region: null clears a region on edit. Include geometry in find() results. |
| World location commands | Add explicit region create/edit/clear support using existing command/session patterns; retain room-admin checks, input limits, and private replies. Do not require pasting a 1,600-tile payload into chat. |
| Door administration | Accept both region forms for insideRegion through the authoritative validator; preserve existing null/removal behavior. |
| Display/help | Show shape, tile count, and bounds; label bounds as a summary, not membership. Retain bounded whisper output. |
| Location-event consumers | Keep the existing point/region event classification. A one-tile region remains a region. Verify casino/shop and other access gates still revoke access on exits. |
| Offline analysis | Convert reviewed exact cell lists to canonical row spans without altering the source analysis or treating inferred labels as policy. |
| Operator tooling | Add a bounded, dry-run-first manifest import path so large shapes need not travel through chat. Reuse repository validation and isolation guards. |
Preserve region precedence when a location also has x/y. Point coordinates
may remain an explicit destination/anchor; never infer a teleport destination
from a region’s first tile or bounding-box center. A region alone must not
silently become a teleportable point.
Configuration reload must recompute membership using the existing observed positions and emit the corresponding entry/exit changes. Geometry changes must not synthesize fresh BC position evidence, mutate BC snapshots, or relax helper scope/lease checks. Unknown positions remain unknown under existing policies.
Door occupancy continues to use the existing tracked-character policy. An excluded courtyard or adjacent street must not defer closure merely because it falls within the region’s bounding rectangle. The door itself remains one tile; coordinating multi-tile doors is a separate design.
Extend the analysis workflow with an explicit region-candidate export:
cells arrays to row spans and verify round-trip membership.R06 to stable
room-scoped location keys or door keys. Never derive durable identities solely
from flood-fill numbering.--apply.The importer updates only reviewed geometry fields and normal update metadata. It must not infer permissions, keypad codes, unlock durations, enabled state, or which side of a door is inside. No database writes occur in the analyzer itself. Keep production/test mappings exact and do not extend the legacy source-database copy allowlist as part of this change.
No bulk data migration is required. Existing points, rectangles, null door regions, stable location keys, permissions, and action outcomes retain their meaning.
veratown_prototype_test with dedicated accounts.veratown_prototype.Old binaries do not understand tile regions. Before rolling back to them, restore the backed-up documents or remove the new region configuration through a deliberate reviewed change. Never approximate an irregular region with its bounds as a rollback shortcut. Mixed-version deployments must not receive new tile-region documents.
Keep each unit tested and use a PR for changes to core or operator scripts. Update the architecture guide, map/helper guides, command help, analysis guide, root README, and documentation index as their implemented contracts change.
package.json. Run lint, formatting, type-check,
the test suite, and production/API build for runtime source changes.This is a moderate cross-layer change: the membership algorithm is small; configuration validation, consistent authorization, and safe rollout are the larger parts. Analysis export and import are separate deliverables, not reasons to delay shared runtime correctness.
Arbitrary polygon input is deferred. If added later, convert polygons to exact tile membership at configuration time. Define tile-edge coordinates (0-40), tile-center inclusion, boundary behavior, self-intersection rejection, and holes before accepting vertices. Do not reuse the 0-39 tile-position validator for polygon corners.
Also out of scope: visual polygon editors, GeoJSON/geospatial indexes, collision or pathfinding changes, automatic region tracking after map edits, automatic door policy generation, and per-tile database documents.