Why doesn't generation work?

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    i want to fix backtracking for room generation
  2. What is the issue? Include screenshots / videos if possible!
    backtracking never works and literally nothing ive done helps
  3. What solutions have you tried so far? Did you look for solutions on the Creator Hub?
    i mean i have no clue why the issue happens so what am i meant to do :sob: (i have tried random stuff but none of it has helped)

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

local RoomSpawning = {}
local AngleBounds = {
	["Min"] = -135,
	["Max"] = 135	
}

--ok so
--line 92 - 113 is buggy and i cant fix :sob: (lines might have changed)
--i literally have no clue what the issue is

local CurrentTurnAngle = 0

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Modules = ReplicatedStorage.Modules
local Rooms = ReplicatedStorage.Rooms

local RoomModels = ReplicatedStorage.Models.RoomModels
local Tools = Modules.Tools

local TableTools = require(Tools.TableTools)

local SpawnedRooms = workspace.SpawnedRooms
local CurrentlyTriedRooms = {}

local OverlapParameters = OverlapParams.new()
OverlapParameters.FilterType = Enum.RaycastFilterType.Exclude

--Performs a weighted search on a list of rooms to find the next room to spawn
function WeightedSearch(RoomDirectory: Folder?, Bias: {})
	Bias = Bias or {}
	
	local ReconstructedTable = {}
	local TotalWeight = 0
	
	for _, Room in pairs(RoomDirectory:GetChildren()) do
		local BiasTowardsItem = Bias[Room.Name] or 0
		local TotalItemWeight = math.max(BiasTowardsItem, Room:GetAttribute("Weight") + BiasTowardsItem)
		ReconstructedTable[Room.Name] = TotalItemWeight
		TotalWeight += TotalItemWeight
	end
	
	local RandomGenerator = Random.new()
	local ChosenWeight = RandomGenerator:NextNumber(0, TotalWeight)
	local CurrentWeight = 0
	local CurrentChoice = RoomDirectory:GetChildren()[1]
	
	for _, PotentialRoomChoice in pairs(RoomDirectory:GetChildren()) do
		CurrentWeight += ReconstructedTable[PotentialRoomChoice.Name]
		
		if ChosenWeight <= CurrentWeight then
			CurrentChoice = PotentialRoomChoice
			break
		end
	end
	
	return CurrentChoice
end

--Gets all parts which collide with any hitbox of the given room
function GetCollidingParts(PreviousRoom: Model, Room: Model)
	local FullCollisionList = {}
	local FakeRoom = Room:Clone()
	FakeRoom:PivotTo(PreviousRoom.End.CFrame)
	
	OverlapParameters.FilterDescendantsInstances = {PreviousRoom, FakeRoom}

	for _, Part in pairs(FakeRoom.Hitboxes:GetChildren()) do
		local OverlapList = workspace:GetPartsInPart(Part, OverlapParameters)
		FullCollisionList = TableTools.Combine(FullCollisionList, OverlapList)
	end
	
	FakeRoom:Destroy()

	return FullCollisionList
end

