ok i have new code and a new idea. ehre is the code Simulation script: -- SimulationScript (ServerScript in ServerScriptService)

local GMFluxContainer = require(game.ServerScriptService.GMFluxContainer)
local SimulationData = require(game.ServerScriptService.SimulationData)
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

-- Simulation parameters
local texSize = 128 -- Adjust as needed (reasonable sizes: 64, 128)
local cellSize = 2 -- Size of each cell in world units
local simPosition = Vector3.new(-100, 0, -200) -- Adjust to move the fluid grid

-- Controllable simulation parameters
local simulationParameters = {
	fluxStrength = 0.25,       -- Controls the fluid flow strength
	fluxAtten = 0.975,         -- Fluid attenuation
	fluxSpill = 0.0,           -- Fluid spill threshold
	simulationSpeed = 0.03,    -- Controls the simulation step interval
	fluidGenerationRate = 100, -- Controls the rate at which fluid is added per source per step
}

-- Create an instance of the simulation container
local sim = GMFluxContainer.new(texSize, simPosition, cellSize, simulationParameters)

-- Store the simulation instance in SimulationData
SimulationData.sim = sim

-- List of collidable objects (Advanced collisions)
local collidableObjectsFolder = Workspace:FindFirstChild("CollidableObjects")
local collidableObjects = {}
if collidableObjectsFolder then
	collidableObjects = collidableObjectsFolder:GetChildren()
end

-- List of water sources
local waterSources = {
	{x = math.floor(sim.texSize / 2), y = math.floor(sim.texSize / 2)},
	-- Add more sources as needed
}

-- Simulation loop
local simulationTimer = 0

RunService.Heartbeat:Connect(function(deltaTime)
	simulationTimer = simulationTimer + deltaTime

	-- Add fluid at specified water sources
	for _, source in ipairs(waterSources) do
		sim:addFluid(source.x, source.y, simulationParameters.fluidGenerationRate)
	end

	-- Perform simulation steps at the specified interval
	if simulationTimer >= simulationParameters.simulationSpeed then
		simulationTimer = 0

		-- Perform simulation steps
		sim:stepFlux()
		sim:stepDepth()

		-- Handle collisions
		sim:handleCollisions(collidableObjects)
	end
end)
Module:-- GMFluxContainer ModuleScript

local GMFluxContainer = {}
GMFluxContainer.__index = GMFluxContainer

-- FluxCell constants
GMFluxContainer.FluxCell = {
	Height = 0,
	Depth = 1,
	FluxLD = 2,
	FluxRU = 3,
}

-- Constructor
function GMFluxContainer.new(texSize, position, cellSize, parameters)
	local self = setmetatable({}, GMFluxContainer)

	-- Simulation parameters
	self.fluxStrength = parameters.fluxStrength or 0.250
	self.fluxAtten = parameters.fluxAtten or 0.975
	self.fluxSpill = parameters.fluxSpill or 0.0

	self.texSize = texSize or 64
	self.cellSize = cellSize or 2

	-- Position of the fluid grid in the world (not used for visualization here)
	self.position = position or Vector3.new(0, 0, 0)

	-- Initialize data structures
	self.cells = {}
	for x = 1, self.texSize do
		self.cells[x] = {}
		for y = 1, self.texSize do
			self.cells[x][y] = {
				[GMFluxContainer.FluxCell.Height] = 0,
				[GMFluxContainer.FluxCell.Depth] = 0,
				[GMFluxContainer.FluxCell.FluxLD] = {left = 0, down = 0},
				[GMFluxContainer.FluxCell.FluxRU] = {right = 0, up = 0},
			}
		end
	end

	return self
end

