feat(#46 PR 2): per-region stat lever (regionChanges) + region-stat hover
CI / build + unit + e2e (pull_request) Successful in 2m31s
CI / build + unit + e2e (pull_request) Successful in 2m31s
v0.29.0 — the LLM can now target individual regions' stats via TurnResult.regionChanges (set/add per stat, clamped 0-100), applied after nation-wide stateChanges and before the weighted-median recompute. The map hover tooltip shows each region's own four stats. The stub stays deterministic (regionChanges: []). Fixes #46 (PR 2 of 2)
This commit is contained in:
@@ -707,6 +707,7 @@ function GameScreen({
|
||||
presetId={game.presetId}
|
||||
unowned={game.unowned ?? []}
|
||||
markers={game.markers ?? []}
|
||||
regionStats={game.regionStats ?? {}}
|
||||
onFocusNation={focusNation}
|
||||
/>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
type RegionMap,
|
||||
type RegionOwner,
|
||||
} from '@shared/protocol';
|
||||
import { MARKER_TYPES, type MapMarker, type Nation } from '@shared/types';
|
||||
import { MARKER_TYPES, type MapMarker, type Nation, type RegionStats } from '@shared/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { idToKey } from '@/lib/regions';
|
||||
|
||||
@@ -410,11 +410,29 @@ function hoverHtml(
|
||||
region?: RegionOwner,
|
||||
nation?: Nation,
|
||||
noMansLand = false,
|
||||
regionKey?: string,
|
||||
regionStats?: Record<string, RegionStats>,
|
||||
): string {
|
||||
if (!region || !nation) {
|
||||
return `<div class="hm-popup"><div class="hm-popup-title">${esc(subRegionName || 'Unclaimed')}</div><div class="hm-popup-sub">${unownedTooltipLabel(noMansLand)}</div></div>`;
|
||||
}
|
||||
const stats = [statRow('Mil', nation.stats.military), statRow('Eco', nation.stats.economy)].join('');
|
||||
// (v0.29, #46 PR 2) The region's OWN per-region stats (not the nation's
|
||||
// derived aggregate), looked up by region key. When the region has no stats
|
||||
// entry (defensive — an owned region should always have one) every stat
|
||||
// shows a placeholder dash. The dossier (NationPanel) still shows the
|
||||
// nation's derived stats, so this tooltip is the one place a single region's
|
||||
// numbers are visible on the map.
|
||||
const rs = regionKey ? regionStats?.[regionKey] : undefined;
|
||||
const row = (label: string, v?: number): string =>
|
||||
rs == null
|
||||
? `<div class="hm-stat"><span class="hm-stat-k">${label}</span><span class="hm-stat-v">–</span></div>`
|
||||
: statRow(label, v);
|
||||
const stats = [
|
||||
row('Mil', rs?.military),
|
||||
row('Eco', rs?.economy),
|
||||
row('Stab', rs?.stability),
|
||||
row('Pop', rs?.population),
|
||||
].join('');
|
||||
return `<div class="hm-popup">
|
||||
<div class="hm-popup-title">${esc(subRegionName || region.owner)}</div>
|
||||
<div class="hm-popup-owner"><span class="hm-chip" style="background:${region.color}"></span>${esc(region.owner)}</div>
|
||||
@@ -453,6 +471,14 @@ interface Props {
|
||||
* a top-most symbol layer (per-type glyph + name, per-type color).
|
||||
*/
|
||||
markers?: MapMarker[];
|
||||
/**
|
||||
* (v0.29, #46 PR 2) The game's per-region stats (`GameState.regionStats`,
|
||||
* keyed by region key). Used only by the hover tooltip to show a single
|
||||
* region's own four 0–100 stats (a region with no entry shows placeholders).
|
||||
* The dossier (NationPanel) is unaffected — it keeps the nation's derived
|
||||
* stats.
|
||||
*/
|
||||
regionStats?: Record<string, RegionStats>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -509,6 +535,7 @@ export default function WorldMap({
|
||||
unowned,
|
||||
onFocusNation,
|
||||
markers,
|
||||
regionStats,
|
||||
}: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const mapRef = useRef<maplibregl.Map | null>(null);
|
||||
@@ -524,6 +551,8 @@ export default function WorldMap({
|
||||
const unownedRef = useRef<Set<string>>(new Set(unowned ?? []));
|
||||
const onFocusNationRef = useRef(onFocusNation);
|
||||
const markersRef = useRef<MapMarker[]>(markers ?? []);
|
||||
// (v0.29, #46 PR 2) latest per-region stats, for the hover tooltip.
|
||||
const regionStatsRef = useRef<Record<string, RegionStats>>(regionStats ?? {});
|
||||
regionsRef.current = regions;
|
||||
nationsRef.current = nations;
|
||||
playerRef.current = playerNationId;
|
||||
@@ -531,6 +560,7 @@ export default function WorldMap({
|
||||
unownedRef.current = new Set(unowned ?? []);
|
||||
onFocusNationRef.current = onFocusNation;
|
||||
markersRef.current = markers ?? [];
|
||||
regionStatsRef.current = regionStats ?? {};
|
||||
|
||||
// Diffing state for the transient territory-change flash (owned by this component
|
||||
// so a resync with identical data never re-flashes).
|
||||
@@ -689,11 +719,11 @@ export default function WorldMap({
|
||||
// (v0.22, #16 P3) whether this region is no man's land (released
|
||||
// mid-game), so the ownerless tooltip can say so.
|
||||
const pKey = f.properties?.regionKey;
|
||||
const noMansLand =
|
||||
!region && typeof pKey === 'string' && pKey !== '' && unownedRef.current.has(pKey);
|
||||
const keyStr = typeof pKey === 'string' && pKey !== '' ? pKey : undefined;
|
||||
const noMansLand = !region && keyStr !== undefined && unownedRef.current.has(keyStr);
|
||||
hoverPopup
|
||||
.setLngLat(e.lngLat)
|
||||
.setHTML(hoverHtml(p?.name ?? subId, region, nation, noMansLand))
|
||||
.setHTML(hoverHtml(p?.name ?? subId, region, nation, noMansLand, keyStr, regionStatsRef.current))
|
||||
.addTo(map);
|
||||
map.getCanvas().style.cursor = 'pointer';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user