Skip to content

feat: Add method to fetch allowed actions on a tile, also misc changes#57

Merged
CLOEI merged 2 commits into
CLOEI:mainfrom
badewen:main
Apr 19, 2026
Merged

feat: Add method to fetch allowed actions on a tile, also misc changes#57
CLOEI merged 2 commits into
CLOEI:mainfrom
badewen:main

Conversation

@badewen

@badewen badewen commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

The next pathfinder will depend on this method (which should be finished soon)
Kept forgetting to switch branch ;-;

Summary by CodeRabbit

  • Bug Fixes

    • Fixed lock access membership check to ensure reliable permission evaluation.
  • New Features

    • Enhanced tile lock and permission system with more granular access levels.
    • Added world-wide lock support that coexists with per-tile locks.
    • Implemented parent/ownership resolution for nested locks and admin-limited options.

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces a world-tile permission system: new bitflags, permission resolution methods, tracking of a world-lock tile index, conversion of lock access UIDs from i32 to u32, and two new tile constants for builder/non-worldlock IDs.

Changes

Cohort / File(s) Summary
Bot type alignment
src/bot/core.rs
Removed an i32 cast in Bot::has_access membership check to align with TileType::Lock.access_uids now using u32.
Tile constants
src/world/constants.rs
Added BUILDERS_LOCK_TILE_ID (u16 = 4994) and NON_WORLDLOCK_TILE_IDS (&[u16] = &[202, 204, 206, 4994]).
Permission & lock system
src/world/mod_impl.rs
Added WorldTilePermission and TileLockFlags bitflags, world_lock_index: Option<usize> to WorldTileMap, get_tile_parent() and get_tile_permission() methods, updated World::update_tile logic, and changed TileType::Lock.access_uids from Vec<i32> to Vec<u32> (parsing and access_count adjusted).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Map as WorldTileMap
    participant Tile as Tile/Lock
    participant Resolver as PermissionResolver

    Client->>Map: get_tile_permission(x,y,user_id)
    Map->>Map: fetch tile at (x,y)
    alt tile has TileType::Lock (direct)
        Map->>Resolver: check direct lock (owner_uid, access_uids, flags)
        Resolver-->>Map: permission (FULL/NONE/BUILD/BREAK)
    else tile has parent
        Map->>Map: fetch parent tile
        Map->>Resolver: resolve parent lock (flags, access_uids)
        Resolver-->>Map: permission (FULL/BUILD/BREAK/NONE)
    else standard tile
        Map->>Map: check world_lock_index
        alt world_lock exists
            Map->>Tile: read world-lock TileType::Lock
            Map->>Resolver: resolve world-lock (owner/access/public)
            Resolver-->>Map: permission
        else
            Resolver-->>Map: default permission (FULL)
        end
    end
    Map-->>Client: WorldTilePermission
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Fix world related things #55: Modifies TileType::Lock.access_uids handling and Bot::has_access membership checks; touches the same lock-access/type-alignment code paths.

Poem