--Chooses a room based off of factors such as collisions and room spawning chance
function ChooseRoom(PreviousRoom: Model, RoomDirectory: Folder?)
	local ChosenRoom
	
	if PreviousRoom:GetAttribute("TendencyToStretch") > 1 then
		local NewTendencyToStretch = PreviousRoom:GetAttribute("TendencyToStretch") - 1
		ChosenRoom = WeightedSearch(RoomDirectory, {[PreviousRoom.Name] = PreviousRoom:GetAttribute("TendencyToStretch")})
		
		if ChosenRoom.Name == PreviousRoom.Name then
			ChosenRoom:SetAttribute("TendencyToStretch", NewTendencyToStretch)
		end
	else
		ChosenRoom = WeightedSearch(RoomDirectory)
	end
	
	if table.find(CurrentlyTriedRooms, ChosenRoom.Name) then
		return ChooseRoom(PreviousRoom, RoomDirectory)
	end
	
	table.insert(CurrentlyTriedRooms, ChosenRoom.Name)
	
	if #CurrentlyTriedRooms >= #RoomDirectory:GetChildren() then
		CurrentlyTriedRooms = {}

		local RoomBeforePrevious = SpawnedRooms:FindFirstChild("Start")

		for _, Room in pairs(SpawnedRooms:GetChildren()) do
			if Room:GetAttribute("Room") == PreviousRoom:GetAttribute("Room") - 1 then
				RoomBeforePrevious = Room
				break
			end
		end

		PreviousRoom:Destroy()
		return ChooseRoom(RoomBeforePrevious, RoomDirectory)
	end
	
	local CollidingParts = GetCollidingParts(PreviousRoom, ChosenRoom)

	if #CollidingParts > 0 then
		return ChooseRoom(PreviousRoom, RoomDirectory)
	end
	
	local StartLookVector = ChosenRoom.Start.CFrame.LookVector
	local EndLookVector = ChosenRoom.End.CFrame.LookVector
	local RoomAngle = math.atan2(StartLookVector.Z, StartLookVector.X) - math.atan2(EndLookVector.Z, EndLookVector.X)
	local RoomAngleInDegrees = math.deg(RoomAngle)
	
	local FutureTurnAngle = CurrentTurnAngle + RoomAngleInDegrees
	
	if FutureTurnAngle > AngleBounds["Max"] or FutureTurnAngle < AngleBounds["Min"] then
		return ChooseRoom(PreviousRoom, RoomDirectory)
	end
	
	CurrentTurnAngle = FutureTurnAngle
	CurrentlyTriedRooms = {}
	
	return ChosenRoom
end

--Adds a random room to the end of the room branch
function RoomSpawning.AppendRoom(PreviousRoom: Model, Type: string)
	Type = Type or "Normal"
	PreviousRoom = PreviousRoom or SpawnedRooms:FindFirstChild("Start")
	
	local End = PreviousRoom.End
	local NextRoom = PreviousRoom:GetAttribute("Room") + 1
	
	local ChosenRoom = ChooseRoom(PreviousRoom, Rooms[Type]):Clone()
	ChosenRoom:PivotTo(End.CFrame)
	ChosenRoom:SetAttribute("Room", NextRoom)
	ChosenRoom.Parent = SpawnedRooms
	
	return ChosenRoom
end

return RoomSpawning

genuinely whoever gives me a solution will be my damn messiah :sob:

Hiya! I went through your script the issue isn’t just one line it’s mainly how your recursive backtracking state tracking ( CurrentlyTriedRooms + CurrentTurnAngle ) interact.

Right now your backtracking looks like it should work but in practice it breaks cus CurrentlyTriedRooms is global and you’re using the one shared table across all recursive calls.

local CurrentlyTriedRooms = {}

So when the recursion happens all the branches share the same tried list which means some rooms gets blocked even when they shouldnt and backtracking becomes inconsistent or impossible.

your CurrentTurnAngle is also global, same issue here

CurrentTurnAngle = FutureTurnAngle

But if the recursion fails later you never revert it so future checks become incorrect and backtracking doesn’t actually undo the turn

the script also has infinite recursion risk

This part :

if table.find(CurrentlyTriedRooms, ChosenRoom.Name) then
	return ChooseRoom(PreviousRoom, RoomDirectory)
end

Your recursively retrying without changing state properly which can loop forever or skip valid rooms

Try replacing your ChooseRoom function with this pattern:

function ChooseRoom(PreviousRoom: Model, RoomDirectory: Folder?, triedRooms, currentAngle)
	triedRooms = triedRooms or {}
	currentAngle = currentAngle or CurrentTurnAngle

	local possibleRooms = RoomDirectory:GetChildren()

	
	if #triedRooms >= #possibleRooms then
		return nil
	end

	local ChosenRoom = WeightedSearch(RoomDirectory)


	if table.find(triedRooms, ChosenRoom.Name) then
		return ChooseRoom(PreviousRoom, RoomDirectory, triedRooms, currentAngle)
	end

	table.insert(triedRooms, ChosenRoom.Name)

	local CollidingParts = GetCollidingParts(PreviousRoom, ChosenRoom)
	if #CollidingParts > 0 then
		return ChooseRoom(PreviousRoom, RoomDirectory, triedRooms, currentAngle)
	end

	
	local StartLookVector = ChosenRoom.Start.CFrame.LookVector
	local EndLookVector = ChosenRoom.End.CFrame.LookVector

	local RoomAngle = math.atan2(StartLookVector.Z, StartLookVector.X)
		- math.atan2(EndLookVector.Z, EndLookVector.X)

	local RoomAngleInDegrees = math.deg(RoomAngle)
	local FutureTurnAngle = currentAngle + RoomAngleInDegrees

	if FutureTurnAngle > AngleBounds.Max or FutureTurnAngle < AngleBounds.Min then
		return ChooseRoom(PreviousRoom, RoomDirectory, triedRooms, currentAngle)
	end

	
	CurrentTurnAngle = FutureTurnAngle

	return ChosenRoom