-- Function to perform a step of the flux simulation
function GMFluxContainer:stepFlux()
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local cell = self.cells[x][y]
			local depth = cell[GMFluxContainer.FluxCell.Depth]
			local height = cell[GMFluxContainer.FluxCell.Height]
			local totalHeight = height + depth

			-- Neighbor positions
			local neighbors = {
				left = x > 1 and self.cells[x - 1][y] or nil,
				right = x < self.texSize and self.cells[x + 1][y] or nil,
				down = y > 1 and self.cells[x][y - 1] or nil,
				up = y < self.texSize and self.cells[x][y + 1] or nil,
			}

			-- Initialize fluxes
			local fluxes = {left = 0, right = 0, down = 0, up = 0}
			local totalFluxOut = 0

			-- Compute fluxes to neighbors
			for dir, neighbor in pairs(neighbors) do
				if neighbor then
					local neighborTotalHeight = neighbor[GMFluxContainer.FluxCell.Height] + neighbor[GMFluxContainer.FluxCell.Depth]
					local fluxComponent = (dir == 'left' or dir == 'down') and cell[GMFluxContainer.FluxCell.FluxLD][dir] or cell[GMFluxContainer.FluxCell.FluxRU][dir]
					local deltaHeight = totalHeight - neighborTotalHeight
					local flux = self.fluxAtten * fluxComponent + self.fluxStrength * math.max(0, deltaHeight)
					fluxes[dir] = flux
					totalFluxOut = totalFluxOut + flux
				end
			end

			-- Limit total flux to available depth
			local maxFlux = math.max(0, depth - self.fluxSpill)
			if totalFluxOut > maxFlux and totalFluxOut > 0 then
				local scale = maxFlux / totalFluxOut
				for dir in pairs(fluxes) do
					fluxes[dir] = fluxes[dir] * scale
				end
			end

			-- Update fluxes in cell
			local fluxLD = cell[GMFluxContainer.FluxCell.FluxLD]
			local fluxRU = cell[GMFluxContainer.FluxCell.FluxRU]
			fluxLD.left = fluxes.left
			fluxLD.down = fluxes.down
			fluxRU.right = fluxes.right
			fluxRU.up = fluxes.up
		end
	end
end

-- Function to perform a depth update step
function GMFluxContainer:stepDepth()
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local cell = self.cells[x][y]
			local depth = cell[GMFluxContainer.FluxCell.Depth]
			local fluxOut = cell[GMFluxContainer.FluxCell.FluxLD].left + cell[GMFluxContainer.FluxCell.FluxLD].down +
				cell[GMFluxContainer.FluxCell.FluxRU].right + cell[GMFluxContainer.FluxCell.FluxRU].up
			local fluxIn = 0

			-- Flux in from neighbors
			if x > 1 then
				fluxIn = fluxIn + self.cells[x - 1][y][GMFluxContainer.FluxCell.FluxRU].right
			end
			if x < self.texSize then
				fluxIn = fluxIn + self.cells[x + 1][y][GMFluxContainer.FluxCell.FluxLD].left
			end
			if y > 1 then
				fluxIn = fluxIn + self.cells[x][y - 1][GMFluxContainer.FluxCell.FluxRU].up
			end
			if y < self.texSize then
				fluxIn = fluxIn + self.cells[x][y + 1][GMFluxContainer.FluxCell.FluxLD].down
			end

			-- Update depth
			local newDepth = depth - fluxOut + fluxIn
			newDepth = math.max(newDepth, 0)
			cell[GMFluxContainer.FluxCell.Depth] = newDepth
		end
	end
end