🐰 I hopped through tiles in morning light,
Counting UIDs that now fit right,
Parent locks whisper, world-locks sing,
Flags decide who builds the ring,
A tiny hop—permissions take flight. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title partially captures the main changes: it mentions adding a method to fetch tile actions, but downplays significant structural changes like the access_uids type conversion and world-lock index refactoring as 'misc changes'.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bot/core.rs`:
- Around line 2291-2293: has_access() currently only checks
TileType::Lock.access_uids which denies access to bots that own the tile/world;
update the TileType::Lock branch in has_access() to also return true when the
locking tile's owner UID equals bot_uid (e.g., tile.owner_uid == bot_uid) or
when the bot is the world owner (compare bot_uid to world.owner_uid), so owned
locks do not block collect() or find_path(); modify the condition that currently
uses access_uids.contains(&bot_uid) to include these ownership checks.

In `@src/world/mod_impl.rs`:
- Around line 349-350: TileType::Lock is constructed with access_count and
access_uids but access_count is left as the original wire count while
access_uids is filtered to only positive IDs; make them consistent by deriving
access_count from the filtered access_uids (e.g. access_count =
access_uids.len() as u32) instead of using the raw wire value, and apply the
same change in the other construction site that mirrors this logic (the second
block that handles access_count/access_uids).
- Line 111: The field world_lock_index on the WorldTileMap is only set in
WorldTileMap::parse() and can become stale when tiles are changed; update_tile()
and any bot tile-update handlers must update this cache so get_tile_permission()
never reads a stale index. Modify World::update_tile (and the tile mutation
handlers) to either recompute world_lock_index by scanning for the lock tile
after a mutation, or invalidate it (set to None) and ensure
WorldTileMap::parse()/accessors lazily recompute it when needed; keep references
to world_lock_index, WorldTileMap::parse(), World::update_tile(), and
get_tile_permission() to locate the fixes. Ensure any code paths that
convert/place/remove locks update or clear world_lock_index immediately after
the mutation.
🪄 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

Run ID: 4ecc4270-837d-4aa1-b744-9937bb1762f0

📥 Commits

Reviewing files that changed from the base of the PR and between 7d0770f and 585d09c.

📒 Files selected for processing (3)
  • src/bot/core.rs
  • src/world/constants.rs
  • src/world/mod_impl.rs

Comment thread src/bot/core.rs
Comment on lines 2291 to 2293
if let TileType::Lock { access_uids, .. } = &tile.tile_type {
if access_uids.contains(&(bot_uid as i32)) {
if access_uids.contains(&bot_uid) {
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Owners still fail has_access().

This only checks access_uids, so a bot in a world it owns but has not explicitly added to the ACL still gets false. collect() and find_path() both use this flag, so owned locks remain path-blocking.

Suggested fix
-                if let TileType::Lock { access_uids, .. } = &tile.tile_type {
-                    if access_uids.contains(&bot_uid) {
+                if let TileType::Lock { owner_uid, access_uids, .. } = &tile.tile_type {
+                    if *owner_uid == bot_uid || access_uids.contains(&bot_uid) {
                         return true;
                     }
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if let TileType::Lock { access_uids, .. } = &tile.tile_type {
if access_uids.contains(&(bot_uid as i32)) {
if access_uids.contains(&bot_uid) {
return true;
if let TileType::Lock { owner_uid, access_uids, .. } = &tile.tile_type {
if *owner_uid == bot_uid || access_uids.contains(&bot_uid) {
return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bot/core.rs` around lines 2291 - 2293, has_access() currently only checks
TileType::Lock.access_uids which denies access to bots that own the tile/world;
update the TileType::Lock branch in has_access() to also return true when the
locking tile's owner UID equals bot_uid (e.g., tile.owner_uid == bot_uid) or
when the bot is the world owner (compare bot_uid to world.owner_uid), so owned
locks do not block collect() or find_path(); modify the condition that currently
uses access_uids.contains(&bot_uid) to include these ownership checks.

Comment thread src/world/mod_impl.rs
pub width: u32,
pub height: u32,
pub tiles: Vec<Tile>,
pub world_lock_index: Option<usize>,

@coderabbitai coderabbitai Bot Apr 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep world_lock_index synchronized after tile mutations.

This cache is only populated during WorldTileMap::parse(), but tiles are later replaced by World::update_tile() and the bot’s tile-update handlers. After a world lock is placed, removed, or converted, get_tile_permission() can keep using a stale index or never see the new lock.

