@buley/hexgrid-3d 3.6.1 → 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 +9 -2
- package/dist/components/GameSphere.d.ts.map +1 -1
- package/dist/components/GameSphere.js +12 -2
- package/dist/components/HexGrid.d.ts.map +1 -1
- package/dist/components/HexGrid.js +20 -65
- package/dist/territory/HexTerritoryGlobe.d.ts +5 -2
- package/dist/territory/HexTerritoryGlobe.d.ts.map +1 -1
- package/dist/territory/HexTerritoryGlobe.js +55 -24
- package/dist/territory/globe.d.ts +12 -0
- package/dist/territory/globe.d.ts.map +1 -1
- package/dist/territory/globe.js +1 -1
- package/dist/territory/narration.d.ts +1 -1
- package/dist/territory/narration.d.ts.map +1 -1
- package/dist/territory/narration.js +25 -0
- 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 +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A reusable 3D hexagonal grid visualization component for displaying content in an immersive spherical layout.
|
|
4
4
|
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [Territory README](./src/territory/README.md)
|
|
8
|
+
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- **3D Hexagonal Grid Layout** - Spherical projection with customizable curvature
|
|
@@ -9,7 +13,7 @@ A reusable 3D hexagonal grid visualization component for displaying content in a
|
|
|
9
13
|
- **Image Texture Mapping** - Automatic texture loading and caching
|
|
10
14
|
- **Real-time Statistics** - Live telemetry and performance metrics
|
|
11
15
|
- **Narration System** - Play-by-play commentary overlay
|
|
12
|
-
- **Territory Globe** - Deterministic wrapped globe topology for paid
|
|
16
|
+
- **Territory Globe** - Deterministic wrapped globe topology for paid seed claims, recursive subdivision, delegated territory overlays, and conquest-driven occupation
|
|
13
17
|
- **Web Worker Rendering** - High-performance offloaded calculations
|
|
14
18
|
- **Multi-Input Support** - Touch gestures, mouse, and keyboard controls
|
|
15
19
|
- **Responsive Design** - Adapts to mobile and desktop viewports
|
|
@@ -155,7 +159,7 @@ Wrapped React Three Fiber surface for territory-aware hex cells on a sphere.
|
|
|
155
159
|
| Prop | Type | Description |
|
|
156
160
|
| ---- | ---- | ----------- |
|
|
157
161
|
| `cells` | `HexTerritoryCell[]` | Deterministic canonical globe cells |
|
|
158
|
-
| `claimedCellIds` | `Iterable<string>` |
|
|
162
|
+
| `claimedCellIds` | `Iterable<string>` | Occupied or currently held roots |
|
|
159
163
|
| `lockedCellIds` | `Iterable<string>` | Visible but unclaimable roots |
|
|
160
164
|
| `colorsByCellId` | `Record<string, string>` | Per-root color overrides |
|
|
161
165
|
| `selectedCellId` | `string` | Active root highlight |
|
|
@@ -173,6 +177,9 @@ The package now exports territory-specific helpers:
|
|
|
173
177
|
- `CanonicalHexGlobeConfig`
|
|
174
178
|
- `HexTerritoryBoard`
|
|
175
179
|
- `HexTerritoryCell`
|
|
180
|
+
- `HexTerritoryAffiliation`
|
|
181
|
+
- `HexTerritoryAllianceBinding`
|
|
182
|
+
- `HexTerritoryRallyMarker`
|
|
176
183
|
- `HexNodePath`
|
|
177
184
|
- `HexwarEmbedRef`
|
|
178
185
|
- `HexTerritoryTickState`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GameSphere.d.ts","sourceRoot":"","sources":["../../src/components/GameSphere.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"GameSphere.d.ts","sourceRoot":"","sources":["../../src/components/GameSphere.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,UAAU,CAAC;AA+OlB,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAyjBhD,CAAC;AAGF,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -65,6 +65,15 @@ function getHighlightColor(highlight) {
|
|
|
65
65
|
return '#000000';
|
|
66
66
|
return HIGHLIGHT_COLORS[highlight] ?? highlight; // If it's a custom color string, use directly
|
|
67
67
|
}
|
|
68
|
+
function normalizeReactNode(node) {
|
|
69
|
+
if (typeof node === 'bigint') {
|
|
70
|
+
return node.toString();
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(node)) {
|
|
73
|
+
return node.map(normalizeReactNode);
|
|
74
|
+
}
|
|
75
|
+
return node;
|
|
76
|
+
}
|
|
68
77
|
function createOrbitControls(camera, canvas, initialDistance) {
|
|
69
78
|
const state = {
|
|
70
79
|
theta: 0,
|
|
@@ -174,6 +183,7 @@ function getCellUnderMouse(event, canvas, camera, cellMeshes) {
|
|
|
174
183
|
// GameSphere Component
|
|
175
184
|
// ---------------------------------------------------------------------------
|
|
176
185
|
export const GameSphere = ({ cellGameState, config: configProp, events, width = '100%', height = '100%', className, style, rendererRef, sceneRef, paused = false, children, }) => {
|
|
186
|
+
const overlayChildren = normalizeReactNode(children);
|
|
177
187
|
const containerRef = useRef(null);
|
|
178
188
|
const canvasRef = useRef(null);
|
|
179
189
|
const rendererInternalRef = useRef(null);
|
|
@@ -609,14 +619,14 @@ export const GameSphere = ({ cellGameState, config: configProp, events, width =
|
|
|
609
619
|
height: '100%',
|
|
610
620
|
display: 'block',
|
|
611
621
|
touchAction: 'none', // Prevent browser gesture interference
|
|
612
|
-
} }),
|
|
622
|
+
} }), overlayChildren != null ? (_jsx("div", { style: {
|
|
613
623
|
position: 'absolute',
|
|
614
624
|
top: 0,
|
|
615
625
|
left: 0,
|
|
616
626
|
right: 0,
|
|
617
627
|
bottom: 0,
|
|
618
628
|
pointerEvents: 'none',
|
|
619
|
-
}, children: _jsx("div", { style: { pointerEvents: 'auto' }, children:
|
|
629
|
+
}, children: _jsx("div", { style: { pointerEvents: 'auto' }, children: overlayChildren }) })) : null] }));
|
|
620
630
|
};
|
|
621
631
|
// Re-export GeodesicHexGrid for game consumers
|
|
622
632
|
export { GeodesicHexGrid } from '../math/HexCoordinates';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAU/C,YAAY,EAAE,KAAK,EAAE,CAAA;AAWrB,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IAEvC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAGhB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAC9C,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC5G,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,0BAA0B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAsLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,4CAkzIjB,CAAA;AAk2DD,eAAe,OAAO,CAAC"}
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type HexTerritoryCell } from './globe';
|
|
2
|
+
import { type HexTerritoryAffiliation, type HexTerritoryAllianceBinding, type HexTerritoryCell, type HexTerritoryRallyMarker } from './globe';
|
|
3
3
|
export interface HexTerritoryGlobeProps {
|
|
4
4
|
cells: HexTerritoryCell[];
|
|
5
5
|
selectedCellId?: string | null;
|
|
@@ -7,9 +7,12 @@ export interface HexTerritoryGlobeProps {
|
|
|
7
7
|
claimedCellIds?: Iterable<string>;
|
|
8
8
|
lockedCellIds?: Iterable<string>;
|
|
9
9
|
colorsByCellId?: Record<string, string>;
|
|
10
|
+
affiliationByCellId?: Record<string, HexTerritoryAffiliation>;
|
|
11
|
+
allianceBindings?: HexTerritoryAllianceBinding[];
|
|
12
|
+
rallyMarkers?: HexTerritoryRallyMarker[];
|
|
10
13
|
tileRadius?: number;
|
|
11
14
|
onSelectCell?: (cell: HexTerritoryCell) => void;
|
|
12
15
|
onHoverCell?: (cell: HexTerritoryCell | null) => void;
|
|
13
16
|
}
|
|
14
|
-
export declare function HexTerritoryGlobe({ cells, selectedCellId, hoverCellId, claimedCellIds, lockedCellIds, colorsByCellId, tileRadius, onSelectCell, onHoverCell, }: HexTerritoryGlobeProps): React.JSX.Element;
|
|
17
|
+
export declare function HexTerritoryGlobe({ cells, selectedCellId, hoverCellId, claimedCellIds, lockedCellIds, colorsByCellId, affiliationByCellId, allianceBindings, rallyMarkers, tileRadius, onSelectCell, onHoverCell, }: HexTerritoryGlobeProps): React.JSX.Element;
|
|
15
18
|
//# sourceMappingURL=HexTerritoryGlobe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexTerritoryGlobe.d.ts","sourceRoot":"","sources":["../../src/territory/HexTerritoryGlobe.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAG1D,OAAO,
|
|
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"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef } from 'react';
|
|
3
3
|
import { Color, CircleGeometry, Matrix4, Object3D } from 'three';
|
|
4
|
-
import { calculateAutoTileRadiusByRow } from './globe';
|
|
4
|
+
import { calculateAutoTileRadiusByRow, } from './globe';
|
|
5
5
|
function asSet(values) {
|
|
6
6
|
return values ? new Set(values) : new Set();
|
|
7
7
|
}
|
|
8
|
-
export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId = null, claimedCellIds, lockedCellIds, colorsByCellId, tileRadius, onSelectCell, onHoverCell, }) {
|
|
8
|
+
export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId = null, claimedCellIds, lockedCellIds, colorsByCellId, affiliationByCellId, allianceBindings, rallyMarkers, tileRadius, onSelectCell, onHoverCell, }) {
|
|
9
9
|
const meshRef = useRef(null);
|
|
10
10
|
const geometry = useMemo(() => new CircleGeometry(1, 6), []);
|
|
11
11
|
const workingObject = useMemo(() => new Object3D(), []);
|
|
12
12
|
const claimed = useMemo(() => asSet(claimedCellIds), [claimedCellIds]);
|
|
13
13
|
const locked = useMemo(() => asSet(lockedCellIds), [lockedCellIds]);
|
|
14
|
+
const cellById = useMemo(() => new Map(cells.map((cell) => [cell.cellId, cell])), [cells]);
|
|
14
15
|
const autoTileRadiusByRow = useMemo(() => tileRadius === undefined ? calculateAutoTileRadiusByRow(cells) : undefined, [cells, tileRadius]);
|
|
15
16
|
useEffect(() => {
|
|
16
17
|
const mesh = meshRef.current;
|
|
@@ -27,6 +28,7 @@ export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId =
|
|
|
27
28
|
workingObject.updateMatrix();
|
|
28
29
|
matrix.copy(workingObject.matrix);
|
|
29
30
|
mesh.setMatrixAt(index, matrix);
|
|
31
|
+
const affiliation = affiliationByCellId?.[cell.cellId] ?? 'neutral';
|
|
30
32
|
const baseColor = locked.has(cell.cellId)
|
|
31
33
|
? '#23345c'
|
|
32
34
|
: colorsByCellId?.[cell.cellId] ??
|
|
@@ -34,9 +36,15 @@ export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId =
|
|
|
34
36
|
? '#7ee7ff'
|
|
35
37
|
: hoverCellId === cell.cellId
|
|
36
38
|
? '#59d0ff'
|
|
37
|
-
:
|
|
38
|
-
? '#
|
|
39
|
-
: '
|
|
39
|
+
: affiliation === 'self'
|
|
40
|
+
? '#7ee7ff'
|
|
41
|
+
: affiliation === 'ally'
|
|
42
|
+
? '#63f2c6'
|
|
43
|
+
: affiliation === 'hostile'
|
|
44
|
+
? '#ff9675'
|
|
45
|
+
: claimed.has(cell.cellId)
|
|
46
|
+
? '#63f2c6'
|
|
47
|
+
: '#1f2a4a');
|
|
40
48
|
mesh.setColorAt(index, new Color(baseColor));
|
|
41
49
|
});
|
|
42
50
|
mesh.instanceMatrix.needsUpdate = true;
|
|
@@ -47,6 +55,7 @@ export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId =
|
|
|
47
55
|
cells,
|
|
48
56
|
claimed,
|
|
49
57
|
colorsByCellId,
|
|
58
|
+
affiliationByCellId,
|
|
50
59
|
hoverCellId,
|
|
51
60
|
locked,
|
|
52
61
|
selectedCellId,
|
|
@@ -54,22 +63,44 @@ export function HexTerritoryGlobe({ cells, selectedCellId = null, hoverCellId =
|
|
|
54
63
|
autoTileRadiusByRow,
|
|
55
64
|
workingObject,
|
|
56
65
|
]);
|
|
57
|
-
return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, undefined, cells.length], onClick: (event) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
}))] }));
|
|
75
106
|
}
|
|
@@ -28,6 +28,18 @@ export interface HexTerritoryTickState {
|
|
|
28
28
|
cohesion: number;
|
|
29
29
|
lastResolvedAt: number;
|
|
30
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
|
+
}
|
|
31
43
|
export interface HexTerritoryCellPoint {
|
|
32
44
|
x: number;
|
|
33
45
|
y: number;
|
|
@@ -1 +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,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"}
|
|
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"}
|
package/dist/territory/globe.js
CHANGED
|
@@ -93,7 +93,7 @@ export function generateCanonicalHexGlobe(config) {
|
|
|
93
93
|
const sphereRadius = config.sphereRadius ?? 1;
|
|
94
94
|
const rows = [];
|
|
95
95
|
for (let rowIndex = 0; rowIndex < config.rowCount; rowIndex += 1) {
|
|
96
|
-
const latitudeProgress = config.rowCount <= 1 ? 0 : rowIndex /
|
|
96
|
+
const latitudeProgress = config.rowCount <= 1 ? 0.5 : (rowIndex + 0.5) / config.rowCount;
|
|
97
97
|
const lat = -90 + latitudeProgress * 180;
|
|
98
98
|
const columnCount = columnCountForLatitude(lat, config);
|
|
99
99
|
const lonStep = 360 / columnCount;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NarrationMessage } from '../lib/narration';
|
|
2
|
-
export type HexwarNarrationEventType = 'claim' | 'embed_set' | 'delegation_issued' | 'delegation_revoked' | 'unlock' | 'tick_surged' | 'tick_entrenched' | 'leaderboard_flip';
|
|
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
3
|
export interface HexwarNarrationEvent {
|
|
4
4
|
id: string;
|
|
5
5
|
eventType: HexwarNarrationEventType;
|
|
@@ -1 +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,WAAW,GACX,mBAAmB,GACnB,oBAAoB,GACpB,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;
|
|
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"}
|
|
@@ -3,11 +3,22 @@ function priorityForEvent(eventType) {
|
|
|
3
3
|
case 'leaderboard_flip':
|
|
4
4
|
case 'unlock':
|
|
5
5
|
return 9;
|
|
6
|
+
case 'nexus_created':
|
|
7
|
+
case 'nexus_connected':
|
|
8
|
+
case 'alliance_surged':
|
|
9
|
+
return 8;
|
|
10
|
+
case 'territory_captured':
|
|
11
|
+
return 8;
|
|
6
12
|
case 'tick_surged':
|
|
7
13
|
case 'tick_entrenched':
|
|
8
14
|
return 8;
|
|
15
|
+
case 'rally_called':
|
|
16
|
+
case 'alliance_root_bound':
|
|
17
|
+
return 7;
|
|
9
18
|
case 'claim':
|
|
10
19
|
return 7;
|
|
20
|
+
case 'allied_border_held':
|
|
21
|
+
return 6;
|
|
11
22
|
case 'delegation_issued':
|
|
12
23
|
case 'delegation_revoked':
|
|
13
24
|
return 6;
|
|
@@ -23,12 +34,26 @@ function formatSingleEvent(event) {
|
|
|
23
34
|
switch (event.eventType) {
|
|
24
35
|
case 'claim':
|
|
25
36
|
return `${actor}claimed root${cell}.`.trim();
|
|
37
|
+
case 'territory_captured':
|
|
38
|
+
return `${actor}captured frontier territory${cell}.`.trim();
|
|
26
39
|
case 'embed_set':
|
|
27
40
|
return `${actor}repointed the frontier payload${cell}.`.trim();
|
|
28
41
|
case 'delegation_issued':
|
|
29
42
|
return `${actor}delegated subhex control${cell}.`.trim();
|
|
30
43
|
case 'delegation_revoked':
|
|
31
44
|
return `${actor}revoked delegated control${cell}.`.trim();
|
|
45
|
+
case 'nexus_created':
|
|
46
|
+
return `${actor}founded a nexus${cell}.`.trim();
|
|
47
|
+
case 'nexus_connected':
|
|
48
|
+
return `${actor}connected a sovereign root into an alliance${cell}.`.trim();
|
|
49
|
+
case 'alliance_root_bound':
|
|
50
|
+
return `A sovereign root joined an allied phyle${cell}.`.trim();
|
|
51
|
+
case 'rally_called':
|
|
52
|
+
return `A new rally directive lit up${cell}.`.trim();
|
|
53
|
+
case 'allied_border_held':
|
|
54
|
+
return `Allied borders held firm${cell}.`.trim();
|
|
55
|
+
case 'alliance_surged':
|
|
56
|
+
return `Allied pressure pushed${cell} into surge state.`.trim();
|
|
32
57
|
case 'unlock':
|
|
33
58
|
return `A new latitude band unlocked${cell}.`.trim();
|
|
34
59
|
case 'tick_surged':
|
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
* These functions have NO side effects, NO logging, and NO mutable state access.
|
|
4
4
|
* They are deterministic and testable in isolation.
|
|
5
5
|
*/
|
|
6
|
+
export interface ScreenGridMetadata {
|
|
7
|
+
isSpherical: false;
|
|
8
|
+
cols: number;
|
|
9
|
+
rows: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SphericalScreenGridMetadata {
|
|
12
|
+
isSpherical: true;
|
|
13
|
+
cols: number;
|
|
14
|
+
rows: number;
|
|
15
|
+
rowColumnCounts: number[];
|
|
16
|
+
indexToRow: number[];
|
|
17
|
+
indexToColumn: number[];
|
|
18
|
+
}
|
|
19
|
+
export interface SphericalScreenGridLayout {
|
|
20
|
+
positions: [number, number, number][];
|
|
21
|
+
metadata: SphericalScreenGridMetadata;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate a screen-space hex grid that preserves local neighbor spacing while
|
|
25
|
+
* carrying enough metadata to project each row across the full spherical span.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateScreenSpaceSphericalHexGrid(_targetCount: number, screenWidth: number, screenHeight: number, curveUDeg: number, curveVDeg: number, densityMultiplier: number | undefined, hexRadius: number): SphericalScreenGridLayout;
|
|
28
|
+
/**
|
|
29
|
+
* Convert a spherical grid index into normalized [0,1] coordinates used for
|
|
30
|
+
* longitude/latitude mapping, independent of the row's raw screen-space width.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getNormalizedScreenSpaceSphericalCoordinates(index: number, metadata: SphericalScreenGridMetadata, curveUDeg: number): {
|
|
33
|
+
u: number;
|
|
34
|
+
v: number;
|
|
35
|
+
} | null;
|
|
6
36
|
/**
|
|
7
37
|
* Calculate the bounding box of a set of 2D/3D positions.
|
|
8
38
|
* @pure - No side effects, deterministic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hexgrid-math.d.ts","sourceRoot":"","sources":["../../src/workers/hexgrid-math.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;;;;;;;EAmBlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC3B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC3B,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzC,WAAW,EAAE,OAAO,GACnB,MAAM,CAeR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAalC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAER;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAgBR"}
|
|
1
|
+
{"version":3,"file":"hexgrid-math.d.ts","sourceRoot":"","sources":["../../src/workers/hexgrid-math.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,KAAK,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACtC,QAAQ,EAAE,2BAA2B,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CACjD,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,iBAAiB,oBAAM,EACvB,SAAS,EAAE,MAAM,GAChB,yBAAyB,CAmE3B;AAED;;;GAGG;AACH,wBAAgB,4CAA4C,CAC1D,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,2BAA2B,EACrC,SAAS,EAAE,MAAM,GAChB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkCjC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;;;;;;;EAmBlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC3B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC3B,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzC,WAAW,EAAE,OAAO,GACnB,MAAM,CAeR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAalC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAER;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,GACxC,MAAM,CAgBR"}
|
|
@@ -3,6 +3,98 @@
|
|
|
3
3
|
* These functions have NO side effects, NO logging, and NO mutable state access.
|
|
4
4
|
* They are deterministic and testable in isolation.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate a screen-space hex grid that preserves local neighbor spacing while
|
|
8
|
+
* carrying enough metadata to project each row across the full spherical span.
|
|
9
|
+
*/
|
|
10
|
+
export function generateScreenSpaceSphericalHexGrid(_targetCount, screenWidth, screenHeight, curveUDeg, curveVDeg, densityMultiplier = 1.4, hexRadius) {
|
|
11
|
+
const positions = [];
|
|
12
|
+
const rowColumnCounts = [];
|
|
13
|
+
const indexToRow = [];
|
|
14
|
+
const indexToColumn = [];
|
|
15
|
+
if (!Number.isFinite(screenWidth) ||
|
|
16
|
+
!Number.isFinite(screenHeight) ||
|
|
17
|
+
!Number.isFinite(curveUDeg) ||
|
|
18
|
+
!Number.isFinite(curveVDeg) ||
|
|
19
|
+
!Number.isFinite(densityMultiplier) ||
|
|
20
|
+
!Number.isFinite(hexRadius) ||
|
|
21
|
+
screenWidth <= 0 ||
|
|
22
|
+
screenHeight <= 0 ||
|
|
23
|
+
hexRadius <= 0) {
|
|
24
|
+
return {
|
|
25
|
+
positions,
|
|
26
|
+
metadata: {
|
|
27
|
+
isSpherical: true,
|
|
28
|
+
cols: 0,
|
|
29
|
+
rows: 0,
|
|
30
|
+
rowColumnCounts,
|
|
31
|
+
indexToRow,
|
|
32
|
+
indexToColumn,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const sqrt3 = Math.sqrt(3);
|
|
37
|
+
const horizontalSpacing = sqrt3 * hexRadius;
|
|
38
|
+
const verticalSpacing = 1.5 * hexRadius;
|
|
39
|
+
const densityScale = Math.sqrt(Math.max(0.1, densityMultiplier));
|
|
40
|
+
const cols = Math.max(3, Math.ceil((screenWidth / horizontalSpacing) * densityScale));
|
|
41
|
+
const rows = Math.max(1, Math.ceil((screenHeight / verticalSpacing) * densityScale));
|
|
42
|
+
const deg2rad = Math.PI / 180;
|
|
43
|
+
for (let row = 0; row < rows; row += 1) {
|
|
44
|
+
const y = row * verticalSpacing;
|
|
45
|
+
const rowProgress = rows === 1 ? 0.5 : (row + 0.5) / rows;
|
|
46
|
+
const lat = (rowProgress - 0.5) * (curveVDeg * deg2rad);
|
|
47
|
+
const latFactor = Math.max(0.3, Math.abs(Math.cos(lat)));
|
|
48
|
+
const effectiveColsForRow = Math.max(3, Math.round(cols * latFactor));
|
|
49
|
+
const rowOffset = row % 2 === 0 ? 0 : 0.5;
|
|
50
|
+
rowColumnCounts.push(effectiveColsForRow);
|
|
51
|
+
for (let col = 0; col < effectiveColsForRow; col += 1) {
|
|
52
|
+
const x = (col + rowOffset) * horizontalSpacing;
|
|
53
|
+
positions.push([x, y, 0]);
|
|
54
|
+
indexToRow.push(row);
|
|
55
|
+
indexToColumn.push(col);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
positions,
|
|
60
|
+
metadata: {
|
|
61
|
+
isSpherical: true,
|
|
62
|
+
cols,
|
|
63
|
+
rows,
|
|
64
|
+
rowColumnCounts,
|
|
65
|
+
indexToRow,
|
|
66
|
+
indexToColumn,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert a spherical grid index into normalized [0,1] coordinates used for
|
|
72
|
+
* longitude/latitude mapping, independent of the row's raw screen-space width.
|
|
73
|
+
*/
|
|
74
|
+
export function getNormalizedScreenSpaceSphericalCoordinates(index, metadata, curveUDeg) {
|
|
75
|
+
if (index < 0 ||
|
|
76
|
+
index >= metadata.indexToRow.length ||
|
|
77
|
+
metadata.rows <= 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const row = metadata.indexToRow[index];
|
|
81
|
+
const column = metadata.indexToColumn[index];
|
|
82
|
+
const rowColumnCount = metadata.rowColumnCounts[row];
|
|
83
|
+
if (row === undefined ||
|
|
84
|
+
column === undefined ||
|
|
85
|
+
rowColumnCount === undefined ||
|
|
86
|
+
rowColumnCount <= 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const rowOffset = row % 2 === 0 ? 0 : 0.5;
|
|
90
|
+
const isWrapped = Math.abs(curveUDeg) >= 359;
|
|
91
|
+
const rawU = (column + 0.5 + rowOffset) / rowColumnCount;
|
|
92
|
+
const u = isWrapped
|
|
93
|
+
? ((rawU % 1) + 1) % 1
|
|
94
|
+
: Math.min(1, Math.max(0, rawU / Math.max(1, 1 + rowOffset / rowColumnCount)));
|
|
95
|
+
const v = metadata.rows === 1 ? 0.5 : (row + 0.5) / metadata.rows;
|
|
96
|
+
return { u, v };
|
|
97
|
+
}
|
|
6
98
|
/**
|
|
7
99
|
* Calculate the bounding box of a set of 2D/3D positions.
|
|
8
100
|
* @pure - No side effects, deterministic
|