diff --git a/src/screens/Map.jsx b/src/screens/Map.jsx
index 5057551..ec53741 100644
--- a/src/screens/Map.jsx
+++ b/src/screens/Map.jsx
@@ -2,11 +2,18 @@ import { useState, useEffect } from 'react'
import { WAYPOINTS, ROUTES } from '../constants.js'
import { fetchMapCells } from '../api.js'
-// Bounding box — Mediterranean basin
const BBOX = { minLng: 5, maxLng: 38, minLat: 28, maxLat: 48 }
const W = 800
const H = 460
+const H5_IDS = {
+ ostia: '851e805bfffffff',
+ capua: '851e8333fffffff',
+ brundisium: '851e8ba3fffffff',
+ carthago: '85386e23fffffff',
+ alexandria: '853f5ba7fffffff',
+}
+
// H3 res-5 cell centre coordinates — hardcoded, h3-js not in browser bundle
const CENTRES = {
ostia: { lat: 41.73, lng: 12.23 },
@@ -16,21 +23,13 @@ const CENTRES = {
alexandria: { lat: 31.20, lng: 29.92 },
}
-// H3 res-5 hex string IDs — used for API calls
-const H5_IDS = {
- ostia: '851e805bfffffff',
- capua: '851e8333fffffff',
- brundisium: '851e8ba3fffffff',
- carthago: '85386e23fffffff',
- alexandria: '853f5ba7fffffff',
-}
-
const WAYPOINT_IDS = ['ostia', 'capua', 'brundisium', 'carthago', 'alexandria']
const ROUTE_ORDER = ['olive', 'wine', 'grain', 'linen']
+const EPOCH = 'roman_14bce'
-// Active epoch — Roman period for OTIVM-II
-// sl_offset_cm = -10 (effectively zero). Coastline matches modern sea level.
-const EPOCH = 'roman_14bce'
+// H7 cell approximate radius in SVG pixels — H7 inradius ~10km
+// At our projection (33° = 800px wide), 1° lng ≈ 24px, 10km ≈ 9px
+const H7_RADIUS = 9
function project(lat, lng) {
const x = ((lng - BBOX.minLng) / (BBOX.maxLng - BBOX.minLng)) * W
@@ -38,40 +37,15 @@ function project(lat, lng) {
return { x, y }
}
-// Approximate H7 cell position within its H5 parent using a sunflower spiral.
-// H7 cells are rendered as circles — exact hex geometry requires h3-js (server-side only).
-// The spiral distributes 49 cells across the H5 area. Visual approximation only.
-// Will be replaced when the API returns per-cell centroids.
-function h7ApproxPos(h5Id, cellIndex) {
- const cx = CENTRES[h5Id]
- if (!cx) return null
- const { x: cx_px, y: cy_px } = project(cx.lat, cx.lng)
- // Golden angle spiral — distributes points evenly across H5 area (~90km)
- // At our projection scale, H5 spans ~120px. Space cells ~16px apart.
- const SPACING = 14
- const angle = cellIndex * 2.399963 // golden angle radians
- const radius = SPACING * Math.sqrt(cellIndex)
- return {
- x: cx_px + Math.cos(angle) * radius,
- y: cy_px + Math.sin(angle) * radius,
- }
-}
-
-const H7_RADIUS = 8
-
export default function Map({ state }) {
const chapter = state?.chapter ?? 1
const [cellData, setCellData] = useState({})
- // Fetch H7 cells for all revealed waypoints.
- // A waypoint is revealed when chapter >= waypoint.chapter.
- // Ostia (chapter 1) is revealed from the start.
- // Cells are fetched once per waypoint and cached in state.
useEffect(() => {
WAYPOINT_IDS.forEach((id) => {
const wp = WAYPOINTS[id]
- if (chapter < wp.chapter) return // not yet revealed
- if (cellData[id]) return // already loaded
+ if (chapter < wp.chapter) return
+ if (cellData[id]) return
fetchMapCells(H5_IDS[id], EPOCH).then(data => {
if (data?.cells) {
setCellData(prev => ({ ...prev, [id]: data.cells }))
@@ -94,24 +68,24 @@ export default function Map({ state }) {
style={{ width: '100%', maxWidth: W, display: 'block', margin: '0 auto' }}
aria-label="Mediterranean trade map"
>
- {/* Sea — permanent darkness beyond fog of war */}
+ {/* Sea — permanent darkness */}
- {/* Fog of war — land H7 cells, revealed by chapter */}
+ {/* Fog of war — land H7 cells at real geographic positions */}
{WAYPOINT_IDS.map((id) => {
const wp = WAYPOINTS[id]
if (chapter < wp.chapter) return null
const cells = cellData[id]
if (!cells) return null
- return cells.map((cell, cellIdx) => {
+ return cells.map((cell) => {
if (!cell.is_land) return null
- const pos = h7ApproxPos(id, cellIdx)
- if (!pos) return null
+ // lat/lon now come directly from the API — real H7 centroids
+ const { x, y } = project(cell.lat, cell.lon)
return (
{
const route = ROUTES.find((r) => r.id === routeId)
if (!route) return null
- // Route visible when both endpoints are revealed
const fromWp = WAYPOINTS[route.from]
const toWp = WAYPOINTS[route.to]
if (chapter < fromWp.chapter || chapter < toWp.chapter) return null