Fix coastline: 2-edge only, explicit waterSide, flip control
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>
This commit is contained in:
@@ -42,13 +42,14 @@ export class HexMap {
|
||||
}
|
||||
|
||||
/** Add or update a linear feature on a hex */
|
||||
setFeature(coord: AxialCoord, terrainId: string, edgeMask: EdgeMask): void {
|
||||
setFeature(coord: AxialCoord, terrainId: string, edgeMask: EdgeMask, waterSide?: 1 | -1): void {
|
||||
const terrain = this.getTerrain(coord);
|
||||
const existing = terrain.features.find(f => f.terrainId === terrainId);
|
||||
if (existing) {
|
||||
existing.edgeMask = edgeMask;
|
||||
if (waterSide !== undefined) existing.waterSide = waterSide;
|
||||
} else {
|
||||
terrain.features.push({ terrainId, edgeMask });
|
||||
terrain.features.push({ terrainId, edgeMask, ...(waterSide !== undefined ? { waterSide } : {}) });
|
||||
}
|
||||
// Remove features with empty mask
|
||||
terrain.features = terrain.features.filter(f => f.edgeMask !== 0);
|
||||
|
||||
@@ -1,84 +1,107 @@
|
||||
/**
|
||||
* Canonical tile patterns for linear features.
|
||||
* Each pattern is defined by a base edge mask and a human-readable name.
|
||||
* Patterns can be rotated by 60° increments to produce all placements.
|
||||
* 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, edgeCount } from './edge-connectivity.js';
|
||||
import { edgeMask, rotateMask } from './edge-connectivity.js';
|
||||
|
||||
export interface TilePattern {
|
||||
id: string;
|
||||
name: string;
|
||||
baseMask: EdgeMask;
|
||||
/** Number of distinct rotations (accounting for symmetry) */
|
||||
/** Max distinct rotations (accounting for symmetry) */
|
||||
rotations: number;
|
||||
/** Number of connected edges */
|
||||
edgeCount: number;
|
||||
}
|
||||
|
||||
/** All canonical patterns for linear features */
|
||||
export const TILE_PATTERNS: TilePattern[] = [
|
||||
/** 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, // Symmetric: E-W = same as W-E
|
||||
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 all unique rotations for a pattern.
|
||||
* Returns array of { mask, rotation } where rotation is the number of 60° steps.
|
||||
* Get the appropriate patterns for a terrain type.
|
||||
*/
|
||||
export function getPatternRotations(pattern: TilePattern): Array<{ mask: EdgeMask; rotation: number }> {
|
||||
const seen = new Set<EdgeMask>();
|
||||
const result: Array<{ mask: EdgeMask; rotation: number }> = [];
|
||||
|
||||
for (let r = 0; r < 6; r++) {
|
||||
const mask = rotateMask(pattern.baseMask, r);
|
||||
if (!seen.has(mask)) {
|
||||
seen.add(mask);
|
||||
result.push({ mask, rotation: r });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
export function getPatternsForTerrain(terrainId: string): TilePattern[] {
|
||||
if (terrainId === 'coastline') return COASTLINE_PATTERNS;
|
||||
return GENERAL_PATTERNS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,13 @@ export interface TerrainType {
|
||||
export interface HexFeature {
|
||||
terrainId: string;
|
||||
edgeMask: EdgeMask;
|
||||
/**
|
||||
* For coastline only: which side of the curve is water.
|
||||
* 1 = clockwise side (right side looking from first edge to second)
|
||||
* -1 = counter-clockwise side (left side)
|
||||
* Undefined for non-coastline features.
|
||||
*/
|
||||
waterSide?: 1 | -1;
|
||||
}
|
||||
|
||||
/** Complete terrain state for a single hex */
|
||||
|
||||
Reference in New Issue
Block a user