Skip to content

Conversation

@Sorikairox
Copy link
Contributor

Description:

Prevent users from "scrolling"/moving the map outside of viewport and "being lost and unable to find the map back". This can happen by pressing keys intentionally (RIP me) or conflict with browser shortcut containing a WASD key which would keep moving.

Related to reports from discord as highlighted by here:

https://discord.com/channels/1359946986937258015/1360078040222142564/1432750863994192003

Here is the new behavior. I am actively pressing WASD key to try to "get out" but I can't. Same with mouse clicks.

https://www.loom.com/share/a9ecb0a7514d4e54b92d24678833eb2e

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

sorikairo

@Sorikairox Sorikairox requested a review from a team as a code owner November 1, 2025 14:33
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 1, 2025

Walkthrough

A new private helper method clampOffsets() is added to constrain camera offsets within computed bounds. This method is invoked after zoom and drag operations to enforce boundary constraints, ensuring panning remains within half-viewport limits outside the map.

Changes

Cohort / File(s) Summary
Camera bounds enforcement
src/client/graphics/TransformHandler.ts
Added private helper method clampOffsets() to compute and enforce boundary constraints on camera offset X and Y values. Method is called after onZoom and onMove operations to constrain panning within half-viewport outside the map boundaries.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TransformHandler
    participant clampOffsets

    User->>TransformHandler: onMove(dragDelta)
    TransformHandler->>TransformHandler: update offsetX, offsetY
    TransformHandler->>clampOffsets: constrain offsets within bounds
    clampOffsets-->>TransformHandler: return clamped values
    TransformHandler->>TransformHandler: apply constrained offsets

    User->>TransformHandler: onZoom(scale)
    TransformHandler->>TransformHandler: update scale
    TransformHandler->>clampOffsets: constrain offsets within bounds
    clampOffsets-->>TransformHandler: return clamped values
    TransformHandler->>TransformHandler: apply constrained offsets
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Verify the bounds computation logic correctly accounts for current scale and viewport dimensions
  • Confirm clampOffsets() is called in all appropriate interaction handlers
  • Check that clamped offsets are properly applied to the transform state

Poem

📍 A camera roams where it pleases,
But boundaries make for peace and eases,
Half-viewport bounds now guard the way,
Keeping chaos held at bay! ✨

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Fix: prevent scrolling outside the map" is concise, clear, and directly describes the main change in the changeset. The title accurately reflects the core objective of adding bounds constraints to camera movement, as evidenced by the raw summary showing the addition of a clampOffsets() helper that enforces viewport boundaries during zoom and drag operations. The title is specific enough for someone scanning the commit history to immediately understand the primary change without any ambiguity or vagueness.
Description Check ✅ Passed The pull request description is clearly related to the changeset and provides valuable context about the problem being solved. It explains the user-facing issue (users becoming lost when scrolling the map outside the viewport), references specific reports from the community, and includes a demonstration video showing the fix in action. The description demonstrates that the author has completed relevant development tasks including testing, translations, and documentation, all of which directly support the implementation of preventing off-map scrolling described in the raw summary.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/client/graphics/TransformHandler.ts (1)

255-289: Clamping logic looks correct; rename gameH for consistency.

The mathematical derivation matches the implementation and correctly constrains panning to half-viewport outside the map. However, line 260 uses gameH while line 259 uses gameWidth—use gameHeight for consistency.

Apply this diff:

-    const gameH = this.game.height();
+    const gameHeight = this.game.height();
     const scale = this.scale;
 
     // Allow panning so that up to half of the viewport can be outside the map on each side.
@@ -271,8 +271,8 @@
     const minOffsetX = -gameWidth / 2 + (gameWidth - canvasWidth) / (2 * scale);
     const maxOffsetX = gameWidth / 2 + (gameWidth - canvasWidth) / (2 * scale);
 
-    const minOffsetY = -gameH / 2 + (gameH - canvasHeight) / (2 * scale);
-    const maxOffsetY = gameH / 2 + (gameH - canvasHeight) / (2 * scale);
+    const minOffsetY = -gameHeight / 2 + (gameHeight - canvasHeight) / (2 * scale);
+    const maxOffsetY = gameHeight / 2 + (gameHeight - canvasHeight) / (2 * scale);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daf3fee and 1b779d4.

📒 Files selected for processing (1)
  • src/client/graphics/TransformHandler.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T15:14:40.664Z
Learnt from: mokizzz
Repo: openfrontio/OpenFrontIO PR: 1956
File: src/client/InputHandler.ts:430-449
Timestamp: 2025-08-28T15:14:40.664Z
Learning: On trackpads and touchscreens, users don't expect purely vertical two-finger swipes to trigger zoom. Pan detection using deltaX !== 0 is appropriate since any horizontal movement indicates pan intent, and zoom should be handled via pinch gestures instead.

Applied to files:

  • src/client/graphics/TransformHandler.ts
🧬 Code graph analysis (1)
src/client/graphics/TransformHandler.ts (1)
src/client/InputHandler.ts (1)
  • DragEvent (61-66)
🔇 Additional comments (4)
src/client/graphics/TransformHandler.ts (4)

251-251: Good: Bounds enforced after zoom.

Calling clampOffsets() after zoom operations correctly prevents the view from moving outside the allowed bounds.


295-295: Good: Bounds enforced after drag.

Calling clampOffsets() after drag operations correctly prevents the view from being dragged outside the allowed bounds.


217-224: Consider clamping offsets in goTo() animation.

The goTo() method animates the camera toward a target (player, position, or unit), but doesn't call clampOffsets(). If the target is near a map edge, the animation might move the view outside the newly established bounds.

Verify whether this causes issues by:

  1. Zooming out fully
  2. Using "go to player" on a player at a map corner
  3. Observing if the view temporarily violates bounds during animation

If confirmed, apply this diff:

     this.offsetY += Math.max(
       Math.min((this.target.y - screenY) * r, CAMERA_MAX_SPEED),
       -CAMERA_MAX_SPEED,
     );
+    this.clampOffsets();
 
     this.changed = true;
   }

307-314: Note: override() bypasses clamping.

The override() method directly sets view position without calling clampOffsets(). This might be intentional (the name suggests bypassing normal constraints), but verify whether bounds should be enforced here or if there's a specific reason to allow unrestricted positioning.

Copy link
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

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

Thanks!

@evanpelle evanpelle added this to the v27 milestone Nov 1, 2025
@evanpelle evanpelle merged commit 935ff7a into openfrontio:main Nov 1, 2025
10 of 11 checks passed
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