Coastline analysis and fix: - Coastlines now only show 2-edge patterns (straight, wide curve, sharp bend) — Y-junctions and crossroads removed since a coast always divides exactly two sides - Added waterSide property to HexFeature (1=CW, -1=CCW) — stored explicitly instead of computed by broken center-distance heuristic that flipped water/land at rotation midpoint - F key or "Flip water side" button in palette to toggle which side is water before placing - Inspector shows flip button (swap arrows) for placed coastlines - Rotation preserves waterSide — no more land/water swaps - Separate pattern lists: GENERAL_PATTERNS for road/river, COASTLINE_PATTERNS for coastline (core/tile-patterns.ts) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
2.6 KiB
TypeScript
113 lines
2.6 KiB
TypeScript
/**
|
|
* Canonical tile patterns for linear features.
|
|
* Each pattern has a base edge mask and can be rotated in 60° increments.
|
|
* Coastlines only use 2-edge patterns and have an additional waterSide property.
|
|
*/
|
|
|
|
import type { EdgeMask } from './types.js';
|
|
import { HexEdge } from './types.js';
|
|
import { edgeMask, rotateMask } from './edge-connectivity.js';
|
|
|
|
export interface TilePattern {
|
|
id: string;
|
|
name: string;
|
|
baseMask: EdgeMask;
|
|
/** Max distinct rotations (accounting for symmetry) */
|
|
rotations: number;
|
|
/** Number of connected edges */
|
|
edgeCount: number;
|
|
}
|
|
|
|
/** General patterns for road/river (all edge counts) */
|
|
export const GENERAL_PATTERNS: TilePattern[] = [
|
|
{
|
|
id: 'dead-end',
|
|
name: 'Dead End',
|
|
baseMask: edgeMask(HexEdge.E),
|
|
rotations: 6,
|
|
edgeCount: 1,
|
|
},
|
|
{
|
|
id: 'straight',
|
|
name: 'Straight',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.W),
|
|
rotations: 3,
|
|
edgeCount: 2,
|
|
},
|
|
{
|
|
id: 'gentle-curve',
|
|
name: 'Wide Curve',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.SW),
|
|
rotations: 6,
|
|
edgeCount: 2,
|
|
},
|
|
{
|
|
id: 'sharp-bend',
|
|
name: 'Sharp Bend',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.SE),
|
|
rotations: 6,
|
|
edgeCount: 2,
|
|
},
|
|
{
|
|
id: 'y-junction',
|
|
name: 'Y Split',
|
|
baseMask: edgeMask(HexEdge.NE, HexEdge.SE, HexEdge.W),
|
|
rotations: 6,
|
|
edgeCount: 3,
|
|
},
|
|
{
|
|
id: 'y-junction-wide',
|
|
name: 'Y Wide',
|
|
baseMask: edgeMask(HexEdge.NE, HexEdge.SW, HexEdge.SE),
|
|
rotations: 6,
|
|
edgeCount: 3,
|
|
},
|
|
{
|
|
id: 'crossroads',
|
|
name: 'Cross',
|
|
baseMask: edgeMask(HexEdge.NE, HexEdge.E, HexEdge.SW, HexEdge.W),
|
|
rotations: 3,
|
|
edgeCount: 4,
|
|
},
|
|
];
|
|
|
|
/** Coastline patterns — only 2-edge (a coast is always a line from A to B) */
|
|
export const COASTLINE_PATTERNS: TilePattern[] = [
|
|
{
|
|
id: 'straight',
|
|
name: 'Straight',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.W),
|
|
rotations: 3,
|
|
edgeCount: 2,
|
|
},
|
|
{
|
|
id: 'gentle-curve',
|
|
name: 'Wide Curve',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.SW),
|
|
rotations: 6,
|
|
edgeCount: 2,
|
|
},
|
|
{
|
|
id: 'sharp-bend',
|
|
name: 'Sharp Bend',
|
|
baseMask: edgeMask(HexEdge.E, HexEdge.SE),
|
|
rotations: 6,
|
|
edgeCount: 2,
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Get the appropriate patterns for a terrain type.
|
|
*/
|
|
export function getPatternsForTerrain(terrainId: string): TilePattern[] {
|
|
if (terrainId === 'coastline') return COASTLINE_PATTERNS;
|
|
return GENERAL_PATTERNS;
|
|
}
|
|
|
|
/**
|
|
* Rotate a mask by N steps (each step = 60° clockwise).
|
|
*/
|
|
export function rotatePattern(mask: EdgeMask, steps: number): EdgeMask {
|
|
return rotateMask(mask, steps);
|
|
}
|