end

lemme know if it fixed your issue

unfortunately the code you’ve provided doesnt work, but thank you for the help!
please also explain it to me more simply, i think i’m just misunderstanding what you are saying because i dont exactly know what you mean either.

im sorry to be frustrating, i just really want this working, and just to clarify, i appreciate the code but i like designing systems myself. if you could just explain the issue in a way thats clearer to understand for me that would be just as appreciated as code itself!

just to list:
not exactly sure what you mean by branches?
i cant really make the connection between having a global “currently tried rooms” and backtracking becoming impossible.

thank you once again for the help!

Hey! I looked through your full script and I think I see the issue more clearly now.

The problem isn’t just one line it’s mainly this:

you’re using recursion (return ChooseRoom(...))
while also using shared state (CurrentlyTriedRooms, CurrentTurnAngle)

So when a room fails:

it retries… but with already “corrupted” state

That’s why:

backtracking feels like it doesn’t work
and sometimes it loops or skips valid rooms

The main breaking part are these:

if table.find(CurrentlyTriedRooms, ChosenRoom.Name) then
	return ChooseRoom(PreviousRoom, RoomDirectory)
end

and also:

if #CollidingParts > 0 then
	return ChooseRoom(PreviousRoom, RoomDirectory)
end

and:

if FutureTurnAngle > AngleBounds["Max"] or FutureTurnAngle < AngleBounds["Min"] then
	return ChooseRoom(PreviousRoom, RoomDirectory)
end

All of these:

retry WITHOUT resetting anything

Instead of recursion, just retry in a loop:

Replace your ChooseRoom function with this:

function ChooseRoom(PreviousRoom: Model, RoomDirectory: Folder?)
	local possibleRooms = RoomDirectory:GetChildren()
	local tried = {}

	for i = 1, #possibleRooms do
		local ChosenRoom

		if PreviousRoom:GetAttribute("TendencyToStretch") > 1 then
			ChosenRoom = WeightedSearch(RoomDirectory, {
				[PreviousRoom.Name] = PreviousRoom:GetAttribute("TendencyToStretch")
			})
		else
			ChosenRoom = WeightedSearch(RoomDirectory)
		end

		if table.find(tried, ChosenRoom.Name) then
			continue
		end

		table.insert(tried, ChosenRoom.Name)

		
		if #GetCollidingParts(PreviousRoom, ChosenRoom) > 0 then
			continue
		end

	
		local StartLookVector = ChosenRoom.Start.CFrame.LookVector
		local EndLookVector = ChosenRoom.End.CFrame.LookVector

		local angle = math.deg(
			math.atan2(StartLookVector.Z, StartLookVector.X)
			- math.atan2(EndLookVector.Z, EndLookVector.X)
		)

		local future = CurrentTurnAngle + angle

		if future > AngleBounds.Max or future < AngleBounds.Min then
			continue
		end

		
		CurrentTurnAngle = future
		return ChosenRoom
	end

	return nil
end

One more thing this part in your original code:

PreviousRoom:Destroy()

is risky it can break your chain if something fails.

You should only destroy AFTER confirming a valid replacement.

If that still didn’t fix it, then the issue is likely not just the retry logic but one of these:

your collision check might always be returning parts
your angle calculation might be rejecting almost every room
or WeightedSearch might be picking the same room repeatedly

ive gotten closer to fixing the issue, not sure if this is a logic error but it seems to be changing previous room somehow

