Home Map Table Power Editor

Map Table — power-user segment editor

Related: 21 — World content schemas, 23 — Harvest & resources, 37 — Economic map zones, 38 — Biomes, content-admin World → Map layout.


1. Problem

The canvas Map layout tab is the right tool for spatial work (paint terrain, place pins, drag poses). As segments grow (hundreds–thousands of resource nodes, wildlife anchors, NPC placements, zone rects), editing one entity at a time in the side panel does not scale.

Designers need a spreadsheet-style view over the same MapLayout draft: filter, multi-select, inline column edits, and bulk apply — without leaving the Map tab’s save/publish/validate flow.


2. Goals

Goal Meaning
Same data, same draft Table reads/writes draftLayout in mountMap — not a second API or Monaco escape hatch
Category-first One entity family per table (resources, wildlife, …) with columns tuned to that family
Power-user speed Keyboard navigation, multi-select, bulk toolbar, virtualized rows for large segments
Labels over IDs Primary columns show label / catalog display_name; id is secondary or pinned column
Validation parity Inline hints for rules the sim already enforces (e.g. loot_table requires harvest_node template) before Publish
Canvas ↔ table sync Select rows → highlight/focus on map; map selection → scroll table to row

Non-goals (v1)

  • Replacing the canvas or Monaco segment tab
  • Real-time multiplayer editing
  • Server-side query API (client filters in-memory layout)
  • Interior blueprint table (follow-up; reuse same grid component)

3. UX placement

Add a view mode toggle on the Map tab toolbar (next to Select / Paint):

[ Canvas ] [ Table ]
  • Canvas — current behavior (unchanged default).
  • Table — full-width grid below toolbar; canvas hidden or minimized to a preview strip (optional phase 2: split pane 30% map / 70% table).

Persist last mode in map_prefs (viewMode: "canvas" | "table").

Within Table view, category tabs (horizontal):

Tab MapLayout source Priority
Resources resource_nodes[] P0 — largest pain
Wildlife wildlife_spawns[] P1
Interactables interactables[] P1
Map NPCs npc_placements[] P1
Spawns spawn_points[] P2
Buildings structures[] P2
Doors derived from structures[].doors (flat rows) P2
Terrain zones terrain_zones[] P3
Tax / Property / Growth / Biome respective *_zones[] P3

Row count badge on each tab (e.g. Resources (1,247)).


4. Shared table shell

New modules (keep map.ts from growing further):

