Skip to content

feat: add auto-zoom minimap to play region map setting - #21

Open
Puupuls wants to merge 4 commits into
sourcelocation:masterfrom
Puupuls:feat/auto-zoom-minimap-play-region
Open

Puupuls wants to merge 4 commits into
sourcelocation:masterfrom
Puupuls:feat/auto-zoom-minimap-play-region

Conversation

@Puupuls

@Puupuls Puupuls commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Adds an opt-in per-map setting that lets map authors have the guess minimap automatically zoom to the map's play region at the start of each round, instead of defaulting to a fully zoomed-out world view. This is useful for regional/country maps where the world view forces players to zoom in manually every round.

When enabled, the minimap fits to the geographic bounds (min/max latitude and longitude) of the map's locations when a round begins.

How it works:

  • A new Auto-zoom minimap to play region checkbox is available in the map edit form.
  • The play-region bounds are precomputed at map ingest time (min/max over the map's locations) and stored on the map. This avoids scanning locations on every match and keeps match setup cheap.
  • The flag and bounds are delivered to the client through MatchConfig (alongside the existing map name). No individual answer locations are exposed to the client — only the aggregate bounding box of the play area.
  • The in-game Leaflet minimap fits to those bounds on round start when the flag is set; otherwise behavior is unchanged. Longitude wrapping is handled so maps spanning the antimeridian still frame correctly.

Summary by CodeRabbit

  • New Features

    • Added an option to automatically zoom the guessing minimap to the map’s play region.
    • Added a lobby setting to enable or disable automatic play-region zooming.
    • Play-region boundaries are now calculated and preserved for maps, including regions crossing the antimeridian.
    • Existing maps are backfilled with geographic boundary data.
  • Bug Fixes

    • Improved minimap positioning and sizing when automatically fitting to play-region boundaries.
  • Tests

    • Added coverage for automatic zoom settings, boundary calculations, and antimeridian-crossing regions.

Puupuls added 2 commits July 13, 2026 23:02
Map authors can enable an "Auto-zoom minimap to play region" option. When
set, the guess minimap zooms to the map's location bounds at the start of
each round instead of the default world view.

Bounds are precomputed at ingest time (min/max lat/lng over the map's
locations) and delivered to the client through MatchConfig, so no answer
locations are exposed and no per-round scan is required.
Add unit coverage for the play-region bounds helper (e7-to-degree
conversion and the disabled/missing-bound guards), source assertions
that ingest computes bounds and the match plan applies them, and
frontend tests for deriving autoZoomBounds from the match config.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6102b624-fcbd-4431-96f1-3f634fd96423

📥 Commits

Reviewing files that changed from the base of the PR and between 75ba20d and 6bfe6f4.

📒 Files selected for processing (4)
  • db/migrations/000055_map_auto_zoom_play_region.down.sql
  • db/migrations/000055_map_auto_zoom_play_region.up.sql
  • pkg/persistence/map_ingest.go
  • pkg/persistence/maps.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/persistence/maps.go
  • pkg/persistence/map_ingest.go

📝 Walkthrough

Walkthrough

The change adds configurable play-region auto-zooming: map bounds are stored and computed during ingestion, exposed through match configuration, editable in map metadata, and applied by the guess minimap with antimeridian-aware bounds.

Changes

Play-region auto-zoom

Layer / File(s) Summary
Contracts and bounds storage
pkg/contracts/contracts.go, db/migrations/...
Contracts and the maps schema add the auto-zoom flag and nullable E7 latitude/longitude bounds, including migration backfills.
Persistence and match configuration
pkg/persistence/map_ingest.go, pkg/persistence/map_match_plans.go, pkg/persistence/maps.go, pkg/persistence/map_scan.go, pkg/persistence/map_match_plans_test.go
Ingestion computes shortest longitude intervals and latitude bounds, map queries expose the setting, and match plans convert valid stored bounds into MatchConfig.
Map editing controls
apps/web/features/lobby/..., apps/web/features/maps/lib/...
Map metadata editing adds an auto-zoom checkbox and includes the selected value in update requests.
Guess minimap auto-zoom
apps/web/components/..., apps/web/features/home/...
Home-model data conditionally carries play-region bounds, and GuessMap fits the guess map to antimeridian-aware corners once.
Estimated code review effort: 3 (Moderate) ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MapEditor
  participant MapAPI
  participant MatchPlanner
  participant HomePageGame
  participant GuessMap
  MapEditor->>MapAPI: save autoZoomPlayRegion
  MatchPlanner->>MapAPI: read setting and stored bounds
  MapAPI-->>MatchPlanner: return play-region configuration
  MatchPlanner->>HomePageGame: provide match configuration
  HomePageGame->>GuessMap: pass autoZoomBounds
  GuessMap->>GuessMap: fit bounds in guess mode
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: an opt-in map setting that auto-zooms the guess minimap to the play region.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/web/features/maps/lib/maps-client.test.ts (1)

153-158: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the enabled payload path as well.

This assertion only verifies the default-false case. Add a request with autoZoomPlayRegion: true and assert that updateMap sends true, covering the path used by MapEditMetadataModal.tsx Line [39].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/maps/lib/maps-client.test.ts` around lines 153 - 158,
Extend the map update payload tests around the existing autoZoomPlayRegion
assertion to include an enabled case. Submit a request with autoZoomPlayRegion
set to true and verify updateMap sends true, while preserving the existing
false-case coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/features/maps/lib/maps-client.ts`:
- Line 68: Update MapUpdateInput and its serialization path around
autoZoomPlayRegion so omitted updates preserve the stored setting instead of
becoming explicit false. Either make autoZoomPlayRegion required and update
every caller, or implement a tri-state representation that distinguishes
omission from false and propagates that distinction through the map update flow.

In `@db/migrations/000054_map_auto_zoom_play_region.up.sql`:
- Around line 7-21: Replace the longitude aggregation in the migration’s maps
backfill with the same longitude-aware wrapped-bounds algorithm used during
location ingest, so antimeridian-crossing locations produce narrow bounds. Reuse
the existing ingest-time representation or leave bounds unset for unsupported
cases; do not persist independent min(lng_e7)/max(lng_e7) values.

In `@pkg/persistence/map_ingest.go`:
- Around line 129-137: Represent longitude bounds as the shortest circular
interval rather than independent MIN/MAX values: update the official-map ingest
at pkg/persistence/map_ingest.go:129-137 and custom-map ingest at
pkg/persistence/map_ingest.go:251-259, using a consistent wrapped-interval
convention and recomputing existing bounds. In
apps/web/components/GuessMap.tsx:155-161, detect that convention, unwrap one
endpoint before calculating the reference longitude, and pass wrapped corners to
Leaflet; add an antimeridian regression case covering the behavior.

---

Nitpick comments:
In `@apps/web/features/maps/lib/maps-client.test.ts`:
- Around line 153-158: Extend the map update payload tests around the existing
autoZoomPlayRegion assertion to include an enabled case. Submit a request with
autoZoomPlayRegion set to true and verify updateMap sends true, while preserving
the existing false-case coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 003e7387-a761-469d-a4ba-72a69281a832

📥 Commits

Reviewing files that changed from the base of the PR and between f151cfb and 10027ea.

📒 Files selected for processing (18)
  • apps/web/components/GuessMap.tsx
  • apps/web/components/ui/types.ts
  • apps/web/features/home/model/derive-home-model.test.ts
  • apps/web/features/home/model/derive-home-model.ts
  • apps/web/features/home/model/types.ts
  • apps/web/features/home/page/HomePageGame.tsx
  • apps/web/features/lobby/components/MapMetadataFields.tsx
  • apps/web/features/lobby/components/maps/MapEditMetadataModal.tsx
  • apps/web/features/maps/lib/maps-client.test.ts
  • apps/web/features/maps/lib/maps-client.ts
  • db/migrations/000054_map_auto_zoom_play_region.down.sql
  • db/migrations/000054_map_auto_zoom_play_region.up.sql
  • pkg/contracts/contracts.go
  • pkg/persistence/map_ingest.go
  • pkg/persistence/map_match_plans.go
  • pkg/persistence/map_match_plans_test.go
  • pkg/persistence/map_scan.go
  • pkg/persistence/maps.go

difficulty: CustomMap["difficulty"];
thumbnailKey: string;
thumbnailVariant?: number;
autoZoomPlayRegion?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve the setting when the update input omits it.

MapUpdateInput.autoZoomPlayRegion is optional, but Line [134] serializes omission as explicit false. Since pkg/persistence/map_ingest.go Lines [278-305] unconditionally persist the received value, callers updating unrelated metadata can silently disable auto-zoom. Make the field required and update all callers, or use a tri-state contract that preserves the stored value when omitted.

Also applies to: 134-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/maps/lib/maps-client.ts` at line 68, Update MapUpdateInput
and its serialization path around autoZoomPlayRegion so omitted updates preserve
the stored setting instead of becoming explicit false. Either make
autoZoomPlayRegion required and update every caller, or implement a tri-state
representation that distinguishes omission from false and propagates that
distinction through the map update flow.

Comment thread db/migrations/000055_map_auto_zoom_play_region.up.sql
Comment thread pkg/persistence/map_ingest.go Outdated
Comment on lines +129 to +137
if _, err := tx.Exec(ctx, `
update maps set status='ready',location_count=$2,
bounds_min_lat_e7=(select min(lat_e7) from locations where map_storage_id=$3),
bounds_max_lat_e7=(select max(lat_e7) from locations where map_storage_id=$3),
bounds_min_lng_e7=(select min(lng_e7) from locations where map_storage_id=$3),
bounds_max_lng_e7=(select max(lng_e7) from locations where map_storage_id=$3),
updated_at=now()
where id=$1
`, mapID, len(parsed), mapStorageID); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Represent antimeridian bounds as a shortest circular interval. MIN/MAX turns locations near +/-180° into an almost-worldwide interval; Line 157 then centers it on 0°, so fitBounds zooms out instead of fitting the play region.

  • pkg/persistence/map_ingest.go#L129-L137: derive and store the shortest longitude arc for official maps, including a wrapped-interval convention.
  • pkg/persistence/map_ingest.go#L251-L259: apply the same derivation for custom-map ingest.
  • apps/web/components/GuessMap.tsx#L155-L161: detect the wrapped convention, unwrap one endpoint before computing the reference longitude, then pass wrapped corners to Leaflet.

Also recompute existing bounds and add an antimeridian regression case.

📍 Affects 2 files
  • pkg/persistence/map_ingest.go#L129-L137 (this comment)
  • pkg/persistence/map_ingest.go#L251-L259
  • apps/web/components/GuessMap.tsx#L155-L161
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/persistence/map_ingest.go` around lines 129 - 137, Represent longitude
bounds as the shortest circular interval rather than independent MIN/MAX values:
update the official-map ingest at pkg/persistence/map_ingest.go:129-137 and
custom-map ingest at pkg/persistence/map_ingest.go:251-259, using a consistent
wrapped-interval convention and recomputing existing bounds. In
apps/web/components/GuessMap.tsx:155-161, detect that convention, unwrap one
endpoint before calculating the reference longitude, and pass wrapped corners to
Leaflet; add an antimeridian regression case covering the behavior.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
pkg/persistence/map_match_plans_test.go (1)

53-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Source-text substring assertions are brittle regression guards.

TestMapIngestComputesPlayRegionBounds reads map_ingest.go as text and greps for exact call-site strings and a hardcoded positional parameter (auto_zoom_play_region=$9). This passes today but breaks on any harmless refactor (renaming the variable, reordering SQL parameters, reformatting the call) even when behavior is unchanged, and it can't detect a subtly wrong implementation that still contains the right substrings. Prefer a behavior-driven test (e.g., invoking computePlayRegionBoundsE7/UpdateCustomMap directly, or a pgxmock-based check of the executed query) over parsing source as text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/persistence/map_match_plans_test.go` around lines 53 - 65, The
TestMapIngestComputesPlayRegionBounds test should stop reading map_ingest.go and
asserting source-text substrings. Replace it with a behavior-driven test that
exercises computePlayRegionBoundsE7 and the relevant
UpdateCustomMap/create-or-import flow, using real persistence setup or pgxmock
to verify the computed bounds are passed and persisted without depending on SQL
placeholder positions or call formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/persistence/map_match_plans_test.go`:
- Around line 53-65: The TestMapIngestComputesPlayRegionBounds test should stop
reading map_ingest.go and asserting source-text substrings. Replace it with a
behavior-driven test that exercises computePlayRegionBoundsE7 and the relevant
UpdateCustomMap/create-or-import flow, using real persistence setup or pgxmock
to verify the computed bounds are passed and persisted without depending on SQL
placeholder positions or call formatting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f202917-f913-4828-a267-1fd034e8c2f4

📥 Commits

Reviewing files that changed from the base of the PR and between 10027ea and 75ba20d.

📒 Files selected for processing (8)
  • apps/web/components/GuessMap.tsx
  • apps/web/components/guess-map-bounds.test.ts
  • apps/web/components/guess-map-bounds.ts
  • apps/web/features/maps/lib/maps-client.test.ts
  • apps/web/features/maps/lib/maps-client.ts
  • db/migrations/000054_map_auto_zoom_play_region.up.sql
  • pkg/persistence/map_ingest.go
  • pkg/persistence/map_match_plans_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • db/migrations/000054_map_auto_zoom_play_region.up.sql
  • apps/web/components/GuessMap.tsx

@sourcelocation

Copy link
Copy Markdown
Owner

Hi there, thanks for the PR. While reviewing it I noticed a bug with the map upload flow, so I committed a fix that is required for db to function properly. Please fix conflicts before I can proceed.

…p-play-region

# Conflicts:
#	pkg/persistence/maps.go

This branch has not been deployed

No deployments
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.

2 participants