local RoomSpawning = {}
local AngleBounds = {
	["Min"] = -135,
	["Max"] = 135	
}

--ok so
--line 92 - 111 is buggy and i cant fix :sob:
--i dont know what the issue is but for some reason when the code tries to delete the previous room it gives an error when next trying to spawn one
--join 

local CurrentTurnAngle = 0
local TriedRoomsAtIndex = {}

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Modules = ReplicatedStorage.Modules
local Rooms = ReplicatedStorage.Rooms

local RoomModels = ReplicatedStorage.Models.RoomModels
local Tools = Modules.Tools

local TableTools = require(Tools.TableTools)

local SpawnedRooms = workspace.SpawnedRooms

local OverlapParameters = OverlapParams.new()
OverlapParameters.FilterType = Enum.RaycastFilterType.Exclude

export type Room = {
	Start: BasePart,
	End: BasePart,
	Hitboxes: Folder
}

--Performs a weighted search on a list of rooms to find the next room to spawn
function WeightedSearch(RoomDirectory: Folder?, Bias: {}, Exclude: {})
	Bias = Bias or {}
	Exclude = Exclude or {}
	
	local ReconstructedTable = {}
	local SearchList = RoomDirectory:GetChildren()
	SearchList = TableTools.RemoveItems(SearchList, ConvertNamesToRooms(Exclude, RoomDirectory))
	
	if #SearchList == 0 then
		return nil
	end
	
	local TotalWeight = 0
	
	for _, Room in pairs(SearchList) do
		local BiasTowardsItem = Bias[Room.Name] or 0
		local TotalItemWeight = math.max(BiasTowardsItem, Room:GetAttribute("Weight") + BiasTowardsItem)
		ReconstructedTable[Room.Name] = TotalItemWeight
		TotalWeight += TotalItemWeight
	end
	
	local RandomGenerator = Random.new()
	local ChosenWeight = RandomGenerator:NextNumber(0, TotalWeight)
	local CurrentWeight = 0
	local CurrentChoice = RoomDirectory:GetChildren()[1]
	
	for _, PotentialRoomChoice in pairs(SearchList) do
		CurrentWeight += ReconstructedTable[PotentialRoomChoice.Name]
		
		if ChosenWeight <= CurrentWeight then
			CurrentChoice = PotentialRoomChoice
			break
		end
	end
	
	return CurrentChoice
end

--Gets all parts which collide with any hitbox of the given room
function GetCollidingParts(PreviousRoom: Room, Room: Room)
	local FullCollisionList = {}
	local FakeRoom = Room:Clone()
	FakeRoom:PivotTo(PreviousRoom.End.CFrame)
	OverlapParameters.FilterDescendantsInstances = {PreviousRoom, FakeRoom}

	for _, Part in pairs(FakeRoom.Hitboxes:GetChildren()) do
		local OverlapList = workspace:GetPartsInPart(Part, OverlapParameters)
		FullCollisionList = TableTools.Combine(true, FullCollisionList, OverlapList)
	end
	
	FakeRoom:Destroy()

	return FullCollisionList
end