-- Function to handle collisions (Advanced collisions)
function GMFluxContainer:handleCollisions(collidableObjects)
	local cellSize = self.cellSize
	local basePosition = self.position

	for _, obj in ipairs(collidableObjects) do
		local objPos = obj.Position
		local objSizeHalf = obj.Size * 0.5

		local minX = math.floor((objPos.X - objSizeHalf.X - basePosition.X) / cellSize) + 1
		local maxX = math.floor((objPos.X + objSizeHalf.X - basePosition.X) / cellSize) + 1
		local minY = math.floor((objPos.Z - objSizeHalf.Z - basePosition.Z) / cellSize) + 1
		local maxY = math.floor((objPos.Z + objSizeHalf.Z - basePosition.Z) / cellSize) + 1

		-- Clamp to grid boundaries
		minX = math.max(1, minX)
		maxX = math.min(self.texSize, maxX)
		minY = math.max(1, minY)
		maxY = math.min(self.texSize, maxY)

		for x = minX, maxX do
			for y = minY, maxY do
				local cell = self.cells[x][y]
				local height = cell[GMFluxContainer.FluxCell.Height]
				local depth = cell[GMFluxContainer.FluxCell.Depth]
				local cellWorldPosX = basePosition.X + (x - 0.5) * cellSize
				local cellWorldPosZ = basePosition.Z + (y - 0.5) * cellSize

				-- Check if object overlaps with the cell
				if math.abs(objPos.X - cellWorldPosX) <= (objSizeHalf.X + cellSize / 2) and
					math.abs(objPos.Z - cellWorldPosZ) <= (objSizeHalf.Z + cellSize / 2) then

					-- If the object's bottom is below the fluid surface, adjust fluid depth
					local objBottomY = objPos.Y - objSizeHalf.Y
					if objBottomY <= height + depth then
						local maxDepth = math.max(0, objBottomY - height)
						cell[GMFluxContainer.FluxCell.Depth] = math.min(depth, maxDepth)
					end
				end
			end
		end
	end
end

-- Encode terrain heightmap
function GMFluxContainer:cellEncodeHeightmap(heightmap, factor)
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local height = heightmap[x][y] * factor
			self.cells[x][y][GMFluxContainer.FluxCell.Height] = height
		end
	end
end

-- Add fluid at a specific position
function GMFluxContainer:addFluid(x, y, amount)
	if x >= 1 and x <= self.texSize and y >= 1 and y <= self.texSize then
		self.cells[x][y][GMFluxContainer.FluxCell.Depth] = self.cells[x][y][GMFluxContainer.FluxCell.Depth] + amount
	end
end

-- Get fluid depth at a position
function GMFluxContainer:getFluidDepthAt(x, y)
	if x >= 1 and x <= self.texSize and y >= 1 and y <= self.texSize then
		return self.cells[x][y][GMFluxContainer.FluxCell.Depth]
	else
		return 0
	end
end

return GMFluxContainer


Module: -- SimulationData ModuleScript

local SimulationData = {}

SimulationData.sim = nil

return SimulationData

Visualization:-- VisualizationScript (ServerScript in ServerScriptService)

local GMFluxContainer = require(game.ServerScriptService.GMFluxContainer)
local SimulationData = require(game.ServerScriptService.SimulationData)
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

-- Wait until the simulation instance is available
while not SimulationData.sim do
	wait()
end

local sim = SimulationData.sim

-- Visualization parts parent
local visualizationFolder = Instance.new("Folder")
visualizationFolder.Name = "VisualizationParts"
visualizationFolder.Parent = Workspace

-- Create parts to visualize the simulation
local partsGrid = {}
local basePosition = sim.position

for x = 1, sim.texSize do
	partsGrid[x] = {}
	local worldX = basePosition.X + (x - 1) * sim.cellSize
	for y = 1, sim.texSize do
		local part = Instance.new("Part")
		part.Size = Vector3.new(sim.cellSize, 0.1, sim.cellSize) -- Initial minimal size
		part.Position = Vector3.new(worldX, basePosition.Y + 0.05, basePosition.Z + (y - 1) * sim.cellSize)
		part.Anchored = true
		part.Material = Enum.Material.Slate -- Terrain material
		part.Color = Color3.fromRGB(139, 69, 19) -- Brown color for terrain
		part.Transparency = 0
		part.CastShadow = false
		part.Parent = visualizationFolder -- Parent to visualization folder

		-- Add attribute to track particle emitter
		part:SetAttribute("HasParticleEmitter", false)
		partsGrid[x][y] = part
	end
end

