Skip to content

feat: replace radar with playable street map - #117

Merged
BrandDead merged 1 commit into
main-tL2525from
feat/playable-territory-map
Aug 21, 2026
Merged

feat: replace radar with playable street map#117
BrandDead merged 1 commit into
main-tL2525from
feat/playable-territory-map

Conversation

@BrandDead

@BrandDead BrandDead commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Player impact

  • replaces the synthetic radar canvas with a real top-down vector street map even when no Mapbox token is configured
  • centers the selected 1208 W Las Olas block at its verified location and preserves a Home Block camera action
  • supports territory, inspect, and close-up placement zoom states
  • overlays fictionalized block footprints, streetside tactical cells, and current crew while preserving existing strategy state
  • keeps the crew picker visible through a body portal and makes deployment require a chosen map cell

Architecture

  • adds MapLibre GL JS 6.5.0 behind a project-owned PlayableMap component
  • uses a configurable style URL with OpenFreeMap as the token-free default
  • keeps vendor map objects out of Zustand/persistence
  • retains Mapbox-backed geocoding/static-image paths when configured

Verification

  • npm run validate
  • 38 test files passed
  • 641 tests passed
  • TypeScript passed
  • asset audit passed (102 assets, 5.73 MB / 20 MB)
  • new playable-map geometry tests: 3 passed
  • manually verified exact West Las Olas street/water/building context, territory zoom, visible crew picker, and unobstructed placement mode

Privacy

Real streets are geographic context. Territory footprints, tactical cells, and gameplay boundaries are explicitly fictionalized and do not claim exact private property data.


Note

Medium Risk
Adds a new map runtime and public tile dependency, plus a changed crew-placement flow. Gameplay stores and auth are unchanged, but camera/overlay bugs could break the core Maps loop.

Overview
The hood map no longer switches between Mapbox GL and a synthetic radar canvas. Territory always renders as a north-up MapLibre vector map (OpenFreeMap by default, VITE_SLIDE_MAP_STYLE_URL for production), so streets and buildings stay visible without a Mapbox token.

Crew drop is now a two-step map action: pick a member, then tap a fictionalized cell on a close-up overlay. BlockOverlay draws footprints, pins, and labels; a Home Block control restores the owned camera. Game state stays in existing stores.

Default Las Olas coordinates move to verified 1208 W Las Olas. Geometry helpers are covered by new unit tests. Mapbox remains only for geocoding/static images when configured.

Reviewed by Cursor Bugbot for commit 10a29dc. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
slide Ready Ready Preview Aug 21, 2026 6:56pm

@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6b38a212-8060-473d-8739-3decca334a56)

@BrandDead
BrandDead merged commit d3829ed into main-tL2525 Aug 21, 2026
6 checks passed
@BrandDead
BrandDead deleted the feat/playable-territory-map branch August 21, 2026 18:57

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10a29dc316

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,56 @@
# Decision Record: Playable Top-Down Territory Map

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record this map architecture in the project log

This lands a new mapping architecture and runtime dependency without adding a dated entry to docs/PROJECT_LOG.md, so the repository’s running record omits a meaningful decision and its operational implications; add the required log entry before landing.

AGENTS.md reference: AGENTS.md:L3-L5

Useful? React with 👍 / 👎.

