Hexamapper Workbench is a browser-based hexcrawl map editor built for the VTT tile packs in assets/.
It is designed for fast map painting, layered composition, and clean export while preserving hex art that overhangs its tile boundaries.
- Paints hex maps with
base,overlay,marker, andfoglayers - Supports
brush,erase,fill,line,select, andlabel/fogworkflows - Renders in PixiJS with row-aware depth sorting (lower map rows draw on top)
- Exports flattened PNG maps
- Saves/loads
.hexamap.jsonproject files - Keeps local autosave state in
localStorage
- React 19 + TypeScript
- Zustand + Immer for editor state/history
- PixiJS 8 for map rendering
- Vite for local dev/build
- Vitest + Playwright (smoke)
npm install
npm run devOpen the local URL printed by Vite.
npm run devstarts the Vite dev server (auto-generates asset manifest first)npm run buildtype-checks and builds production output (also regenerates manifest)npm run previewserves the production build locallynpm run lintruns ESLintnpm run testruns Vitestnpm run test:e2eruns the Playwright smoke testnpm run generate:manifestrebuildssrc/data/assetManifest.generated.ts
BbrushEeraseFfillVselectLlabel toolGfog tool
Ctrl/Cmd + ZundoCtrl/Cmd + Shift + Zredo
Space + dragpanMiddle click dragpanRight click dragpanMouse wheelzoom
Hexes are stored as axial coordinates (q,r), but map bounds are interpreted as a rectangular offset-row footprint.
Why this matters:
- The map shape appears as a rectangle instead of a visual parallelogram
- Bounds checks use an offset-row transform
- Iteration across bounds uses offset rows and converts back to axial coordinates
Relevant implementation lives in:
src/domain/hexMath.ts(isWithinBounds,iterateBounds,boundsPixelEnvelope)
Many assets extend outside the strict hex area (especially upward). To preserve depth cues, draw order is row-aware:
- Lower on-screen rows render above higher rows
- Layer priority (
base->overlay->marker-> labels) is included in z-index tie-breaking
Relevant implementation:
src/renderer/PixiMapCanvas.tsxsrc/lib/exportPng.ts
The app reads PNGs from the base and extended VTT packs and generates a typed manifest:
- Source script:
scripts/generate-asset-manifest.mjs - Generated file:
src/data/assetManifest.generated.ts
Do not hand-edit generated manifest output.
Projects are saved as .hexamap.json and include:
versionmetadata(name, timestamps, bounds)calibration(hex size, origin, frame, sprite anchor, nudge)cellskeyed as"q,r"
Validation/normalization is handled by src/domain/projectSchema.ts.
npm run lint
npm run test
npm run buildOptional smoke test:
npm run test:e2eNote: Playwright may require browser installation in a fresh environment.
Clear autosave and reload:
localStorage.removeItem("hexamapper.autosave.v1")Rebuild the manifest:
npm run generate:manifestThis is expected right now because Pixi and asset-heavy flows are bundled together.
assets/is intentionally large and included in the repo- Debug screenshots and Playwright artifacts are ignored by
.gitignore - Windows
:Zone.Identifiersidecar files are ignored by.gitignore