Module Responsibility
web/src/map_table/types.ts Row ids, column defs, category enum
web/src/map_table/rows.ts Pure functions: layout → row DTOs, apply patch → layout
web/src/map_table/validate_row.ts Client-side checks mirroring flatland_sim content validation for editable fields
web/src/map_table/MapTableView.tsx React island: toolbar, tabs, grid, bulk bar
web/src/map_table/columns/*.tsx Per-category column sets
web/src/map_table/filters.ts Search + structured filters

Mount via existing island pattern (mountMap calls mountMapTable when view is Table), same as mountMapNpcAttachments.

4.1 Grid behavior

  • Virtualized body (@tanstack/react-virtual or lightweight custom) — required for starter-plains scale.
  • Sticky header + optional sticky first columns (, id).
  • Row select: click row, Shift+click range, ⌘/Ctrl+click toggle; header checkbox = all filtered rows.
  • Inline edit: click cell → control (input, select, checkbox). Commit on blur / Enter; Escape reverts.
  • Tab / Shift+Tab moves across editable cells in row order (spreadsheet habit).
  • Dirty: any cell commit calls existing markDirty() + onDirty().

4.2 Toolbar (per category)

Control Behavior
Search Case-insensitive match on id, label, template/npc_ref, coords string
Filters Dropdowns: item template, loot table, blocking, locked, “validation errors only”
Spatial (resources) “In growth zone …”, “In tax zone …” using existing zoneAtCell / econ helpers
Columns Show/hide optional columns (pose, crop_tags, tile_id) — persist in map_prefs
Bulk actions Apply field to selection (see §5)
Export CSV Current filter + visible columns
Import CSV Merge by id (update) or append new rows with generated ids — P2
Jump to map Focus canvas selection on first selected row

4.3 Bulk edit bar

When ≥1 row selected, show a compact bar:

N selected · [Field ▾] [Value …] [Apply] · Delete · Duplicate · Lock · Unlock

Apply writes the same patch to every selected row (with undo snapshot — see §7).

Fields bulk-editable should match what the side panel already supports per kind (reuse bindEntityPanel field names where possible).


5. Column sets (most relevant first)

5.1 Resources (ResourceNodeDef)

Column Edit Notes
Selection
Label text Primary display
Template itemTemplateSelect + isWorldResourceNodeTemplate Show catalog display_name
X, Y, Z number (0.5 step) Bulk nudge ±1m actions
Loot table catalog select Required — rolls harvest drops
Qty min / max number Hidden when loot table set (or show muted)
Harvest / respawn ticks number
Blocking checkbox
Block radius number
Draw scale number Optional column
Yaw ° number Optional; store radians in layout
Crop tags chip editor Reuse multiChipField patterns
Lock toggle editor_locks via lockKey
Id text Secondary; warn on rename (migrate lock key)
icon Row validation tooltip

Row validation (client):

  • Unknown item_template / loot_table
  • loot_table set but template not harvest_node (same message as sim)
  • Missing or unknown loot_table
  • Duplicate cell occupancy (two resources same rounded cell) — warning not hard fail

5.2 Wildlife (WildlifeSpawnDef)

Label (id), NPC ref (combo + display name), X/Y, radius, count, boundary radius, movement mode, respawn overrides.

5.3 Interactables

Kind, label, board_id (quest boards), X/Y/Z.

5.4 Map NPC placements

npc_ref, schedule ref if present, exterior coords, building link fields from NpcPlacement.

5.5 Spawns

label, X/Y/Z (index-based id in table: spawn#3).

5.6 Buildings / doors

Buildings: label, x/y, width/depth, tags, storage fields.
Doors: flat table with building_id, door id, offset coords, link to building row.

5.7 Zones (terrain + overlays)

id, label/kind, z_order, rect count, first rect x0..y1, tile/kind/econ fields — bulk kind/tile already exists for terrain multi-select on canvas; reuse renderBulkTerrainPanel logic in table bulk apply.


6. Integration with existing Map tab

flowchart LR subgraph draft [Single draft] L[MapLayout draftLayout] end Canvas[Canvas view map.ts] Table[Table view MapTableView] Panel[Side panel map_panels.ts] Save[putMapLayout + validate] L --> Canvas L --> Table L --> Panel Canvas -->|selection sync| Table Table -->|selection sync| Canvas Panel --> L Table --> L L --> Save
  • Selection model: extend or mirror selections: SelectionItem[] so table row click calls setSelMulti with { kind: "resource", id }.
  • Undo: push JSON snapshot to undoStack on bulk apply (same as canvas mutations).
  • New rows: “+ Add row” duplicates defaults from map paint (defaultLootTableForResourceTemplate, template from paintResourceTemplate pref).
  • Delete: selected rows → same paths as Del key on canvas.

7. Implementation phases

Phase 0 — scaffolding (1 PR)

  • View toggle Canvas / Table + prefs
  • Empty table shell with Resources tab only, read-only columns, virtualization, search on label/id
  • Selection sync to map (select row → set selection; no inline edit yet)

Phase 1 — Resources power edit (1–2 PRs) MVP

  • Inline edit all P0 resource columns
  • Multi-select + bulk apply (template, loot_table, ticks, blocking, delete)
  • Client validation column
  • npm run build in content-admin/web

Phase 2 — scale & export

  • CSV export/import for resources
  • Spatial filters (growth/tax zone)
  • Column show/hide persistence
  • Performance budget: 5k rows scroll at 60fps on M-series laptop

Phase 3 — other categories

  • Wildlife, interactables, map NPCs, spawns (same grid component, new column defs)
  • Tab badges + category-specific bulk actions

Phase 4 — structures & zones

  • Buildings, doors, terrain/econ/biome zone tables
  • Reuse bulk terrain kind/tile from zone-bulk form handler

Phase 5 — polish

  • Split pane map+table
  • flatland-admin map table export mirroring CSV (optional CLI)
  • Short designer doc in tools/content-admin/README.md + docs/ link

8. Technical choices

Topic Recommendation
UI framework React island inside Map tab (consistent with Items, Sprites, DataRowTable)
Grid TanStack Table + Virtual or lightweight custom <table> with virtual rows — avoid heavy AG Grid dependency unless needed
Styling Extend styles.css .data-table / .table-wrap; sticky header patterns from perf-table
Catalog Existing api.catalog() — template labels, loot ids, npc ids
No new REST Table is a view over in-memory layout; save path unchanged
Interiors Later: InteriorBlueprint.resource_nodes / wildlife using same column defs with room_id column

9. Content-admin checklist (definition of done)


10. Risks

Risk Mitigation
map.ts size Table logic only in map_table/*; thin mount hook
Undo complexity for bulk Single undo entry per bulk apply
ID renames break quests/routes Show confirm + grep hint; prefer label edits
CSV import corrupts layout Dry-run preview + validate before merge
Locked rows Bulk skip locked unless “include locked” override

11. Open questions (defaults if unanswered)

  1. Split pane vs full table first? → Full table first (Phase 0–1), split pane Phase 5.
  2. Edit segment Monaco YAML in parallel? → Allowed but Table does not auto-reload until Cancel/segment change; document refresh behavior.
  3. Interior placements? → Phase 5+; same component, different source array.