-- Function to generate the terrain heightmap using raycasts
local function generateTerrainHeightmap()
	local heightmap = {}
	local basePosition = sim.position
	local cellSize = sim.cellSize

	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {visualizationFolder} -- Exclude visualization parts
	raycastParams.IgnoreWater = false

	local maxTerrainHeight = 1000 -- Adjust based on your terrain
	local direction = Vector3.new(0, -maxTerrainHeight - 100, 0)

	for x = 1, sim.texSize do
		heightmap[x] = {}
		local worldX = basePosition.X + (x - 0.5) * cellSize
		for y = 1, sim.texSize do
			local worldZ = basePosition.Z + (y - 0.5) * cellSize
			local origin = Vector3.new(worldX, maxTerrainHeight, worldZ)
			local raycastResult = Workspace:Raycast(origin, direction, raycastParams)
			local height = basePosition.Y

			if raycastResult then
				height = raycastResult.Position.Y
			else
				-- No hit, set to base level
				height = basePosition.Y
			end

			heightmap[x][y] = height
		end
	end

	return heightmap
end

-- Generate the initial terrain heightmap
local heightmap = generateTerrainHeightmap()

-- Set the terrain heightmap in the simulation
sim:cellEncodeHeightmap(heightmap, 1) -- Height factor is 1

-- Periodically update the heightmap
local heightmapUpdateInterval = 1 -- Interval in seconds to update the heightmap
local lastHeightmapUpdate = 0

-- Visualization update interval
local visualizationUpdateInterval = 0.03 -- Adjust as needed
local lastVisualizationUpdate = 0

-- Visualization loop
RunService.Heartbeat:Connect(function(deltaTime)
	lastHeightmapUpdate = lastHeightmapUpdate + deltaTime
	lastVisualizationUpdate = lastVisualizationUpdate + deltaTime

	-- Update the heightmap periodically
	if lastHeightmapUpdate >= heightmapUpdateInterval then
		lastHeightmapUpdate = 0

		-- Update the heightmap
		heightmap = generateTerrainHeightmap()
		sim:cellEncodeHeightmap(heightmap, 1)
	end

	-- Update visualization periodically
	if lastVisualizationUpdate >= visualizationUpdateInterval then
		lastVisualizationUpdate = 0

		-- Update visualization based on the simulation data
		for x = 1, sim.texSize do
			local partsColumn = partsGrid[x]
			local simCellsColumn = sim.cells[x]
			local worldX = basePosition.X + (x - 1) * sim.cellSize
			for y = 1, sim.texSize do
				local part = partsColumn[y]
				local cell = simCellsColumn[y]
				local depth = cell[GMFluxContainer.FluxCell.Depth]
				local height = cell[GMFluxContainer.FluxCell.Height]
				local worldZ = basePosition.Z + (y - 1) * sim.cellSize

				if depth > 0 then
					-- Scale the part to represent the fluid depth
					part.Size = Vector3.new(sim.cellSize, depth, sim.cellSize)
					part.Position = Vector3.new(worldX, height + depth / 2, worldZ)
					-- Set material and color for water
					part.Material = Enum.Material.Water
					-- Color intensity based on depth
					local colorIntensity = math.clamp(depth / 10, 0, 1)
					part.Color = Color3.new(0, 0, colorIntensity)
					part.Transparency = 0.3 -- Less transparent with more depth
				else
					-- Minimal size for empty cells
					part.Size = Vector3.new(sim.cellSize, 0.1, sim.cellSize)
					part.Position = Vector3.new(worldX, height + 0.05, worldZ)
					-- Set material and color for terrain
					part.Material = Enum.Material.Slate -- Terrain material
					part.Color = Color3.fromRGB(139, 69, 19) -- Brown color for terrain
					part.Transparency = 0
				end

				-- Handle particle effects or additional visualization here if needed
			end
		end
	end
end)

I want you to make this code based on another script that uses paralell.


The structure is 
–Actor
	–Script
–Event

The script and the bindableevent are inside the actor. There are a lot of actors like 20 of them. 
The code in the script is: local actorCore = require(script.Parent.Parent.ActorCore)

local event = script.Parent.Event
event.Event:ConnectParallel(function(methodName, parameters, settings, callback)
	local returnValue = actorCore:Invoke(methodName, parameters, settings)
	
	if (callback) then
		callback:Fire(returnValue)
	end
end)



The module is: local module = {}

