From 7d76673fa0707d204c7d084c8f90c8133c22a31a Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 29 Jun 2026 18:13:33 -0400 Subject: feat: migrated map (and bfs) to full 3D instead of individual 2D maps on different planes. --- internal/validate/checks.go | 102 +++++++++++++++-------------------------- internal/validate/grid_test.go | 61 ++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 70 deletions(-) (limited to 'internal/validate') diff --git a/internal/validate/checks.go b/internal/validate/checks.go index a41ad86..d835a53 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -870,20 +870,29 @@ func validateRoomWiring(s Source) []Issue { } -// validateRoomGrid lays each reachable horizontal plane on a 2D grid starting -// from the configured root rooms and reports when the exit layout cannot be -// embedded without conflict: +// validateRoomGrid embeds the entire reachable world on a single 3D grid +// starting from the configured root rooms and reports when the exit layout +// cannot be embedded without conflict: // - overlap: two distinct rooms land on the same grid cell. // - twist: one room is forced onto two different grid cells. // -// Up/Down exits don't move on the grid; each leads to a new plane that is laid -// out independently (fresh origin), so one root validates every reachable -// floor. Exit conditions are ignored (geometry is independent of gating), and +// All 10 exits (including Up/Down) participate in coordinate assignment: +// horizontal exits shift (x,y); Up/Down shift z at the same (x,y). +// Exit conditions are ignored (geometry is independent of gating), and // exits to nonexistent rooms are skipped (covered by referential checks). func validateRoomGrid(s Source) []Issue { var issues []Issue roomIndex := s.World.RoomIndex() + load := func(id int) (*world.Room, bool) { + r, err := s.World.LoadRoom(id) + if err != nil { + return nil, false + } + return r, true + } + include := func(id int) bool { return roomIndex[id] } + placed := make(map[int]bool) var seeds []int for _, r := range s.RootRooms { @@ -892,71 +901,34 @@ func validateRoomGrid(s Source) []Issue { } } - for len(seeds) > 0 { - origin := seeds[0] - seeds = seeds[1:] + for _, origin := range seeds { if placed[origin] { continue } - coordOf := map[int][2]int{origin: {0, 0}} - roomAt := map[[2]int]int{{0, 0}: origin} - placed[origin] = true - queue := []int{origin} - - for len(queue) > 0 { - rid := queue[0] - queue = queue[1:] - room, err := s.World.LoadRoom(rid) - if err != nil { - continue + grid := world.BuildGrid(origin, load, include, func(c world.GridConflict) { + switch c.Kind { + case "twist": + issues = append(issues, Issue{ + Level: "ERROR", + Type: "integrity", + Message: fmt.Sprintf( + "Grid twist: room %d (via %d %s) maps to (%d,%d,%d) but was already placed at (%d,%d,%d) [origin %d]", + c.Target, c.From, c.Dir, c.Want[0], c.Want[1], c.Want[2], c.Existing[0], c.Existing[1], c.Existing[2], origin), + }) + case "overlap": + issues = append(issues, Issue{ + Level: "ERROR", + Type: "integrity", + Message: fmt.Sprintf( + "Grid overlap: room %d (via %d %s) wants (%d,%d,%d), already used by room %d [origin %d]", + c.Target, c.From, c.Dir, c.Want[0], c.Want[1], c.Want[2], c.Occupier, origin), + }) } - c := coordOf[rid] - for _, dir := range world.ExitOrder { - exit, ok := room.Exits[dir] - if !ok || exit.Room <= 0 || !roomIndex[exit.Room] { - continue - } - target := exit.Room - - if dir == world.Up || dir == world.Down { - if !placed[target] { - seeds = append(seeds, target) - } - continue - } - - d := world.DirectionDeltas[dir] - want := [2]int{c[0] + d[0], c[1] + d[1]} - - if existing, ok := coordOf[target]; ok { - if existing != want { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "integrity", - Message: fmt.Sprintf( - "Grid twist: room %d (via %d %s) maps to grid (%d,%d) but was already placed at (%d,%d) [plane origin %d]", - target, rid, dir, want[0], want[1], existing[0], existing[1], origin), - }) - } - continue - } - if occupier, ok := roomAt[want]; ok && occupier != target { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "integrity", - Message: fmt.Sprintf( - "Grid overlap: room %d (via %d %s) wants grid (%d,%d), already used by room %d [plane origin %d]", - target, rid, dir, want[0], want[1], occupier, origin), - }) - continue - } + }) - coordOf[target] = want - roomAt[want] = target - placed[target] = true - queue = append(queue, target) - } + for rid := range grid.Coord { + placed[rid] = true } } diff --git a/internal/validate/grid_test.go b/internal/validate/grid_test.go index b3f834a..716cec1 100644 --- a/internal/validate/grid_test.go +++ b/internal/validate/grid_test.go @@ -87,9 +87,8 @@ func TestGridTwist(t *testing.T) { } func TestGridMultiPlaneViaUpDown(t *testing.T) { - // Plane A is just room 1. Going up seeds plane B (origin 10), which has an - // overlap. This proves up/down crosses planes and frames are independent - // (room 1 and room 10 both sit at (0,0) without conflicting). + // Room 1 at (0,0,0) goes up to room 10 at (0,0,1). Rooms 13 and 14 both + // resolve to (1,1,1) — 3D overlap on the upper level. rooms := map[int]string{ 1: "exits:\n up: 10\n", 10: "exits:\n south: 12\n east: 11\n", @@ -99,8 +98,60 @@ func TestGridMultiPlaneViaUpDown(t *testing.T) { 14: "name: fourteen\n", } issues := runGridCheck(t, rooms, 1) - if !containsMsg(issues, "plane origin 10") { - t.Errorf("expected an overlap in plane origin 10, got: %+v", issues) + if !containsMsg(issues, "Grid overlap") { + t.Errorf("expected a 3D grid overlap, got: %+v", issues) + } +} + +func TestGrid3DCleanViaUpDown(t *testing.T) { + // Clean 3D layout: up/down maintain same (x,y) across z-levels. + // 1(0,0,0) up→2(0,0,1) east→3(1,0,1) down→4(1,0,0) south→5(1,1,0) + rooms := map[int]string{ + 1: "exits:\n up: 2\n", + 2: "exits:\n east: 3\n down: 1\n", + 3: "exits:\n down: 4\n west: 2\n", + 4: "exits:\n south: 5\n up: 3\n", + 5: "name: five\n exits:\n north: 4\n", + } + if issues := runGridCheck(t, rooms, 1); len(issues) != 0 { + t.Errorf("expected no grid issues, got: %+v", issues) + } +} + +func TestGrid3DOverlapUpDown(t *testing.T) { + // Two rooms land on the same 3D cell via up/down paths. + // 1(0,0,0) up→2(0,0,1). + // 1(0,0,0) east→3(1,0,0) up→4(1,0,1) west→5 wants (0,0,1) — already room 2. + rooms := map[int]string{ + 1: "exits:\n up: 2\n east: 3\n", + 2: "name: two\n", + 3: "exits:\n up: 4\n west: 1\n", + 4: "exits:\n west: 5\n down: 3\n", + 5: "name: five\n", + } + issues := runGridCheck(t, rooms, 1) + if !containsMsg(issues, "Grid overlap") { + t.Errorf("expected a 3D grid overlap, got: %+v", issues) + } +} + +func TestGrid3DTwistUpDown(t *testing.T) { + // Room 4 forced to two different coordinates via up/down paths. + // path1: 1 up→2(0,0,1) south→3(0,1,1) east→4 places 4 at (1,1,1) + // path2: 1 east→A(1,0,0) up→B(1,0,1) east→C(2,0,1) south→4 wants (2,1,1) + // 4 already at (1,1,1) from path1 → twist. + rooms := map[int]string{ + 1: "exits:\n up: 2\n east: 6\n", + 2: "exits:\n south: 3\n down: 1\n", + 3: "exits:\n east: 4\n north: 2\n", + 4: "name: four\n", + 6: "exits:\n up: 7\n west: 1\n", + 7: "exits:\n east: 8\n down: 6\n", + 8: "exits:\n south: 4\n west: 7\n", + } + issues := runGridCheck(t, rooms, 1) + if !containsMsg(issues, "Grid twist") { + t.Errorf("expected a 3D grid twist, got: %+v", issues) } } -- cgit v1.2.3