feat(game): NAVIGATION AI unlocks two-hop jumps in one action (#35)
CI / build + unit + e2e (pull_request) Failing after 3m11s
CI / build + unit + e2e (pull_request) Failing after 3m11s
This commit is contained in:
+12
-11
@@ -6,10 +6,12 @@ import type { SimulationNodeDatum } from 'd3-force'
|
||||
import { select } from 'd3-selection'
|
||||
import { zoom, zoomIdentity } from 'd3-zoom'
|
||||
import type { WorldState } from '@/lib/game/save'
|
||||
import { reachableWithin } from '@/lib/game/world'
|
||||
|
||||
// WORLD MAP tab — real world state from /api/world on a force-directed chart (d3-force) with
|
||||
// pan + zoom (d3-zoom), per #28. Clicking a system adjacent to the current one jumps there
|
||||
// (same POST /api/jump flow as before); the current and unreachable systems are dimmed and not
|
||||
// pan + zoom (d3-zoom), per #28. Clicking a system within jump range of the current one jumps
|
||||
// there (same POST /api/jump flow as before; two hops away once NAVIGATION AI is researched,
|
||||
// #35); the current and unreachable systems are dimmed and not
|
||||
// clickable, and every node is locked while a jump is in flight. Turn-based: fetch on mount,
|
||||
// refetch via the jump response — no polling. In wide mode (#36) the chart fills the whole tab
|
||||
// pane (no internal scrollbar); narrow keeps a fixed fallback height since the page scrolls naturally there.
|
||||
@@ -17,6 +19,7 @@ import type { WorldState } from '@/lib/game/save'
|
||||
interface WorldMapPanelProps {
|
||||
height?: number // fallback chart height when the pane gives no definite space (narrow mode)
|
||||
narrow?: boolean
|
||||
navAi?: boolean // #35 — NAVIGATION AI researched: two-hop systems are jumpable fields too
|
||||
onJumped?: () => void // #30 — let the parent refresh /api/system after a successful jump
|
||||
}
|
||||
|
||||
@@ -46,7 +49,7 @@ const STATUS_COLOR: Record<NodeStatus, string> = {
|
||||
offline: 'var(--border)',
|
||||
}
|
||||
|
||||
export function WorldMapPanel({ height = 380, narrow = false, onJumped }: WorldMapPanelProps) {
|
||||
export function WorldMapPanel({ height = 380, narrow = false, navAi = false, onJumped }: WorldMapPanelProps) {
|
||||
// Wide mode: the flex chain (Tabs → TabsContent → this panel) provides a definite pane size to fill (#36).
|
||||
const filling = !narrow
|
||||
const [world, setWorld] = useState<WorldState | null>(null)
|
||||
@@ -108,19 +111,17 @@ export function WorldMapPanel({ height = 380, narrow = false, onJumped }: WorldM
|
||||
const W = container.clientWidth || 800
|
||||
const H = container.clientHeight || height
|
||||
const current = world.systems.find((s) => s.current)!
|
||||
// Systems one jump away — the only clickable chart fields (the same set as before).
|
||||
const reachableIds = new Set(
|
||||
world.edges.filter(([a, b]) => a === current.id || b === current.id).map(([a, b]) => (a === current.id ? b : a))
|
||||
)
|
||||
// Clickable chart fields: one hop away, or two hops once NAVIGATION AI is researched (#35).
|
||||
const hops = reachableWithin(world.edges, current.id, navAi ? 2 : 1)
|
||||
|
||||
const nodes: ChartNode[] = world.systems.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name.toUpperCase(),
|
||||
status: s.current ? 'current' : reachableIds.has(s.id) ? 'jumpable' : 'offline',
|
||||
status: s.current ? 'current' : hops.has(s.id) ? 'jumpable' : 'offline',
|
||||
sublabel: s.current
|
||||
? 'CURRENT'
|
||||
: reachableIds.has(s.id)
|
||||
? (s.discovered ? 'DISCOVERED // JUMP' : 'UNKNOWN // JUMP')
|
||||
: hops.has(s.id)
|
||||
? `${s.discovered ? 'DISCOVERED' : 'UNKNOWN'} // JUMP${(hops.get(s.id) ?? 1) > 1 ? ' ×2' : ''}`
|
||||
: s.discovered ? 'DISCOVERED' : 'UNKNOWN',
|
||||
x: (s.x / 100) * W, // seed from the API coordinates so the layout settles near the old chart
|
||||
y: (s.y / 100) * H,
|
||||
@@ -245,7 +246,7 @@ export function WorldMapPanel({ height = 380, narrow = false, onJumped }: WorldM
|
||||
simulation.stop()
|
||||
svg.remove()
|
||||
}
|
||||
}, [world, jumpingId, jump, height])
|
||||
}, [world, jumpingId, jump, height, navAi])
|
||||
|
||||
if (error && !world) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user