@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 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 root claims, recursive subdivision, and delegated territory overlays
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>` | Claimed sovereign roots |
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;AAqOlB,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwjBhD,CAAC;AAGF,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,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
- } }), children && (_jsx("div", { style: {
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: 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;AAI/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;AAoLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,4CA4xIjB,CAAA;AAk7DD,eAAe,OAAO,CAAC"}
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 = generateSphericalHexGrid(totalHexagons, screenWidth, screenHeight, curveUDeg, curveVDeg, densityMultiplier, effectiveHexRadius // Pass the actual hex radius so spacing matches drawing
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 u = (x - minX) / Math.max(1e-6, w);
1308
- const v = (y - minY) / Math.max(1e-6, h);
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 = gridMetadataRef.current?.isSpherical ?? false;
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 nu = (np[0] - minX) / Math.max(1e-6, w);
1442
- const nv = (np[1] - minY) / Math.max(1e-6, h);
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,EAAgC,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE9E,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,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,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,EAAE,sBAAsB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA6F5C"}
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
- : claimed.has(cell.cellId)
38
- ? '#63f2c6'
39
- : '#1f2a4a');
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
- if (typeof event.instanceId !== 'number') {
59
- return;
60
- }
61
- const cell = cells[event.instanceId];
62
- if (cell) {
63
- onSelectCell?.(cell);
64
- }
65
- }, onPointerMove: (event) => {
66
- if (typeof event.instanceId !== 'number') {
67
- onHoverCell?.(null);
68
- return;
69
- }
70
- const cell = cells[event.instanceId];
71
- onHoverCell?.(cell ?? null);
72
- }, onPointerOut: () => {
73
- onHoverCell?.(null);
74
- }, children: _jsx("meshStandardMaterial", { transparent: true, opacity: 0.92, metalness: 0.14, roughness: 0.42 }) }));
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"}
@@ -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 / (config.rowCount - 1);
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;AAgDD,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,oBAAoB,EAAE,GAC7B,gBAAgB,EAAE,CAmDpB"}
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buley/hexgrid-3d",
3
- "version": "3.6.1",
3
+ "version": "3.6.2",
4
4
  "description": "3D hexagonal grid visualization component for React",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",