Skip to content

Obstructed maze

ObstructedMaze (issue #183): the outlier of the remaining families, per that issue's own scoping - it needs a real RoomGrid (#174, now built generically in grid.py's room_grid/room_mask/ room_grid_door_position/etc, reusable by MultiRoom/#182 too) plus a wholly new mechanic (actions.open_box, opening a Box to reveal a hidden Key). Per the issue's staged-rollout recommendation, this file starts with only the smallest variant, ObstructedMaze1Dlhb (a 1x2 room grid) - the other 12 registrations are deliberately deferred to follow-up work, once this one is confirmed to play correctly end to end.

Bases: Environment

A 1x2 room_grid: the agent starts in the left room, a target Ball sits in the right room, behind a locked Door. Optionally the door's key is hidden inside a Box in the left room (must be opened with toggle to reveal it), and/or a second Ball blocks the door on the player's own side.

The key_in_box/blocked flags are exactly MiniGrid's own - the three 1D* env ids are all the same class there too, registered with different values (verified against MiniGrid's actual registrations, room_size=6 throughout):

env id key_in_box blocked
1Dl-v0 False False
1Dlh-v0 True False
1Dlhb-v0 True True

Both are static (pytree_node=False) fields, so they branch at trace-construction time - each registered id gets its own entity layout (no Box at all when key_in_box=False, one Ball instead of two when blocked=False), rather than padding unused slots. Same convention as unlock.py's block_door.

Reward + termination only on picking up the target ball - the blocking ball can be picked up too (nothing in actions.pickup prevents it, matching real MiniGrid) without ending the episode, since terminations/rewards.on_target_fetched key off state.mission's specific tracked position.

Bases: Environment

A 3x3 room_grid. The centre room connects to num_quarters side rooms through unlocked doors; each of those side rooms connects on to two corner rooms through locked ones. Every locked door's key lives in the side room it is opened from - optionally hidden inside a Box, optionally with a Ball blocking the door. The target ball waits in one of the first num_quarters corners.

Verified against MiniGrid's actual ObstructedMaze_Full: room_size=6, keys placed via place_in_room(*side_room), blocking balls at door_pos - DIR_TO_VEC[door_idx] (i.e. backed off the door into the side room), corners sliced [:num_quarters] before one is drawn for the target.

This is MiniGrid's -v1 behaviour, under a -v0 name

MiniGrid ships both -v0 and -v1 of 2Dlhb, 1Q, 2Q and Full. Its -v1 fixes a placement bug in -v0: the blocking ball could be dropped onto a cell that already held a key, covering it and making the episode unsolvable. navix implements only the fixed behaviour and registers it under the plain -v0 name - blocking-ball cells are computed first (they follow deterministically from the doors) and then excluded, along with their own orthogonal neighbours, from every subsequent player/key/box draw. Excluding only a blocker's own cell (not its neighbourhood) still let a key/box land on every remaining side of a blocker sitting at a room-interior corner, fully enclosing it - a real, ~1-4%-of-seeds bug on the multi-box variants, found by review and fixed here (see test_obstructed_maze_full_blocking_balls_never_enclosed). 2Dl/2Dlh have no -v1 upstream and port across unchanged.

navix id MiniGrid id num_quarters key_in_box blocked
2Dl-v0 2Dl-v0 1 False False
2Dlh-v0 2Dlh-v0 1 True False
2Dlhb-v0 2Dlhb-v1 1 True True
1Q-v0 1Q-v1 1 True True
2Q-v0 2Q-v1 2 True True
Full-v0 Full-v1 4 True True

(2Dlhb and 1Q differ only by agent_room: 1Q starts in the centre, 2Dlhb already inside the side room.)