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.
ObstructedMaze1Dlhb
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.
ObstructedMazeFull
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.)