Worldshaper/docs/index.html

1810 lines
76 KiB
HTML
Raw Normal View History

2026-06-26 18:18:14 -04:00
<!doctype html>
<html lang="en" data-theme="azure">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TES:VIII Map Editor Wiki</title>
<style>
:root {
color-scheme: dark;
--editor-shell-bg: #0A1020;
--editor-shell-fg: #D8E8FF;
--editor-menu-grad-1: #152645;
--editor-menu-grad-2: #10203C;
--editor-sidebar-bg: #0E1A33;
--editor-stage-bg: #060A14;
--editor-border: #2E426C;
--editor-border-strong: #3C5E95;
--editor-panel-bg: #121F3B;
--editor-panel-bg-alt: #132B4F;
--editor-panel-bg-elevated: #10284B;
--editor-panel-bg-hover: #1A3F6D;
--editor-control-bg: #1A345E;
--editor-control-bg-hover: #214679;
--editor-control-bg-active: #1E4B82;
--editor-control-border: #3C5E95;
--editor-control-fg: #D6E7FF;
--editor-muted: #9FB8E5;
--editor-muted-strong: #CFE2FF;
--editor-accent: #64AAF8;
--editor-accent-strong: #8FD0FF;
--editor-accent-soft: #22466E;
--editor-warn: #FFD166;
--editor-danger: #3C1A1A;
--editor-danger-border: #7F4C4C;
--editor-danger-hover: #5A2323;
--editor-preview-bg: #0D1B34;
--editor-drop-line: #64AAF8;
--editor-drop-shadow: rgba(100, 170, 248, 0.3);
--editor-status-ok: #B9CFEF;
--editor-status-error: #FF9E9E;
--editor-tooltip-shadow: rgba(0, 0, 20, 0.8);
--editor-tab-shadow: rgba(3, 8, 18, 0.8);
}
html[data-theme="verdant"] {
--editor-shell-bg: #081510;
--editor-shell-fg: #DDF7EA;
--editor-menu-grad-1: #16352B;
--editor-menu-grad-2: #0E251E;
--editor-sidebar-bg: #0D221B;
--editor-stage-bg: #06100D;
--editor-border: #305847;
--editor-border-strong: #46806A;
--editor-panel-bg: #122A22;
--editor-panel-bg-alt: #17362C;
--editor-panel-bg-elevated: #133127;
--editor-panel-bg-hover: #21503F;
--editor-control-bg: #1B4335;
--editor-control-bg-hover: #245844;
--editor-control-bg-active: #2D7258;
--editor-control-border: #46806A;
--editor-control-fg: #DDF7EA;
--editor-muted: #A5D2BE;
--editor-muted-strong: #D2F0E0;
--editor-accent: #70D8A6;
--editor-accent-strong: #8AE8BE;
--editor-accent-soft: #275845;
--editor-warn: #F5D66D;
--editor-danger: #472123;
--editor-danger-border: #8B5559;
--editor-danger-hover: #633034;
--editor-preview-bg: #10271F;
--editor-drop-line: #70D8A6;
--editor-drop-shadow: rgba(112, 216, 166, 0.3);
--editor-status-ok: #C0E9D5;
--editor-status-error: #FFADAD;
--editor-tooltip-shadow: rgba(0, 12, 8, 0.78);
--editor-tab-shadow: rgba(2, 10, 8, 0.76);
}
html[data-theme="ember"] {
--editor-shell-bg: #160C0B;
--editor-shell-fg: #FFE8D9;
--editor-menu-grad-1: #432018;
--editor-menu-grad-2: #24110F;
--editor-sidebar-bg: #21110E;
--editor-stage-bg: #100706;
--editor-border: #6A3B33;
--editor-border-strong: #9A5A4D;
--editor-panel-bg: #311A16;
--editor-panel-bg-alt: #3F211B;
--editor-panel-bg-elevated: #341B17;
--editor-panel-bg-hover: #5A2E26;
--editor-control-bg: #4A261F;
--editor-control-bg-hover: #67352B;
--editor-control-bg-active: #8B4937;
--editor-control-border: #9A5A4D;
--editor-control-fg: #FFE8D9;
--editor-muted: #E2B6A2;
--editor-muted-strong: #FFE0CF;
--editor-accent: #FFB36C;
--editor-accent-strong: #FFD08E;
--editor-accent-soft: #684133;
--editor-warn: #FFE17A;
--editor-danger: #512225;
--editor-danger-border: #A16063;
--editor-danger-hover: #6A2E32;
--editor-preview-bg: #281311;
--editor-drop-line: #FFB36C;
--editor-drop-shadow: rgba(255, 179, 108, 0.32);
--editor-status-ok: #F6C8AF;
--editor-status-error: #FFB1A3;
--editor-tooltip-shadow: rgba(20, 6, 0, 0.76);
--editor-tab-shadow: rgba(16, 6, 2, 0.76);
}
html[data-theme="amethyst"] {
--editor-shell-bg: #0F0B19;
--editor-shell-fg: #F0E6FF;
--editor-menu-grad-1: #2A1F45;
--editor-menu-grad-2: #171125;
--editor-sidebar-bg: #17112A;
--editor-stage-bg: #0A0712;
--editor-border: #4E4474;
--editor-border-strong: #7662A9;
--editor-panel-bg: #211A39;
--editor-panel-bg-alt: #2A2149;
--editor-panel-bg-elevated: #241D41;
--editor-panel-bg-hover: #3B3066;
--editor-control-bg: #35295D;
--editor-control-bg-hover: #473678;
--editor-control-bg-active: #5B4594;
--editor-control-border: #7662A9;
--editor-control-fg: #F0E6FF;
--editor-muted: #C6B3E6;
--editor-muted-strong: #E5D9FF;
--editor-accent: #C38BFF;
--editor-accent-strong: #DDB5FF;
--editor-accent-soft: #493C72;
--editor-warn: #F7D37E;
--editor-danger: #4A213F;
--editor-danger-border: #935C8A;
--editor-danger-hover: #632D56;
--editor-preview-bg: #1A1430;
--editor-drop-line: #C38BFF;
--editor-drop-shadow: rgba(195, 139, 255, 0.32);
--editor-status-ok: #D9C5FF;
--editor-status-error: #FFB6DE;
--editor-tooltip-shadow: rgba(10, 4, 24, 0.8);
--editor-tab-shadow: rgba(10, 6, 20, 0.78);
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
width: 100%;
height: 100%;
background: var(--editor-shell-bg);
color: var(--editor-shell-fg);
font-family: Segoe UI, Arial, sans-serif;
}
body {
overflow: hidden;
}
a {
color: var(--editor-accent-strong);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code, pre {
font-family: Consolas, "Courier New", monospace;
}
.shell {
display: grid;
grid-template-rows: 40px 1fr;
width: 100vw;
height: 100vh;
}
.menu-bar {
width: 100%;
height: 40px;
min-height: 40px;
max-height: 40px;
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-bottom: 1px solid var(--editor-border);
background: linear-gradient(180deg, var(--editor-menu-grad-1) 0%, var(--editor-menu-grad-2) 100%);
overflow: hidden;
user-select: none;
}
.menu-btn {
height: 30px;
padding: 0 12px;
border: 1px solid var(--editor-control-border);
border-radius: 8px;
background: var(--editor-control-bg);
color: var(--editor-control-fg);
font-size: 12px;
font-weight: 700;
cursor: pointer;
white-space: nowrap;
}
.menu-btn:hover {
background: var(--editor-control-bg-hover);
}
.menu-bar-center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
pointer-events: none;
}
.menu-bar-center > * {
pointer-events: auto;
}
.menu-title {
color: var(--editor-muted-strong);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.menu-bar-right {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.theme-preset-bar {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 2px;
}
.theme-preset-btn {
width: 40px;
height: 40px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.theme-preset-swatch {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid var(--editor-control-border);
background:
linear-gradient(135deg,
var(--theme-swatch-a) 0%,
var(--theme-swatch-a) 50%,
var(--theme-swatch-b) 50%,
var(--theme-swatch-b) 100%);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
position: relative;
transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
}
.theme-preset-swatch::before,
.theme-preset-swatch::after {
content: "";
position: absolute;
inset: 0;
border-radius: 8px;
pointer-events: none;
}
.theme-preset-swatch::before {
background:
linear-gradient(135deg, transparent 0 50%, var(--theme-swatch-c) 50% 75%, transparent 75% 100%);
opacity: 0.95;
}
.theme-preset-swatch::after {
background:
linear-gradient(315deg, transparent 0 52%, var(--theme-swatch-d) 52% 100%);
opacity: 0.9;
}
.theme-preset-btn.active .theme-preset-swatch,
.theme-preset-btn:hover .theme-preset-swatch {
transform: scale(1.14);
border-color: var(--editor-accent);
box-shadow: 0 0 0 1px var(--editor-accent), inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.menu-status {
color: var(--editor-status-ok);
font-size: 12px;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.body {
min-height: 0;
display: grid;
grid-template-columns: 325px 1fr;
}
.sidebar {
min-height: 0;
border-right: 1px solid var(--editor-border);
background: var(--editor-sidebar-bg);
display: flex;
flex-direction: column;
padding: 10px;
overflow-y: scroll;
overflow-x: hidden;
position: relative;
}
.sidebar-tabs {
display: grid;
gap: 6px;
margin-bottom: 10px;
position: sticky;
top: 0;
z-index: 30;
background: var(--editor-sidebar-bg);
border: 1px solid var(--editor-border);
border-radius: 10px;
padding: 6px;
box-shadow: 0 8px 14px var(--editor-tab-shadow);
isolation: isolate;
}
.sidebar-tabs::before {
content: "";
position: absolute;
inset: -1px;
background: var(--editor-sidebar-bg);
border-radius: 10px;
z-index: -1;
}
.sidebar-tab-row {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
}
.sidebar-tab-btn {
height: 28px;
width: 100%;
padding: 0 8px;
border: 1px solid var(--editor-control-border);
border-radius: 7px;
background: var(--editor-control-bg);
color: var(--editor-control-fg);
font-size: 12px;
line-height: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.sidebar-tab-btn.active {
background: var(--editor-control-bg-active);
border-color: var(--editor-accent);
color: var(--editor-shell-fg);
}
.sidebar-copy {
display: grid;
gap: 10px;
}
.sidebar-card,
.wiki-panel,
.wiki-card,
.callout,
.modal-card {
border: 1px solid var(--editor-border);
border-radius: 8px;
background: var(--editor-panel-bg);
color: var(--editor-shell-fg);
}
.sidebar-card {
padding: 10px;
display: grid;
gap: 8px;
}
.eyebrow {
margin: 0;
color: var(--editor-accent-strong);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.sidebar-title {
margin: 0;
font-size: 18px;
line-height: 1.2;
}
.sidebar-text,
.muted {
margin: 0;
color: var(--editor-muted);
font-size: 12px;
line-height: 1.5;
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.chip {
border: 1px solid var(--editor-control-border);
border-radius: 999px;
background: var(--editor-panel-bg-alt);
color: var(--editor-muted-strong);
font-size: 11px;
padding: 4px 8px;
white-space: nowrap;
}
.sidebar-actions {
display: grid;
gap: 6px;
}
.stage {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
background: var(--editor-stage-bg);
}
.meta {
height: 28px;
display: flex;
align-items: center;
gap: 10px;
padding: 0 10px;
border-bottom: 1px solid var(--editor-border);
font-size: 12px;
color: var(--editor-muted);
flex-shrink: 0;
}
.meta-main {
min-width: 0;
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.meta-stats {
margin-left: auto;
color: var(--editor-muted-strong);
white-space: nowrap;
}
.wiki-scroll {
min-height: 0;
flex: 1 1 auto;
overflow-y: auto;
padding: 14px;
}
.wiki-view {
display: none;
gap: 12px;
}
.wiki-view.active {
display: grid;
}
.wiki-panel {
padding: 14px;
display: grid;
gap: 10px;
}
.wiki-panel-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding-bottom: 8px;
}
.wiki-panel h1,
.wiki-panel h2,
.wiki-panel h3 {
margin: 0;
line-height: 1.2;
}
.wiki-panel h1 { font-size: 26px; }
.wiki-panel h2 { font-size: 18px; }
.wiki-panel h3 { font-size: 14px; }
.wiki-panel p,
.wiki-panel li,
.wiki-panel td,
.wiki-panel th {
font-size: 13px;
line-height: 1.55;
}
.wiki-grid {
display: grid;
gap: 12px;
}
.wiki-grid.two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.wiki-grid.three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.wiki-card {
padding: 12px;
display: grid;
gap: 8px;
background: linear-gradient(180deg, var(--editor-panel-bg) 0%, var(--editor-panel-bg-elevated) 100%);
}
.wiki-card-title {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--editor-muted-strong);
}
.wiki-card p,
.wiki-card ul,
.wiki-card ol {
margin: 0;
}
.callout {
padding: 10px 12px;
background: var(--editor-panel-bg-alt);
}
.callout.warn {
border-color: var(--editor-warn);
background: var(--editor-accent-soft);
}
.callout.good {
border-color: var(--editor-accent);
}
.details-list {
display: grid;
gap: 8px;
}
details {
border: 1px solid var(--editor-border);
border-radius: 8px;
background: var(--editor-panel-bg-elevated);
overflow: hidden;
}
summary {
cursor: pointer;
padding: 10px 12px;
font-size: 13px;
font-weight: 700;
color: var(--editor-muted-strong);
background: rgba(255, 255, 255, 0.02);
}
details > div {
padding: 0 12px 12px;
display: grid;
gap: 8px;
}
.docs-table {
width: 100%;
border-collapse: collapse;
border: 1px solid var(--editor-border);
background: var(--editor-panel-bg-elevated);
}
.docs-table th,
.docs-table td {
border: 1px solid var(--editor-border);
padding: 8px 9px;
text-align: left;
vertical-align: top;
}
.docs-table th {
color: var(--editor-muted-strong);
background: rgba(255, 255, 255, 0.03);
font-weight: 700;
}
.code-block {
margin: 0;
padding: 12px;
border: 1px solid var(--editor-border);
border-radius: 8px;
background: var(--editor-preview-bg);
color: var(--editor-shell-fg);
overflow: auto;
font-size: 12px;
line-height: 1.55;
white-space: pre;
}
.tree {
white-space: pre-wrap;
font-size: 12px;
line-height: 1.55;
}
.flowchart-shell {
padding: 12px;
border: 1px solid var(--editor-border);
border-radius: 8px;
background: var(--editor-preview-bg);
overflow: auto;
}
.flowchart {
display: block;
width: 100%;
min-width: 880px;
height: auto;
}
.footer-note {
font-size: 12px;
color: var(--editor-muted);
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(4, 10, 20, 0.7);
display: grid;
place-items: center;
padding: 24px;
z-index: 80;
}
.modal-backdrop.hidden {
display: none;
}
.modal-card {
width: min(900px, 100%);
max-height: min(82vh, 900px);
overflow: auto;
padding: 14px;
box-shadow: 0 16px 40px var(--editor-tooltip-shadow);
background: linear-gradient(180deg, var(--editor-panel-bg) 0%, var(--editor-panel-bg-elevated) 100%);
display: grid;
gap: 12px;
}
.modal-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
@media (max-width: 1080px) {
.body {
grid-template-columns: 1fr;
}
.sidebar {
border-right: none;
border-bottom: 1px solid var(--editor-border);
max-height: 42vh;
}
.wiki-grid.two,
.wiki-grid.three {
grid-template-columns: 1fr;
}
}
@media (max-width: 760px) {
.menu-bar-center {
display: none;
}
.menu-bar {
gap: 6px;
}
.menu-bar-right {
gap: 6px;
}
.theme-preset-bar {
gap: 4px;
}
.theme-preset-btn {
width: 32px;
height: 32px;
}
.theme-preset-swatch {
width: 24px;
height: 24px;
}
.sidebar-tab-row {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body>
<div class="shell">
<div class="menu-bar">
<button class="menu-btn" type="button" onclick="window.location.href='/'">Main Editor</button>
<button class="menu-btn" id="quickRefBtn" type="button">Quick Ref</button>
<button class="menu-btn" type="button" onclick="window.location.href='/wiki-assets/world-chunk-manual.html'">World Chunk Manual</button>
<button class="menu-btn" type="button" onclick="window.location.href='/wiki-assets/dialogue-builder.html'">Dialogue Builder</button>
<div class="menu-bar-center">
<span class="menu-title">TES:VIII Map Editor Wiki</span>
</div>
<div class="menu-bar-right">
<div class="theme-preset-bar" role="group" aria-label="Wiki theme presets">
<button class="theme-preset-btn active" data-theme="azure" type="button" title="Azure theme" aria-label="Switch to Azure theme">
<span class="theme-preset-swatch" style="--theme-swatch-a:#17325d; --theme-swatch-b:#244c84; --theme-swatch-c:#7fd1ff; --theme-swatch-d:#10203c;"></span>
</button>
<button class="theme-preset-btn" data-theme="verdant" type="button" title="Verdant theme" aria-label="Switch to Verdant theme">
<span class="theme-preset-swatch" style="--theme-swatch-a:#17372E; --theme-swatch-b:#285B4A; --theme-swatch-c:#7CE0AF; --theme-swatch-d:#0E251E;"></span>
</button>
<button class="theme-preset-btn" data-theme="ember" type="button" title="Ember theme" aria-label="Switch to Ember theme">
<span class="theme-preset-swatch" style="--theme-swatch-a:#4F231C; --theme-swatch-b:#87412F; --theme-swatch-c:#FFB36C; --theme-swatch-d:#24110F;"></span>
</button>
<button class="theme-preset-btn" data-theme="amethyst" type="button" title="Amethyst theme" aria-label="Switch to Amethyst theme">
<span class="theme-preset-swatch" style="--theme-swatch-a:#342456; --theme-swatch-b:#5A3B8A; --theme-swatch-c:#D3A8FF; --theme-swatch-d:#171125;"></span>
</button>
</div>
<span class="menu-status">/wiki static manual</span>
</div>
</div>
<div class="body">
<aside class="sidebar">
<div class="sidebar-tabs">
<div class="sidebar-tab-row">
<button class="sidebar-tab-btn active" data-view-target="overview" type="button">Overview</button>
<button class="sidebar-tab-btn" data-view-target="workflows" type="button">How-To</button>
</div>
<div class="sidebar-tab-row">
<button class="sidebar-tab-btn" data-view-target="features" type="button">Features</button>
<button class="sidebar-tab-btn" data-view-target="systems" type="button">Systems</button>
</div>
<div class="sidebar-tab-row">
<button class="sidebar-tab-btn" data-view-target="api" type="button">API + Storage</button>
<button class="sidebar-tab-btn" data-view-target="troubleshooting" type="button">Fixes</button>
</div>
<div class="sidebar-tab-row">
<button class="sidebar-tab-btn" data-view-target="future" type="button">Future</button>
<button class="sidebar-tab-btn" type="button" onclick="document.getElementById('quickRefBtn').click()">Modal Ref</button>
</div>
</div>
<div class="sidebar-copy">
<div class="sidebar-card">
<p class="eyebrow">Standalone App</p>
<h1 class="sidebar-title">Map Editor Manual</h1>
<p class="sidebar-text">
This wiki documents the popup map editor as it exists today: its daily workflows,
runtime systems, save flow, file layout, communication contract, and where it should go next.
</p>
<div class="chip-row">
<span class="chip">Menu Bar</span>
<span class="chip">Tools</span>
<span class="chip">Canvas</span>
<span class="chip">Warm Minimap</span>
<span class="chip">Per-map History</span>
</div>
</div>
<div class="sidebar-card">
<p class="eyebrow">What This Covers</p>
<p class="muted">- Launch and save lifecycle</p>
<p class="muted">- Layer, tile, and instance authoring</p>
<p class="muted">- Performance and rendering strategy</p>
<p class="muted">- API endpoints, postMessage events, and file layout</p>
<p class="muted">- Roadmap ideas that would push the tool much further</p>
</div>
<div class="sidebar-card">
<p class="eyebrow">Current Reality</p>
<p class="muted">
Triggers, Paths, and Transitions already have tool panels and folder UI,
but they are still prototype surfaces. The documentation below calls those out clearly
so the manual stays aligned with the actual tool instead of promising hidden behavior.
</p>
</div>
</div>
</aside>
<section class="stage">
<div class="meta">
<div class="meta-main" id="metaMain">Overview - purpose, terminology, lifecycle, and the core editing model.</div>
<div class="meta-stats" id="metaStats">Served from <code>/wiki</code></div>
</div>
<div class="wiki-scroll">
<article class="wiki-view active" data-view="overview" data-meta="Overview - purpose, terminology, lifecycle, and the core editing model.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<div>
<p class="eyebrow">Map Editor</p>
<h1>What This Tool Is</h1>
</div>
<div class="chip-row">
<span class="chip">Popup editor</span>
<span class="chip">Theme-aware</span>
<span class="chip">JSON-backed</span>
</div>
</div>
<p>
The map editor is a standalone popup app launched from the main content editor. It is not a thin
form field sitting on top of map JSON. It owns its own state, controls, render loop, history stack,
and save workflow, then syncs its results back into the host editor through API writes and
postMessage events.
</p>
<div class="wiki-grid three">
<div class="wiki-card">
<p class="wiki-card-title">Menu Bar</p>
<p>Undo, redo, save, quick layer selection, and theme switching live here. This is the stable command layer.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Tools</p>
<p>The left tool panel hosts Information, Maps, History, Instances, Tiles, Layers, and prototype placement tabs.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Canvas</p>
<p>The main viewport is a tile-grid world with snapping, drag placement, context menus, zoom, pan, selection, and the minimap drawer.</p>
</div>
</div>
<div class="callout good">
The editor is intentionally moving toward becoming the main map-authoring platform. It already has enough local behavior,
persistence, and rendering independence to be treated as a real app rather than a popup form.
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Terminology and Mental Model</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Naming</p>
<ul>
<li><strong>Menu Bar</strong> - the top command strip.</li>
<li><strong>Tools</strong> - the left-side panel with tabs and lists.</li>
<li><strong>Canvas</strong> - the world viewport where tiles and instances are edited.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Authored entities</p>
<ul>
<li><strong>Tile</strong> - a sprite-backed paintable map cell resource.</li>
<li><strong>Template</strong> - a reusable instance source used like a stamp.</li>
<li><strong>Instance</strong> - a placed or unplaced map-local entity created from a template by value, not by live linkage.</li>
</ul>
</div>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Layer numbering</p>
<p>
Internal layer <code>0</code> is the anchored <strong>Background</strong>. The first user-facing non-background
paint layer is displayed as <strong>Layer 0</strong>, because its internal id is <code>1</code>.
</p>
<p>
In short: Background is special, anchored at the bottom, and every other displayed layer name is offset by one.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Ownership boundary</p>
<ul>
<li>The <strong>host editor</strong> owns dataset loading, popup launch, and background refresh after save.</li>
<li>The <strong>popup editor</strong> owns editing state, rendering, history, drag logic, and the final composed save payload.</li>
</ul>
</div>
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Core Principles</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Edit local, save explicit</p>
<p>The popup mutates local runtime state first. Nothing is persisted until Save writes maps and NPCs back through the API.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">History is per map</p>
<p>Undo and redo belong to the active map, not the whole app. Branches are truncated if you edit after undoing.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Templates are stamps</p>
<p>Selecting a template keeps stamp mode active so repeated clicks place new instances. A created instance is then independent.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Rendering is viewport-first</p>
<p>The editor draws only what the current viewport needs, then uses cached preview surfaces for pan, zoom, scroll, and the minimap.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Background is optimized</p>
<p>The editor can compress fully implicit background rows so huge maps do not waste JSON storing the same map-wide fill repeatedly.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Prototype tabs are honest</p>
<p>Monsters, Triggers, Paths, and Transitions already share selector and folder UI, but placement logic is intentionally not claimed yet.</p>
</div>
</div>
</section>
</article>
<article class="wiki-view" data-view="workflows" data-meta="How-To - launch, paint, place, save, import, and navigate inside the popup editor.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>Daily Workflows</h1>
</div>
<p>
This section is written as direct operating procedure. If you want to use the tool instead of study it,
start here.
</p>
<div class="details-list">
<details open>
<summary>Open the editor for a map</summary>
<div>
<ol>
<li>Open the main content editor and select <strong>Maps</strong>.</li>
<li>Select the map record you want to edit.</li>
<li>Use the dedicated launch button to open the popup editor.</li>
<li>The host assembles bootstrap data, opens <code>map-editor-popup.html?token=...</code>, and hands the popup a full in-memory startup package.</li>
</ol>
</div>
</details>
<details open>
<summary>Paint tiles</summary>
<div>
<ol>
<li>Go to <strong>Tiles</strong> in Tools.</li>
<li>Select a tile brush entry from the palette list.</li>
<li>Choose your active layer from the Layers tab or the menu-bar layer selector.</li>
<li>Click-drag on the canvas to paint.</li>
<li>Use <code>Alt + Drag</code> to erase on the active layer.</li>
<li>Use <code>L Shift + Drag</code> to line-lock after leaving the origin tile.</li>
<li>Use <code>L Ctrl + Drag</code> for a rectangle outline, or <code>R Ctrl + Drag</code> for a circle outline.</li>
</ol>
</div>
</details>
<details>
<summary>Fill the background layer</summary>
<div>
<ol>
<li>Switch to <strong>Background</strong> or set the current editable layer so drawing resolves to the background layer.</li>
<li>Right-click a tile in the tile palette.</li>
<li>Choose <strong>Fill Background</strong>.</li>
<li>The editor stores the background tile id at map level and can compress fully implicit background rows on save.</li>
</ol>
<p class="muted">Roomwide fill is intentionally restricted to Background.</p>
</div>
</details>
<details>
<summary>Place instances from templates</summary>
<div>
<ol>
<li>Open <strong>Instances</strong>.</li>
<li>In the <strong>Templates</strong> section, select the template you want.</li>
<li>Click the canvas to stamp a new instance.</li>
<li>The template remains active so each click keeps creating a fresh instance.</li>
<li>Each created instance copies template values by value and is no longer live-linked to the template record.</li>
</ol>
</div>
</details>
<details>
<summary>Select, center, and place an instance record</summary>
<div>
<ol>
<li>In the <strong>Instances</strong> list, click an existing instance.</li>
<li>If it is already placed, the camera recenters toward it.</li>
<li>If it is unplaced, the editor enters ghost placement mode and shows the silhouette under the cursor.</li>
<li>Click the canvas to drop it with grid snapping.</li>
</ol>
<p class="muted">Placeholder instances can exist without placement. That is part of the authoring model, not a bug.</p>
</div>
</details>
<details>
<summary>Reorder layers</summary>
<div>
<ol>
<li>Open <strong>Layers</strong>.</li>
<li>Drag a non-background layer by its handle.</li>
<li>Dropping changes its draw depth and remaps tile and instance layer references accordingly.</li>
<li>The background layer is anchored and cannot be dragged below or above other layers.</li>
</ol>
</div>
</details>
<details>
<summary>Use folders in selector lists</summary>
<div>
<ol>
<li>Use the folder button at the top of a supported panel.</li>
<li>Create folders for Tiles, Templates, Instances, Monsters, Triggers, Paths, or Transitions.</li>
<li>Drag selectors into a folder to group them.</li>
<li>Drag them back out to return them to the root of that panel.</li>
</ol>
<p class="muted">Folder data is editor UI persistence only. It changes organization, not gameplay payloads.</p>
</div>
</details>
<details>
<summary>Navigate the room quickly</summary>
<div>
<ul>
<li><code>MMB + Drag</code> pans the room.</li>
<li><code>Ctrl + Wheel</code> zooms around the pointer anchor.</li>
<li>The minimap drawer provides click-to-center navigation and a live viewport rectangle.</li>
<li>Dragging an instance near the viewport edge auto-pans the camera in that direction.</li>
<li><code>R Shift</code> temporarily hides the grid so you can inspect the runtime-like composition.</li>
</ul>
</div>
</details>
<details>
<summary>Import sprites or tiles from another editor build</summary>
<div>
<ol>
<li>Open <strong>Information</strong>.</li>
<li>Expand <strong>Experimental Imports</strong>.</li>
<li>Import from file, or open the JSON paste modal with the writing-pad button.</li>
<li>The import pipeline accepts a single entry or a whole compatible gallery payload.</li>
<li>Known resources are deduped by normalized dimensions, pixel scale, and row content signature.</li>
</ol>
</div>
</details>
<details open>
<summary>Save without surprises</summary>
<div>
<ol>
<li>Use Save in the menu bar when the save button is enabled.</li>
<li>The popup writes <code>maps</code> first, then <code>npcs</code>.</li>
<li>After success it posts <code>map-editor-saved</code> to the opener so the host editor can refresh quietly.</li>
<li>If you undo, then make a new edit, the impossible future branch is discarded and history continues from the new point.</li>
</ol>
</div>
</details>
</div>
</section>
</article>
<article class="wiki-view" data-view="features" data-meta="Features - every user-facing system, what it does, and how it behaves.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>Feature Reference</h1>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Menu Bar</p>
<ul>
<li>Undo and Redo are bound to toolbar buttons and <code>Ctrl+Z</code> / <code>Ctrl+Y</code>.</li>
<li>Save reflects dirty history state and is disabled when nothing changed or a save is running.</li>
<li>The centered layer selector mirrors the Layers tab and stays in sync with it.</li>
<li>Theme preset buttons apply editor-wide palette swaps through <code>/api/editor-settings</code>.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Information Tab</p>
<ul>
<li>Locked map id.</li>
<li>Editable map name.</li>
<li>Width and height with explicit apply/cancel controls.</li>
<li>Map background color and background brush mode.</li>
<li>Experimental sprite/tile import tools.</li>
<li>In-editor controls reference and footer links.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Maps Tab</p>
<p>
Switch maps, create maps, and delete maps. Switching away with unsaved changes prompts first.
Creating a new map seeds a background layer and a first editable layer.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Layers Tab</p>
<ul>
<li>All Layers mode for draw-depth inspection.</li>
<li>Visibility toggle per layer.</li>
<li>Layer reordering for non-background layers.</li>
<li>Context-menu rename support.</li>
<li>Background layer anchored at the bottom.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Tiles Tab</p>
<ul>
<li>Sprite-backed tile selector list instead of simple swatches.</li>
<li>Right-click actions: select tile, fill background, replace on current layer, inspect id/symbol.</li>
<li>Transparency honors <code>.</code> as no-color data.</li>
<li>Selection reticle scales with grid size.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Instances Tab</p>
<ul>
<li>Templates stamp fresh records repeatedly.</li>
<li>Placed instances recenter the camera when selected.</li>
<li>Unplaced instances enter ghost placement mode.</li>
<li>Placeholder markers use a clashing multi-color orb so they remain visible on mixed backgrounds.</li>
</ul>
</div>
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Canvas Interaction Set</h2>
</div>
<div class="wiki-grid three">
<div class="wiki-card">
<p class="wiki-card-title">Selection</p>
<p>Tile and instance selection uses a reusable reticle with directional markers so selected cells read clearly across different grid sizes.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Dragging</p>
<p>Instance dragging snaps to the grid, previews the destination, and now auto-pans near the viewport edges so long repositioning feels continuous.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Context Menus</p>
<p>The reusable right-click panel can be attached across the editor. It already powers layer actions, tile actions, and canvas entity actions.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Minimap Drawer</p>
<p>The minimap is a docked drawer with a live maintained surface. Opening it reveals current state immediately rather than taking a fresh snapshot first.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Hotkey Cursor Feedback</p>
<p>Shift, Alt, and inspect modes swap the cursor so the canvas communicates line draw, erase, and no-grid inspection states without needing extra text.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Warm Preview Modes</p>
<p>Zoom, drag-pan, and wheel-scroll all use cached low-res preview frames before a sharper redraw lands, which keeps movement feeling much smoother.</p>
</div>
</div>
<div class="callout warn">
Prototype panels for Monsters, Triggers, Paths, and Transitions already reuse selector, folder, and panel framing. They are scaffolding for future map-local authoring, not finished gameplay editors yet.
</div>
</section>
</article>
<article class="wiki-view" data-view="systems" data-meta="Systems - internal architecture, render strategy, history model, imports, and persistence rules.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>Technical Systems</h1>
</div>
<table class="docs-table">
<thead>
<tr>
<th>File</th>
<th>Role</th>
<th>What It Owns</th>
</tr>
</thead>
<tbody>
<tr><td><code>src/components/MapEditorPanels.tsx</code></td><td>Host bridge</td><td>Launches the popup, assembles bootstrap payload, handles save/open postMessage events, and persists popup bounds.</td></tr>
<tr><td><code>src/mapEditorPopup/bootstrap.ts</code></td><td>Popup handoff</td><td>Token generation, opener registry, sessionStorage fallback, and bootstrap retrieval.</td></tr>
<tr><td><code>src/mapEditorPopup/main.ts</code></td><td>Popup boot</td><td>Loads the bootstrap, applies editor theme settings, injects popup HTML/CSS, and starts the runtime.</td></tr>
<tr><td><code>src/mapEditorPopup/runtime.ts</code></td><td>State root</td><td>Global editor state, DOM lookup, layer helpers, data catalogs, and controller wiring.</td></tr>
<tr><td><code>src/mapEditorPopup/renderController.ts</code></td><td>Render loop</td><td>Viewport drawing, tile surface cache, minimap surface, preview frames, overlay drawing, and meta telemetry.</td></tr>
<tr><td><code>src/mapEditorPopup/interactionController.ts</code></td><td>Input system</td><td>Mouse, wheel, keyboard, paint strokes, shape tools, drag logic, auto-pan, and context menu triggers.</td></tr>
<tr><td><code>src/mapEditorPopup/sidebarController.ts</code></td><td>Tools UI</td><td>Tab switching, layer list, information panel logic, palette lists, folder rendering, and inline status text.</td></tr>
<tr><td><code>src/mapEditorPopup/npcController.ts</code></td><td>Instance semantics</td><td>Template assignment, instance centering, placeholder handling, sprite binding, and instance selection behavior.</td></tr>
<tr><td><code>src/mapEditorPopup/historyController.ts</code></td><td>Undo/redo engine</td><td>State capture, branch truncation, persistence, restore, toolbar dirty state, and preview diffs.</td></tr>
<tr><td><code>src/mapEditorPopup/persistenceController.ts</code></td><td>Save pipeline</td><td>Payload rebuild, map compression rules, dual-save ordering, and host notification after save.</td></tr>
<tr><td><code>src/mapEditorPopup/importController.ts</code></td><td>Resource import</td><td>Sprite/tile import normalization, dedupe signatures, JSON modal import, and content save for imported art.</td></tr>
</tbody>
</table>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Rendering Strategy</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Viewport-local canvas</p>
<p>
The main canvas only sizes itself to the current viewport, while a spacer tracks total world dimensions.
This keeps actual draw cost tied to what the user can see rather than full map size.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Tile surface cache</p>
<p>
<code>tileSurfaceCanvas</code> stores the current visible tile result. Painting can patch single cells instead
of forcing a full layer redraw every time.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Frame preview cache</p>
<p>
<code>framePreviewCanvas</code> is a cached snapshot of the viewport used during pan, wheel scroll,
and zoom-preview motion so interaction stays smooth before the sharp redraw completes.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Warm minimap surface</p>
<p>
<code>minimapSurfaceCanvas</code> is maintained continuously in the background. Opening the drawer reveals
current state instantly, and tile edits patch the minimap instead of waking it from scratch.
</p>
</div>
</div>
<div class="callout">
The rendering pipeline is intentionally doing the least honest work possible per frame: draw only the current viewport,
reuse cached surfaces while moving, patch individual cells when feasible, and reserve full refreshes for bigger invalidations.
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>State and History Model</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Captured state</p>
<p>History snapshots include map dimensions, map name, background color, background tile id, room layers, tile instances, NPC overlays, and editor UI folder layout state.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Branch behavior</p>
<p>If you undo and then perform a new edit, all future states beyond the current point are discarded. The new action becomes the forward branch.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Per-map storage</p>
<p>History persistence is scoped to the active map through a map-specific localStorage key, so switching maps does not smear history across rooms.</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Save awareness</p>
<p>The toolbar compares the current history id to the last saved history id. That is what drives dirty-state messaging and save enablement.</p>
</div>
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Data Models That Matter</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Templates vs instances</p>
<p>
Templates are reusable creation sources. Selecting a template is a stamp tool. Once an instance is created, it copies the template values
it needs and becomes a separate record. This is deliberate so authored rooms do not rewire themselves unexpectedly when a template changes.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Tile identity</p>
<p>
Tile placement is stored by tile id, not just visible symbol. Symbol compatibility still exists, but the authoritative authored resource is the tile record id.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Background compression</p>
<p>
If the map uses a background tile id and the background layer is fully implicit, the save pipeline can store empty background rows and reconstruct them from map metadata.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Folder persistence</p>
<p>
Panel folder layouts are saved under <code>editorUi.panelLayouts</code>. They affect selector presentation only and do not change runtime gameplay data.
</p>
</div>
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Import Pipeline</h2>
</div>
<p>
Experimental imports accept either a single compatible record or a full gallery payload from another build of this editor.
The import controller normalizes width, height, pixelScale, and row data before signature comparison.
</p>
<table class="docs-table">
<thead>
<tr>
<th>Step</th>
<th>What happens</th>
</tr>
</thead>
<tbody>
<tr><td>Normalize</td><td>Rows are padded, width/height are inferred or clamped, and records with no valid pixel content are rejected.</td></tr>
<tr><td>Signature</td><td>The editor builds a deterministic signature from width, height, pixelScale, and serialized rows.</td></tr>
<tr><td>Deduplicate</td><td>Existing signatures and same-batch signatures are skipped.</td></tr>
<tr><td>Generate ids</td><td>New sprite ids or tile ids are generated. Imported tiles also receive the next free tile symbol.</td></tr>
<tr><td>Persist</td><td>The updated <code>sprites</code> or <code>tiles</code> payload is posted to the same content API used elsewhere in the app.</td></tr>
</tbody>
</table>
</section>
</article>
<article class="wiki-view" data-view="api" data-meta="API + Storage - popup launch flow, endpoints, postMessage contract, and on-disk file layout.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>API and Communication Flow</h1>
</div>
<p>
The map editor is a cross-window system. The host editor launches it, the popup owns editing, the API persists data,
and postMessage closes the loop for save and map-switch events.
</p>
<div class="flowchart-shell">
<svg class="flowchart" viewBox="0 0 1120 620" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Map editor API and communication flow">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#64AAF8"></polygon>
</marker>
</defs>
<rect x="40" y="40" width="240" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
<text x="60" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Main editor shell</text>
<text x="60" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">MapEditorPanels.tsx</text>
<rect x="340" y="40" width="250" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
<text x="360" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Bootstrap handoff</text>
<text x="360" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">token + opener registry + sessionStorage fallback</text>
<rect x="650" y="40" width="250" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
<text x="670" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Popup startup</text>
<text x="670" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">main.ts -> runtime.ts</text>
<rect x="340" y="190" width="250" height="96" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
<text x="360" y="220" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Local edit loop</text>
<text x="360" y="248" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">tiles, instances, folders, history, minimap</text>
<text x="360" y="268" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">render + interaction + sidebar controllers</text>
<rect x="650" y="190" width="250" height="96" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
<text x="670" y="220" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Save pipeline</text>
<text x="670" y="248" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">saveCurrentState()</text>
<text x="670" y="268" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">POST maps, then POST npcs</text>
<rect x="650" y="360" width="250" height="120" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
<text x="670" y="392" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Express API</text>
<text x="670" y="420" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/content/maps</text>
<text x="670" y="440" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/content/npcs</text>
<text x="670" y="460" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/editor-settings</text>
<rect x="40" y="360" width="240" height="120" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
<text x="60" y="392" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">On-disk content</text>
<text x="60" y="420" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/maps.json</text>
<text x="60" y="440" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/maps/&lt;mapId&gt;/...</text>
<text x="60" y="460" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/npcs.json</text>
<rect x="340" y="520" width="250" height="60" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
<text x="360" y="555" fill="#D8E8FF" font-size="18" font-family="Segoe UI, Arial">postMessage back to host</text>
<line x1="280" y1="83" x2="340" y2="83" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="590" y1="83" x2="650" y2="83" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="465" y1="126" x2="465" y2="190" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="590" y1="238" x2="650" y2="238" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="775" y1="286" x2="775" y2="360" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="650" y1="420" x2="280" y2="420" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="650" y1="480" x2="590" y2="520" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="340" y1="550" x2="160" y2="480" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
<line x1="160" y1="360" x2="160" y2="126" stroke="#64AAF8" stroke-width="3" stroke-dasharray="8 8" marker-end="url(#arrow)" />
<text x="182" y="242" fill="#9FB8E5" font-size="13" font-family="Segoe UI, Arial">refresh maps + npc data</text>
</svg>
</div>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Endpoints and Messages</h2>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Important GET endpoints</p>
<table class="docs-table">
<thead>
<tr><th>Endpoint</th><th>Used for</th></tr>
</thead>
<tbody>
<tr><td><code>/api/content/maps</code></td><td>Host editor loads and refreshes map records.</td></tr>
<tr><td><code>/api/content/npcs</code></td><td>Host editor loads NPC instances and templates needed by the popup.</td></tr>
<tr><td><code>/api/content/tiles</code></td><td>Tile resource catalog.</td></tr>
<tr><td><code>/api/content/sprites</code></td><td>Sprite resource catalog used for previews and overlays.</td></tr>
<tr><td><code>/api/editor-settings</code></td><td>Theme preset load for the popup editor.</td></tr>
<tr><td><code>/api/images</code></td><td>UI image slug catalog for small editor icons.</td></tr>
</tbody>
</table>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Important POST endpoints</p>
<table class="docs-table">
<thead>
<tr><th>Endpoint</th><th>Used for</th></tr>
</thead>
<tbody>
<tr><td><code>/api/content/maps</code></td><td>Persist the rebuilt map payload and per-map storage files.</td></tr>
<tr><td><code>/api/content/npcs</code></td><td>Persist map-local NPC instances and compatibility data.</td></tr>
<tr><td><code>/api/content/tiles</code></td><td>Persist imported or edited tile resources.</td></tr>
<tr><td><code>/api/content/sprites</code></td><td>Persist imported sprite resources.</td></tr>
<tr><td><code>/api/editor-settings</code></td><td>Persist selected editor theme preset.</td></tr>
</tbody>
</table>
</div>
</div>
<table class="docs-table">
<thead>
<tr>
<th>Message</th>
<th>Sender</th>
<th>Receiver</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr><td><code>map-editor-saved</code></td><td>Popup</td><td>Main editor</td><td>Triggers a quiet refresh of map, NPC, and template background data after save.</td></tr>
<tr><td><code>map-editor-open-map</code></td><td>Popup</td><td>Main editor</td><td>Requests that the host reload and reopen a different map record.</td></tr>
</tbody>
</table>
</section>
<section class="wiki-panel">
<div class="wiki-panel-header">
<h2>Storage Layout</h2>
</div>
<pre class="code-block tree">content/
maps.json
npcs.json
npc_templates.json
sprites.json
tiles.json
maps/
&lt;mapId&gt;/
tiles.json
layer_0.json
layer_1.json
...
instances.json
</pre>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Why split map files exist</p>
<p>
The split per-map layout keeps large rooms scalable, makes layer files addressable on their own,
and sets up cleaner future systems for chunking, streaming, and non-tile authoring data.
</p>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Compatibility mirrors</p>
<p>
The server still composes payloads into the shapes older editor flows expect. That lets the storage model evolve
without forcing every existing authoring surface to change at once.
</p>
</div>
</div>
<div class="callout warn">
If save looks successful in the UI but files do not change where you expect, check the actual content root on the server.
This project supports a writable local content folder beside <code>server.js</code> and can also be overridden by environment configuration.
</div>
</section>
</article>
<article class="wiki-view" data-view="troubleshooting" data-meta="Fixes - common failure modes, save issues, content-root problems, and performance notes.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>Troubleshooting</h1>
</div>
<div class="details-list">
<details open>
<summary>Save appears to work but data is missing after reload</summary>
<div>
<ul>
<li>Check the actual content root being written by the server, not just the route that served the page.</li>
<li>Confirm the host page and the popup are pointing at the same API base.</li>
<li>Inspect <code>/api/content/maps</code> and <code>/api/content/npcs</code> responses if needed.</li>
</ul>
</div>
</details>
<details>
<summary>Popup opens but content or previews look wrong</summary>
<div>
<ul>
<li>Verify sprite ids and tile ids exist in their catalogs.</li>
<li>Check case-sensitive image paths on Linux or VPS deployments.</li>
<li>Make sure the popup received a valid bootstrap token and did not fall back to an empty opener state.</li>
</ul>
</div>
</details>
<details>
<summary>Background fill is unavailable</summary>
<div>
<ul>
<li>Roomwide fill belongs to the Background layer only.</li>
<li>If you are on a non-background layer, the context action is intentionally disabled.</li>
</ul>
</div>
</details>
<details>
<summary>Large rooms feel sluggish</summary>
<div>
<ul>
<li>The editor already uses viewport-local rendering, surface caching, and preview frames.</li>
<li>Very large rooms still cost more when many distinct tiles are visible at once.</li>
<li>Best future wins are chunk-aware tile surfaces, coarser minimap sampling on giant maps, and brush batching tuned for extremely dense edits.</li>
</ul>
</div>
</details>
<details>
<summary>History feels odd after undo</summary>
<div>
<p>
This is usually intentional. If you undo and then make a different edit, the old future branch is removed.
The history system is branch-truncating by design.
</p>
</div>
</details>
<details>
<summary>Selector folders are not affecting runtime data</summary>
<div>
<p>
Correct. Folder layout lives in editor UI state so authors can organize selectors without mutating gameplay data contracts.
</p>
</div>
</details>
</div>
</section>
</article>
<article class="wiki-view" data-view="future" data-meta="Future - practical next steps and moonshot ideas that would push the project much further.">
<section class="wiki-panel">
<div class="wiki-panel-header">
<h1>Future - What Pushes This Project To The Moon</h1>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Authoring power</p>
<ul>
<li>Brush presets and saved tool loadouts.</li>
<li>Tile stamp prefabs and multi-tile pattern brushes.</li>
<li>Selection transform tools for copy, move, rotate, mirror, and flood replace.</li>
<li>Real trigger, path, and transition placement layers with visual handles on the canvas.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Runtime confidence</p>
<ul>
<li>Live runtime preview mode that uses the same asset and draw rules as the game.</li>
<li>Validation overlays for missing sprite ids, orphaned references, and impossible layer combinations.</li>
<li>One-click audit reports for map-local dependencies.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Performance ceiling</p>
<ul>
<li>Chunked tile surfaces instead of a single viewport tile cache.</li>
<li>Background surface baking with selective dirty regions.</li>
<li>Multi-resolution minimap sampling for giant rooms.</li>
<li>Optional worker-backed serialization and save prep for very heavy rooms.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">World scale</p>
<ul>
<li>Map-to-map travel graph editing.</li>
<li>Biome and region metadata surfaces.</li>
<li>Cross-map search for instances, tile ids, and scripted references.</li>
<li>World atlas view that treats maps as navigable nodes instead of isolated records.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Editor maturity</p>
<ul>
<li>Graduating the popup into the primary map platform while the older shell becomes a host and archive tool.</li>
<li>Shared command palette, unified modal system, and global settings panel.</li>
<li>More visual inline documentation surfaced directly inside the editor where decisions happen.</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Moonshot ideas</p>
<ul>
<li>Collaborative sessions with author locks and merge-safe map diffs.</li>
<li>Rule-driven procedural placement helpers that still keep authored intent visible.</li>
<li>Playback scrubbing for event layers once triggers and transitions go live.</li>
</ul>
</div>
</div>
<div class="callout good">
The biggest strategic move is still the same one the editor has already started: treat the map editor like the main product.
It already has the strongest identity, the richest interaction model, and the clearest path to becoming the center of world authoring.
</div>
</section>
</article>
</div>
</section>
</div>
</div>
<div class="modal-backdrop hidden" id="quickRefModal">
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="quickRefTitle">
<div class="modal-head">
<div>
<p class="eyebrow">Quick Ref</p>
<h2 id="quickRefTitle">Map Editor Fast Reference</h2>
</div>
<button class="menu-btn" id="closeQuickRefBtn" type="button">Close</button>
</div>
<div class="wiki-grid two">
<div class="wiki-card">
<p class="wiki-card-title">Hotkeys</p>
<ul>
<li><code>Ctrl + Wheel</code> - zoom canvas</li>
<li><code>MMB + Drag</code> - pan room view</li>
<li><code>Alt + Drag</code> - erase on active layer</li>
<li><code>L Shift + Drag</code> - straight line draw</li>
<li><code>L Ctrl + Drag</code> - rectangle outline</li>
<li><code>R Ctrl + Drag</code> - circle outline</li>
<li><code>R Shift</code> - hide grid while held</li>
<li><code>Escape</code> - clear canvas selection / collapse cells</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Messages + writes</p>
<ul>
<li><code>map-editor-saved</code> - popup tells host save is complete</li>
<li><code>map-editor-open-map</code> - popup asks host to reopen another map</li>
<li><code>POST /api/content/maps</code> - map payload and split map files</li>
<li><code>POST /api/content/npcs</code> - map-local NPC instance payload</li>
<li><code>POST /api/editor-settings</code> - theme preset persistence</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Files</p>
<ul>
<li><code>content/maps.json</code> - map index</li>
<li><code>content/maps/&lt;mapId&gt;/tiles.json</code> - base tile payload</li>
<li><code>content/maps/&lt;mapId&gt;/layer_&lt;n&gt;.json</code> - room layers</li>
<li><code>content/maps/&lt;mapId&gt;/instances.json</code> - map-local entities</li>
</ul>
</div>
<div class="wiki-card">
<p class="wiki-card-title">Current prototypes</p>
<ul>
<li>Monsters panel - selector UI scaffold</li>
<li>Triggers panel - selector UI scaffold</li>
<li>Paths panel - selector UI scaffold</li>
<li>Transitions panel - selector UI scaffold</li>
</ul>
</div>
</div>
<div class="modal-actions">
<button class="menu-btn" type="button" onclick="window.location.href='/'">Open Main Editor</button>
<button class="menu-btn" id="closeQuickRefBtnFooter" type="button">Done</button>
</div>
</div>
</div>
<script>
(function () {
const THEME_KEY = "tes8-map-editor-wiki-theme";
const metaMain = document.getElementById("metaMain");
const quickRefModal = document.getElementById("quickRefModal");
const quickRefBtn = document.getElementById("quickRefBtn");
const closeQuickRefBtn = document.getElementById("closeQuickRefBtn");
const closeQuickRefBtnFooter = document.getElementById("closeQuickRefBtnFooter");
const viewButtons = Array.from(document.querySelectorAll("[data-view-target]"));
const views = Array.from(document.querySelectorAll(".wiki-view"));
const themeButtons = Array.from(document.querySelectorAll(".theme-preset-btn"));
function applyTheme(themeId) {
const nextTheme = String(themeId || "azure");
document.documentElement.setAttribute("data-theme", nextTheme);
themeButtons.forEach((button) => {
const isActive = button.getAttribute("data-theme") === nextTheme;
button.classList.toggle("active", isActive);
button.setAttribute("aria-pressed", isActive ? "true" : "false");
});
try {
window.localStorage.setItem(THEME_KEY, nextTheme);
} catch (_err) {}
}
function setView(viewId) {
const nextView = String(viewId || "overview");
viewButtons.forEach((button) => {
button.classList.toggle("active", button.getAttribute("data-view-target") === nextView);
});
views.forEach((view) => {
const isActive = view.getAttribute("data-view") === nextView;
view.classList.toggle("active", isActive);
if (isActive && metaMain) {
metaMain.textContent = view.getAttribute("data-meta") || "Map editor documentation";
}
});
}
function openQuickRef() {
quickRefModal.classList.remove("hidden");
}
function closeQuickRef() {
quickRefModal.classList.add("hidden");
}
viewButtons.forEach((button) => {
button.addEventListener("click", () => {
setView(button.getAttribute("data-view-target"));
});
});
themeButtons.forEach((button) => {
button.addEventListener("click", () => {
applyTheme(button.getAttribute("data-theme"));
});
});
quickRefBtn.addEventListener("click", openQuickRef);
closeQuickRefBtn.addEventListener("click", closeQuickRef);
closeQuickRefBtnFooter.addEventListener("click", closeQuickRef);
quickRefModal.addEventListener("click", (event) => {
if (event.target === quickRefModal) {
closeQuickRef();
}
});
window.addEventListener("keydown", (event) => {
if (event.key === "Escape" && !quickRefModal.classList.contains("hidden")) {
closeQuickRef();
}
});
let initialTheme = "azure";
try {
initialTheme = window.localStorage.getItem(THEME_KEY) || "azure";
} catch (_err) {}
applyTheme(initialTheme);
setView("overview");
})();
</script>
</body>
</html>