Also applies to: 120-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/world/mod_impl.rs` at line 111, The field world_lock_index on the
WorldTileMap is only set in WorldTileMap::parse() and can become stale when
tiles are changed; update_tile() and any bot tile-update handlers must update
this cache so get_tile_permission() never reads a stale index. Modify
World::update_tile (and the tile mutation handlers) to either recompute
world_lock_index by scanning for the lock tile after a mutation, or invalidate
it (set to None) and ensure WorldTileMap::parse()/accessors lazily recompute it
when needed; keep references to world_lock_index, WorldTileMap::parse(),
World::update_tile(), and get_tile_permission() to locate the fixes. Ensure any
code paths that convert/place/remove locks update or clear world_lock_index
immediately after the mutation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Noted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@badewen, understood! When you do get around to addressing it, the simplest approach would be to invalidate the cache in World::update_tile() by setting self.tile_map.world_lock_index = None after the tile is replaced — and then lazily recompute it on first access in get_tile_permission(). That way you don't need to do a full scan on every tile update, only when the world-lock index is actually queried. Feel free to reach out if you'd like help drafting the fix.

Comment thread src/world/mod_impl.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/world/mod_impl.rs (1)

49-59: ⚠️ Potential issue | 🟠 Major

world_lock_index can still go stale here.

This only refreshes the cache when the incoming tile is a world lock. If the cached world lock is removed/replaced, or get_tile_mut() fails after Line 55, world_lock_index keeps pointing at stale data and get_tile_permission() will resolve permissions against the wrong tile.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/world/mod_impl.rs` around lines 49 - 59, The code sets
self.tile_map.world_lock_index prematurely and never clears it when a world-lock
tile is removed or when get_tile_mut() fails; parse the Tile first but do NOT
mutate world_lock_index before confirming the target tile exists and is updated:
call self.get_tile_mut(x,y) and fail early if None, then assign *target_tile =
tile, and after the assignment update world_lock_index—set Some(idx) only if the
stored tile.type is Lock and fg not in NON_WORLDLOCK_TILE_IDS, otherwise if
self.tile_map.world_lock_index == Some(idx) set it to None so the cache is
cleared when a lock is replaced/removed (also ensure you don't change
world_lock_index on parse failure).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/world/mod_impl.rs`:
- Around line 166-173: The index calculation does unchecked u32 math and can
wrap before casting to usize; change get_tile_parent to perform bounds-safe
indexing by converting operands to usize and using checked_mul/checked_add
(e.g., let x = x as usize; let y = y as usize; let idx =
y.checked_mul(self.width as usize).and_then(|v| v.checked_add(x)).and_then(|i|
if i < self.tiles.len() { Some(i) } else { None }) ), returning None on
overflow/out-of-bounds, then proceed to check TileFlags::HAS_PARENT and use
tile.parent_block as usize via safe indexing (self.tiles.get(parent_idx)). Apply
the same checked-index pattern to the other new tile helper that computes y *
self.width + x (the method referenced in the review).
- Around line 183-185: The pattern matches are attempting to move non-Copy enums
out of borrowed structs; change the match arms to match on references (e.g., use
&tile.tile_type, &world_lock.tile_type, &lock.tile_type) and adjust comparisons
to dereference any borrowed fields (for example compare user_id to *owner_uid
when owner_uid is a &Uid) so you don't move values out of &Tile; update the
three occurrences around the TileType::Lock matches (the block that returns
WorldTilePermission::FULL_ACCESS / NONE and the similar matches at the other
noted locations) accordingly.

---

Duplicate comments:
In `@src/world/mod_impl.rs`:
- Around line 49-59: The code sets self.tile_map.world_lock_index prematurely
and never clears it when a world-lock tile is removed or when get_tile_mut()
fails; parse the Tile first but do NOT mutate world_lock_index before confirming
the target tile exists and is updated: call self.get_tile_mut(x,y) and fail
early if None, then assign *target_tile = tile, and after the assignment update
world_lock_index—set Some(idx) only if the stored tile.type is Lock and fg not
in NON_WORLDLOCK_TILE_IDS, otherwise if self.tile_map.world_lock_index ==
Some(idx) set it to None so the cache is cleared when a lock is replaced/removed
(also ensure you don't change world_lock_index on parse failure).
🪄 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

Run ID: 63ee28f5-b203-4119-bbea-ab3d001053af

📥 Commits

Reviewing files that changed from the base of the PR and between 585d09c and b808bc8.

📒 Files selected for processing (1)
  • src/world/mod_impl.rs

Comment thread src/world/mod_impl.rs
Comment on lines +166 to +173
pub fn get_tile_parent(&self, x: u32, y: u32) -> Option<&Tile> {
let idx = (y * self.width + x) as usize;
if idx >= self.tiles.len() { return None; }

let tile = self.tiles.get(idx).unwrap();
if !tile.flags.contains(TileFlags::HAS_PARENT) { return None; }

self.tiles.get(tile.parent_block as usize)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use checked index math in the new tile helpers.

Both methods compute y * self.width + x in u32. For oversized coordinates that can wrap before the cast to usize, so an invalid query may read a different tile instead of returning None / WorldTilePermission::NONE. Reusing the checked indexing pattern from World::get_tile() would avoid that edge case.

Also applies to: 177-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/world/mod_impl.rs` around lines 166 - 173, The index calculation does
unchecked u32 math and can wrap before casting to usize; change get_tile_parent
to perform bounds-safe indexing by converting operands to usize and using
checked_mul/checked_add (e.g., let x = x as usize; let y = y as usize; let idx =
y.checked_mul(self.width as usize).and_then(|v| v.checked_add(x)).and_then(|i|
if i < self.tiles.len() { Some(i) } else { None }) ), returning None on
overflow/out-of-bounds, then proceed to check TileFlags::HAS_PARENT and use
tile.parent_block as usize via safe indexing (self.tiles.get(parent_idx)). Apply
the same checked-index pattern to the other new tile helper that computes y *
self.width + x (the method referenced in the review).