local sunVec
local maxSamples
local neonBrightness
local brightness
local gridSize
local materialColors = {}
local rayDirs 
local pointLights = {}
local sunLength = 1024
local surfaceCastDistance = 1024
local debugging = false

local lightRecords = {}

local broadPhase = {}


local function GetLightAABB(pos, radius)
	
	local square = radius * 1.44
	return pos.x - square, pos.y - square, pos.z - square, pos.x + square, pos.y + square, pos.z + square
end


local function GetPosition(light)
	if (light.Parent:IsA("Attachment")) then
		return light.Parent.WorldPosition
	end
	return light.Parent.Position
end

local function BroadphaseKey(vector)
	return Vector3.new(math.floor(vector.x / gridSize), math.floor(vector.y / gridSize), math.floor(vector.z / gridSize))
end 

local function WriteLightToBroadphase(light, pos, radius, gridSize)
		
	local minx, miny, minz, maxx, maxy, maxz = GetLightAABB(pos, radius)

	for x = math.floor(minx / gridSize), math.ceil(maxx / gridSize) - 1 do
		for z = math.floor(minz / gridSize), math.ceil(maxz / gridSize) - 1 do
			for y = math.floor(miny / gridSize), math.ceil(maxy / gridSize) - 1 do
				local vec = Vector3.new(x,y,z)
				local cell = broadPhase[vec]
				if (cell == nil) then
					cell = {}
					broadPhase[vec] = cell
				end
				
				table.insert(cell, light)
			end
		end
	end
end


local function GeneratePoints(samples)

	local points = {}
	local secondHalf = {}
	local phi = math.pi * (3 - math.sqrt(5))

	for i  = 0, samples-1 do

		local y = 1 - (i / (samples - 1)) * 2   
		local radius = math.sqrt(1 - y * y)   

		local theta = phi * i

		local x = math.cos(theta) * radius
		local z = math.sin(theta) * radius

		table.insert(points, Vector3.new(x, y, z))
	end

	--shuffle
	for i = #points, 2, -1 do
		local j = math.random(i)
		points[i], points[j] = points[j], points[i]
	end

	return points 
end

local function TerrainDesat(color)

	local h,s,v = color:ToHSV()
	return Color3.fromHSV(h,s * 0.33,v)

end



local function Setup(settings, records)
	
	
	sunVec = settings.sunVec

	maxSamples = settings.maxSamples

	neonBrightness = settings.neonBrightness	
	brightness = settings.brightness
	gridSize = settings.gridSize

	rayDirs = GeneratePoints(maxSamples)
	
	local terrainMaterials = {
		Enum.Material.Asphalt,
		Enum.Material.Basalt,
		Enum.Material.Brick,
		Enum.Material.Cobblestone,
		Enum.Material.Concrete,
		Enum.Material.CrackedLava,
		Enum.Material.Glacier,
		Enum.Material.Grass,
		Enum.Material.Ground,
		Enum.Material.Ice,
		Enum.Material.LeafyGrass,
		Enum.Material.Limestone,
		Enum.Material.Mud,
		Enum.Material.Pavement,
		Enum.Material.Rock,
		Enum.Material.Salt,
		Enum.Material.Sand,
		Enum.Material.Sandstone,
		Enum.Material.Slate,
		Enum.Material.Snow,
		Enum.Material.WoodPlanks
	}
	
	task.synchronize()

	for key,value in pairs(terrainMaterials) do
		local col = game.Workspace.Terrain:GetMaterialColor(value)
		if (col ~= nil) then
			col = TerrainDesat(col)
			materialColors[value] =  Vector3.new(col.r, col.g, col.b)
		end
	end
	
	
	local waterCol = game.Workspace.Terrain.WaterColor
	waterCol = TerrainDesat(waterCol)
	materialColors[Enum.Material.Water] = Vector3.new(waterCol.r, waterCol.g, waterCol.b)


	rayCastParams = RaycastParams.new()
	rayCastParams.FilterType = Enum.RaycastFilterType.Whitelist
	rayCastParams.FilterDescendantsInstances = settings.whiteList

	
	--Gather pointlights
	broadPhase = {}
	pointLights = {}
	for key,value in game.Workspace:GetDescendants() do

		if (value:IsA("PointLight") and value.Enabled == true and value.Parent) then
			table.insert(pointLights, value)
			WriteLightToBroadphase(value, GetPosition(value), value.Range, gridSize)
		end
	end

	lightRecords = records.lights
	
	--init
	for key,record in pairs(lightRecords) do
		record.numSamples = 0
		record.color = Vector3.zero
		record.rayIndex = 1
		record.light.Brightness = 0
	end
