Issue with 3D Cellular Automata chunk loading script

Heya, so I am having some trouble with my Cellular Automata code. If I use it like I originally did (see first .rbxm file) it works fine. However, I have recently been trying to make the code compatible with loading chunks (so I can make the cave system infinite), however my code is not working properly. I am getting the error ServerScriptService.Script:104: attempt to index nil with number. This is my code:

math.randomseed(tick())

local scale = 4
local size = 16
local offset = Vector3.new(0, 0, 0)

local aliveChance = 0.45
local birthLimit = 12
local deathLimit = 13
local stepAmount = 4

-- Generates a 2d grid based on certain dimensions
function createGrid(xOffset, yOffset, zOffset)
	local grid = {}
	for i = 1, size do
		grid[i] = grid[i] or {}
		for j = 1, size do
			grid[i][j] = grid[i][j] or {}
			for k = 1, size do
				if math.random() <= aliveChance then
					grid[i][j][k] = "O"
				else
					grid[i][j][k] = "X"
				end
			end
		end
	end
	return grid
end
-- Appends new data to existing grid for cellular automata to base off of
function appendToGrid(grid, xOffset, yOffset, zOffset)
	for i = 1, size do
		grid[i + xOffset] = grid[i + xOffset] or {}
		print(i + xOffset)
		for j = 1, size do
			print(j + xOffset)
			grid[i + xOffset][j + yOffset] = grid[i + xOffset][j + yOffset] or {}
			for k = 1, size do
				if math.random() <= aliveChance then
					grid[i + xOffset][j + yOffset][k + zOffset] = "O"
				else
					grid[i + xOffset][j + yOffset][k + zOffset] = "X"
				end
			end
		end
	end
	return grid
end

-- Function to print the grid for troubleshooting
function printGrid(grid)
	for i, v in pairs(grid) do
		for j, k in pairs(v) do
			local printString = table.concat(k, "")
			print(printString)
		end
	end
end
-- Function to count neighbors in a 2d array
function countNeighbors(grid, xPos, yPos, zPos)
	local neighbors = 0

	for x = -1, 1 do
		for y = -1, 1 do
			for z = -1, 1 do
				if xPos + x < 1 or xPos + x > size or yPos + y < 1 or yPos + y > size or zPos + z < 1 or zPos + z > size then
					-- neighbors += 1
				else
					local neighbor = grid[xPos + x][yPos + y][zPos + z]
					if neighbor == "O" then
						neighbors += 1
					end
				end
			end
		end
	end

	return neighbors
end
-- Function to perform a step of cellular automata
function doSimulationStep(grid, chunkCoordinates)
	local newGrid = grid
	for i = 1, size do
		newGrid[i + chunkCoordinates.X] = newGrid[i + chunkCoordinates.X] or {}
		for j = 1, size do
			newGrid[i + chunkCoordinates.X][j + chunkCoordinates.Y] = newGrid[i + chunkCoordinates.X][j + chunkCoordinates.Y] or {}
		end
	end
	for x, v in pairs(grid) do
		for y, w in pairs(v) do
			for z, u in pairs(w) do
				local neighbors = countNeighbors(grid, x + chunkCoordinates.X, y + chunkCoordinates.Y, z + chunkCoordinates.Z)
				if u == "O" then
					if neighbors < deathLimit then
						print("Appended grid to" .. x + chunkCoordinates.X .. " " .. y + chunkCoordinates.Y .. " " .. z + chunkCoordinates.Z)
						newGrid[x + chunkCoordinates.X][y + chunkCoordinates.Y][z + chunkCoordinates.Z] = "X"
					else
						newGrid[x + chunkCoordinates.X][y + chunkCoordinates.Y][z + chunkCoordinates.Z] = "O"
					end
				else
					if neighbors > birthLimit then
						newGrid[x + chunkCoordinates.X][y + chunkCoordinates.Y][z + chunkCoordinates.Z] = "O"
					else
						newGrid[x + chunkCoordinates.X][y + chunkCoordinates.Y][z + chunkCoordinates.Z] = "X"
					end
				end
			end
		end
		wait()
	end

	return newGrid
end
-- Function to place grid into workspace
function placeGrid(grid, chunk)
	local offset = chunk * size
	print(offset)
	print(chunk * scale)
	for x, v in pairs(grid) do
		for y, w in pairs(v) do
			for z, u in pairs(w) do
				if u == "O" then
					local part = Instance.new("Part")
					part.Size = Vector3.new(1, 1, 1) * scale
					part.Position = (Vector3.new(x + offset.X, y + offset.Y, z + offset.Z) * scale) - (chunk * scale * 2)
					part.Anchored = true
					part.Parent = workspace.CaveParts
				end
			end
		end
		wait()
	end
end

function loadChunk(grid, chunkCoordinates, iterations)
	chunkCoordinates *= size
	print(chunkCoordinates)
	grid = appendToGrid(grid, chunkCoordinates.X, chunkCoordinates.Y, chunkCoordinates.Z)
	for i = 1, iterations do
		grid = doSimulationStep(grid, chunkCoordinates)
		print(grid)
	end

	placeGrid(grid, chunkCoordinates)
end

local grid = createGrid()

for x = -1, 1 do
	for y = -1, 1 do
		for z = -1, 1 do
			loadChunk(grid, Vector3.new(x, y, z), stepAmount)
		end
	end
end

It’s very messy, I apologize. However, it seems to cause the error after 0, 0, 0, where it then tries to index the nil value, and always on the if statements between lines 94 and 105

Any help would be greatly appreciated as this is a new thing for me and I’m not quite sure how to fix this issue.

Also just realized my print statement is in a weird place but it does confirm that it is doing steps of cellular automata until it errors out

Edit: Oops, forgot to attach the old game with the code, here it is: Cellular Automata 3D Testing-2.rbxl (25.0 KB)