--Finds the room with a certain number
function FindRoomWithNumber(Number: number)
	for _, Room in pairs(SpawnedRooms:GetChildren()) do
		if Room:GetAttribute("Room") == Number then
			return Room
		end
	end
	
	if Number ~= math.clamp(Number, 0, #SpawnedRooms:GetChildren()) then
		return SpawnedRooms:FindFirstChild("Start")
	end
	
	return nil
end

--Converts every room name inside a list of rooms names to a proper room
function ConvertNamesToRooms(RoomList: {}, RoomDirectory: Folder)
	local Directory = {}
	
	for _, RoomName in pairs(RoomList) do
		local Room = RoomDirectory:FindFirstChild(RoomName)
		table.insert(Directory, Room)
	end
	
	return Directory
end

function ComputeRoomAngle(Room: Room)
	local StartLookVector = Room.Start.CFrame.LookVector
	local EndLookVector = Room.End.CFrame.LookVector
	local RoomAngle = math.atan2(StartLookVector.Z, StartLookVector.X) - math.atan2(EndLookVector.Z, EndLookVector.X)
	local RoomAngleInDegrees = math.deg(RoomAngle)
	
	return RoomAngleInDegrees
end

--Chooses a room based off of factors such as collisions and room spawning chance
function ChooseRoom(PreviousRoom: Room, RoomDirectory: Folder?, TriedRooms: {}, Exclude: {})
	Exclude = Exclude or {}
	TriedRooms = TriedRooms or {}
	
	local BiasList = {[PreviousRoom.Name] = PreviousRoom:GetAttribute("TendencyToStretch")}
	local FullExclusionList = TableTools.Combine(true, TriedRooms, Exclude)
	local NewTendencyToStretch = PreviousRoom:GetAttribute("TendencyToStretch") - 1
	local ChosenRoom = WeightedSearch(RoomDirectory, BiasList, FullExclusionList)
	
	local RoomBeforePreviousIndex = PreviousRoom:GetAttribute("Room") - 1
	local RoomBeforePrevious = FindRoomWithNumber(RoomBeforePreviousIndex)
	print(RoomBeforePrevious)
	
	while not ChosenRoom do
		if not ChosenRoom then
			print("\n No room to spawn")

			if RoomBeforePrevious then
				print("Room before previous available")
				local NewPreviousRoom = PreviousRoom
				
				RoomBeforePreviousIndex = PreviousRoom:GetAttribute("Room") - 1
				RoomBeforePrevious = FindRoomWithNumber(RoomBeforePreviousIndex)

				if PreviousRoom ~= SpawnedRooms:FindFirstChild("Start") then
					print("can recreate previous room")
					CurrentTurnAngle -= ComputeRoomAngle(PreviousRoom)
					NewPreviousRoom = RoomSpawning.AppendRoom(RoomBeforePrevious, "Normal", {}, {})
					PreviousRoom:Destroy()
				else
					print("cannot recreate previous room")
				end

				print("Edited previous room: ".. PreviousRoom.Name)
				PreviousRoom = NewPreviousRoom

				print("went back a room")
				ChosenRoom = WeightedSearch(RoomDirectory, BiasList, FullExclusionList)
			else
				--just force through at that point
				ChosenRoom = WeightedSearch(RoomDirectory, BiasList, Exclude)
				print("no previous room, continuing")
			end
		end
		
		task.wait()
	end
	
	table.insert(TriedRooms, ChosenRoom.Name)

	if ChosenRoom.Name == PreviousRoom.Name then
		ChosenRoom:SetAttribute("TendencyToStretch", NewTendencyToStretch)
	end
	
	local CollidingParts = GetCollidingParts(PreviousRoom, ChosenRoom)
	local FutureTurnAngle = CurrentTurnAngle + ComputeRoomAngle(ChosenRoom)

	local IsColliding = #CollidingParts > 0
	local IsOutOfAngleBounds = FutureTurnAngle > AngleBounds["Max"] or FutureTurnAngle < AngleBounds["Min"]

	if IsColliding or IsOutOfAngleBounds then
		return ChooseRoom(PreviousRoom, RoomDirectory, TriedRooms, Exclude), PreviousRoom
	end

	CurrentTurnAngle = FutureTurnAngle
	TriedRoomsAtIndex[PreviousRoom:GetAttribute("Room") + 1] = TriedRooms
	TriedRooms = nil

	return ChosenRoom, PreviousRoom
end

--Adds a random room to the end of the room branch
function RoomSpawning.AppendRoom(PreviousRoom: Room, Type: string, Exclude: {})
	Type = Type or "Normal"
	PreviousRoom = PreviousRoom or SpawnedRooms:FindFirstChild("Start")
	
	local ChosenRoom, NewPreviousRoom = ChooseRoom(PreviousRoom, Rooms[Type])
	
	if ChosenRoom and NewPreviousRoom then
		ChosenRoom = ChosenRoom:Clone()
		ChosenRoom:PivotTo(NewPreviousRoom.End.CFrame)
		ChosenRoom:SetAttribute("Room", NewPreviousRoom:GetAttribute("Room") + 1)
		ChosenRoom.Parent = SpawnedRooms
	end
	
	return ChosenRoom
end

return RoomSpawning

Hi, could you explain what is backtracking?

Try this script, and let me know if it fixes your issue.

local RoomSpawning = {}
local AngleBounds = {
	["Min"] = -135,
	["Max"] = 135	
}

--ok so
--line 92 - 111 is buggy and i cant fix :sob:
--i dont know what the issue is but for some reason when the code tries to delete the previous room it gives an error when next trying to spawn one
--join 

local CurrentTurnAngle = 0
local TriedRoomsAtIndex = {}

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Modules = ReplicatedStorage.Modules
local Rooms = ReplicatedStorage.Rooms

local RoomModels = ReplicatedStorage.Models.RoomModels
local Tools = Modules.Tools

local TableTools = require(Tools.TableTools)

local SpawnedRooms = workspace.SpawnedRooms

local OverlapParameters = OverlapParams.new()
OverlapParameters.FilterType = Enum.RaycastFilterType.Exclude

export type Room = {
	Start: BasePart,
	End: BasePart,
	Hitboxes: Folder
}

--Performs a weighted search on a list of rooms to find the next room to spawn
function WeightedSearch(RoomDirectory: Folder?, Bias: {}, Exclude: {})
	Bias = Bias or {}
	Exclude = Exclude or {}
	
	local ReconstructedTable = {}
	local SearchList = RoomDirectory:GetChildren()
	SearchList = TableTools.RemoveItems(SearchList, ConvertNamesToRooms(Exclude, RoomDirectory))
	
	if #SearchList == 0 then
		return nil
	end
	
	local TotalWeight = 0
	
	for _, Room in pairs(SearchList) do
		local BiasTowardsItem = Bias[Room.Name] or 0
		local TotalItemWeight = math.max(BiasTowardsItem, Room:GetAttribute("Weight") + BiasTowardsItem)
		ReconstructedTable[Room.Name] = TotalItemWeight
		TotalWeight += TotalItemWeight
	end
	
	local RandomGenerator = Random.new()
	local ChosenWeight = RandomGenerator:NextNumber(0, TotalWeight)
	local CurrentWeight = 0
	local CurrentChoice = RoomDirectory:GetChildren()[1]
	
	for _, PotentialRoomChoice in pairs(SearchList) do
		CurrentWeight += ReconstructedTable[PotentialRoomChoice.Name]
		
		if ChosenWeight <= CurrentWeight then
			CurrentChoice = PotentialRoomChoice
			break
		end
	end
	
	return CurrentChoice
end

--Gets all parts which collide with any hitbox of the given room
function GetCollidingParts(PreviousRoom: Room, Room: Room)
	local FullCollisionList = {}
	local FakeRoom = Room:Clone()
	FakeRoom:PivotTo(PreviousRoom.End.CFrame)
	OverlapParameters.FilterDescendantsInstances = {PreviousRoom, FakeRoom}

	for _, Part in pairs(FakeRoom.Hitboxes:GetChildren()) do
		local OverlapList = workspace:GetPartsInPart(Part, OverlapParameters)
		FullCollisionList = TableTools.Combine(true, FullCollisionList, OverlapList)
	end
	
	FakeRoom:Destroy()

	return FullCollisionList
end

--Finds the room with a certain number
function FindRoomWithNumber(Number: number)
	for _, Room in pairs(SpawnedRooms:GetChildren()) do
		if Room:GetAttribute("Room") == Number then
			return Room
		end
	end
	
	if Number ~= math.clamp(Number, 0, #SpawnedRooms:GetChildren()) then
		return SpawnedRooms:FindFirstChild("Start")
	end
	
	return nil
end

--Converts every room name inside a list of rooms names to a proper room
function ConvertNamesToRooms(RoomList: {}, RoomDirectory: Folder)
	local Directory = {}
	
	for _, RoomName in pairs(RoomList) do
		local Room = RoomDirectory:FindFirstChild(RoomName)
		table.insert(Directory, Room)
	end
	
	return Directory
end

function ComputeRoomAngle(Room: Room)
	local StartLookVector = Room.Start.CFrame.LookVector
	local EndLookVector = Room.End.CFrame.LookVector
	local RoomAngle = math.atan2(StartLookVector.Z, StartLookVector.X) - math.atan2(EndLookVector.Z, EndLookVector.X)
	local RoomAngleInDegrees = math.deg(RoomAngle)
	
	return RoomAngleInDegrees
end

--Chooses a room based off of factors such as collisions and room spawning chance
function ChooseRoom(PreviousRoom: Room, RoomDirectory: Folder?, TriedRooms: {}, Exclude: {})
	Exclude = Exclude or {}
	TriedRooms = TriedRooms or {}
	
	local BiasList = {[PreviousRoom.Name] = PreviousRoom:GetAttribute("TendencyToStretch")}
	local FullExclusionList = TableTools.Combine(true, TriedRooms, Exclude)
	local NewTendencyToStretch = PreviousRoom:GetAttribute("TendencyToStretch") - 1
	local ChosenRoom = WeightedSearch(RoomDirectory, BiasList, FullExclusionList)
	
	local RoomBeforePreviousIndex = PreviousRoom:GetAttribute("Room") - 1
	local RoomBeforePrevious = FindRoomWithNumber(RoomBeforePreviousIndex)
	print(RoomBeforePrevious)
	
	local attempts = 0
	
	while not ChosenRoom and attempts < 20 do
		attempts += 1
		
		print("\n No room to spawn")

		if RoomBeforePrevious then
			print("Room before previous available")
			
			-- FIX: dont recreate or destroy, just step back logically
			if PreviousRoom ~= SpawnedRooms:FindFirstChild("Start") then
				print("stepping back a room")
				CurrentTurnAngle -= ComputeRoomAngle(PreviousRoom)
				PreviousRoom = RoomBeforePrevious
			else
				print("cannot go back further")
			end

			print("Edited previous room: ".. PreviousRoom.Name)
			print("went back a room")
			
			-- recompute with new previous room
			RoomBeforePreviousIndex = PreviousRoom:GetAttribute("Room") - 1
			RoomBeforePrevious = FindRoomWithNumber(RoomBeforePreviousIndex)
			
			ChosenRoom = WeightedSearch(RoomDirectory, BiasList, FullExclusionList)
		else
			--just force through at that point
			ChosenRoom = WeightedSearch(RoomDirectory, BiasList, Exclude)
			print("no previous room, continuing")
		end
		
		task.wait()
	end
	
	if not ChosenRoom then
		return nil, PreviousRoom
	end
	
	table.insert(TriedRooms, ChosenRoom.Name)

	if ChosenRoom.Name == PreviousRoom.Name then
		ChosenRoom:SetAttribute("TendencyToStretch", NewTendencyToStretch)
	end
	
	local CollidingParts = GetCollidingParts(PreviousRoom, ChosenRoom)
	local FutureTurnAngle = CurrentTurnAngle + ComputeRoomAngle(ChosenRoom)

	local IsColliding = #CollidingParts > 0
	local IsOutOfAngleBounds = FutureTurnAngle > AngleBounds["Max"] or FutureTurnAngle < AngleBounds["Min"]

	if IsColliding or IsOutOfAngleBounds then
		return ChooseRoom(PreviousRoom, RoomDirectory, TriedRooms, Exclude), PreviousRoom
	end

	CurrentTurnAngle = FutureTurnAngle
	TriedRoomsAtIndex[PreviousRoom:GetAttribute("Room") + 1] = TriedRooms
	TriedRooms = nil

	return ChosenRoom, PreviousRoom
end

--Adds a random room to the end of the room branch
function RoomSpawning.AppendRoom(PreviousRoom: Room, Type: string, Exclude: {})
	Type = Type or "Normal"
	PreviousRoom = PreviousRoom or SpawnedRooms:FindFirstChild("Start")
	
	local ChosenRoom, NewPreviousRoom = ChooseRoom(PreviousRoom, Rooms[Type])
	
	if ChosenRoom and NewPreviousRoom then
		ChosenRoom = ChosenRoom:Clone()
		ChosenRoom:PivotTo(NewPreviousRoom.End.CFrame)
		ChosenRoom:SetAttribute("Room", NewPreviousRoom:GetAttribute("Room") + 1)
		ChosenRoom.Parent = SpawnedRooms
	end
	
	return ChosenRoom
end

return RoomSpawning