end



function LDotN(l,n)

	return math.clamp(-l:Dot(n),0,1)
end


function DistanceToBox( point, size )

	local q = Vector3.new( math.abs(point.x) - size.x,math.abs(point.y) - size.y,math.abs(point.z) - size.z)
	local b = Vector3.new(math.max(q.x,0),math.max(q.y,0),math.max(q.z,0))
	local c =  math.min(math.max(q.x,math.max(q.y,q.z)),0.0)
	return b.Magnitude + c;
end

function Falloff(dist, total)
	if (total == 0) then
		return 0
	end
	dist/=total

	dist = dist*dist*dist
	dist = 1-dist

	return math.max(dist,0)
end

function PointFalloff(dist, total)
	if (total == 0) then
		return 0
	end

	dist/=total
	dist = math.clamp(dist,0,1)
	--dist = ( dist * dist)
	dist = 1-dist


	return math.max(dist,0)
end


function RaycastThroughTransparency(pos, dir, vecLength, rayCastParams)

	local transparency = Vector3.one
	while (vecLength > 0) do

		local res = game.Workspace:Raycast(pos, dir * vecLength, rayCastParams)
		if (res ~= nil) then

			if (res.Instance.Transparency > 0) then
				--redo
				local covered = res.Distance + 0.05 --Push the ray inside whatever we hit a little bit
				dir += dir * covered
				vecLength -= covered


				if (res.Instance.Transparency < 1) then
					local col = Vector3.new(res.Instance.Color.r,res.Instance.Color.g,res.Instance.Color.b)
					--Tint and darken
					transparency *=	col:Lerp(Vector3.one, res.Instance.Transparency) --* (res.Instance.Transparency)
				end

				continue
			else
				--Todo: if we could read the surfaceAppearance for transparency info, we'd do that here..
				return res, transparency
			end
		end
		return 0, transparency
	end

end

function CheckDirectLightingForPoint(pos, normal, ignoreLight)


	local accum = Vector3.zero


	--game.Workspace:Raycast(pos, sunVec * sunLength, rayCastParams)
	local rayCast, transparency = RaycastThroughTransparency(pos, sunVec, sunLength, rayCastParams)

	if (rayCast == 0) then
		local brightness = LDotN(-sunVec, normal) * game.Lighting.Brightness  
		accum += Vector3.new(brightness,brightness,brightness)  * (transparency)

	end

	
	local lights = broadPhase[BroadphaseKey(pos)]
	
	if (lights == nil) then
		return accum
	end
	for key, value in pairs(lights) do

	--for key,value in pairs(pointLights) do
	
		if (value == ignoreLight) then
			continue
		end
		
		local lightPos = GetPosition(value)

		local range = value.Range 

		local vec = lightPos - pos
		local vecMag = vec.Magnitude
		if (vecMag > range) then
			continue
		end


		local rayCast,transparency = RaycastThroughTransparency(pos, vec.unit, vecMag, rayCastParams)
		--local rayCast = game.Workspace:Raycast(pos, vec, rayCastParams)
		if (rayCast == 0) then

			local scale = PointFalloff(vecMag, value.Range) * LDotN(-vec, normal)

			accum += Vector3.new(value.Color.r,value.Color.g,value.Color.b) * (value.Brightness * scale) * (transparency)


			if (debugging == true) then
				--DebugLine(pos, pos+vec, Color3.new(1,0,0))
			end
		else
			if (debugging == true) then
				--	DebugLine(pos, pos+vec, Color3.new(0,1,0))
			end
		end
	end


	return accum