Comment on lines +98 to 100
const selected = selectedBlockId ? blocks[selectedBlockId] : null;
const home = selected?.owner === 'player' ? selected : liveList.find((block) => block.owner === 'player');
return home ? { lat: home.lat, lng: home.lng } : { lat: LAS_OLAS_CENTER[1], lng: LAS_OLAS_CENTER[0] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Home Block anchored when selecting owned strips

When a player owns multiple blocks and clicks a different owned strip, selectBlock updates selectedBlockId, causing origin to move to that strip. The Home Block button then searches using this new origin and returns to the currently selected strip rather than the original home, while the procedural recon ring also jumps to the new origin. Preserve a stable home-block identity separately from the selected block.

Useful? React with 👍 / 👎.

Comment on lines +173 to +177
const click = (event: MapLayerMouseEvent) => {
const properties = event.features?.[0]?.properties;
if (!properties || properties.passable !== 1 || properties.occupied === 1) return;
const zone = block.grid[Number(properties.y)]?.[Number(properties.x)];
if (zone) onChooseCell(zone);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add a keyboard path for choosing a tactical cell

For keyboard-only or non-visual users, placement cannot be completed because the only path to onChooseCell is a pointer click on a canvas-rendered MapLibre layer; the cells expose no focus targets, key handler, or accessible list alternative. This also misses the non-visual placement alternative required by docs/DECISION_RECORD_PLAYABLE_MAP.md:51, leaving users stuck in placement mode except for cancellation.

Useful? React with 👍 / 👎.

Comment on lines +313 to +317
const mapCenter = useMemo<[number, number]>(() => {
if (placementDraft && activeBlock) return [activeBlock.lng, activeBlock.lat];
if (selectedMapBlock) return [selectedMapBlock.lng, selectedMapBlock.lat];
return [origin.lng, origin.lat];
}, [activeBlock, origin.lat, origin.lng, placementDraft, selectedMapBlock]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid recentering after every block-store tick

While the player is panning or zooming the map, the 30-second income tick replaces activeBlock with a new object even when its coordinates are unchanged. Because mapCenter depends on that object, it creates a new tuple, which triggers PlayableMap’s [center, ...] effect and eases the camera back to the selected/home block. Depend on coordinate primitives or compare coordinates before issuing the camera update so routine economy updates do not interrupt scouting.

Useful? React with 👍 / 👎.

return;
}

placeMember(block.id, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Confirm placement before persisting the selected cell

When a player taps any valid cell, this immediately calls placeMember, clears the draft, and starts the best-effort backend persistence; there is no confirm or undo stage despite the accepted camera contract listing both for placement mode. An accidental tap therefore commits the deployment before the player can review the cell’s exposure or income, so keep the chosen cell as draft state until explicit confirmation.

Useful? React with 👍 / 👎.

Comment thread frontend/package.json
"framer-motion": "^10.16.16",
"lucide-react": "^1.23.0",
"mapbox-gl": "^3.0.1",
"maplibre-gl": "^6.5.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the pnpm lockfile for MapLibre

The tracked frontend/pnpm-lock.yaml importer does not contain maplibre-gl, although the repository still has a pnpm workspace and documents pnpm install. In a CI environment using pnpm, installation fails before the build because pnpm install --help specifies that --frozen-lockfile is on by default in CI and will “fail if an update is needed.” Regenerate the pnpm lockfile alongside the npm lockfile.

Useful? React with 👍 / 👎.

id: block.id,
owner: block.owner,
address: block.address,
label: block.owner === 'player' ? 'Your block' : block.ownerGangName ?? (block.owner === 'npc' ? 'Rival block' : 'Open block'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve rival gang names in overlay features

For every NPC block produced through recon.map(toOverlay), the source value is named gangName, but toOverlay drops it and this new label reads only ownerGangName. Consequently all rival pins display the generic “Rival block” label, whereas the replaced tactical map showed each available gang name. Map the recon gang name into the overlay contract before building these features.

Useful? React with 👍 / 👎.

Comment on lines +377 to +381
.map-camera-actions {
position: absolute;
z-index: 22;
top: 116px;
right: 10px;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move the Home Block action below the zoom controls

The MapLibre navigation group starts at 64px and contains two 38px-high zoom buttons, so it extends to roughly 140px, while this higher-z-index action begins at 116px on the same right edge. The Home Block button therefore covers about 24px of the zoom-out control and intercepts its clicks on every map view. Place the camera actions below the navigation group or move them to a different edge.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant