feat: replace radar with playable street map - #117
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Bugbot couldn't run - usage limit reachedBugbot 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) |
There was a problem hiding this comment.
💡 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 | |||
There was a problem hiding this comment.
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 👍 / 👎.
| 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] }; |
There was a problem hiding this comment.
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 👍 / 👎.
| 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); |
There was a problem hiding this comment.
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 👍 / 👎.
| 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]); |
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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 👍 / 👎.
| "framer-motion": "^10.16.16", | ||
| "lucide-react": "^1.23.0", | ||
| "mapbox-gl": "^3.0.1", | ||
| "maplibre-gl": "^6.5.0", |
There was a problem hiding this comment.
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'), |
There was a problem hiding this comment.
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 👍 / 👎.
| .map-camera-actions { | ||
| position: absolute; | ||
| z-index: 22; | ||
| top: 116px; | ||
| right: 10px; |
There was a problem hiding this comment.
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 👍 / 👎.
Player impact
Architecture
Verification
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_URLfor 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.
BlockOverlaydraws 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.