end



local function GetLightAtRay(ray, lightToIgnore)


	local contribution = Vector3.zero

	if (ray.neon == true) then
		--Neon with range
		--contribution += ray.color * Falloff(ray.dist, neonRange)  * neonBrightness
		contribution += ray.color * neonBrightness
	end


	--Eltob says: the natural dirvergence of the rays means you dont need falloff calcs here
	contribution += CheckDirectLightingForPoint(ray.dest, ray.normal, lightToIgnore) * ray.color 

	if (debugging == true) then
		local col = contribution.Unit
		DebugLine(part.Position, ray.dest,Color3.new(col.x, col.y, col.z) )
	end

	--contribution += CheckDirectLightingForPoint(ray.dest, ray.normal) * ray.color * Falloff(ray.dist, surfaceCastDistance)

	--DebugShowLightAtPoint(ray.dest, ray.normal)


	return contribution  
end

local function GetNextRay(record)

	local ray = {}
	ray.pos = record.lightAttachment.WorldPosition

	--ray.dir = GetPoint()
	ray.dir = rayDirs[record.rayIndex]
	record.rayIndex+=1
	if (record.rayIndex>#rayDirs) then
		record.rayIndex = 1
	end

	local vecLength = surfaceCastDistance
	while (vecLength > 0) do

		local res = game.Workspace:Raycast(ray.pos, ray.dir * vecLength, rayCastParams)
		if (res ~= nil) then
			if (res.Instance.Transparency > 0) then
				--redo
				local covered = res.Distance + 0.05 --Push the ray inside whatever we hit a little bit
				ray.pos += ray.dir * covered
				vecLength -= covered
				continue
			end

			ray.instance = res.Instance
			ray.normal = res.Normal
			ray.dest = res.Position
			ray.dist = res.Distance
			ray.color = Vector3.new(ray.instance.Color.r, ray.instance.Color.g, ray.instance.Color.b )
			if (ray.instance.Material == Enum.Material.Neon) then
				ray.neon = true
			end

			if(res.Instance == game.Workspace.Terrain) then
				ray.color = materialColors[res.Material]

				if (ray.color == nil) then
					print("Unknown material", res.Material)
					ray.color = Vector3.new(1,1,1)
				end
			end

			--if (debugging == true) then
			--	DebugLine(ray.pos, ray.pos + ray.dir * vecLength, ray.instance.Color)		
			--end
			break
		else
			--didn't hit anything
			return nil 
		end
	end

	return ray
end





local function Gather()


	local recalculate = false
	local samplesPerRun = 1
	
	for key,record in pairs(lightRecords) do
			
		
		for counter = 1, samplesPerRun do 
			local ray = GetNextRay(record)
			if (ray == nil) then 
				continue
			end

			local new = GetLightAtRay(ray, record.light)

			if (new.x < 0.01 and new.y < 0.01 and new.z < 0.01) then
				continue
			end

			--recalculate = true
			--table.insert(samples, new)
			
			record.numSamples+=1			
			record.color = record.color + ((new - record.color) / record.numSamples)
		end
	end
	
	return { lights  = lightRecords }
end

local function UpdateLight()
 
	task.synchronize()
	for key,record in pairs(lightRecords) do

		local mag = record.color.Magnitude
		local rgb = record.color.Unit
		if (mag < 0.01) then
		--	part.Color = Color3.new(0,0,0)
		else
			record.light.Color = Color3.new(rgb.x, rgb.y, rgb.z)
			record.light.Brightness = mag * brightness
			--part.Color =  light.Color
		end

		--part:SetAttribute("Samples", numSamples)
		--script.Parent.Parent:SetAttribute("Samples", numSamples)
	end
	return { lights  = lightRecords }
		
end

function module:Invoke(methodName, record, settings)
	
	if (methodName == "Setup") then
		return Setup(settings, record)
	end
	if (methodName == "Gather") then
		 
		return Gather()
	end
	if (methodName == "UpdateLight") then
		return UpdateLight()
	end
end

return module



I want you to make my fluid simulation using the actor communication like this.