@buley/hexgrid-3d 3.6.0 → 3.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/components/GamePieceRenderer.d.ts +133 -0
- package/dist/components/GamePieceRenderer.d.ts.map +1 -0
- package/dist/components/GamePieceRenderer.js +688 -0
- package/dist/components/GameSphere.d.ts +13 -0
- package/dist/components/GameSphere.d.ts.map +1 -0
- package/dist/components/GameSphere.js +632 -0
- package/dist/components/HexGrid.d.ts.map +1 -1
- package/dist/components/HexGrid.js +20 -65
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/territory/HexTerritoryGlobe.d.ts +18 -0
- package/dist/territory/HexTerritoryGlobe.d.ts.map +1 -0
- package/dist/territory/HexTerritoryGlobe.js +106 -0
- package/dist/territory/globe.d.ts +66 -0
- package/dist/territory/globe.d.ts.map +1 -0
- package/dist/territory/globe.js +180 -0
- package/dist/territory/index.d.ts +4 -0
- package/dist/territory/index.d.ts.map +1 -0
- package/dist/territory/index.js +3 -0
- package/dist/territory/narration.d.ts +12 -0
- package/dist/territory/narration.d.ts.map +1 -0
- package/dist/territory/narration.js +109 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workers/hexgrid-math.d.ts +30 -0
- package/dist/workers/hexgrid-math.d.ts.map +1 -1
- package/dist/workers/hexgrid-math.js +92 -0
- package/package.json +3 -1
|
@@ -10,6 +10,7 @@ import { setCustomAccentColor, clearCustomAccentColor, getCurrentAccentHex, getA
|
|
|
10
10
|
import { decodeHTMLEntities } from '../lib/html-utils';
|
|
11
11
|
import { getProxiedImageUrl } from '../utils/image-utils';
|
|
12
12
|
import { gridItemToPhoto } from '../compat';
|
|
13
|
+
import { generateScreenSpaceSphericalHexGrid, getNormalizedScreenSpaceSphericalCoordinates, } from '../workers/hexgrid-math';
|
|
13
14
|
// Fallback no-op logger at module scope (unused - component-level dlog is used instead)
|
|
14
15
|
// Kept for backward compatibility but renamed to avoid shadowing confusion
|
|
15
16
|
const noopLog = (..._args) => { };
|
|
@@ -1118,7 +1119,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
1118
1119
|
if (hasSignificantCurvature) {
|
|
1119
1120
|
// Use spherical hexagonal grid for compact 3D packing
|
|
1120
1121
|
const densityMultiplier = workerDebug.sphericalDensity ?? 1.4;
|
|
1121
|
-
const result =
|
|
1122
|
+
const result = generateScreenSpaceSphericalHexGrid(totalHexagons, screenWidth, screenHeight, curveUDeg, curveVDeg, densityMultiplier, effectiveHexRadius // Pass the actual hex radius so spacing matches drawing
|
|
1122
1123
|
);
|
|
1123
1124
|
gridMetadataRef.current = result.metadata;
|
|
1124
1125
|
return result.positions;
|
|
@@ -1304,8 +1305,15 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
1304
1305
|
const minY = gridBounds.minY;
|
|
1305
1306
|
const w = gridBounds.width;
|
|
1306
1307
|
const h = gridBounds.height;
|
|
1307
|
-
const
|
|
1308
|
-
const
|
|
1308
|
+
const currentGridMetadata = gridMetadataRef.current;
|
|
1309
|
+
const sphericalGridMetadata = currentGridMetadata.isSpherical
|
|
1310
|
+
? currentGridMetadata
|
|
1311
|
+
: null;
|
|
1312
|
+
const sphericalCoordinates = sphericalGridMetadata && typeof idx === 'number'
|
|
1313
|
+
? getNormalizedScreenSpaceSphericalCoordinates(idx, sphericalGridMetadata, curveUDeg)
|
|
1314
|
+
: null;
|
|
1315
|
+
const u = sphericalCoordinates?.u ?? (x - minX) / Math.max(1e-6, w);
|
|
1316
|
+
const v = sphericalCoordinates?.v ?? (y - minY) / Math.max(1e-6, h);
|
|
1309
1317
|
// Clear, explicit spherical mapping
|
|
1310
1318
|
const deg2rad = Math.PI / 180;
|
|
1311
1319
|
// longitude: center u=0.5 -> lon=0
|
|
@@ -1432,14 +1440,17 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
1432
1440
|
try {
|
|
1433
1441
|
if (typeof idx === 'number' && Array.isArray(hexPositions) && hexPositions.length > idx) {
|
|
1434
1442
|
// Find neighbor indices using grid geometry (pass isSpherical flag)
|
|
1435
|
-
const isSpherical =
|
|
1443
|
+
const isSpherical = sphericalGridMetadata !== null;
|
|
1436
1444
|
const neighborIndices = getNeighbors(idx, hexPositions, drawnHexRadius, isSpherical);
|
|
1437
1445
|
const projNeighbors = [];
|
|
1438
1446
|
for (const ni of neighborIndices) {
|
|
1439
1447
|
const np = hexPositions[ni];
|
|
1440
1448
|
// Map neighbor pixel to lon/lat using same gridBounds mapping
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1449
|
+
const neighborCoordinates = sphericalGridMetadata
|
|
1450
|
+
? getNormalizedScreenSpaceSphericalCoordinates(ni, sphericalGridMetadata, curveUDeg)
|
|
1451
|
+
: null;
|
|
1452
|
+
const nu = neighborCoordinates?.u ?? (np[0] - minX) / Math.max(1e-6, w);
|
|
1453
|
+
const nv = neighborCoordinates?.v ?? (np[1] - minY) / Math.max(1e-6, h);
|
|
1443
1454
|
const nlon = (nu - 0.5) * (curveUDeg * deg2rad);
|
|
1444
1455
|
const nlat = (nv - 0.5) * (curveVDeg * deg2rad);
|
|
1445
1456
|
const cosLatN = Math.cos(nlat);
|
|
@@ -2855,7 +2866,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2855
2866
|
// Use the same projection helper as hit-tests so angles/positions match exactly.
|
|
2856
2867
|
if (dbg?.renderBothSides) {
|
|
2857
2868
|
try {
|
|
2858
|
-
const anti = mapAndProject(hexPositions[index], true);
|
|
2869
|
+
const anti = mapAndProject(hexPositions[index], true, index);
|
|
2859
2870
|
const antiScale = anti.scale || 1;
|
|
2860
2871
|
const antiAngle = anti.angle || 0;
|
|
2861
2872
|
drawHexagon(ctx, [anti.x, anti.y, 0], drawnHexRadius * antiScale, infection, textures, index, blankCount, smoothed, sheenProgress, sheenIntensity, sheenEnabled, scratchEnabled, scratchCanvasRef.current, seamInset, pulseProgress, false, true, antiAngle);
|
|
@@ -3295,7 +3306,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
3295
3306
|
// If configured, also check the antipodal (inside) projection
|
|
3296
3307
|
if (workerDebug.renderBothSides) {
|
|
3297
3308
|
try {
|
|
3298
|
-
const anti = mapAndProject(hexPositions[i], true);
|
|
3309
|
+
const anti = mapAndProject(hexPositions[i], true, i);
|
|
3299
3310
|
const antiScale = anti.scale || 1;
|
|
3300
3311
|
const antiRadius = drawnHexRadius * antiScale;
|
|
3301
3312
|
const antiAngle = anti.angle || 0;
|
|
@@ -3420,7 +3431,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
3420
3431
|
}
|
|
3421
3432
|
if (workerDebug.renderBothSides) {
|
|
3422
3433
|
try {
|
|
3423
|
-
const anti = mapAndProject(hexPositions[i], true);
|
|
3434
|
+
const anti = mapAndProject(hexPositions[i], true, i);
|
|
3424
3435
|
const antiRadius = drawnHexRadius * (anti.scale || 1);
|
|
3425
3436
|
const antiAngle = anti.angle || 0;
|
|
3426
3437
|
if (isPointInHexagon(x, y, [anti.x, anti.y, 0], antiRadius, antiAngle)) {
|
|
@@ -4359,62 +4370,6 @@ function generatePixelScreen(cols, rows, hexRadius) {
|
|
|
4359
4370
|
// No centering needed for canvas - positions are already in canvas coordinates
|
|
4360
4371
|
return positions;
|
|
4361
4372
|
}
|
|
4362
|
-
// Generate hexagons directly in spherical space for compact 3D packing
|
|
4363
|
-
function generateSphericalHexGrid(targetCount, screenWidth, screenHeight, curveUDeg, curveVDeg, densityMultiplier = 1.4, hexRadius // The actual hex radius used for drawing
|
|
4364
|
-
) {
|
|
4365
|
-
const positions = [];
|
|
4366
|
-
const sqrt3 = Math.sqrt(3);
|
|
4367
|
-
// Calculate grid dimensions based on hex radius and screen size
|
|
4368
|
-
// Use the SAME spacing math as the flat grid for consistency
|
|
4369
|
-
const baseHorizontalSpacing = sqrt3 * hexRadius;
|
|
4370
|
-
const baseVerticalSpacing = 1.5 * hexRadius;
|
|
4371
|
-
// Calculate how many hexes fit based on actual hex size
|
|
4372
|
-
const baseCols = Math.ceil(screenWidth / baseHorizontalSpacing);
|
|
4373
|
-
const baseRows = Math.ceil(screenHeight / baseVerticalSpacing);
|
|
4374
|
-
// Apply density multiplier to get more/fewer hexes
|
|
4375
|
-
const cols = Math.ceil(baseCols * Math.sqrt(densityMultiplier));
|
|
4376
|
-
const rows = Math.ceil(baseRows * Math.sqrt(densityMultiplier));
|
|
4377
|
-
const deg2rad = Math.PI / 180;
|
|
4378
|
-
// Use the same spacing for generation (already calculated above)
|
|
4379
|
-
const verticalSpacing = baseVerticalSpacing;
|
|
4380
|
-
const horizontalSpacing = baseHorizontalSpacing;
|
|
4381
|
-
// Generate positions directly in lat/lon space with proper hexagonal offsets
|
|
4382
|
-
// Apply adaptive density based on latitude to naturally handle pole convergence
|
|
4383
|
-
for (let row = 0; row < rows; row++) {
|
|
4384
|
-
// Use vertical spacing for proper hex packing
|
|
4385
|
-
const y = row * verticalSpacing;
|
|
4386
|
-
// Calculate latitude for this row to determine if we're near a pole
|
|
4387
|
-
const v = y / Math.max(1, screenHeight);
|
|
4388
|
-
// Clamp v to [0, 1] range
|
|
4389
|
-
if (v < 0 || v > 1)
|
|
4390
|
-
continue;
|
|
4391
|
-
const lat = (v - 0.5) * (curveVDeg * deg2rad);
|
|
4392
|
-
// Natural pole density reduction: fewer hexes per row near poles
|
|
4393
|
-
// This is more physically accurate for spherical surfaces
|
|
4394
|
-
const latFactor = Math.max(0.3, Math.abs(Math.cos(lat)));
|
|
4395
|
-
const effectiveColsForRow = Math.max(3, Math.round(cols * latFactor));
|
|
4396
|
-
// CRITICAL: Calculate hex offset based on the base spacing (not per-row spacing)
|
|
4397
|
-
// This ensures proper nesting at all latitudes
|
|
4398
|
-
const hexOffsetX = horizontalSpacing * 0.5;
|
|
4399
|
-
for (let col = 0; col < effectiveColsForRow; col++) {
|
|
4400
|
-
// Start with evenly distributed positions in screen space
|
|
4401
|
-
let x = col * horizontalSpacing;
|
|
4402
|
-
// Apply hexagonal offset for odd rows in SCREEN SPACE
|
|
4403
|
-
// This ensures hexagons nestle properly between rows below
|
|
4404
|
-
if (row % 2 !== 0) {
|
|
4405
|
-
x += hexOffsetX;
|
|
4406
|
-
}
|
|
4407
|
-
// Wrap/clamp x to screen bounds
|
|
4408
|
-
// For full 360° wrap, hexes can exceed screen width and will wrap in projection
|
|
4409
|
-
// For partial coverage, clamp to screen
|
|
4410
|
-
if (Math.abs(curveUDeg) < 359) {
|
|
4411
|
-
x = Math.max(0, Math.min(screenWidth, x));
|
|
4412
|
-
}
|
|
4413
|
-
positions.push([x, y, 0]);
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
return { positions, metadata: { cols, rows, isSpherical: true } };
|
|
4417
|
-
}
|
|
4418
4373
|
function initializeInfectionSystem(positions, photos, hexRadius, initialClusterMax = 3, loggerParam = logger, isSpherical = false) {
|
|
4419
4374
|
const logger = loggerParam;
|
|
4420
4375
|
// GUARD: Validate inputs to prevent creating invalid infections
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export { HexGrid, default } from './HexGrid';
|
|
2
2
|
export type { Photo } from './HexGrid';
|
|
3
3
|
export type { HexGridProps } from '../types';
|
|
4
|
+
export { GameSphere } from './GameSphere';
|
|
5
|
+
export { buildPieceMesh, placePieceOnSphere, animatePiece, buildCellMesh, buildCellBorder, buildHighlightRing, buildFogOverlay, buildAttackTrail, buildOrbitalStrike, disposePieceGroup, applyCellState, } from './GamePieceRenderer';
|
|
6
|
+
export type { AttackAnimationConfig, OrbitalStrikeConfig } from './GamePieceRenderer';
|
|
7
|
+
export type { GamePiece, PieceShape, PieceAnimation, PieceAnimationConfig, CellGameState, CellHighlight, CellBorder, FogLevel, GameSphereProps, GameSphereConfig, GameSphereEvents, } from '../types';
|
|
8
|
+
export { HexTerritoryGlobe } from '../territory/HexTerritoryGlobe';
|
|
4
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAGtF,YAAY,EACV,SAAS,EACT,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,UAAU,EACV,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
1
|
export { HexGrid, default } from './HexGrid';
|
|
2
|
+
// Game piece rendering system
|
|
3
|
+
export { GameSphere } from './GameSphere';
|
|
4
|
+
export { buildPieceMesh, placePieceOnSphere, animatePiece, buildCellMesh, buildCellBorder, buildHighlightRing, buildFogOverlay, buildAttackTrail, buildOrbitalStrike, disposePieceGroup, applyCellState, } from './GamePieceRenderer';
|
|
5
|
+
export { HexTerritoryGlobe } from '../territory/HexTerritoryGlobe';
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAG3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAG5D,cAAc,QAAQ,CAAC;AAGvB,cAAc,cAAc,CAAC;AAG7B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,QAAQ,CAAC;AAGvB,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAG3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAG5D,cAAc,QAAQ,CAAC;AAGvB,cAAc,cAAc,CAAC;AAG7B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,QAAQ,CAAC;AAGvB,cAAc,YAAY,CAAC;AAG3B,cAAc,aAAa,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type HexTerritoryAffiliation, type HexTerritoryAllianceBinding, type HexTerritoryCell, type HexTerritoryRallyMarker } from './globe';
|
|
3
|
+
export interface HexTerritoryGlobeProps {
|
|
4
|
+
cells: HexTerritoryCell[];
|
|
5
|
+
selectedCellId?: string | null;
|
|
6
|
+
hoverCellId?: string | null;
|
|
7
|
+
claimedCellIds?: Iterable<string>;
|
|
8
|
+
lockedCellIds?: Iterable<string>;
|
|
9
|
+
colorsByCellId?: Record<string, string>;
|
|
10
|
+
affiliationByCellId?: Record<string, HexTerritoryAffiliation>;
|
|
11
|
+
allianceBindings?: HexTerritoryAllianceBinding[];
|
|
12
|
+
rallyMarkers?: HexTerritoryRallyMarker[];
|
|
13
|
+
tileRadius?: number;
|
|
14
|
+
onSelectCell?: (cell: HexTerritoryCell) => void;
|
|
15
|
+
onHoverCell?: (cell: HexTerritoryCell | null) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function HexTerritoryGlobe({ cells, selectedCellId, hoverCellId, claimedCellIds, lockedCellIds, colorsByCellId, affiliationByCellId, allianceBindings, rallyMarkers, tileRadius, onSelectCell, onHoverCell, }: HexTerritoryGlobeProps): React.JSX.Element;
|
|
18
|
+
//# sourceMappingURL=HexTerritoryGlobe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HexTerritoryGlobe.d.ts","sourceRoot":"","sources":["../../src/territory/HexTerritoryGlobe.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAG1D,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC7B,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAC9D,gBAAgB,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACjD,YAAY,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;CACvD;AAMD,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,cAAqB,EACrB,WAAkB,EAClB,cAAc,EACd,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,EAAE,sBAAsB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyJ5C"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { Color, CircleGeometry, Matrix4, Object3D } from 'three';
|
|
4
|
+
import { calculateAutoTileRadiusByRow, } from './globe';
|
|
5
|
+
function asSet(values) {
|
|
6
|
+
return values ? new Set(values) : new Set();
|
|
7
|
+
}
|
|
8
|
+
export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId = null, claimedCellIds, lockedCellIds, colorsByCellId, affiliationByCellId, allianceBindings, rallyMarkers, tileRadius, onSelectCell, onHoverCell, }) {
|
|
9
|
+
const meshRef = useRef(null);
|
|
10
|
+
const geometry = useMemo(() => new CircleGeometry(1, 6), []);
|
|
11
|
+
const workingObject = useMemo(() => new Object3D(), []);
|
|
12
|
+
const claimed = useMemo(() => asSet(claimedCellIds), [claimedCellIds]);
|
|
13
|
+
const locked = useMemo(() => asSet(lockedCellIds), [lockedCellIds]);
|
|
14
|
+
const cellById = useMemo(() => new Map(cells.map((cell) => [cell.cellId, cell])), [cells]);
|
|
15
|
+
const autoTileRadiusByRow = useMemo(() => tileRadius === undefined ? calculateAutoTileRadiusByRow(cells) : undefined, [cells, tileRadius]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const mesh = meshRef.current;
|
|
18
|
+
if (!mesh) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const matrix = new Matrix4();
|
|
22
|
+
cells.forEach((cell, index) => {
|
|
23
|
+
const point = cell.surfacePoint;
|
|
24
|
+
const effectiveTileRadius = tileRadius ?? autoTileRadiusByRow?.get(cell.rowIndex) ?? 0;
|
|
25
|
+
workingObject.position.set(point.x, point.y, point.z);
|
|
26
|
+
workingObject.lookAt(point.x * 2, point.y * 2, point.z * 2);
|
|
27
|
+
workingObject.scale.setScalar(effectiveTileRadius);
|
|
28
|
+
workingObject.updateMatrix();
|
|
29
|
+
matrix.copy(workingObject.matrix);
|
|
30
|
+
mesh.setMatrixAt(index, matrix);
|
|
31
|
+
const affiliation = affiliationByCellId?.[cell.cellId] ?? 'neutral';
|
|
32
|
+
const baseColor = locked.has(cell.cellId)
|
|
33
|
+
? '#23345c'
|
|
34
|
+
: colorsByCellId?.[cell.cellId] ??
|
|
35
|
+
(selectedCellId === cell.cellId
|
|
36
|
+
? '#7ee7ff'
|
|
37
|
+
: hoverCellId === cell.cellId
|
|
38
|
+
? '#59d0ff'
|
|
39
|
+
: affiliation === 'self'
|
|
40
|
+
? '#7ee7ff'
|
|
41
|
+
: affiliation === 'ally'
|
|
42
|
+
? '#63f2c6'
|
|
43
|
+
: affiliation === 'hostile'
|
|
44
|
+
? '#ff9675'
|
|
45
|
+
: claimed.has(cell.cellId)
|
|
46
|
+
? '#63f2c6'
|
|
47
|
+
: '#1f2a4a');
|
|
48
|
+
mesh.setColorAt(index, new Color(baseColor));
|
|
49
|
+
});
|
|
50
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
51
|
+
if (mesh.instanceColor) {
|
|
52
|
+
mesh.instanceColor.needsUpdate = true;
|
|
53
|
+
}
|
|
54
|
+
}, [
|
|
55
|
+
cells,
|
|
56
|
+
claimed,
|
|
57
|
+
colorsByCellId,
|
|
58
|
+
affiliationByCellId,
|
|
59
|
+
hoverCellId,
|
|
60
|
+
locked,
|
|
61
|
+
selectedCellId,
|
|
62
|
+
tileRadius,
|
|
63
|
+
autoTileRadiusByRow,
|
|
64
|
+
workingObject,
|
|
65
|
+
]);
|
|
66
|
+
return (_jsxs(_Fragment, { children: [_jsx("instancedMesh", { ref: meshRef, args: [geometry, undefined, cells.length], onClick: (event) => {
|
|
67
|
+
if (typeof event.instanceId !== 'number') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const cell = cells[event.instanceId];
|
|
71
|
+
if (cell) {
|
|
72
|
+
onSelectCell?.(cell);
|
|
73
|
+
}
|
|
74
|
+
}, onPointerMove: (event) => {
|
|
75
|
+
if (typeof event.instanceId !== 'number') {
|
|
76
|
+
onHoverCell?.(null);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const cell = cells[event.instanceId];
|
|
80
|
+
onHoverCell?.(cell ?? null);
|
|
81
|
+
}, onPointerOut: () => {
|
|
82
|
+
onHoverCell?.(null);
|
|
83
|
+
}, children: _jsx("meshStandardMaterial", { transparent: true, opacity: 0.92, metalness: 0.14, roughness: 0.42 }) }), (rallyMarkers ?? []).map((marker) => {
|
|
84
|
+
const cell = cellById.get(marker.cellId);
|
|
85
|
+
if (!cell) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const intensity = Math.max(0.35, Math.min(marker.intensity ?? 1, 2));
|
|
89
|
+
const scale = 0.028 * intensity;
|
|
90
|
+
const point = cell.surfacePoint;
|
|
91
|
+
return (_jsxs("mesh", { position: [point.x * 1.015, point.y * 1.015, point.z * 1.015], children: [_jsx("sphereGeometry", { args: [scale, 10, 10] }), _jsx("meshBasicMaterial", { color: marker.directive === 'surge'
|
|
92
|
+
? '#ffc857'
|
|
93
|
+
: marker.directive === 'fortify'
|
|
94
|
+
? '#7ee7ff'
|
|
95
|
+
: marker.directive === 'support'
|
|
96
|
+
? '#63f2c6'
|
|
97
|
+
: '#c9d4ff' })] }, `${marker.cellId}:${marker.directive}`));
|
|
98
|
+
}), (allianceBindings ?? []).flatMap((binding) => binding.rootCellIds.map((cellId) => {
|
|
99
|
+
const cell = cellById.get(cellId);
|
|
100
|
+
if (!cell) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const point = cell.surfacePoint;
|
|
104
|
+
return (_jsxs("mesh", { position: [point.x * 1.005, point.y * 1.005, point.z * 1.005], children: [_jsx("sphereGeometry", { args: [0.018, 8, 8] }), _jsx("meshBasicMaterial", { color: "#7ee7ff", transparent: true, opacity: 0.75 })] }, `${binding.phyleId}:${cellId}:halo`));
|
|
105
|
+
}))] }));
|
|
106
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface CanonicalHexGlobeConfig {
|
|
2
|
+
boardId: string;
|
|
3
|
+
curveUDeg: number;
|
|
4
|
+
curveVDeg: number;
|
|
5
|
+
rowCount: number;
|
|
6
|
+
equatorColumns: number;
|
|
7
|
+
minimumColumnsPerRow: number;
|
|
8
|
+
poleMinScale: number;
|
|
9
|
+
sphereRadius?: number;
|
|
10
|
+
}
|
|
11
|
+
export type HexSubdivisionSegment = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
12
|
+
export type HexNodePath = HexSubdivisionSegment[];
|
|
13
|
+
export type HexwarEmbedProvider = 'youtube' | 'x' | 'instagram' | 'threads' | 'tiktok';
|
|
14
|
+
export interface HexwarEmbedRef {
|
|
15
|
+
provider: HexwarEmbedProvider;
|
|
16
|
+
submittedUrl: string;
|
|
17
|
+
canonicalUrl: string;
|
|
18
|
+
kind: 'video' | 'post' | 'thread' | 'short' | 'reel';
|
|
19
|
+
title?: string;
|
|
20
|
+
authorName?: string;
|
|
21
|
+
thumbnailUrl?: string;
|
|
22
|
+
embedAllowed: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface HexTerritoryTickState {
|
|
25
|
+
stage: 'dormant' | 'active' | 'surging' | 'entrenched' | 'fading';
|
|
26
|
+
energy: number;
|
|
27
|
+
pressure: number;
|
|
28
|
+
cohesion: number;
|
|
29
|
+
lastResolvedAt: number;
|
|
30
|
+
}
|
|
31
|
+
export type HexTerritoryAffiliation = 'self' | 'ally' | 'neutral' | 'hostile';
|
|
32
|
+
export interface HexTerritoryAllianceBinding {
|
|
33
|
+
phyleId: string;
|
|
34
|
+
rootCellIds: string[];
|
|
35
|
+
displayName?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface HexTerritoryRallyMarker {
|
|
38
|
+
cellId: string;
|
|
39
|
+
directive: 'fortify' | 'surge' | 'watch' | 'support';
|
|
40
|
+
phyleId?: string;
|
|
41
|
+
intensity?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface HexTerritoryCellPoint {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
z: number;
|
|
47
|
+
}
|
|
48
|
+
export interface HexTerritoryCell {
|
|
49
|
+
cellId: string;
|
|
50
|
+
rowIndex: number;
|
|
51
|
+
columnIndex: number;
|
|
52
|
+
columnCount: number;
|
|
53
|
+
lat: number;
|
|
54
|
+
lon: number;
|
|
55
|
+
surfacePoint: HexTerritoryCellPoint;
|
|
56
|
+
neighborCellIds: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface HexTerritoryBoard {
|
|
59
|
+
boardId: string;
|
|
60
|
+
config: CanonicalHexGlobeConfig;
|
|
61
|
+
configHash: string;
|
|
62
|
+
cells: HexTerritoryCell[];
|
|
63
|
+
}
|
|
64
|
+
export declare function generateCanonicalHexGlobe(config: CanonicalHexGlobeConfig): HexTerritoryBoard;
|
|
65
|
+
export declare function calculateAutoTileRadiusByRow(cells: readonly HexTerritoryCell[]): Map<number, number>;
|
|
66
|
+
//# sourceMappingURL=globe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globe.d.ts","sourceRoot":"","sources":["../../src/territory/globe.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9D,MAAM,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,GAAG,GACH,WAAW,GACX,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,qBAAqB,CAAC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,uBAAuB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AA2HD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,uBAAuB,GAC9B,iBAAiB,CAoDnB;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,gBAAgB,EAAE,GACjC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAiErB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const boardCache = new Map();
|
|
2
|
+
const HEX_HORIZONTAL_RADIUS_RATIO = Math.sqrt(3);
|
|
3
|
+
const HEX_VERTICAL_RADIUS_RATIO = 1.5;
|
|
4
|
+
const TERRITORY_TILE_RADIUS_SAFETY_MARGIN = 0.96;
|
|
5
|
+
function toRadians(value) {
|
|
6
|
+
return (value * Math.PI) / 180;
|
|
7
|
+
}
|
|
8
|
+
function normalizeLongitude(value) {
|
|
9
|
+
let next = value;
|
|
10
|
+
while (next < -180) {
|
|
11
|
+
next += 360;
|
|
12
|
+
}
|
|
13
|
+
while (next >= 180) {
|
|
14
|
+
next -= 360;
|
|
15
|
+
}
|
|
16
|
+
return next;
|
|
17
|
+
}
|
|
18
|
+
function buildConfigHash(config) {
|
|
19
|
+
return [
|
|
20
|
+
config.boardId,
|
|
21
|
+
config.curveUDeg,
|
|
22
|
+
config.curveVDeg,
|
|
23
|
+
config.rowCount,
|
|
24
|
+
config.equatorColumns,
|
|
25
|
+
config.minimumColumnsPerRow,
|
|
26
|
+
config.poleMinScale,
|
|
27
|
+
config.sphereRadius ?? 1,
|
|
28
|
+
].join(':');
|
|
29
|
+
}
|
|
30
|
+
function toSurfacePoint(lat, lon, sphereRadius) {
|
|
31
|
+
const latRadians = toRadians(lat);
|
|
32
|
+
const lonRadians = toRadians(lon);
|
|
33
|
+
const cosLat = Math.cos(latRadians);
|
|
34
|
+
return {
|
|
35
|
+
x: sphereRadius * cosLat * Math.cos(lonRadians),
|
|
36
|
+
y: sphereRadius * Math.sin(latRadians),
|
|
37
|
+
z: sphereRadius * cosLat * Math.sin(lonRadians),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function shortestWrappedDistance(a, b) {
|
|
41
|
+
const delta = Math.abs(a - b);
|
|
42
|
+
return Math.min(delta, 360 - delta);
|
|
43
|
+
}
|
|
44
|
+
function pointDistance(left, right) {
|
|
45
|
+
const dx = left.x - right.x;
|
|
46
|
+
const dy = left.y - right.y;
|
|
47
|
+
const dz = left.z - right.z;
|
|
48
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
49
|
+
}
|
|
50
|
+
function columnCountForLatitude(latitude, config) {
|
|
51
|
+
const cosScale = Math.abs(Math.cos(toRadians(latitude)));
|
|
52
|
+
const scaled = Math.max(config.poleMinScale, cosScale);
|
|
53
|
+
return Math.max(config.minimumColumnsPerRow, Math.round(config.equatorColumns * scaled));
|
|
54
|
+
}
|
|
55
|
+
function findClosestColumns(rowCells, lon) {
|
|
56
|
+
const ranked = rowCells
|
|
57
|
+
.map((cell) => ({
|
|
58
|
+
cell,
|
|
59
|
+
distance: shortestWrappedDistance(cell.lon, lon),
|
|
60
|
+
}))
|
|
61
|
+
.sort((left, right) => left.distance - right.distance);
|
|
62
|
+
return ranked.slice(0, Math.min(3, ranked.length)).map((item) => item.cell);
|
|
63
|
+
}
|
|
64
|
+
function buildNeighbors(cell, rows) {
|
|
65
|
+
const currentRow = rows[cell.rowIndex] ?? [];
|
|
66
|
+
const neighborIds = new Set();
|
|
67
|
+
const sameRowCount = currentRow.length;
|
|
68
|
+
if (sameRowCount > 1) {
|
|
69
|
+
const left = currentRow[(cell.columnIndex - 1 + sameRowCount) % sameRowCount] ?? null;
|
|
70
|
+
const right = currentRow[(cell.columnIndex + 1) % sameRowCount] ?? null;
|
|
71
|
+
if (left) {
|
|
72
|
+
neighborIds.add(left.cellId);
|
|
73
|
+
}
|
|
74
|
+
if (right) {
|
|
75
|
+
neighborIds.add(right.cellId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const adjacentRows = [cell.rowIndex - 1, cell.rowIndex + 1];
|
|
79
|
+
for (const rowIndex of adjacentRows) {
|
|
80
|
+
const row = rows[rowIndex] ?? [];
|
|
81
|
+
for (const adjacent of findClosestColumns(row, cell.lon)) {
|
|
82
|
+
neighborIds.add(adjacent.cellId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Array.from(neighborIds);
|
|
86
|
+
}
|
|
87
|
+
export function generateCanonicalHexGlobe(config) {
|
|
88
|
+
const configHash = buildConfigHash(config);
|
|
89
|
+
const cached = boardCache.get(configHash);
|
|
90
|
+
if (cached) {
|
|
91
|
+
return cached;
|
|
92
|
+
}
|
|
93
|
+
const sphereRadius = config.sphereRadius ?? 1;
|
|
94
|
+
const rows = [];
|
|
95
|
+
for (let rowIndex = 0; rowIndex < config.rowCount; rowIndex += 1) {
|
|
96
|
+
const latitudeProgress = config.rowCount <= 1 ? 0.5 : (rowIndex + 0.5) / config.rowCount;
|
|
97
|
+
const lat = -90 + latitudeProgress * 180;
|
|
98
|
+
const columnCount = columnCountForLatitude(lat, config);
|
|
99
|
+
const lonStep = 360 / columnCount;
|
|
100
|
+
const lonOffset = rowIndex % 2 === 0 ? 0 : lonStep / 2;
|
|
101
|
+
const rowCells = [];
|
|
102
|
+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
|
|
103
|
+
const lon = normalizeLongitude(-180 + lonOffset + columnIndex * lonStep + lonStep / 2);
|
|
104
|
+
rowCells.push({
|
|
105
|
+
cellId: `${config.boardId}:r${rowIndex}:c${columnIndex}`,
|
|
106
|
+
rowIndex,
|
|
107
|
+
columnIndex,
|
|
108
|
+
columnCount,
|
|
109
|
+
lat,
|
|
110
|
+
lon,
|
|
111
|
+
surfacePoint: toSurfacePoint(lat, lon, sphereRadius),
|
|
112
|
+
neighborCellIds: [],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
rows.push(rowCells);
|
|
116
|
+
}
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
for (const cell of row) {
|
|
119
|
+
cell.neighborCellIds = buildNeighbors(cell, rows);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const board = {
|
|
123
|
+
boardId: config.boardId,
|
|
124
|
+
config,
|
|
125
|
+
configHash,
|
|
126
|
+
cells: rows.flat(),
|
|
127
|
+
};
|
|
128
|
+
boardCache.set(configHash, board);
|
|
129
|
+
return board;
|
|
130
|
+
}
|
|
131
|
+
export function calculateAutoTileRadiusByRow(cells) {
|
|
132
|
+
const rowMap = new Map();
|
|
133
|
+
const cellById = new Map();
|
|
134
|
+
for (const cell of cells) {
|
|
135
|
+
const row = rowMap.get(cell.rowIndex);
|
|
136
|
+
if (row) {
|
|
137
|
+
row.push(cell);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
rowMap.set(cell.rowIndex, [cell]);
|
|
141
|
+
}
|
|
142
|
+
cellById.set(cell.cellId, cell);
|
|
143
|
+
}
|
|
144
|
+
const radiusByRow = new Map();
|
|
145
|
+
for (const [rowIndex, unsortedRow] of rowMap.entries()) {
|
|
146
|
+
const row = [...unsortedRow].sort((left, right) => left.columnIndex - right.columnIndex);
|
|
147
|
+
let minimumSameRowDistance = Number.POSITIVE_INFINITY;
|
|
148
|
+
let minimumAdjacentRowDistance = Number.POSITIVE_INFINITY;
|
|
149
|
+
if (row.length > 1) {
|
|
150
|
+
for (let columnIndex = 0; columnIndex < row.length; columnIndex += 1) {
|
|
151
|
+
const current = row[columnIndex];
|
|
152
|
+
const next = row[(columnIndex + 1) % row.length];
|
|
153
|
+
if (current && next) {
|
|
154
|
+
minimumSameRowDistance = Math.min(minimumSameRowDistance, pointDistance(current.surfacePoint, next.surfacePoint));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const cell of row) {
|
|
159
|
+
for (const neighborId of cell.neighborCellIds) {
|
|
160
|
+
const neighbor = cellById.get(neighborId);
|
|
161
|
+
if (!neighbor || Math.abs(neighbor.rowIndex - rowIndex) !== 1) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
minimumAdjacentRowDistance = Math.min(minimumAdjacentRowDistance, pointDistance(cell.surfacePoint, neighbor.surfacePoint));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const horizontalRadius = Number.isFinite(minimumSameRowDistance)
|
|
168
|
+
? minimumSameRowDistance / HEX_HORIZONTAL_RADIUS_RATIO
|
|
169
|
+
: Number.POSITIVE_INFINITY;
|
|
170
|
+
const verticalRadius = Number.isFinite(minimumAdjacentRowDistance)
|
|
171
|
+
? minimumAdjacentRowDistance / HEX_VERTICAL_RADIUS_RATIO
|
|
172
|
+
: Number.POSITIVE_INFINITY;
|
|
173
|
+
const unconstrainedRadius = Math.min(horizontalRadius, verticalRadius);
|
|
174
|
+
const safeRadius = Number.isFinite(unconstrainedRadius) && unconstrainedRadius > 0
|
|
175
|
+
? unconstrainedRadius * TERRITORY_TILE_RADIUS_SAFETY_MARGIN
|
|
176
|
+
: 0;
|
|
177
|
+
radiusByRow.set(rowIndex, safeRadius);
|
|
178
|
+
}
|
|
179
|
+
return radiusByRow;
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/territory/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NarrationMessage } from '../lib/narration';
|
|
2
|
+
export type HexwarNarrationEventType = 'claim' | 'territory_captured' | 'embed_set' | 'delegation_issued' | 'delegation_revoked' | 'nexus_created' | 'nexus_connected' | 'alliance_root_bound' | 'rally_called' | 'allied_border_held' | 'alliance_surged' | 'unlock' | 'tick_surged' | 'tick_entrenched' | 'leaderboard_flip';
|
|
3
|
+
export interface HexwarNarrationEvent {
|
|
4
|
+
id: string;
|
|
5
|
+
eventType: HexwarNarrationEventType;
|
|
6
|
+
occurredAtMs: number;
|
|
7
|
+
actorName?: string;
|
|
8
|
+
cellId?: string;
|
|
9
|
+
details?: Record<string, string | number | boolean | null | undefined>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createHexwarNarrationAdapter(events: HexwarNarrationEvent[]): NarrationMessage[];
|
|
12
|
+
//# sourceMappingURL=narration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration.d.ts","sourceRoot":"","sources":["../../src/territory/narration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,MAAM,wBAAwB,GAChC,OAAO,GACP,oBAAoB,GACpB,WAAW,GACX,mBAAmB,GACnB,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,cAAc,GACd,oBAAoB,GACpB,iBAAiB,GACjB,QAAQ,GACR,aAAa,GACb,iBAAiB,GACjB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,wBAAwB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACxE;AAyED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,oBAAoB,EAAE,GAC7B,gBAAgB,EAAE,CAmDpB"}
|