Comment thread src/world/mod_impl.rs
Comment on lines +183 to +185
if let TileType::Lock { owner_uid, .. } = tile.tile_type {
return if user_id == owner_uid { WorldTilePermission::FULL_ACCESS }
else { WorldTilePermission::NONE };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Rust, does if let Enum::Variant { .. } = borrowed_struct.fieldcompile whenborrowed_struct: &Structandfield is a non-Copy enum? What is the correct way to pattern-match that field by reference?

💡 Result:

No, if let Enum::Variant { .. } = borrowed_struct.field does not compile when borrowed_struct: &Struct and field is a non-Copy enum. This attempts to move the non-Copy enum value out of the borrowed struct field, which is not allowed ("cannot move out of borrowed content"). The correct way to pattern-match the field by reference is to match on a reference to the field: if let Enum::Variant { .. } = &borrowed_struct.field { ... } This works because the pattern automatically dereferences the & reference (via reference patterns) and matches the enum value without moving it. Inside the if let block, you have an immutable borrow of the field limited to that scope, avoiding partial borrow issues in most cases. For struct-like variants, use { .. } to ignore fields or bind specific ones like { field1, .. }. For match, use: match &borrowed_struct.field { Enum::Variant { .. } => { ... }, _ => { ... }, } Modern Rust (1.26+) allows matching &enum_value directly without ref in subpatterns for many cases due to match ergonomics.

Citations:


🏁 Script executed:

# Find and examine the file structure
find . -name "mod_impl.rs" -path "*/world/*" | head -5

Repository: CLOEI/Mori

Length of output: 77


🏁 Script executed:

# Look at the specific lines mentioned in the review
sed -n '180,220p' src/world/mod_impl.rs | cat -n

Repository: CLOEI/Mori

Length of output: 2134


🏁 Script executed:

# Find the TileType definition to check derives
rg "enum TileType|pub enum TileType" -A 15

Repository: CLOEI/Mori

Length of output: 1481


🏁 Script executed:

# Check the function signature and context around line 183
sed -n '170,230p' src/world/mod_impl.rs | cat -n

Repository: CLOEI/Mori

Length of output: 3303


🏁 Script executed:

# Find the complete TileType enum definition with derives
rg "enum TileType" -B 5 -A 30 src/world/mod_impl.rs

Repository: CLOEI/Mori

Length of output: 769


🏁 Script executed:

# Check if the code actually compiles by looking at Cargo.toml and trying to understand the project structure
head -20 Cargo.toml

Repository: CLOEI/Mori

Length of output: 590


🏁 Script executed:

# Verify what `tile` is by looking at the exact types in the function
sed -n '175,195p' src/world/mod_impl.rs | cat -n

Repository: CLOEI/Mori

Length of output: 1197


Match TileType by reference in these pattern matches.

tile, world_lock, and lock are all borrowed references (&Tile). Matching directly on .tile_type attempts to move a non-Copy enum out of borrowed content, which will not compile. Match on &tile.tile_type, &world_lock.tile_type, and &lock.tile_type instead, and dereference copied fields where needed.

Suggested fix
-        if let TileType::Lock { owner_uid, .. } = tile.tile_type {
-            return if user_id == owner_uid { WorldTilePermission::FULL_ACCESS }
+        if let TileType::Lock { owner_uid, .. } = &tile.tile_type {
+            return if user_id == *owner_uid { WorldTilePermission::FULL_ACCESS }
             else { WorldTilePermission::NONE };
         }
@@
-                let TileType::Lock { owner_uid, ref access_uids, .. } = world_lock.tile_type else {
+                let TileType::Lock { owner_uid, access_uids, .. } = &world_lock.tile_type else {
                     eprintln!("[world] world_lock tile type is not TileType::Lock. {:?}", world_lock.tile_type);
                     return WorldTilePermission::NONE;
                 };
 
-                if user_id == owner_uid {
+                if user_id == *owner_uid {
                     return WorldTilePermission::FULL_ACCESS;
                 }
@@
-            let TileType::Lock { settings, owner_uid, ref access_uids, .. } = lock.tile_type else {
+            let TileType::Lock { settings, owner_uid, access_uids, .. } = &lock.tile_type else {
                 eprintln!("[world] parent_tile type is not TileType::Lock. {:?}", lock.tile_type);
                 return WorldTilePermission::NONE;
             };
 
-            if user_id == owner_uid {
+            if user_id == *owner_uid {
                 return WorldTilePermission::FULL_ACCESS;
             }
 
-            let flags = TileLockFlags::from_bits_retain(settings);
+            let flags = TileLockFlags::from_bits_retain(*settings);

Also applies to: 192-195, 212-215

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/world/mod_impl.rs` around lines 183 - 185, The pattern matches are
attempting to move non-Copy enums out of borrowed structs; change the match arms
to match on references (e.g., use &tile.tile_type, &world_lock.tile_type,
&lock.tile_type) and adjust comparisons to dereference any borrowed fields (for
example compare user_id to *owner_uid when owner_uid is a &Uid) so you don't
move values out of &Tile; update the three occurrences around the TileType::Lock
matches (the block that returns WorldTilePermission::FULL_ACCESS / NONE and the
similar matches at the other noted locations) accordingly.

@CLOEI

CLOEI commented Apr 19, 2026

Copy link
Copy Markdown
Owner

Thanks for the hard work! Really appreciate it!

@CLOEI CLOEI merged commit 8381974 into CLOEI:main Apr 19, 2026
9 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