v0.1.0 — Major refactor: Serial & Parallel casters, SoA simulation, Motor6D movement, visualization, benchmarks#39
v0.1.0 — Major refactor: Serial & Parallel casters, SoA simulation, Motor6D movement, visualization, benchmarks#39weenachuangkud wants to merge 370 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughFastCast2 v0.1.0 refactors the projectile library to support both Serial (main-thread) and Parallel (Actor VM) execution modes with a unified trajectory contract, SoA-backed simulation engines, Motor6D pooling, and comprehensive documentation. ChangesSerial/Parallel Caster Architecture with SoA Simulation
🎯 4 (Complex) | ⏱️ ~45 minutes Possibly Related Issues
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/FastCast2/BaseCast.luau (1)
175-191:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftCasts registered with
ParallelSimulationbut not stored inActivestable breaks cleanup and sync systems.The cast created at lines 175-191 is registered with
ParallelSimulation.Register(cast)but never added to theActivestable. However,Activesis still expected by:
HandleBulkMoveTo(line 56): IteratesActivesto batch-update cosmetic bullet positionsActiveCastCleaner.Event(line 127): ExpectsActives[activeCastID]to remove cleaned castsSyncChanges.Event(line 139): ExpectsActives[ID]to sync cast stateDestroy(line 368): IteratesActivesto terminate remaining castsAdditionally,
ParallelSimulation.Terminate()does not fire theActiveCastCleanerevent when a cast completes. The cast object is passed both cleanup and sync event references during creation, but these events will never be triggered by the parallel simulation, leaving cleanup callbacks unreachable.The same pattern exists in
Blockcast(lines 238-255) andSpherecast(lines 282-299).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/BaseCast.luau` around lines 175 - 191, The new casts (created in the Raycast/Blockcast/Spherecast paths using ActiveCast.createCastData and then ParallelSimulation.Register(cast)) must be added into the Actives table and have their completion trigger the existing cleanup/sync events; after you call ParallelSimulation.Register(cast) add Actives[cast.ID] = cast, and ensure when the parallel simulation signals a cast completion you fire ActiveCastCleaner.Event(cast.ID) (and remove Actives[cast.ID]) and also fire SyncChanges.Event(cast.ID) (or call the same cleanup callback path used elsewhere) so ActiveCastCleaner, SyncChanges and Destroy/HandleBulkMoveTo logic see and remove these casts; update the Raycast, Blockcast, and Spherecast blocks accordingly and/or attach a termination callback from ParallelSimulation.Register to invoke ActiveCastCleaner and SyncChanges for the cast.src/FastCast2/ActiveCast.luau (1)
367-380:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFinish the
Trajectoryrename insideSimulateCast().These lines switch to
local trajectory = cast.StateInfo.Trajectory, but the function still readslatestTrajectorylater on. That variable is undefined here, so the first simulation step will nil-index onStartTime/Acceleration.Suggested fix
local trajectory = cast.StateInfo.Trajectory @@ - totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime + totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime @@ - if typeof(latestTrajectory.Acceleration) ~= "Vector3" then - latestTrajectory.Acceleration = Vector3.new() + if typeof(trajectory.Acceleration) ~= "Vector3" then + trajectory.Acceleration = Vector3.new() endAlso applies to: 416-418
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` around lines 367 - 380, In SimulateCast(), you started using local trajectory = cast.StateInfo.Trajectory but left uses of latestTrajectory later; replace every use of latestTrajectory with trajectory (e.g., when computing totalDelta and accessing StartTime/Acceleration) so lines that compute totalDelta, and any subsequent references (also around the block at the other occurrence ~416-418) use trajectory.StartTime, trajectory.Acceleration, etc.; verify GetPositionAtTime/GetVelocityAtTime calls and TotalRuntime updates all reference trajectory instead of latestTrajectory.README.md (1)
200-202:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse
Hit, notRayHit, in the events module example.The runtime lookup uses
FastCastEvents.Hit;module.RayHitwill never be invoked.Suggested fix
-module.RayHit = function() +module.Hit = function() print("Hit!") end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 200 - 202, The example registers the wrong event name; replace the handler assignment of module.RayHit with the runtime-expected FastCastEvents.Hit by defining module.Hit (or assigning to module[FastCastEvents.Hit]) so the runtime lookup calls it—update the example to use module.Hit instead of module.RayHit and ensure the string/constant FastCastEvents.Hit is referenced consistently with the rest of the module.src/FastCast2/init.luau (2)
128-145:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftUnify the
StateInfo.Trajectoryshape before using it here.This file currently treats
Trajectoryas both an array (trajectories[index],table.insert(...)) and a single segment (trajectory.StartTime). It also writes throughlastTrajectory, which is never defined in this scope. The first transform update on this path will hit a nil access or mutate the wrong structure.Also applies to: 154-190
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/init.luau` around lines 128 - 145, GetTrajectoryInfo and GetLatestTrajectoryEndInfo assume StateInfo.Trajectory is an array but elsewhere it may be a single segment and code writes through lastTrajectory (undefined here), causing nil/mutation bugs; fix by normalizing StateInfo.Trajectory to a consistent array shape before use (e.g., if StateInfo.Trajectory is a map with StartTime, wrap it in a one-element array) and update any writers to always push/replace elements on that array, and remove or properly define lastTrajectory (use the local variable trajectory or StateInfo.Trajectory[#] instead) so GetTrajectoryInfo(GetLatestTrajectoryEndInfo) never indexes a non-array or mutates the wrong structure.
645-651:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReturn after warning when the parallel caster is not initialized.
This guard only logs, then immediately calls
self.Dispatcher:DispatchAll(...)anyway. Ifself.Dispatcheris nil,SetBulkMoveEnabled()still crashes on the next line.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/init.luau` around lines 645 - 651, In SetBulkMoveEnabled, the guard only warns when the caster isn't initialized but proceeds to call self.Dispatcher:DispatchAll and set self.BulkMoveEnabled, causing a crash if self.Dispatcher is nil; update the function (FastCastParallel:SetBulkMoveEnabled) to return immediately after the warn (or otherwise check for self.Dispatcher) so DispatchAll and setting BulkMoveEnabled only run when self.AlreadyInit and self.Dispatcher are valid.
🧹 Nitpick comments (2)
src/FastCast2/Motor6DPool.luau (1)
46-53: 💤 Low valueConsider simplifying
Get()to avoid recursion.The recursive call after
GrowPoolworks correctly but could be simplified with a direct return.Simplified version
local function Get(): Motor6D - if `#FreeMotor6Ds` > 0 then - return table.remove(FreeMotor6Ds) :: Motor6D - else + if `#FreeMotor6Ds` == 0 then GrowPool(PoolSize * GROWTH_RATE) - return Get() end + return table.remove(FreeMotor6Ds) :: Motor6D end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/Motor6DPool.luau` around lines 46 - 53, The Get function currently recurses after calling GrowPool; replace that recursion with direct logic: call GrowPool(PoolSize * GROWTH_RATE) when FreeMotor6Ds is empty and then immediately remove and return a Motor6D from FreeMotor6Ds. Update the Get function to check FreeMotor6Ds, call GrowPool if empty, then perform a single table.remove(FreeMotor6Ds) :: Motor6D return; keep references to FreeMotor6Ds, GrowPool, PoolSize and GROWTH_RATE intact.Benchmarks/benchSerial.client.luau (1)
55-70: 💤 Low valueConsider adding
ProjectileContainerto filter exclusions.The
FilterDescendantsInstancesis empty, butcastBehavior.AutoIgnoreContainer = trueshould handle this. However, explicitly adding the container ensures it's excluded even before AutoIgnore processes it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Benchmarks/benchSerial.client.luau` around lines 55 - 70, The RaycastParams FilterDescendantsInstances is left empty which can let the ProjectileContainer be hit before AutoIgnoreContainer runs; update CastParams.FilterDescendantsInstances to include the ProjectileContainer instance so the container is explicitly excluded from raycasts (adjust the CastParams used by castBehavior), i.e. add ProjectileContainer to CastParams.FilterDescendantsInstances so CastParams (used by castBehavior.RaycastParams) will proactively ignore that container.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Benchmarks/benchSerial.client.luau`:
- Around line 106-121: The current random direction uses integer
math.random(-1,1) which can produce a zero vector; update the Caster:RaycastFire
call to build float random components instead (e.g. x = (math.random() * 2 - 1)
* 5000) and normalize the Vector3 before passing it, and if the normalized
vector has zero magnitude replace it with a safe fallback direction (e.g.
Vector3.new(0,0,1)); apply these changes where Caster:RaycastFire is invoked so
the direction passed to RaycastFire is never a zero vector and maintains
expected length handling with the 35 parameter and castBehavior.
In `@README.md`:
- Around line 96-100: The README example uses FastCastEnums but never defines or
requires it, so copy-pasting fails; update the example to first require or
define FastCastEnums (the same module providing enums used by FastCast2) before
creating the behavior—e.g., add a line showing how to obtain FastCastEnums (the
module that exposes HighFidelityBehavior) and then keep the existing code that
calls FastCast2.newBehavior() and sets behavior.HighFidelityBehavior =
FastCastEnums.HighFidelityBehavior.Default so the snippet runs as-is.
In `@src/FastCast2/ActiveCast.luau`:
- Around line 854-857: High-fidelity branch still reads from latestTrajectory
instead of the new local trajectory, causing the crash in
HighFidelityBehavior.Always; update the Always branch to use the local variable
trajectory (from cast.StateInfo.Trajectory, where you already default
trajectory.Acceleration) when pulling origin/startTime/velocity/acceleration
(replace references to latestTrajectory in the HighFidelityBehavior.Always code
path with trajectory) so the branch uses the validated trajectory object.
In `@src/FastCast2/ActiveCastSerial.luau`:
- Around line 148-156: The RayInfo table in ActiveCastSerial.luau doesn't copy
shape fields, so SerialSimulation.Register (which reads cast.RayInfo.Size and
cast.RayInfo.Radius for Blockcast/Spherecast) sees nil; update the constructor
that builds RayInfo to include Size = castData.Size and Radius = castData.Radius
(or the appropriate source names on castData) so Blockcast and Spherecast have
their shape parameters when Type is set via CastVariantTypes[castData.CastType];
ensure the names match what SerialSimulation.Register expects.
In `@src/FastCast2/BaseCastSerial.luau`:
- Around line 32-45: The module-level mutable state (Output, ParentCaster,
ObjectCache, BulkMoveToConnection, NextProjectileID) must be made instance-local
so multiple BaseCastSerial instances do not share or clobber each other; modify
BaseCastSerial.Init to set self.Output, self.ParentCaster, self.ObjectCache,
self.BulkMoveToConnection and self.NextProjectileID (and initialize them
appropriately) instead of writing the module-scope variables, then update all
functions in BaseCastSerial that currently reference Output, ParentCaster,
ObjectCache, BulkMoveToConnection or NextProjectileID to use self.Output,
self.ParentCaster, self.ObjectCache, self.BulkMoveToConnection and
self.NextProjectileID so every caster keeps its own event sink/cache/parent and
ID counter.
In `@src/FastCast2/init.luau`:
- Around line 919-935: FastCastSerial:TerminateCast currently only disconnects
local state and destroys cosmetic objects but never delegates termination to the
base serial, leaving the cast registered; update FastCastSerial:TerminateCast to
call self.BaseCast:TerminateCast(cast, castTerminatingFunction) (instead of
directly invoking castTerminatingFunction) after performing the local cleanup
(disconnecting UpdateConnection and destroying CosmeticBulletObject) and before
firing self.Output:Fire("CastTerminating", cast) so the cast is properly
deregistered from SerialSimulation/BaseCast.
- Around line 815-818: GetVelocityCast in FastCastSerial currently returns the
trajectory's InitialVelocity, which ignores acceleration and elapsed time;
update GetVelocityCast(cast: vaildcast) to compute velocity at runtime by using
cast.StateInfo.Trajectory and the elapsed time (use TotalRuntime - StartTime
from the same cast.StateInfo like the position helper does), then evaluate the
trajectory's velocity at that elapsed time and return it instead of
InitialVelocity so accelerating serial casts report the current velocity.
- Around line 847-903: The setters/adders (SetVelocityCast, SetAccelerationCast,
SetPositionCast, AddPositionCast, AddVelocityCast, AddAccelerationCast) mutate
cast.StateInfo.Trajectory in-place without advancing StartTime to the cast's
TotalRuntime, causing retroactive path changes; before changing
Origin/InitialVelocity/Acceleration you must "rebase" the serial trajectory by
integrating the current Trajectory forward to cast.StateInfo.TotalRuntime
(compute the new Origin at that time), set Trajectory.StartTime =
cast.StateInfo.TotalRuntime, and replace
Trajectory.Origin/InitialVelocity/Acceleration with values relative to that new
StartTime (or add deltas for the Add* methods) so subsequent solves treat the
change as occurring only from the current runtime onward. Ensure this rebase
logic is invoked from each of the listed methods (or factor into a helper like
RebaseSerialTrajectory(cast) and call it first).
- Around line 1034-1044: The constructors FastCast.new and FastCast.newParallel
build caster tables (e.g., local fs = { ... } and local fs = { ... }) and set
their metatables (setmetatable(fs, FastCastSerial) / setmetatable(fs,
FastCastParallel)), but currently don't return them; update both functions to
return the constructed fs at the end so callers receive the caster instance
(ensure return fs is added after setmetatable in both FastCast.new and
FastCast.newParallel).
- Around line 721-733: The serial BindableEvent created as BindableOutput is not
wired to the public signals after calling BaseCastSerial.Init, so events like
"CastFire" and "CastTerminating" emitted by self.BaseCast never reach consumers;
add a listener on BindableOutput.Event (after self.BaseCast =
BaseCastSerial.Init(...)) that inspects the incoming event name and forwards it
to the corresponding public signal on this instance (for example, map "CastFire"
-> self.CastFire:Fire(...), "CastTerminating" -> self.CastTerminating:Fire(...),
and forward any other serial event names similarly) so that
FastCastSerial:Init/instance exposes the same callbacks as BaseCastSerial.
In `@src/FastCast2/ParallelSimulation.luau`:
- Around line 263-270: The call to CastHandler is passing a table named variant
for all cast types but Blockcast expects a Vector3 and Spherecast expects a
number; change how variant is constructed so that for EnumCastTypes.Blockcast
you set variant to castSize[i] (a Vector3) and for EnumCastTypes.Spherecast set
variant to castRadius[i] (a number), leaving the existing table form only for
other cast types if needed, then call CastHandler(castWorldRoot[i],
lastPosition, rayDir, castRaycastParams[i], variant).
- Around line 313-316: The loop that calls ParallelSimulation.Terminate over
destroyedIds must iterate in descending numeric order because Unregister() uses
swap-delete and removing lower indices first can change positions of higher
indices; update the code that processes destroyedIds (the block calling
ParallelSimulation.Terminate) to sort or iterate destroyedIds from highest to
lowest (e.g., iterate numeric indices in reverse) so Terminate is invoked on IDs
in descending order to avoid invalidating queued ids after Unregister().
- Around line 161-221: The removal logic swaps the tail (lastId) into slot id
but then disconnects and clears fields from id (corrupting the moved cast); fix
by treating lastId as the source: when id ~= lastId assign casts[id] =
casts[lastId] (in addition to the per-field arrays) and update casts[lastId].ID
= id, then call Motor6DPool.Disconnect(castMotor6D[lastId]) and nil out all
cast*[...] entries using lastId (not id) before decrementing castCount; ensure
castCFrame and castMotor6D clears reference lastId so the moved cast in slot id
remains intact.
- Around line 321-324: ParallelSimulation.Start currently connects RS.PreRender
(client-only) which will error on servers; modify ParallelSimulation.Start to
first check the RunService client/server state (use RS:IsClient() or similar)
and only call RS.PreRender:ConnectParallel(UpdateCasts) when running on the
client, or alternatively use RS.Heartbeat for server-side behavior; ensure you
still set and guard ParallelSimulation.StepConnection and early-return if
already connected. This change keeps ParallelSimulation.Start, UpdateCasts, and
ParallelSimulation.StepConnection as the anchors for the fix.
In `@src/FastCast2/SerialSimulation.luau`:
- Around line 317-320: The loop that calls SerialSimulation.Terminate for each
id in destroyedIds must iterate destroyedIds in descending index order to avoid
invalidation caused by swap-delete compaction; change the iteration over
destroyedIds so you process highest indices first (e.g., iterate from
`#destroyedIds` down to 1) and call SerialSimulation.Terminate(id) in that
descending order to ensure removals do not shift and invalidate later ids.
- Around line 263-274: The code always passes the variant table to CastHandler,
but Blockcast and Spherecast handlers expect a raw Vector3/number; update the
call so it passes variant.Size for EnumCastTypes.Blockcast and variant.Radius
for EnumCastTypes.Spherecast (use castSize[i] and castRadius[i] or the variant
fields you already set) instead of the whole variant table when castType is
Blockcast or Spherecast; keep passing variant only for other cast types. Ensure
this change is applied at the CastHandler(...) invocation so the correct shape
argument is forwarded to block/sphere handlers.
- Around line 164-225: The code in Unregister swaps the tail row into id but
then disconnects and clears entries at index id (which now holds the moved
cast), causing the moved cast to be corrupted; change the cleanup to operate on
lastId instead: perform Motor6DPool.Disconnect(castMotor6D[lastId]) and set
castIDs[lastId], castOrigin[lastId], ..., castCFrame[lastId],
castMotor6D[lastId], casts[lastId] = nil, then set castCount = lastId - 1. Keep
the earlier copy/ID update that moves lastId into id, but ensure all nil
assignments and the disconnect target lastId (not id) so the moved cast at id
remains intact.
---
Outside diff comments:
In `@README.md`:
- Around line 200-202: The example registers the wrong event name; replace the
handler assignment of module.RayHit with the runtime-expected FastCastEvents.Hit
by defining module.Hit (or assigning to module[FastCastEvents.Hit]) so the
runtime lookup calls it—update the example to use module.Hit instead of
module.RayHit and ensure the string/constant FastCastEvents.Hit is referenced
consistently with the rest of the module.
In `@src/FastCast2/ActiveCast.luau`:
- Around line 367-380: In SimulateCast(), you started using local trajectory =
cast.StateInfo.Trajectory but left uses of latestTrajectory later; replace every
use of latestTrajectory with trajectory (e.g., when computing totalDelta and
accessing StartTime/Acceleration) so lines that compute totalDelta, and any
subsequent references (also around the block at the other occurrence ~416-418)
use trajectory.StartTime, trajectory.Acceleration, etc.; verify
GetPositionAtTime/GetVelocityAtTime calls and TotalRuntime updates all reference
trajectory instead of latestTrajectory.
In `@src/FastCast2/BaseCast.luau`:
- Around line 175-191: The new casts (created in the
Raycast/Blockcast/Spherecast paths using ActiveCast.createCastData and then
ParallelSimulation.Register(cast)) must be added into the Actives table and have
their completion trigger the existing cleanup/sync events; after you call
ParallelSimulation.Register(cast) add Actives[cast.ID] = cast, and ensure when
the parallel simulation signals a cast completion you fire
ActiveCastCleaner.Event(cast.ID) (and remove Actives[cast.ID]) and also fire
SyncChanges.Event(cast.ID) (or call the same cleanup callback path used
elsewhere) so ActiveCastCleaner, SyncChanges and Destroy/HandleBulkMoveTo logic
see and remove these casts; update the Raycast, Blockcast, and Spherecast blocks
accordingly and/or attach a termination callback from
ParallelSimulation.Register to invoke ActiveCastCleaner and SyncChanges for the
cast.
In `@src/FastCast2/init.luau`:
- Around line 128-145: GetTrajectoryInfo and GetLatestTrajectoryEndInfo assume
StateInfo.Trajectory is an array but elsewhere it may be a single segment and
code writes through lastTrajectory (undefined here), causing nil/mutation bugs;
fix by normalizing StateInfo.Trajectory to a consistent array shape before use
(e.g., if StateInfo.Trajectory is a map with StartTime, wrap it in a one-element
array) and update any writers to always push/replace elements on that array, and
remove or properly define lastTrajectory (use the local variable trajectory or
StateInfo.Trajectory[#] instead) so
GetTrajectoryInfo(GetLatestTrajectoryEndInfo) never indexes a non-array or
mutates the wrong structure.
- Around line 645-651: In SetBulkMoveEnabled, the guard only warns when the
caster isn't initialized but proceeds to call self.Dispatcher:DispatchAll and
set self.BulkMoveEnabled, causing a crash if self.Dispatcher is nil; update the
function (FastCastParallel:SetBulkMoveEnabled) to return immediately after the
warn (or otherwise check for self.Dispatcher) so DispatchAll and setting
BulkMoveEnabled only run when self.AlreadyInit and self.Dispatcher are valid.
---
Nitpick comments:
In `@Benchmarks/benchSerial.client.luau`:
- Around line 55-70: The RaycastParams FilterDescendantsInstances is left empty
which can let the ProjectileContainer be hit before AutoIgnoreContainer runs;
update CastParams.FilterDescendantsInstances to include the ProjectileContainer
instance so the container is explicitly excluded from raycasts (adjust the
CastParams used by castBehavior), i.e. add ProjectileContainer to
CastParams.FilterDescendantsInstances so CastParams (used by
castBehavior.RaycastParams) will proactively ignore that container.
In `@src/FastCast2/Motor6DPool.luau`:
- Around line 46-53: The Get function currently recurses after calling GrowPool;
replace that recursion with direct logic: call GrowPool(PoolSize * GROWTH_RATE)
when FreeMotor6Ds is empty and then immediately remove and return a Motor6D from
FreeMotor6Ds. Update the Get function to check FreeMotor6Ds, call GrowPool if
empty, then perform a single table.remove(FreeMotor6Ds) :: Motor6D return; keep
references to FreeMotor6Ds, GrowPool, PoolSize and GROWTH_RATE intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7a57e535-492e-4838-826f-86da925a99ab
📒 Files selected for processing (18)
Benchmarks/benchParallel.client.luauBenchmarks/benchSerial.client.luauREADME.mdTODO.mddocs/api-reference.mddocs/changelog.mdsrc/FastCast2/ActiveCast.luausrc/FastCast2/ActiveCastSerial.luausrc/FastCast2/BaseCast.luausrc/FastCast2/BaseCastSerial.luausrc/FastCast2/Configs.luausrc/FastCast2/DefaultConfigs.luausrc/FastCast2/FastCastEnums.luausrc/FastCast2/Motor6DPool.luausrc/FastCast2/ParallelSimulation.luausrc/FastCast2/SerialSimulation.luausrc/FastCast2/TypeDefinitions.luausrc/FastCast2/init.luau
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
README.md (1)
117-121:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDefine
FastCastEnumsbefore usingHighFidelityBehavior.
FastCastEnumsis referenced on Line 121 but never required in this snippet, so copy-paste usage breaks immediately.Suggested fix
-- Requires local FastCast2 = require(Rep:WaitForChild("FastCast2")) +local FastCastEnums = require(FastCast2:WaitForChild("FastCastEnums"))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 117 - 121, The snippet uses FastCastEnums but never defines it; before calling FastCast2.newBehavior() and setting behavior.HighFidelityBehavior, define FastCastEnums by requiring or referencing the enum module (e.g., local FastCastEnums = require(<fastcast-enums-module>) or use FastCast2.Enums if provided) so the symbol FastCastEnums exists; update the README snippet to show the required line (defining FastCastEnums) immediately above the behavior creation and then keep behavior.HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@README.md`:
- Around line 69-74: The README's Rojo setup sequence removes the wrong .git
path after changing into the repo: the step currently runs "rm -rf
FastCast2/.git" even though you're already in the FastCast2 directory; update
the removal command to target the current directory's git metadata (replace the
"rm -rf FastCast2/.git" invocation with "rm -rf .git") so the sequence correctly
detaches the cloned repo's git history after "git clone" and "cd FastCast2".
- Around line 177-179: The README example is inconsistent: earlier the constant
is defined as SPEED but the RaycastFire call uses speed; update the snippet so
the same identifier is used throughout (either rename the earlier definition to
speed or change the RaycastFire call to use SPEED) and ensure the
Caster:RaycastFire(origin, direction, speed, behavior) line matches the declared
variable name (SPEED) so users can copy-paste without renaming.
---
Duplicate comments:
In `@README.md`:
- Around line 117-121: The snippet uses FastCastEnums but never defines it;
before calling FastCast2.newBehavior() and setting
behavior.HighFidelityBehavior, define FastCastEnums by requiring or referencing
the enum module (e.g., local FastCastEnums = require(<fastcast-enums-module>) or
use FastCast2.Enums if provided) so the symbol FastCastEnums exists; update the
README snippet to show the required line (defining FastCastEnums) immediately
above the behavior creation and then keep behavior.HighFidelityBehavior =
FastCastEnums.HighFidelityBehavior.Default.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
- README.md: Fix RayHit -> Hit event name - ActiveCast.luau: Fix latestTrajectory -> trajectory variable references - BaseCast.luau: Add Actives table for parallel casts - init.luau: Add return statements, fix SetBulkMoveEnabled guard, delegate termination to BaseCast - ActiveCastSerial.luau: Add Size/Radius to RayInfo - ParallelSimulation.luau: Fix iteration order, server check, fix swap-delete to reference lastId correctly - SerialSimulation.luau: Fix iteration order - Motor6DPool.luau: Simplify Get() recursion - benchSerial.client.luau: Fix direction normalization
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/FastCast2/ActiveCast.luau (2)
107-118:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
DebrisAddschedules destruction even after immediate destroy.Same issue as in
ActiveCastSerial.luau: whenLifetime <= 0, the function destroys the object but continues to schedule a delayed destroy attempt.Proposed fix
local function DebrisAdd(obj: Instance, Lifetime: number) if not obj then return end if Lifetime <= 0 then obj:Destroy() + return end task.delay(Lifetime, function() obj:Destroy() end) end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` around lines 107 - 118, DebrisAdd currently destroys obj when Lifetime <= 0 but then still schedules task.delay, causing a second destroy attempt; update DebrisAdd (the function named DebrisAdd) so that after calling obj:Destroy() for Lifetime <= 0 it immediately returns (i.e., ensure the early-terminate branch prevents the task.delay from being scheduled), leaving the nil check and the delayed-destroy logic unchanged for positive lifetimes.
380-380:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winUndefined variable
latestTrajectorywill cause runtime error.Line 380 references
latestTrajectorywhich is never defined inSimulateCast. The function definestrajectoryat line 367 but this line uses the wrong variable name.Proposed fix
- totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime + totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` at line 380, In SimulateCast, the variable latestTrajectory is undefined and causes a runtime error; change the reference to the existing local variable trajectory (from the declaration at line 367) so the subtraction uses trajectory.StartTime instead of latestTrajectory.StartTime, and add a nil-check (or assert) on trajectory before using trajectory.StartTime to avoid future nil derefs; locate the assignment to totalDelta in SimulateCast and replace latestTrajectory with trajectory and guard usage accordingly.
♻️ Duplicate comments (5)
README.md (3)
177-178:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse the same speed variable name as defined in the example.
Line 178 uses
speed, but the example definesSPEED(Line 100). This causes a copy-paste error for users.Proposed fix
-- Fire same as serial -Caster:RaycastFire(origin, direction, speed, behavior) +Caster:RaycastFire(origin, direction, SPEED, behavior)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 177 - 178, The README example is inconsistent: the RaycastFire call uses lowercase speed but the example defines SPEED; update the invocation (Caster:RaycastFire(origin, direction, SPEED)) or change the constant to match (rename SPEED to speed) so the variable name is consistent with the declaration; adjust any surrounding text to use the same symbol (SPEED or speed) throughout the README to prevent copy-paste errors.
71-73:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the
.gitremoval path in Rojo setup commands.After
cd FastCast2, the command targetsFastCast2/.gitwhich is incorrect. The.gitfolder is in the current directory after thecd.Proposed fix
git clone https://github.com/weenachuangkud/FastCast2.git cd FastCast2 - rm -rf FastCast2/.git + rm -rf .git🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 71 - 73, The README's Rojo setup commands remove the wrong .git path after changing into the project directory: after the sequence that runs "git clone ...", "cd FastCast2", the removal targets "FastCast2/.git" instead of the repository's .git in the current directory; update the final command to remove the repository metadata from the current directory (use ".git") so the sequence becomes: git clone ..., cd FastCast2, rm -rf .git (i.e., replace the "FastCast2/.git" target with ".git").
117-121:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDefine
FastCastEnumsbefore using it in the example.Line 121 references
FastCastEnums.HighFidelityBehavior.Default, but the example never requiresFastCastEnums. Users copy-pasting this example will get an error.Proposed fix
-- Requires local FastCast2 = require(Rep:WaitForChild("FastCast2")) +local FastCastEnums = require(Rep:WaitForChild("FastCast2"):WaitForChild("FastCastEnums"))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 117 - 121, The example uses FastCastEnums.HighFidelityBehavior but never defines or requires FastCastEnums, causing copy-paste errors; update the snippet to require or define FastCastEnums before it’s referenced (place a require/import for FastCastEnums near where FastCast2 is referenced) so that FastCastEnums, FastCast2, and any referenced CastParams are available when setting behavior.HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default.src/FastCast2/SerialSimulation.luau (2)
267-274:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winPass raw shape argument to block/sphere handlers instead of variant table.
castHandlers[Blockcast]expectssize: Vector3andcastHandlers[Spherecast]expectsradius: number, but line 274 passes the wholevarianttable. This will cause Roblox API errors for block/sphere casts.Proposed fix
- local variant = {} - if castType == EnumCastTypes.Blockcast then - variant.Size = castSize[i] - elseif castType == EnumCastTypes.Spherecast then - variant.Radius = castRadius[i] - end - - local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], variant) + local result + if castType == EnumCastTypes.Blockcast then + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], castSize[i]) + elseif castType == EnumCastTypes.Spherecast then + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], castRadius[i]) + else + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i]) + end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/SerialSimulation.luau` around lines 267 - 274, The call to CastHandler is passing a variant table but castHandlers for EnumCastTypes.Blockcast and EnumCastTypes.Spherecast expect a raw size (Vector3) or radius (number); change the code so that when castType == EnumCastTypes.Blockcast you pass castSize[i] directly (not variant), and when castType == EnumCastTypes.Spherecast you pass castRadius[i] directly; keep using variant (or nil) for other cast types, and update the CastHandler invocation to pass the raw shape value for block/sphere cases instead of the variant table.
161-226:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftSwap-delete cleanup still has multiple bugs.
The
Unregisterfunction has several issues:
Motor6D disconnect targets wrong cast: After swap,
castMotor6D[id]contains the moved cast's motor6d. The removed cast's motor6d should be saved before the swap.
castsarray not updated during swap: The individual SoA arrays are copied, butcasts[id]is never reassigned tocasts[lastId], leaving the old reference.Line 212 clears wrong index:
castCFrame[id] = nilshould becastCFrame[lastId] = nil.Line 224 clears wrong index:
casts[id] = nilshould becasts[lastId] = nil.Proposed fix
function SerialSimulation.Unregister(id: number) if not casts[id] then return end local lastId = castCount + local removedMotor6D = castMotor6D[id] + if id ~= lastId then + casts[id] = casts[lastId] castIDs[id] = castIDs[lastId] castOrigin[id] = castOrigin[lastId] castVelocity[id] = castVelocity[lastId] castAcceleration[id] = castAcceleration[lastId] castTotalRuntime[id] = castTotalRuntime[lastId] castDistanceCovered[id] = castDistanceCovered[lastId] castMaxDistance[id] = castMaxDistance[lastId] castPaused[id] = castPaused[lastId] castHighFidelitySegmentSize[id] = castHighFidelitySegmentSize[lastId] castHighFidelityBehavior[id] = castHighFidelityBehavior[lastId] castIsActivelyResimulating[id] = castIsActivelyResimulating[lastId] castCancelHighResCast[id] = castCancelHighResCast[lastId] castCFrame[id] = castCFrame[lastId] castWorldRoot[id] = castWorldRoot[lastId] castRaycastParams[id] = castRaycastParams[lastId] castCosmeticBullet[id] = castCosmeticBullet[lastId] castCastType[id] = castCastType[lastId] castSize[id] = castSize[lastId] castRadius[id] = castRadius[lastId] castVisualize[id] = castVisualize[lastId] castVisualizeSettings[id] = castVisualizeSettings[lastId] castCaster[id] = castCaster[lastId] castMotor6D[id] = castMotor6D[lastId] - if casts[lastId] then - casts[lastId].ID = id + if casts[id] then + casts[id].ID = id end end -- Return motor6d to pool - if castMotor6D[id] then - Motor6DPool.Disconnect(castMotor6D[id]) + if removedMotor6D then + Motor6DPool.Disconnect(removedMotor6D) end castIDs[lastId] = nil castOrigin[lastId] = nil castVelocity[lastId] = nil castAcceleration[lastId] = nil castTotalRuntime[lastId] = nil castDistanceCovered[lastId] = nil castMaxDistance[lastId] = nil castPaused[lastId] = nil castHighFidelitySegmentSize[lastId] = nil castHighFidelityBehavior[lastId] = nil castIsActivelyResimulating[lastId] = nil castCancelHighResCast[lastId] = nil - castCFrame[id] = nil + castCFrame[lastId] = nil castWorldRoot[lastId] = nil castRaycastParams[lastId] = nil castCosmeticBullet[lastId] = nil castCastType[lastId] = nil castSize[lastId] = nil castRadius[lastId] = nil castVisualize[lastId] = nil castVisualizeSettings[lastId] = nil castCaster[lastId] = nil castMotor6D[lastId] = nil - casts[id] = nil + casts[lastId] = nil castCount = lastId - 1 end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/SerialSimulation.luau` around lines 161 - 226, In SerialSimulation.Unregister, save the removed cast's motor6d reference (e.g. local removedMotor = castMotor6D[id]) before doing the swap-delete; perform the swap for the SoA arrays AND assign casts[id] = casts[lastId] (and if casts[lastId] exists update its .ID = id) so the moved object reference is preserved; after the swap, disconnect the saved removedMotor (not castMotor6D[id]); fix the nil clears to clear lastId indices (e.g. castCFrame[lastId] = nil) and set casts[lastId] = nil instead of casts[id]; finally decrement castCount as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/FastCast2/ActiveCastSerial.luau`:
- Around line 66-74: In DebrisAdd, when Lifetime <= 0 the function destroys obj
but continues to schedule task.delay which will try to destroy the object again;
modify the DebrisAdd function (the Lifetime <= 0 branch) to destroy the object
and then return immediately to avoid scheduling the delayed destroy via
task.delay.
---
Outside diff comments:
In `@src/FastCast2/ActiveCast.luau`:
- Around line 107-118: DebrisAdd currently destroys obj when Lifetime <= 0 but
then still schedules task.delay, causing a second destroy attempt; update
DebrisAdd (the function named DebrisAdd) so that after calling obj:Destroy() for
Lifetime <= 0 it immediately returns (i.e., ensure the early-terminate branch
prevents the task.delay from being scheduled), leaving the nil check and the
delayed-destroy logic unchanged for positive lifetimes.
- Line 380: In SimulateCast, the variable latestTrajectory is undefined and
causes a runtime error; change the reference to the existing local variable
trajectory (from the declaration at line 367) so the subtraction uses
trajectory.StartTime instead of latestTrajectory.StartTime, and add a nil-check
(or assert) on trajectory before using trajectory.StartTime to avoid future nil
derefs; locate the assignment to totalDelta in SimulateCast and replace
latestTrajectory with trajectory and guard usage accordingly.
---
Duplicate comments:
In `@README.md`:
- Around line 177-178: The README example is inconsistent: the RaycastFire call
uses lowercase speed but the example defines SPEED; update the invocation
(Caster:RaycastFire(origin, direction, SPEED)) or change the constant to match
(rename SPEED to speed) so the variable name is consistent with the declaration;
adjust any surrounding text to use the same symbol (SPEED or speed) throughout
the README to prevent copy-paste errors.
- Around line 71-73: The README's Rojo setup commands remove the wrong .git path
after changing into the project directory: after the sequence that runs "git
clone ...", "cd FastCast2", the removal targets "FastCast2/.git" instead of the
repository's .git in the current directory; update the final command to remove
the repository metadata from the current directory (use ".git") so the sequence
becomes: git clone ..., cd FastCast2, rm -rf .git (i.e., replace the
"FastCast2/.git" target with ".git").
- Around line 117-121: The example uses FastCastEnums.HighFidelityBehavior but
never defines or requires FastCastEnums, causing copy-paste errors; update the
snippet to require or define FastCastEnums before it’s referenced (place a
require/import for FastCastEnums near where FastCast2 is referenced) so that
FastCastEnums, FastCast2, and any referenced CastParams are available when
setting behavior.HighFidelityBehavior =
FastCastEnums.HighFidelityBehavior.Default.
In `@src/FastCast2/SerialSimulation.luau`:
- Around line 267-274: The call to CastHandler is passing a variant table but
castHandlers for EnumCastTypes.Blockcast and EnumCastTypes.Spherecast expect a
raw size (Vector3) or radius (number); change the code so that when castType ==
EnumCastTypes.Blockcast you pass castSize[i] directly (not variant), and when
castType == EnumCastTypes.Spherecast you pass castRadius[i] directly; keep using
variant (or nil) for other cast types, and update the CastHandler invocation to
pass the raw shape value for block/sphere cases instead of the variant table.
- Around line 161-226: In SerialSimulation.Unregister, save the removed cast's
motor6d reference (e.g. local removedMotor = castMotor6D[id]) before doing the
swap-delete; perform the swap for the SoA arrays AND assign casts[id] =
casts[lastId] (and if casts[lastId] exists update its .ID = id) so the moved
object reference is preserved; after the swap, disconnect the saved removedMotor
(not castMotor6D[id]); fix the nil clears to clear lastId indices (e.g.
castCFrame[lastId] = nil) and set casts[lastId] = nil instead of casts[id];
finally decrement castCount as before.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4502e50a-8c86-4cbc-baeb-08ac3a2217c6
📒 Files selected for processing (9)
Benchmarks/benchSerial.client.luauREADME.mdsrc/FastCast2/ActiveCast.luausrc/FastCast2/ActiveCastSerial.luausrc/FastCast2/BaseCast.luausrc/FastCast2/Motor6DPool.luausrc/FastCast2/ParallelSimulation.luausrc/FastCast2/SerialSimulation.luausrc/FastCast2/init.luau
✅ Files skipped from review due to trivial changes (4)
- src/FastCast2/Motor6DPool.luau
- src/FastCast2/ParallelSimulation.luau
- src/FastCast2/init.luau
- Benchmarks/benchSerial.client.luau
🚧 Files skipped from review as they are similar to previous changes (1)
- src/FastCast2/BaseCast.luau
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. The branch was updated while autofix was in progress. Please try again. |
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found. |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/FastCast2/ActiveCast.luau (1)
379-382:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winReplace stale
latestTrajectoryreference inSimulateCast.Line 381 still dereferences
latestTrajectory, which is undefined in this path and will hard-fail at runtime.Suggested fix
- totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime + totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` around lines 379 - 382, In SimulateCast the code uses the undefined local latestTrajectory when computing totalDelta; replace that reference with the stored trajectory on the cast (cast.StateInfo.LatestTrajectory.StartTime) and add a nil-check before dereferencing (fall back to 0 or cast.StateInfo.StartTime if LatestTrajectory is nil) so totalDelta is computed from a valid start time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/FastCast2/ActiveCast.luau`:
- Around line 368-373: The code reads trajectory.Acceleration into local
acceleration before any validation and then uses it later (see trajectory,
trajectory.Acceleration, and variables initialVelocity/totalDelta/origin), which
can crash if Acceleration is nil/invalid; change the flow to first validate
trajectory and trajectory.Acceleration (and its expected numeric/vector type)
with the same guard used at lines ~417-419, only then assign local acceleration
= trajectory.Acceleration and proceed to use it for kinematics (or provide a
safe fallback/default if invalid); in short, move or duplicate the validation to
precede the read and usage of trajectory.Acceleration (and adjust
initialVelocity/read order accordingly).
In `@src/FastCast2/ActiveCastSerial.luau`:
- Around line 149-151: RayInfo currently stores a shared reference to
castData.RaycastParams which allows later mutations to affect active serial
casts; change the assignment so RayInfo.Parameters gets a cloned copy (e.g. use
castData.RaycastParams:Clone() for a RaycastParams instance, or
table.clone(castData.RaycastParams) for plain tables) when building RayInfo in
ActiveCastSerial so each cast retains its own immutable raycast parameters.
---
Duplicate comments:
In `@src/FastCast2/ActiveCast.luau`:
- Around line 379-382: In SimulateCast the code uses the undefined local
latestTrajectory when computing totalDelta; replace that reference with the
stored trajectory on the cast (cast.StateInfo.LatestTrajectory.StartTime) and
add a nil-check before dereferencing (fall back to 0 or cast.StateInfo.StartTime
if LatestTrajectory is nil) so totalDelta is computed from a valid start time.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c90d504-b27b-461e-af54-b66eb8dff4c0
📒 Files selected for processing (3)
README.mdsrc/FastCast2/ActiveCast.luausrc/FastCast2/ActiveCastSerial.luau
✅ Files skipped from review due to trivial changes (1)
- README.md
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/FastCast2/ActiveCast.luau (1)
381-381:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winCritical bug:
latestTrajectoryis undefined.Line 381 references
latestTrajectory.StartTime, butlatestTrajectoryis never declared inSimulateCast. This will cause a runtime error. It should reference the localtrajectoryvariable declared on Line 368.Proposed fix
- totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime + totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` at line 381, The code in SimulateCast mistakenly references an undeclared variable latestTrajectory when computing totalDelta; replace latestTrajectory.StartTime with the local trajectory.StartTime (the trajectory variable declared earlier in SimulateCast) and ensure trajectory is non-nil before using it so totalDelta is computed as cast.StateInfo.TotalRuntime - trajectory.StartTime.
🧹 Nitpick comments (1)
src/FastCast2/ActiveCast.luau (1)
787-791: 💤 Low valueRedundant double-clone of RaycastParams.
cast.RayInfo.Parametersis already initialized at Line 762 (either as a clone or a new instance), so the condition at Line 787 will always be true. This results in cloning the params twice or unnecessarily cloning a fresh instance.Proposed fix - remove redundant block
- if cast.RayInfo.Parameters ~= nil then - cast.RayInfo.Parameters = CloneCastParams(cast.RayInfo.Parameters) - else - cast.RayInfo.Parameters = RaycastParams.new() - end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ActiveCast.luau` around lines 787 - 791, The block that re-checks and re-clones cast.RayInfo.Parameters (using CloneCastParams and RaycastParams.new) is redundant because cast.RayInfo.Parameters is already initialized earlier; remove the entire if/else that reassigns cast.RayInfo.Parameters so the initial initialization remains the single source of truth and no double-clone occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/FastCast2/ActiveCast.luau`:
- Line 381: The code in SimulateCast mistakenly references an undeclared
variable latestTrajectory when computing totalDelta; replace
latestTrajectory.StartTime with the local trajectory.StartTime (the trajectory
variable declared earlier in SimulateCast) and ensure trajectory is non-nil
before using it so totalDelta is computed as cast.StateInfo.TotalRuntime -
trajectory.StartTime.
---
Nitpick comments:
In `@src/FastCast2/ActiveCast.luau`:
- Around line 787-791: The block that re-checks and re-clones
cast.RayInfo.Parameters (using CloneCastParams and RaycastParams.new) is
redundant because cast.RayInfo.Parameters is already initialized earlier; remove
the entire if/else that reassigns cast.RayInfo.Parameters so the initial
initialization remains the single source of truth and no double-clone occurs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: be854d14-8910-4289-8c71-eb84c9e1ac4e
📒 Files selected for processing (2)
src/FastCast2/ActiveCast.luausrc/FastCast2/ActiveCastSerial.luau
✅ Files skipped from review due to trivial changes (1)
- src/FastCast2/ActiveCastSerial.luau
|
Tip For best results, initiate chat on the files or code changes.
Just let me know what you'd like! 😊 |
|
@coderabbitai Could you answers all my replies? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me check the full PR for any other comments or replies from you that need responses. 🏁 Script executed: Length of output: 183542
For reference, here are the remaining open issues in this PR that still need attention (not yet marked ✅):
Would you like me to generate a combined AI agent prompt for all of these remaining issues at once? |
Sure @coderabbitai |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me read the current state of all affected files before generating the combined prompt. 🏁 Script executed: Length of output: 109272
Combined AI Agent Prompt — FastCast2 Bug FixesFix the following 10 bugs across 5 files in the FastCast2 Roblox Luau library. Apply every change exactly as shown. Fix 1 —
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/FastCast2/init.luau (1)
998-1011:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard
FastCastParallel:Destroy()whenInit()never completed.Line 1010 assumes
self.Dispatcherexists, but new parallel casters start withDispatcher = nil. CallingDestroy()before a successfulInit()will error during cleanup.🛡️ Minimal fix
- self.Dispatcher:Destroy() + if self.Dispatcher then + self.Dispatcher:Destroy() + self.Dispatcher = nil + end setmetatable(self, nil) end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/init.luau` around lines 998 - 1011, FastCastParallel:Destroy() assumes Init() completed and unconditionally calls self.Dispatcher:Destroy(); change Destroy to guard against partially-initialized instances by nil-checking any optional fields before calling methods on them (at minimum check self.Dispatcher and self.ObjectCache exist before calling :Destroy()), and only call DestroySignal on signals that are non-nil; keep setmetatable(self, nil) at the end so cleanup still completes for partially-initialized objects.
♻️ Duplicate comments (2)
src/FastCast2/ParallelSimulation.luau (2)
264-271:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winPass raw shape values to the block/sphere cast handlers.
Lines 264-271 always pass a table as the fifth argument, but
Blockcastexpects aVector3andSpherecastexpects anumber. Parallel block/sphere casts will fail until the raw size/radius is forwarded.🔧 Minimal fix
- local variant = {} - if castType == EnumCastTypes.Blockcast then - variant.Size = castSize[i] - elseif castType == EnumCastTypes.Spherecast then - variant.Radius = castRadius[i] - end - - local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], variant) + local result + if castType == EnumCastTypes.Blockcast then + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], castSize[i] :: Vector3) + elseif castType == EnumCastTypes.Spherecast then + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], castRadius[i] :: number) + else + result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i]) + end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ParallelSimulation.luau` around lines 264 - 271, The code always builds a table `variant` and passes it to `CastHandler`, but `EnumCastTypes.Blockcast` expects a raw Vector3 (`castSize[i]`) and `EnumCastTypes.Spherecast` expects a raw number (`castRadius[i]`); change the call site so when `castType` equals `EnumCastTypes.Blockcast` pass `castSize[i]` as the fifth argument and when `EnumCastTypes.Spherecast` pass `castRadius[i]`, otherwise build/pass the `variant` table as before; update the call to `CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], <appropriate value>)` accordingly so block/sphere casts receive the raw shape values.
161-221:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winPreserve the swapped-in cast during
Unregister().This swap-delete path still tears down the destination slot after moving
lastIdintoid: the moved cast loses itsCFrame,casts[id]is nilled, and itsMotor6Dis disconnected, while tail removals leak their own motor. Clear/disconnect the removed row, not the destination row.🧩 Minimal fix
function ParallelSimulation.Unregister(id: number) if not casts[id] then return end local lastId = castCount + local removedMotor6D = castMotor6D[id] + local movedCast = casts[lastId] if id ~= lastId then + casts[id] = movedCast castIDs[id] = castIDs[lastId] castOrigin[id] = castOrigin[lastId] castVelocity[id] = castVelocity[lastId] @@ castCaster[id] = castCaster[lastId] castMotor6D[id] = castMotor6D[lastId] - if casts[lastId] then - casts[lastId].ID = id - casts[id] = casts[lastId] + if movedCast then + movedCast.ID = id end end - if id ~= lastId and castMotor6D[lastId] then - Motor6DPool.Disconnect(castMotor6D[lastId]) + if removedMotor6D then + Motor6DPool.Disconnect(removedMotor6D) end @@ - castCFrame[id] = nil + castCFrame[lastId] = nil @@ - casts[id] = nil + casts[lastId] = nil castCount = lastId - 1 end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCast2/ParallelSimulation.luau` around lines 161 - 221, In Unregister(), the cleanup is tearing down the destination slot after a swap: change the cleanup to clear the removed row (lastId) instead of id — fix the mistaken castCFrame[id] = nil to castCFrame[lastId] = nil and set casts[lastId] = nil (not casts[id]); also only call Motor6DPool.Disconnect for the tail-removal case (when id == lastId) so we don't disconnect the Motor6D that was moved into id (adjust the conditional around Motor6DPool.Disconnect(castMotor6D[lastId]) accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/FastCast2/BaseCastSerial.luau`:
- Around line 106-110: The serial Raycast path is using incorrect
double-dereferences (self.self) which will error before registration or
emitting; update the references in the Serial path to use the same instance
fields used by Blockcast/Spherecast (replace self.self.ParentCaster with
self.ParentCaster when calling ActiveCastSerial.new, and replace
self.self.Output with self.Output before calling :Fire("CastFire", ...)),
keeping the existing calls to SerialSimulation.Register(cast) and the CastFire
emission intact.
In `@src/FastCast2/init.luau`:
- Around line 921-929: The TerminateCast path currently calls
self.BaseCast:TerminateCast(cast, castTerminatingFunction) and then
unconditionally calls castTerminatingFunction(cast) and
self.Output:Fire("CastTerminating", cast), causing duplicate serial callbacks
and signals; change the logic so that if self.BaseCast exists you only delegate
to BaseCast:TerminateCast(...) and do not call castTerminatingFunction or fire
the output again, and if self.BaseCast is nil then call
castTerminatingFunction(cast) (if present) and fire
self.Output:Fire("CastTerminating", cast) exactly once. Ensure the unique
symbols are BaseCast:TerminateCast, castTerminatingFunction, and
self.Output:Fire("CastTerminating", cast).
---
Outside diff comments:
In `@src/FastCast2/init.luau`:
- Around line 998-1011: FastCastParallel:Destroy() assumes Init() completed and
unconditionally calls self.Dispatcher:Destroy(); change Destroy to guard against
partially-initialized instances by nil-checking any optional fields before
calling methods on them (at minimum check self.Dispatcher and self.ObjectCache
exist before calling :Destroy()), and only call DestroySignal on signals that
are non-nil; keep setmetatable(self, nil) at the end so cleanup still completes
for partially-initialized objects.
---
Duplicate comments:
In `@src/FastCast2/ParallelSimulation.luau`:
- Around line 264-271: The code always builds a table `variant` and passes it to
`CastHandler`, but `EnumCastTypes.Blockcast` expects a raw Vector3
(`castSize[i]`) and `EnumCastTypes.Spherecast` expects a raw number
(`castRadius[i]`); change the call site so when `castType` equals
`EnumCastTypes.Blockcast` pass `castSize[i]` as the fifth argument and when
`EnumCastTypes.Spherecast` pass `castRadius[i]`, otherwise build/pass the
`variant` table as before; update the call to `CastHandler(castWorldRoot[i],
lastPosition, rayDir, castRaycastParams[i], <appropriate value>)` accordingly so
block/sphere casts receive the raw shape values.
- Around line 161-221: In Unregister(), the cleanup is tearing down the
destination slot after a swap: change the cleanup to clear the removed row
(lastId) instead of id — fix the mistaken castCFrame[id] = nil to
castCFrame[lastId] = nil and set casts[lastId] = nil (not casts[id]); also only
call Motor6DPool.Disconnect for the tail-removal case (when id == lastId) so we
don't disconnect the Motor6D that was moved into id (adjust the conditional
around Motor6DPool.Disconnect(castMotor6D[lastId]) accordingly).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 700c67d6-f5c7-4a96-b320-98347bb4b2dd
📒 Files selected for processing (6)
README.mdsrc/FastCast2/ActiveCast.luausrc/FastCast2/ActiveCastSerial.luausrc/FastCast2/BaseCastSerial.luausrc/FastCast2/ParallelSimulation.luausrc/FastCast2/init.luau
✅ Files skipped from review due to trivial changes (1)
- src/FastCast2/ActiveCastSerial.luau
🚧 Files skipped from review as they are similar to previous changes (2)
- README.md
- src/FastCast2/ActiveCast.luau
|
@coderabbitai Write me a single prompt for ai agent
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me read the relevant files first before generating the prompt. 🏁 Script executed: Length of output: 54706 I need to check the full body of both simulation files to confirm which constants are actually used. 🏁 Script executed: Length of output: 22229
FastCast2 AI Agent Prompt — Cleanup & Bug FixesFix all of the following issues across Fix 1 —
|
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 45265
FastCast2 AI Agent Prompt — Implement HighFidelitySegmentSize & HighFidelityBehavior in SoA Simulations
Background: how high-fidelity modes work (from
|
| Enum | Behaviour |
|---|---|
Default = 1 |
No subdivision — current behaviour, one cast per frame |
Automatic = 2 |
One coarse cast per frame; if a hit is detected, subdivide the displacement into floor(rayDisplacement / HighFidelitySegmentSize) sub-segments and re-cast each one to find the precise hit point |
Always = 3 |
Every frame, estimate the frame displacement, subdivide the full deltaTime into floor(displacement / HighFidelitySegmentSize) sub-segments, and simulate each one individually |
Both modes share these details:
numSegmentsReal = max(1, floor(rayDisplacement / segmentSize))timeIncrement = deltaTime / numSegmentsReal- A
castIsActivelyResimulating[i]guard prevents cascading lag — if already resimulating when re-entered, terminate + warn. - A
castCancelHighResCast[i]flag can break out of the sub-loop early (set externally, e.g. bySyncChangesToCast).
Shared helpers needed (add near the top of each file, alongside GetPositionAtTime/GetVelocityAtTime)
Both files already have GetPositionAtTime and GetVelocityAtTime. No new helpers are needed — the sub-loop inlines the same kinematics.
Fix — Replace UpdateCasts inner loop body in both simulation files
Replace the existing per-cast block inside the for i = 1, castCount do loop with the following. The only difference between the two files is the Terminate call at the end (SerialSimulation.Terminate vs ParallelSimulation.Terminate). The template below uses ThisSimulation as a placeholder — substitute accordingly.
local function UpdateCasts(deltaTime: number)
if castCount == 0 then return end
local destroyedIds = {} :: { number }
for i = 1, castCount do
if castPaused[i] then continue end
local caster = castCaster[i]
local castType = castCastType[i]
local CastHandler = castHandlers[castType]
local origin = castOrigin[i]
local velocity = castVelocity[i]
local acceleration = castAcceleration[i]
-- Resolve the raw size/radius value (not a table)
local sizeOrRadius: (Vector3 | number)?
if castType == EnumCastTypes.Blockcast then
sizeOrRadius = castSize[i]
elseif castType == EnumCastTypes.Spherecast then
sizeOrRadius = castRadius[i]
end
local hifiBehavior = castHighFidelityBehavior[i]
local hifiSegmentSize = castHighFidelitySegmentSize[i]
local bullet = castCosmeticBullet[i]
local motor6d = castMotor6D[i]
-- ──────────────────────────────────────────────────────────────
-- HighFidelityBehavior.Always (3):
-- Subdivide every frame into sub-segments before casting.
-- ──────────────────────────────────────────────────────────────
if hifiBehavior == 3 and hifiSegmentSize > 0 then
-- Cascading lag guard
if castIsActivelyResimulating[i] then
table.insert(destroyedIds, i)
warn("Cascading cast lag encountered on cast " .. i .. "! Consider increasing HighFidelitySegmentSize.")
continue
end
castIsActivelyResimulating[i] = true
-- Estimate frame displacement to compute segment count
local totalDeltaForEstimate = castTotalRuntime[i]
local posNow = GetPositionAtTime(totalDeltaForEstimate, origin, velocity, acceleration)
local velNow = GetVelocityAtTime(totalDeltaForEstimate, velocity, acceleration)
local posNext = GetPositionAtTime(totalDeltaForEstimate + deltaTime, origin, velocity, acceleration)
local estimatedDisplacement = (posNext - posNow).Magnitude
-- Estimate via coarse cast to see if we even need subdivision
local coarseRayDir = (posNext - posNow).Unit * velNow.Magnitude * deltaTime
local coarseResult = CastHandler(castWorldRoot[i], posNow, coarseRayDir, castRaycastParams[i], sizeOrRadius)
if coarseResult then
estimatedDisplacement = (coarseResult.Position - posNow).Magnitude
end
local numSegmentsReal = math.max(1, math.floor(estimatedDisplacement / hifiSegmentSize))
local timeIncrement = deltaTime / numSegmentsReal
local hitScheduled = false
for segIndex = 1, numSegmentsReal do
if castCancelHighResCast[i] then
castCancelHighResCast[i] = false
break
end
local preDelta = castTotalRuntime[i]
local lastPoint = GetPositionAtTime(preDelta, origin, velocity, acceleration)
castTotalRuntime[i] += timeIncrement
local nowDelta = castTotalRuntime[i]
local currentPosition = GetPositionAtTime(nowDelta, origin, velocity, acceleration)
local currentVelocity = GetVelocityAtTime(nowDelta, velocity, acceleration)
local displacement = currentPosition - lastPoint
local rayDir = displacement.Unit * currentVelocity.Magnitude * timeIncrement
local result = CastHandler(castWorldRoot[i], lastPoint, rayDir, castRaycastParams[i], sizeOrRadius)
local hitPoint = currentPosition
local hitPart = nil
if result then
hitPoint = result.Position
hitPart = result.Instance
end
local rayDisplacement = (hitPoint - lastPoint).Magnitude
castDistanceCovered[i] += rayDisplacement
local newCFrame = CFrame.new(lastPoint, lastPoint + rayDir) * CFrame.new(0, 0, -rayDisplacement / 2)
castCFrame[i] = newCFrame
-- Move cosmetic bullet
if bullet then
if motor6d then
motor6d.Transform = newCFrame
elseif bullet:IsA("BasePart") then
bullet.CFrame = newCFrame
else
bullet:PivotTo(newCFrame)
end
end
QueueFire(caster, "LengthChanged", casts[i], lastPoint, rayDir.Unit, rayDisplacement, currentVelocity, bullet)
if result and hitPart ~= bullet then
QueueFire(caster, "Hit", casts[i], result, currentVelocity, bullet)
hitScheduled = true
break
end
if castDistanceCovered[i] >= castMaxDistance[i] then
hitScheduled = true
break
end
end
castIsActivelyResimulating[i] = false
if hitScheduled then
table.insert(destroyedIds, i)
end
-- ──────────────────────────────────────────────────────────────
-- HighFidelityBehavior.Automatic (2):
-- Do one coarse cast; on hit, subdivide for precision.
-- ──────────────────────────────────────────────────────────────
elseif hifiBehavior == 2 and hifiSegmentSize > 0 then
-- Coarse cast (same as Default)
local preDelta = castTotalRuntime[i]
local lastPoint = GetPositionAtTime(preDelta, origin, velocity, acceleration)
castTotalRuntime[i] += deltaTime
local nowDelta = castTotalRuntime[i]
local currentPosition = GetPositionAtTime(nowDelta, origin, velocity, acceleration)
local currentVelocity = GetVelocityAtTime(nowDelta, velocity, acceleration)
local displacement = currentPosition - lastPoint
local rayDir = displacement.Unit * currentVelocity.Magnitude * deltaTime
local result = CastHandler(castWorldRoot[i], lastPoint, rayDir, castRaycastParams[i], sizeOrRadius)
local hitPoint = currentPosition
local hitPart = nil
if result then
hitPoint = result.Position
hitPart = result.Instance
end
local rayDisplacement = (hitPoint - lastPoint).Magnitude
castDistanceCovered[i] += rayDisplacement
local newCFrame = CFrame.new(lastPoint, lastPoint + rayDir) * CFrame.new(0, 0, -rayDisplacement / 2)
castCFrame[i] = newCFrame
if bullet then
if motor6d then
motor6d.Transform = newCFrame
elseif bullet:IsA("BasePart") then
bullet.CFrame = newCFrame
else
bullet:PivotTo(newCFrame)
end
end
QueueFire(caster, "LengthChanged", casts[i], lastPoint, rayDir.Unit, rayDisplacement, currentVelocity, bullet)
-- On coarse hit → refine with sub-segments
if result and hitPart ~= bullet then
-- Cascading lag guard
if castIsActivelyResimulating[i] then
QueueFire(caster, "Hit", casts[i], result, currentVelocity, bullet)
table.insert(destroyedIds, i)
warn("Cascading cast lag encountered on cast " .. i .. "! Consider increasing HighFidelitySegmentSize.")
continue
end
castIsActivelyResimulating[i] = true
local numSegmentsReal = math.max(1, math.floor(rayDisplacement / hifiSegmentSize))
local timeIncrement = deltaTime / numSegmentsReal
-- Rewind TotalRuntime to re-simulate sub-segments from preDelta
castTotalRuntime[i] = preDelta
-- Also rewind distance contribution for re-counting
castDistanceCovered[i] -= rayDisplacement
local preciseHit = false
for segIndex = 1, numSegmentsReal do
if castCancelHighResCast[i] then
castCancelHighResCast[i] = false
break
end
local subPreDelta = castTotalRuntime[i]
local subLastPoint = GetPositionAtTime(subPreDelta, origin, velocity, acceleration)
castTotalRuntime[i] += timeIncrement
local subNowDelta = castTotalRuntime[i]
local subCurrentPosition = GetPositionAtTime(subNowDelta, origin, velocity, acceleration)
local subCurrentVelocity = GetVelocityAtTime(subNowDelta, velocity, acceleration)
local subDisplacement = subCurrentPosition - subLastPoint
local subRayDir = subCurrentVelocity * timeIncrement
local subResult = CastHandler(castWorldRoot[i], subLastPoint, subRayDir, castRaycastParams[i], sizeOrRadius)
local subHitPoint = subCurrentPosition
local subHitPart = nil
if subResult then
subHitPoint = subResult.Position
subHitPart = subResult.Instance
end
local subRayDisplacement = (subHitPoint - subLastPoint).Magnitude
castDistanceCovered[i] += subRayDisplacement
local subCFrame = CFrame.new(subLastPoint, subLastPoint + subRayDir) * CFrame.new(0, 0, -subRayDisplacement / 2)
castCFrame[i] = subCFrame
if bullet then
if motor6d then
motor6d.Transform = subCFrame
elseif bullet:IsA("BasePart") then
bullet.CFrame = subCFrame
else
bullet:PivotTo(subCFrame)
end
end
if subResult and subHitPart ~= bullet then
QueueFire(caster, "Hit", casts[i], subResult, subCurrentVelocity, bullet)
preciseHit = true
break
end
if castDistanceCovered[i] >= castMaxDistance[i] then
preciseHit = true
break
end
end
castIsActivelyResimulating[i] = false
-- If sub-loop found no hit (can pierce case / numeric edge), don't terminate
if preciseHit then
table.insert(destroyedIds, i)
end
elseif castDistanceCovered[i] >= castMaxDistance[i] then
table.insert(destroyedIds, i)
end
-- ──────────────────────────────────────────────────────────────
-- HighFidelityBehavior.Default (1) — unchanged behaviour
-- ──────────────────────────────────────────────────────────────
else
local preDelta = castTotalRuntime[i]
local lastPoint = GetPositionAtTime(preDelta, origin, velocity, acceleration)
castTotalRuntime[i] += deltaTime
local nowDelta = castTotalRuntime[i]
local currentPosition = GetPositionAtTime(nowDelta, origin, velocity, acceleration)
local currentVelocity = GetVelocityAtTime(nowDelta, velocity, acceleration)
local displacement = currentPosition - lastPoint
local rayDir = displacement.Unit * currentVelocity.Magnitude * deltaTime
local result = CastHandler(castWorldRoot[i], lastPoint, rayDir, castRaycastParams[i], sizeOrRadius)
local hitPoint = currentPosition
local hitPart = nil
if result then
hitPoint = result.Position
hitPart = result.Instance
end
local rayDisplacement = (hitPoint - lastPoint).Magnitude
castDistanceCovered[i] += rayDisplacement
local newCFrame = CFrame.new(lastPoint, lastPoint + rayDir) * CFrame.new(0, 0, -rayDisplacement / 2)
castCFrame[i] = newCFrame
if bullet then
if motor6d then
motor6d.Transform = newCFrame
elseif bullet:IsA("BasePart") then
bullet.CFrame = newCFrame
else
bullet:PivotTo(newCFrame)
end
end
QueueFire(caster, "LengthChanged", casts[i], lastPoint, rayDir.Unit, rayDisplacement, currentVelocity, bullet)
if result and hitPart ~= bullet then
QueueFire(caster, "Hit", casts[i], result, currentVelocity, bullet)
table.insert(destroyedIds, i)
elseif castDistanceCovered[i] >= castMaxDistance[i] then
table.insert(destroyedIds, i)
end
end
end
-- Terminate in reverse to avoid swap-with-last index invalidation
for i = `#destroyedIds`, 1, -1 do
ThisSimulation.Terminate(destroyedIds[i]) -- replace with SerialSimulation or ParallelSimulation
end
DispatchAllEvents()
endSummary of what changed vs. current UpdateCasts
| Change | Reason |
|---|---|
Branch on castHighFidelityBehavior[i] |
Route to correct simulation mode |
Always (3): estimate displacement → subdivide deltaTime → simulate each sub-segment |
Every-frame precision |
Automatic (2): coarse cast → on hit, rewind TotalRuntime and DistanceCovered, then subdivide for precise hit |
On-demand precision only when needed |
castIsActivelyResimulating[i] guard in both HiFi paths |
Prevents cascading lag |
castCancelHighResCast[i] checked per sub-segment iteration |
Allows external early-exit |
Raw sizeOrRadius passed to CastHandler (not a table) |
Fixes pre-existing bug (Fix 13) |
| Default (1): identical to current code | No regression |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me read the simulation files and the existing high-fidelity logic in 🏁 Script executed: Length of output: 54537
FastCast2 AI Agent Prompt — Implement HighFidelitySegmentSize & HighFidelityBehavior in SerialSimulation and ParallelSimulationBoth Reference the existing implementation in
Step 1 — Add the
|
| Concern | Handled |
|---|---|
subRayDir = subVelocity * timeIncrement (not unit * magnitude * dt) |
✅ matches ActiveCast.luau fix |
Cascading lag guard for both Always and Automatic modes |
✅ |
CancelHighResCast flag respected in PerformHighFidelitySubcasts |
✅ |
castTotalRuntime[i] advanced inside sub-cast loop so distance/position stay consistent |
✅ |
destroyedIds deduplication — same index could be inserted twice (hit + maxDistance in Always) |
if not table.find(destroyedIds, i) then table.insert(destroyedIds, i) end in both the hit and maxDistance checks inside PerformHighFidelitySubcasts |
- Bug 1: Use subResult.Position instead of point in sub-cast hit visualizations - Bug 2: Add missing QueueVisualize in not subHitFound fallback - Bug 3: Add missing QueueVisualize in max-distance hit (serial only) - Bug 4: Add return after pierce block to prevent double-fire Hit event (serial only) - Bug 5: Capture queuedVisualizes before FireQueuedEvents to prevent data loss
- Rename _Destroyed to _Destroying across all cast types - FastCast:TerminateCast now defers cleanup: sets _Destroying, fires CastTerminating callback/event, destroys cosmetic immediately, then queues CastTerminating event for next FireQueuedEvents cycle - _TerminateCast: callback fires only once via _Destroying guard, cleanup (nil keys, nil Actives, Unregister) always runs - UpdateCasts skips _Destroying casts in both serial and parallel - Parallel: remove nil-key loop from TerminateCast to keep cast table intact (ActiveCastCleaner handles cleanup) - TypeDefinitions: add _Destroying: boolean to all cast data types
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/FastCastVMs/ServerVM.server.luau (1)
27-38:⚠️ Potential issue | 🟠 Major | ⚡ Quick winProtect VM handlers against uninitialized
BaseCast.These handlers assume Init has already run; without a guard they can nil-deref and kill the actor script.
Also applies to: 53-57, 60-72, 74-86, 90-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/FastCastVMs/ServerVM.server.luau` around lines 27 - 38, The VM message handlers call BaseCast methods without checking that Init ran and BaseCast is initialized; add a guard at the start of each handler (e.g., the handler bound to "Raycast" and the other handlers at the same file regions) that verifies BaseCast is non-nil and returns early (and optionally log/warn) if it's not initialized to avoid nil-dereference and killing the actor script; apply this same nil-check pattern to the handlers referenced around lines 53-57, 60-72, 74-86, and 90-92 so all BaseCast usages are protected.
🧹 Nitpick comments (2)
AGENTS.md (1)
56-56: 💤 Low valueClarify the
@skills/architecture.mdreference syntax.The
@skills/prefix is unclear—the actual file path isskills/architecture.md. If@is aider-specific syntax for file references, consider adding a note explaining this convention, or use the standard file path for broader compatibility.Alternative wording
-To understand specific project workflows, refer to the skills defined here: -- `@skills/architecture.md` +To understand specific project workflows, refer to: +- `skills/architecture.md` (architecture and execution flow documentation)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` at line 56, Update the reference in AGENTS.md: replace the ambiguous '`@skills/architecture.md`' with the actual relative path 'skills/architecture.md' or add a short note in AGENTS.md explaining that the '@' prefix is an aider-specific alias (and document its resolution rules), so readers understand whether '`@skills/architecture.md`' is a special alias or should be written as 'skills/architecture.md'.default.project.json (1)
7-9: Verify$ignoreUnknownInstancesscope indefault.project.json.
$ignoreUnknownInstances: trueis underReplicatedStorage, so it applies to the entireReplicatedStoragecontainer (not justFastCast2). In thisdefault.project.json, the onlyReplicatedStoragechild being mapped isFastCast2, so this won’t change what Rojo syncs from this file—but it will still ignore unknown instances anywhere else underReplicatedStorage. If the intent is to limit it, move it underFastCast2:"FastCast2": { "$path": "src", "$ignoreUnknownInstances": true }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@default.project.json` around lines 7 - 9, The $ignoreUnknownInstances property is currently set on the ReplicatedStorage container in default.project.json which makes it apply to all children; to limit its scope to just FastCast2 move the "$ignoreUnknownInstances": true entry from the ReplicatedStorage object into the FastCast2 object (so FastCast2 contains both "$path": "src" and "$ignoreUnknownInstances": true), ensuring other ReplicatedStorage children are not globally ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@AGENTS.md`:
- Around line 21-40: Update the fenced code block in AGENTS.md to include a
language specifier (e.g., change the opening ``` to ```plaintext or ```text) so
markdownlint stops flagging the directory tree; locate the directory tree block
shown (starting with "src/" and the list of .luau files like init.luau,
BaseCastSerial.luau, FastCastVMs/) and replace its fence accordingly.
In `@src/ActiveCast.luau`:
- Around line 93-94: ActiveCast currently sets Type = variant.CastType (a
numeric enum) while TypeDefinitions.luau expects the string literals "Raycast" |
"Blockcast" | "Spherecast"; fix by converting the numeric enum to the matching
string before storing or by aligning the type definition to the enum. Locate
ActiveCast and replace the direct assignment of variant.CastType in
ActiveCast.Type with a mapping to the string (e.g. use a small lookup that maps
variant.CastType -> "Raycast"/"Blockcast"/"Spherecast" or a helper function
castTypeToString), or alternatively update ActiveCastData.Type in
TypeDefinitions.luau to accept the numeric enum type used by variant.CastType;
update any references to ActiveCast.Type accordingly.
- Around line 57-82: StateInfo currently misses the
TypeDef.CastStateInfo-required boolean IsActivelySimulatingPierce and
FastCastEventsConfig (and the related FastCastEventsModuleConfig shape) is
missing UseCastFire; update the StateInfo table in src/ActiveCast.luau to
include IsActivelySimulatingPierce = false (or derive from existing
behavior/state if appropriate) and add UseCastFire =
behavior.FastCastEventsConfig.UseCastFire (or false) into FastCastEventsConfig
(and mirror into FastCastEventsModuleConfig where those defaults are
constructed) so the object matches TypeDef.CastStateInfo and
TypeDef.FastCastEventsConfig/FastCastEventsModuleConfig.
- Line 22: CastVariants currently omits the Raycast variant and createCastData
in ActiveCast mismatches several TypeDefinitions: it receives raycasts via
BaseCast:Raycast (cast passed as :: any), omits UseCastFire from
StateInfo.FastCastEventsConfig / FastCastEventsModuleConfig, omits
StateInfo.IsActivelySimulatingPierce, and assigns numeric variant.CastType to
cast.Type instead of the required string literal ("Raycast" | "Blockcast" |
"Spherecast"); fix by updating the CastVariants union to include RaycastVariant,
modify ActiveCast.createCastData to accept and type the raycast variant (remove
:: any usage in BaseCast:Raycast and forward the properly typed variant),
include UseCastFire in the created events config objects
(StateInfo.FastCastEventsConfig and StateInfo.FastCastEventsModuleConfig),
populate StateInfo.IsActivelySimulatingPierce on the returned CastStateInfo, and
map variant.CastType to the correct string literal values for
Active*CastData.Type rather than assigning the numeric enum.
In `@src/BaseCastParallel.luau`:
- Around line 313-324: The code currently destroys Motor6DCacheInstance before
asking the simulation to stop using Motor6D mode, causing attached joints to
never be returned; change the ordering so
ParallelSimulation.SetMovementModeEnabled(enabled, mode) is called before you
destroy or create Motor6DCacheInstance, and when disabling Motor6D (enabled ==
false and mode == "Motor6D") ensure you call
ParallelSimulation.SetMovementModeEnabled(false, "Motor6D") first so the
simulation can return joints, then call Motor6DCacheInstance:Destroy() and set
Motor6DCacheInstance = nil; likewise, when enabling create Motor6DCacheInstance
(Motor6DCache.new()) after switching the simulation into Motor6D mode.
- Around line 371-392: The code currently destroys ObjectCacheInstance and
Motor6DCacheInstance before terminating active casts, which leaks live
resources; move the termination loop so that all active casts from self.Actives
are terminated via TerminateCast (collect into activeList and call TerminateCast
on each) before calling :Destroy() on ObjectCacheInstance and
Motor6DCacheInstance, then clear FastCastEventsModule and disconnect
SyncChangesConnection as before; ensure you still nil out the cache variables
and SyncChangesConnection after destroying them to avoid dangling references.
- Around line 60-64: TerminateCast currently calls
cast.Caster.ActiveCastCleaner:Fire(cast.ID) then immediately zeroes the cast
table, causing a race because BindableEvent:Fire is asynchronous; instead, stop
clearing the cast in TerminateCast and let the ActiveCastCleaner listener
perform the table cleanup (or acknowledge completion before TerminateCast
clears). Concretely: in TerminateCast remove the for key,_ in (cast :: any) do
cast[key] = nil end loop and move that cleanup logic into the ActiveCastCleaner
handler (the listener registered for cast.Caster.ActiveCastCleaner) or implement
a completion signal (e.g., set a __cleaned flag or Fire a cleanup-complete event
from the cleaner and Wait for it) so the cast table is only nilled after the
cleaner runs.
In `@src/BaseCastSerial.luau`:
- Around line 249-264: BaseCast:Destroy currently stops/destroys caches before
running per-cast cleanup and calls TerminateCast (public) instead of the
internal _TerminateCast, which skips cosmetic/bullet return logic and can leak
parts; change the destruction order so you first iterate self.Actives and call
self:_TerminateCast(v) for each active cast, then stop SerialSimulation and
finally destroy ObjectCacheInstance and Motor6DCacheInstance; apply the same
reorder and use of _TerminateCast in the other similar block (lines ~272-297) to
ensure per-cast cleanup runs before caches are torn down.
- Line 70: The call at BaseCastSerial.luau currently uses
SerialSimulation:SetMovementMode(self.CurrentMovementMode, true) which diverges
from the runtime usage SerialSimulation:SetMovementModeEnabled(...); change the
initialization call to use SetMovementModeEnabled consistently (e.g., call
SerialSimulation:SetMovementModeEnabled with the same movement-mode flag used
elsewhere, ensuring you map self.CurrentMovementMode to the enabled boolean/enum
expected by SetMovementModeEnabled) so both initialization and runtime toggles
use the same API (reference symbols: SerialSimulation, SetMovementMode,
SetMovementModeEnabled, CurrentMovementMode).
In `@src/FastCastVMs/ClientVM.client.luau`:
- Around line 27-34: Handlers bound via actor:BindToMessage (e.g., the "Raycast"
handler with actor:BindToMessage("Raycast", function(...) ... end) and the other
message handlers at the referenced blocks) call BaseCast:Raycast (and similar
methods) without verifying BaseCast is initialized; add a guard at the top of
each handler that checks if BaseCast is non-nil (if not, log/warn and return
early) so messages received before Init() do not attempt to index nil, keeping
the rest of the handler unchanged.
- Line 31: Replace the loose "behavior: any" payload type used in the
BindToMessage handlers for "Raycast", "Blockcast", and "Spherecast" with the
proper FastCast behavior type: change the parameter/type annotation to
TypeDefinitions.FastCastBehavior (or TypeDefinitions.FastCastBehavior? if nil
values must be accepted) so the handlers match
BaseCast:Raycast/Blockcast/Spherecast expectations; update the three
BindToMessage handlers that reference behavior (the "Raycast", "Blockcast", and
"Spherecast" handlers) to use that type and adjust any nil-checks accordingly.
In `@src/FastCastVMs/init.luau`:
- Around line 208-211: The Dispatcher:Dispatch method currently computes
self._nextIndex and indexes into self.Threads without checking for an empty
Threads array; add an early guard at the top of Dispatcher:Dispatch that checks
if `#self.Threads` == 0 and then either return early (or throw) after logging or
warning (e.g., using warn or a Logger) to avoid modulo by zero and invalid
indexing; keep the rest of the round-robin logic (_nextIndex = self._nextIndex %
`#self.Threads` + 1 and SendMessage) unchanged and only run them when the guard
passes.
In `@src/FastCastVMs/ServerVM.server.luau`:
- Line 31: The worker message handler currently types the payload as behavior :
any; replace that with the concrete type (e.g. TypeDef.FastCastBehavior) in the
VM message handler(s) so the payload is strongly typed—import/require the type
definitions module into the script scope (the TypeDef/TypeDefinitions alias used
elsewhere) and update every occurrence of behavior : any in the Server VM and
the Client VM to behavior : TypeDef.FastCastBehavior (or the project’s
TypeDefinitions alias) so the handler signatures and local variables reflect the
proper type.
In `@src/init.luau`:
- Around line 109-123: The setter FastCastSerial.__newindex currently rejects
non-function values for keys in VALID_EVENTS, which prevents clearing handlers
during FastCastSerial:Destroy; update __newindex so that when key ∈ VALID_EVENTS
it accepts nil as a valid value (treat nil as "clear the handler") and either
call self.BaseCast:_UpdateEvents(key, nil) if self.BaseCast exists or
rawset(self, key, nil) otherwise, while still warning only when the value is
neither a function nor nil; keep references to VALID_EVENTS,
FastCastSerial.__newindex, BaseCast:_UpdateEvents, and FastCastSerial:Destroy to
locate and verify the change.
In `@src/Motor6DCache.luau`:
- Around line 67-72: The Return path for Motor6D objects fails to restore the
projectile's Anchored state after Connect() forces projectilePart.Anchored =
false; update Motor6DCache:Return (and the analogous return loop at lines 75-92)
to set the associated projectile part back to Anchored = true before clearing
Part0/Part1/Parent and inserting into FreeMotor6Ds — e.g. obtain the part from
motor6d.Part0 or a stored reference (whichever the pool uses), set part.Anchored
= true if the part exists, then proceed to reset motor6d.Transform and push it
back into the pool. Ensure you handle nil checks so you don’t touch non-existent
parts.
In `@src/ObjectCache.luau`:
- Around line 127-129: Cache:ExpandCache currently calls self:_GetNew(Amount,
false) but _GetNew always ends with table.remove(FreeObjectsContainer), causing
every expansion (including ExpandCache(0)) to under-allocate by one; change
ExpandCache to call _GetNew in a mode that prevents the final table.remove
(e.g., pass a "skipRemove" / "reserveOnly" flag or flip the boolean argument to
true) or update _GetNew to accept and honor a parameter that skips removing the
last free object so ExpandCache performs pure expansion without removing any
free object; reference: Cache:ExpandCache and _GetNew and FreeObjectsContainer.
In `@src/ParallelSimulation.luau`:
- Around line 777-787: The branch compares casts_DistanceCovered[id] against
casts_RayInfo[id].MaxDistance which can be nil; instead use the normalized
cached max-distance set by Register() (DEFAULT_MAX_DISTANCE is applied there) so
the comparison never reads a potentially nil field—store the normalized value in
a per-cast cache (e.g. casts_MaxDistance[id] or a localMaxDistance captured when
the cast is registered) and replace casts_RayInfo[id].MaxDistance in this >=
check with that cached value.
- Around line 623-633: The code calls rayDir.Unit and CFrame.new(lastPoint,
lastPoint + rayDir) for QueueEvent and QueueVisualize without guarding
zero-length rayDir, causing invalid orientation when the segment length is zero;
update the block around casts_CFrame, QueueEvent, and QueueVisualize to check
rayDir.Magnitude (or rayDir ~= Vector3.new()) before normalizing or building
CFrames and handle the zero-length case by using a safe fallback
direction/orientation (e.g., keep previous orientation or use a constant unit
vector) so casts_CFrame, QueueEvent(id, "LengthChanged", ... rayDir.Unit ...),
and QueueVisualize's castStartCFrame are only passed valid directions when delta
== 0 or rayDir is zero.
- Around line 692-705: Subcasts use the wrong time base and drop variant data:
when building each subcast replace subRayDir = subVelocity * delta with
subVelocity * timeIncrement so each substep spans only its segment, and include
the shape/variant info by passing casts_CastVariant[id] (or merging it with
casts_RayInfo[id].Parameters) into the castHandler call; update the call sites
around GetPositionAtTime, GetVelocityAtTime, castHandler and the variables
totalDelta, lastDelta, timeIncrement, delta, casts_RayInfo, casts_CastVariant to
ensure subcasts use the correct time offset and receive the variant
(Size/Radius) parameter.
---
Outside diff comments:
In `@src/FastCastVMs/ServerVM.server.luau`:
- Around line 27-38: The VM message handlers call BaseCast methods without
checking that Init ran and BaseCast is initialized; add a guard at the start of
each handler (e.g., the handler bound to "Raycast" and the other handlers at the
same file regions) that verifies BaseCast is non-nil and returns early (and
optionally log/warn) if it's not initialized to avoid nil-dereference and
killing the actor script; apply this same nil-check pattern to the handlers
referenced around lines 53-57, 60-72, 74-86, and 90-92 so all BaseCast usages
are protected.
---
Nitpick comments:
In `@AGENTS.md`:
- Line 56: Update the reference in AGENTS.md: replace the ambiguous
'`@skills/architecture.md`' with the actual relative path 'skills/architecture.md'
or add a short note in AGENTS.md explaining that the '@' prefix is an
aider-specific alias (and document its resolution rules), so readers understand
whether '`@skills/architecture.md`' is a special alias or should be written as
'skills/architecture.md'.
In `@default.project.json`:
- Around line 7-9: The $ignoreUnknownInstances property is currently set on the
ReplicatedStorage container in default.project.json which makes it apply to all
children; to limit its scope to just FastCast2 move the
"$ignoreUnknownInstances": true entry from the ReplicatedStorage object into the
FastCast2 object (so FastCast2 contains both "$path": "src" and
"$ignoreUnknownInstances": true), ensuring other ReplicatedStorage children are
not globally ignored.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d37d8587-3eb8-4689-91cf-6af3c4887351
📒 Files selected for processing (37)
.gitignore.vscode/settings.jsonAGENTS.mdBenchmarks/bench1.client.luauREADME.mdTODO.mddefault.project.jsondocs/api-reference.mddocs/changelog.mddocs/cheatsheet.mddocs/intro.mdpackage.jsonrokit.tomlskills/architecture.mdsourcemap.jsonsrc/ActiveCast.luausrc/BaseCastParallel.luausrc/BaseCastSerial.luausrc/Config.luausrc/DefaultConfigs.luausrc/FastCast2/ActiveCast.luausrc/FastCast2/FastCastVMs/ClientVM.meta.jsonsrc/FastCast2/FastCastVMs/ServerVM.meta.jsonsrc/FastCast2/Signal.luausrc/FastCastEnums.luausrc/FastCastVMs/ClientVM.client.luausrc/FastCastVMs/ClientVM.meta.jsonsrc/FastCastVMs/ServerVM.meta.jsonsrc/FastCastVMs/ServerVM.server.luausrc/FastCastVMs/init.luausrc/Motor6DCache.luausrc/ObjectCache.luausrc/ParallelSimulation.luausrc/SerialSimulation.luausrc/TypeDefinitions.luausrc/init.luauwally.toml
💤 Files with no reviewable changes (7)
- src/FastCastEnums.luau
- src/FastCast2/FastCastVMs/ServerVM.meta.json
- Benchmarks/bench1.client.luau
- src/FastCast2/FastCastVMs/ClientVM.meta.json
- .vscode/settings.json
- src/FastCast2/Signal.luau
- src/FastCast2/ActiveCast.luau
✅ Files skipped from review due to trivial changes (9)
- docs/intro.md
- wally.toml
- rokit.toml
- sourcemap.json
- package.json
- TODO.md
- README.md
- docs/changelog.md
- src/SerialSimulation.luau
| ``` | ||
| src/ | ||
| ├── init.luau # Entry: FastCast (static), FastCastSerial, FastCastParallel | ||
| ├── BaseCastSerial.luau # Serial: cast handler, routes events to SerialSimulation | ||
| ├── BaseCastParallel.luau # Parallel: runs inside each Actor VM, casts per-VM | ||
| ├── SerialSimulation.luau # Serial: SoA physics engine, single-threaded | ||
| ├── ParallelSimulation.luau # Parallel: SoA physics engine, one per Actor VM | ||
| ├── ActiveCast.luau # Cast data container (used by both modes) | ||
| ├── ObjectCache.luau # Cosmetic bullet part pooling | ||
| ├── Motor6DCache.luau # Motor6D pooling for Transform movement mode | ||
| ├── TypeDefinitions.luau # All Luau type definitions | ||
| ├── FastCastEnums.luau # Enum values (HighFidelityBehavior, CastType) | ||
| ├── Config.luau # Debug logging flags | ||
| ├── DefaultConfigs.luau # Default FastCastBehavior values | ||
| └── FastCastVMs/ | ||
| ├── init.luau # Dispatcher: creates/manages Actor VMs, load balancing | ||
| ├── ClientVM.client.luau # Client-side Actor script | ||
| ├── ServerVM.server.luau # Server-side Actor script | ||
| └── *.meta.json # Rojo metadata (Enabled = false) | ||
| ``` |
There was a problem hiding this comment.
Add language specifier to fenced code block.
The fenced code block lacks a language specifier, flagged by markdownlint. For a directory tree structure, use plaintext or text:
📝 Proposed fix
## Project Structure
-```
+```plaintext
src/
├── init.luau # Entry: FastCast (static), FastCastSerial, FastCastParallel📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| src/ | |
| ├── init.luau # Entry: FastCast (static), FastCastSerial, FastCastParallel | |
| ├── BaseCastSerial.luau # Serial: cast handler, routes events to SerialSimulation | |
| ├── BaseCastParallel.luau # Parallel: runs inside each Actor VM, casts per-VM | |
| ├── SerialSimulation.luau # Serial: SoA physics engine, single-threaded | |
| ├── ParallelSimulation.luau # Parallel: SoA physics engine, one per Actor VM | |
| ├── ActiveCast.luau # Cast data container (used by both modes) | |
| ├── ObjectCache.luau # Cosmetic bullet part pooling | |
| ├── Motor6DCache.luau # Motor6D pooling for Transform movement mode | |
| ├── TypeDefinitions.luau # All Luau type definitions | |
| ├── FastCastEnums.luau # Enum values (HighFidelityBehavior, CastType) | |
| ├── Config.luau # Debug logging flags | |
| ├── DefaultConfigs.luau # Default FastCastBehavior values | |
| └── FastCastVMs/ | |
| ├── init.luau # Dispatcher: creates/manages Actor VMs, load balancing | |
| ├── ClientVM.client.luau # Client-side Actor script | |
| ├── ServerVM.server.luau # Server-side Actor script | |
| └── *.meta.json # Rojo metadata (Enabled = false) | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 21-21: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AGENTS.md` around lines 21 - 40, Update the fenced code block in AGENTS.md to
include a language specifier (e.g., change the opening ``` to ```plaintext or
```text) so markdownlint stops flagging the directory tree; locate the directory
tree block shown (starting with "src/" and the list of .luau files like
init.luau, BaseCastSerial.luau, FastCastVMs/) and replace its fence accordingly.
|
|
||
| type BlockcastVariant = { CastType: number, Size: Vector3} | ||
| type SpherecastVariant = { CastType: number, Radius: number } | ||
| type CastVariants = BlockcastVariant | SpherecastVariant |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify raycast call sites and passed variant shapes.
rg -n -C3 'createCastData|CastType\\s*=\\s*FastCastEnums\\.CastType\\.Raycast|CastType\\s*=\\s*EnumCastTypes\\.Raycast' srcRepository: weenachuangkud/FastCast2
Length of output: 2916
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect ActiveCast.luau around the referenced lines
echo "---- src/ActiveCast.luau (lines 1-160) ----"
sed -n '1,160p' src/ActiveCast.luau | nl -ba
# Find type/variant definitions and config construction
echo "---- rg: CastVariants / Raycast / CastType literals in src/ActiveCast.luau ----"
rg -n "CastVariants|Raycast|RaycastVariant|BlockcastVariant|SpherecastVariant|FastCastEventsConfig|FastCastEventsModuleConfig|IsActivelySimulatingPierce|CastType" src/ActiveCast.luau
# Check TypeDefinitions for Active* types and IsActivelySimulatingPierce + CastType literals
echo "---- rg: TypeDefinitions Active* / IsActivelySimulatingPierce / CastType in src ----"
rg -n "TypeDefinitions|IsActivelySimulatingPierce|CastType.*(Raycast|Blockcast|Spherecast)|Active.*Cast" src | head -n 200Repository: weenachuangkud/FastCast2
Length of output: 173
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- tool check: cat -n availability ----"
command -v cat >/dev/null && echo "cat ok"
( cat -n /dev/null >/dev/null 2>&1 && echo "cat -n ok" ) || echo "cat -n missing"
echo "---- src/ActiveCast.luau (lines 1-170) ----"
sed -n '1,170p' src/ActiveCast.luau | cat -n
echo "---- rg: CastVariants / Raycast / config + type fields in src/ActiveCast.luau ----"
rg -n "CastVariants|RaycastVariant|BlockcastVariant|SpherecastVariant|FastCastEventsConfig|FastCastEventsModuleConfig|IsActivelySimulatingPierce|CastType" src/ActiveCast.luau
echo "---- rg: createCastData signature + variant param typing ----"
rg -n "function ActiveCast\\.createCastData\\(|variant|CastType\\s*=|EnumCastTypes\\.|FastCastEventsModuleConfig" src/ActiveCast.luauRepository: weenachuangkud/FastCast2
Length of output: 8610
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- Locate TypeDefinitions file ----"
fd -t f -i "typed*definitions*.luau" src || true
fd -t f -i "TypeDefinitions*.luau" src || true
fd -t f "TypeDefinitions.lua*" src || true
fd -t f -i "TypeDefinitions" src || true
echo "---- Find TypeDefinitions requires in src ----"
rg -n "WaitForChild\\(\"TypeDefinitions\"\\)|require\\(.*TypeDefinitions" src || true
# Pick likely file paths from common layouts if present
echo "---- Dump relevant parts of TypeDefinitions (best-effort) ----"
for f in $(fd -t f -i "TypeDefinitions*.luau" src 2>/dev/null || true); do
echo "=== $f (head 220) ==="
sed -n '1,220p' "$f" | cat -n
done
echo "---- Search for referenced identifiers across src ----"
rg -n "UseCastFire|IsActivelySimulatingPierce|CastStateInfo|Active.*Cast|CastType.*(Raycast|Blockcast|Spherecast)|CastType.*\"|FastCastEventsConfig" src || trueRepository: weenachuangkud/FastCast2
Length of output: 23900
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- TypeDefinitions: CastStateInfo + Active*Data Type field (ranges) ----"
sed -n '380,460p' src/TypeDefinitions.luau | cat -n
sed -n '460,540p' src/TypeDefinitions.luau | cat -n
echo "---- FastCastEnums: inspect CastType enum values ----"
sed -n '1,220p' src/FastCastEnums.luau | cat -n || sed -n '1,220p' src/FastCastEnums.lua | cat -n
echo "---- Find CastStateInfo field names in TypeDefinitions ----"
rg -n "type CastStateInfo|IsActivelySimulatingPierce|IsActivelyResimulating|FastCastEventsConfig" src/TypeDefinitions.luau
echo "---- Search UseCastFire usage in simulations ----"
rg -n "UseCastFire" src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luau src/BaseCastParallel.luau src/BaseCastSerial.luau
echo "---- Search IsActivelySimulatingPierce usage in code ----"
rg -n "IsActivelySimulatingPierce" srcRepository: weenachuangkud/FastCast2
Length of output: 9991
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- usages of cast.Type / Type comparisons ----"
rg -n "cast\\.Type|\\.Type\\s*==" src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luau src/BaseCastSerial.luau src/BaseCastParallel.luau src | head -n 200
echo "---- direct string literal checks (\"Raycast\" etc.) ----"
rg -n "\"Raycast\"|\"Blockcast\"|\"Spherecast\"" src | head -n 200
echo "---- direct numeric castType checks (EnumCastTypes\\.Raycast etc.) ----"
rg -n "EnumCastTypes\\.(Raycast|Blockcast|Spherecast)" src/SerialSimulation.luau src/ParallelSimulation.luau src | head -n 200Repository: weenachuangkud/FastCast2
Length of output: 117
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- SerialSimulation: locate castType routing + eventConfig usage ----"
rg -n "castType|FastCastEventsConfig|casts_FastCastEventsConfig|casts_IsActivelySimulatingPierce|UseCastFire|IsActivelySimulatingPierce" src/SerialSimulation.luau
echo "---- SerialSimulation: print around initialization and routing (best-effort ranges) ----"
sed -n '200,320p' src/SerialSimulation.luau | cat -n
sed -n '360,520p' src/SerialSimulation.luau | cat -n
echo "---- ParallelSimulation: locate castType routing + eventConfig usage ----"
rg -n "castType|FastCastEventsConfig|casts_FastCastEventsConfig|casts_IsActivelySimulatingPierce|UseCastFire|IsActivelySimulatingPierce" src/ParallelSimulation.luau
echo "---- ParallelSimulation: print around initialization and routing (best-effort ranges) ----"
sed -n '420,560p' src/ParallelSimulation.luau | cat -n
sed -n '280,420p' src/ParallelSimulation.luau | cat -nRepository: weenachuangkud/FastCast2
Length of output: 23851
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- usages of StateInfo.FastCastEventsConfig.UseCastFire ----"
rg -n "StateInfo\\.(FastCastEventsConfig|FastCastEventsModuleConfig)\\.UseCastFire|FastCastEventsConfig\\.UseCastFire|FastCastEventsModuleConfig\\.UseCastFire" src || true
echo "---- usages of StateInfo.IsActivelySimulatingPierce ----"
rg -n "StateInfo\\.IsActivelySimulatingPierce|IsActivelySimulatingPierce" src || true
echo "---- usages of cast.Type and comparisons to \"Raycast\"/EnumCastTypes ----"
rg -n "\\bcast\\.Type\\b|\\b\\.Type\\b\\s*==|Type\\s*:\\s*\"(Raycast|Blockcast|Spherecast)\"" src || true
echo "---- usages of CastFire event routing in simulations ----"
rg -n "\\bCastFire\\b" src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luau src/BaseCastSerial.luau src/BaseCastParallel.luau || trueRepository: weenachuangkud/FastCast2
Length of output: 3636
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- Find where cast.Type is accessed / cast typed as ActiveCastData ----"
rg -n "cast:\\s*TypeDef\\.ActiveCastData|TypeDef\\.ActiveCastData|\\.Type\\b" src/BaseCastParallel.luau src/BaseCastSerial.luau src/init.luau src/SerialSimulation.luau src/ParallelSimulation.luau
echo "---- Narrow: .Type usage only in base/simulation files ----"
rg -n "\\.Type\\b" src/BaseCastParallel.luau src/BaseCastSerial.luau src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luauRepository: weenachuangkud/FastCast2
Length of output: 500
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- BaseCastSerial: around createCastData calls and UseCastFire checks ----"
sed -n '60,220p' src/BaseCastSerial.luau | cat -n
echo "---- BaseCastParallel: around createCastData calls and UseCastFire checks ----"
sed -n '130,340p' src/BaseCastParallel.luau | cat -n
echo "---- BaseCastParallel: where cast.StateInfo.FastCastEventsConfig is used (if any) ----"
sed -n '1,140p' src/BaseCastParallel.luau | cat -nRepository: weenachuangkud/FastCast2
Length of output: 17688
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- Search for cast.Type usage (callbacks/event handlers) ----"
rg -n "cast\\.Type" src || true
echo "---- Search for 'Type : \"Raycast\"' assumptions (string usage) ----"
rg -n "\"Raycast\"|\"Blockcast\"|\"Spherecast\"" src || true
echo "---- Search for CastVariant.CastType usage (already seen numeric routing) ----"
rg -n "CastVariant\\.CastType|cast\\.CastVariant\\.CastType|casts_CastType" src | head -n 200Repository: weenachuangkud/FastCast2
Length of output: 2857
Fix Luau strict-typing contract in ActiveCast (raycast + event config + cast type)
CastVariantsexcludes raycast, butActiveCast.createCastDatais used for raycasts;BaseCast:Raycastpasses the raycast variant via:: any, defeating strict typing.ActiveCast.createCastDataomitsUseCastFirefromStateInfo.FastCastEventsConfigandStateInfo.FastCastEventsModuleConfig, contradictingTypeDefinitions.ActiveCast.createCastDataomitsStateInfo.IsActivelySimulatingPierce, contradictingTypeDefinitions.CastStateInfo.ActiveCast.createCastDatasetscast.Typeto numericvariant.CastType, whileTypeDefinitions.Active*CastData.Typeis the string literals"Raycast" | "Blockcast" | "Spherecast".
💡 Suggested type fix
type BlockcastVariant = { CastType: number, Size: Vector3}
type SpherecastVariant = { CastType: number, Radius: number }
-type CastVariants = BlockcastVariant | SpherecastVariant
+type RaycastVariant = { CastType: number }
+type CastVariants = RaycastVariant | BlockcastVariant | SpherecastVariant🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ActiveCast.luau` at line 22, CastVariants currently omits the Raycast
variant and createCastData in ActiveCast mismatches several TypeDefinitions: it
receives raycasts via BaseCast:Raycast (cast passed as :: any), omits
UseCastFire from StateInfo.FastCastEventsConfig / FastCastEventsModuleConfig,
omits StateInfo.IsActivelySimulatingPierce, and assigns numeric variant.CastType
to cast.Type instead of the required string literal ("Raycast" | "Blockcast" |
"Spherecast"); fix by updating the CastVariants union to include RaycastVariant,
modify ActiveCast.createCastData to accept and type the raycast variant (remove
:: any usage in BaseCast:Raycast and forward the properly typed variant),
include UseCastFire in the created events config objects
(StateInfo.FastCastEventsConfig and StateInfo.FastCastEventsModuleConfig),
populate StateInfo.IsActivelySimulatingPierce on the returned CastStateInfo, and
map variant.CastType to the correct string literal values for
Active*CastData.Type rather than assigning the numeric enum.
| StateInfo = { | ||
| TotalRuntime = 0, | ||
| DistanceCovered = 0, | ||
| HighFidelitySegmentSize = behavior.HighFidelitySegmentSize, | ||
| HighFidelityBehavior = behavior.HighFidelityBehavior, | ||
| IsActivelyResimulating = false, | ||
| CancelHighResCast = false, | ||
| Trajectory = { | ||
| StartTime = 0, | ||
| EndTime = -1, | ||
| Origin = origin, | ||
| InitialVelocity = if typeof(velocity) == "number" then direction * velocity else velocity, | ||
| Acceleration = behavior.Acceleration, | ||
| }, | ||
|
|
||
| FastCastEventsConfig = { | ||
| UseLengthChanged = behavior.FastCastEventsConfig.UseLengthChanged, | ||
| UseHit = behavior.FastCastEventsConfig.UseHit, | ||
| UsePierced = behavior.FastCastEventsConfig.UsePierced, | ||
| UseCastTerminating = behavior.FastCastEventsConfig.UseCastTerminating, | ||
| UseCanPierce = behavior.FastCastEventsConfig.UseCanPierce | ||
| }, | ||
|
|
||
| VisualizeCasts = behavior.VisualizeCasts, | ||
| VisualizeCastSettings = behavior.VisualizeCastSettings, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify required config keys and StateInfo fields consumed by simulations/event routing.
rg -n -C3 'UseCastFire|IsActivelySimulatingPierce|FastCastEventsConfig|FastCastEventsModuleConfig' src/TypeDefinitions.luau src/ActiveCast.luau src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luauRepository: weenachuangkud/FastCast2
Length of output: 15128
Fix src/ActiveCast.luau cast state/config to satisfy TypeDefinitions contracts.
cast.StateInfoomits requiredIsActivelySimulatingPierce(required byTypeDef.CastStateInfo).FastCastEventsConfigand parallelFastCastEventsModuleConfigomit requiredUseCastFire(required byTypeDef.FastCastEventsConfig/TypeDef.FastCastEventsModuleConfig).
💡 Suggested patch
StateInfo = {
TotalRuntime = 0,
DistanceCovered = 0,
HighFidelitySegmentSize = behavior.HighFidelitySegmentSize,
HighFidelityBehavior = behavior.HighFidelityBehavior,
+ IsActivelySimulatingPierce = false,
IsActivelyResimulating = false,
CancelHighResCast = false,
@@
FastCastEventsConfig = {
UseLengthChanged = behavior.FastCastEventsConfig.UseLengthChanged,
UseHit = behavior.FastCastEventsConfig.UseHit,
UsePierced = behavior.FastCastEventsConfig.UsePierced,
UseCastTerminating = behavior.FastCastEventsConfig.UseCastTerminating,
- UseCanPierce = behavior.FastCastEventsConfig.UseCanPierce
+ UseCanPierce = behavior.FastCastEventsConfig.UseCanPierce,
+ UseCastFire = behavior.FastCastEventsConfig.UseCastFire,
},
@@
if _parallel then
cast.StateInfo.FastCastEventsModuleConfig = {
UseLengthChanged = behavior.FastCastEventsModuleConfig.UseLengthChanged,
UseHit = behavior.FastCastEventsModuleConfig.UseHit,
UsePierced = behavior.FastCastEventsModuleConfig.UsePierced,
UseCastTerminating = behavior.FastCastEventsModuleConfig.UseCastTerminating,
UseCanPierce = behavior.FastCastEventsModuleConfig.UseCanPierce,
+ UseCastFire = behavior.FastCastEventsModuleConfig.UseCastFire,
}
end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ActiveCast.luau` around lines 57 - 82, StateInfo currently misses the
TypeDef.CastStateInfo-required boolean IsActivelySimulatingPierce and
FastCastEventsConfig (and the related FastCastEventsModuleConfig shape) is
missing UseCastFire; update the StateInfo table in src/ActiveCast.luau to
include IsActivelySimulatingPierce = false (or derive from existing
behavior/state if appropriate) and add UseCastFire =
behavior.FastCastEventsConfig.UseCastFire (or false) into FastCastEventsConfig
(and mirror into FastCastEventsModuleConfig where those defaults are
constructed) so the object matches TypeDef.CastStateInfo and
TypeDef.FastCastEventsConfig/FastCastEventsModuleConfig.
| Type = variant.CastType, | ||
| CastVariant = variant, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify Type field expectations and actual comparisons in runtime logic.
rg -n -C3 'Type\\s*[:=]|"Raycast"|"Blockcast"|"Spherecast"|CastType' src/TypeDefinitions.luau src/ActiveCast.luau src/SerialSimulation.luau src/ParallelSimulation.luau src/init.luauRepository: weenachuangkud/FastCast2
Length of output: 17776
Fix ActiveCast.Type contract mismatch (string literals vs numeric enum)
src/TypeDefinitions.luau types ActiveCastData.Type as "Raycast" | "Blockcast" | "Spherecast", but src/ActiveCast.luau assigns variant.CastType (numeric enum) into Type:
Type = variant.CastType,
CastVariant = variant,Update either ActiveCast to store the corresponding string literal or change the TypeDefinitions.luau contract to match the numeric enum.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ActiveCast.luau` around lines 93 - 94, ActiveCast currently sets Type =
variant.CastType (a numeric enum) while TypeDefinitions.luau expects the string
literals "Raycast" | "Blockcast" | "Spherecast"; fix by converting the numeric
enum to the matching string before storing or by aligning the type definition to
the enum. Locate ActiveCast and replace the direct assignment of
variant.CastType in ActiveCast.Type with a mapping to the string (e.g. use a
small lookup that maps variant.CastType -> "Raycast"/"Blockcast"/"Spherecast" or
a helper function castTypeToString), or alternatively update ActiveCastData.Type
in TypeDefinitions.luau to accept the numeric enum type used by
variant.CastType; update any references to ActiveCast.Type accordingly.
| cast.Caster.ActiveCastCleaner:Fire(cast.ID) | ||
|
|
||
| for key, _ in (cast :: any) do | ||
| cast[key] = nil | ||
| end |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Roblox Luau, does BindableEvent:Fire()invokeEvent listeners synchronously or asynchronously relative to the caller?
💡 Result:
BindableEvent:Fire invokes connected Event listeners asynchronously relative to the caller (it does not run the handlers synchronously on the caller’s thread). Roblox’s BindableEvent documentation states that a BindableEvent enables “asynchronous… communication” between scripts, and that when you call BindableEvent:Fire, “the firing script does not yield.” [1][2] It also explains that “BindableEvents create threads of each connected function,” meaning each connected handler runs on its own thread rather than blocking/synchronously continuing the firing script’s execution path. [1][2] So: Fire itself returns immediately without yielding, and the connected listeners run on separate threads (i.e., not synchronously with respect to the caller). [1][2]
Citations:
- 1: https://create.roblox.com/docs/reference/engine/classes/BindableEvent
- 2: https://create.roblox.com/docs/scripting/events/bindable
Fix cleanup race: don’t nil/clear cast state until ActiveCastCleaner runs
TerminateCast() calls cast.Caster.ActiveCastCleaner:Fire(cast.ID) and then immediately clears the cast table; BindableEvent:Fire() dispatches listeners asynchronously (the caller doesn’t yield), so the cleaner may observe already-cleared fields and skip cleanup.
Suggested fix
cast.Caster.ActiveCastCleaner:Fire(cast.ID)
-
- for key, _ in (cast :: any) do
- cast[key] = nil
- end
end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cast.Caster.ActiveCastCleaner:Fire(cast.ID) | |
| for key, _ in (cast :: any) do | |
| cast[key] = nil | |
| end | |
| cast.Caster.ActiveCastCleaner:Fire(cast.ID) | |
| end |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/BaseCastParallel.luau` around lines 60 - 64, TerminateCast currently
calls cast.Caster.ActiveCastCleaner:Fire(cast.ID) then immediately zeroes the
cast table, causing a race because BindableEvent:Fire is asynchronous; instead,
stop clearing the cast in TerminateCast and let the ActiveCastCleaner listener
perform the table cleanup (or acknowledge completion before TerminateCast
clears). Concretely: in TerminateCast remove the for key,_ in (cast :: any) do
cast[key] = nil end loop and move that cleanup logic into the ActiveCastCleaner
handler (the listener registered for cast.Caster.ActiveCastCleaner) or implement
a completion signal (e.g., set a __cleaned flag or Fire a cleanup-complete event
from the cleaner and Wait for it) so the cast table is only nilled after the
cleaner runs.
| function Cache:ExpandCache(Amount: number) | ||
| assert(typeof(Amount) ~= "number" or Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) | ||
| assert(typeof(Amount) == "number" and Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) | ||
| self:_GetNew(Amount, false) |
There was a problem hiding this comment.
ExpandCache() currently removes one free object.
_GetNew() still ends with table.remove(FreeObjectsContainer), so this method under-allocates every expansion and ExpandCache(0) now shrinks the cache by one.
Suggested fix
function Cache:ExpandCache(Amount: number)
assert(typeof(Amount) == "number" and Amount >= 0, `Invalid argument `#1` to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`)
- self:_GetNew(Amount, false)
+ if Amount == 0 then
+ return
+ end
+
+ local object = self:_GetNew(Amount, false)
+ table.insert(self._FreeObjects, object)
end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function Cache:ExpandCache(Amount: number) | |
| assert(typeof(Amount) ~= "number" or Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) | |
| assert(typeof(Amount) == "number" and Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) | |
| self:_GetNew(Amount, false) | |
| function Cache:ExpandCache(Amount: number) | |
| assert(typeof(Amount) == "number" and Amount >= 0, `Invalid argument `#1` to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) | |
| if Amount == 0 then | |
| return | |
| end | |
| local object = self:_GetNew(Amount, false) | |
| table.insert(self._FreeObjects, object) | |
| end |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ObjectCache.luau` around lines 127 - 129, Cache:ExpandCache currently
calls self:_GetNew(Amount, false) but _GetNew always ends with
table.remove(FreeObjectsContainer), causing every expansion (including
ExpandCache(0)) to under-allocate by one; change ExpandCache to call _GetNew in
a mode that prevents the final table.remove (e.g., pass a "skipRemove" /
"reserveOnly" flag or flip the boolean argument to true) or update _GetNew to
accept and honor a parameter that skips removing the last free object so
ExpandCache performs pure expansion without removing any free object; reference:
Cache:ExpandCache and _GetNew and FreeObjectsContainer.
| casts_CFrame[id] = (if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)) * CFrame.new(0, 0, -rayDisplacement / 2) | ||
|
|
||
| QueueEvent(id, "LengthChanged", lastPoint, rayDir.Unit, rayDisplacement) | ||
|
|
||
| casts_DistanceCovered[id] += rayDisplacement | ||
|
|
||
| if delta > 0 then | ||
| QueueVisualize( | ||
| id, | ||
| { | ||
| castStartCFrame = CFrame.new(lastPoint, lastPoint+rayDir), |
There was a problem hiding this comment.
Guard zero-length segments before normalizing rayDir.
When a cast doesn't move this frame, rayDir.Unit and CFrame.new(lastPoint, lastPoint + rayDir) will produce invalid direction/orientation data for listeners and debug visualization.
Suggested fix
- casts_CFrame[id] = (if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)) * CFrame.new(0, 0, -rayDisplacement / 2)
-
- QueueEvent(id, "LengthChanged", lastPoint, rayDir.Unit, rayDisplacement)
+ local segmentCFrame = if rayDir ~= Vector3.zero then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)
+ local segmentDirection = if rayDir ~= Vector3.zero then rayDir.Unit else Vector3.zero
+ casts_CFrame[id] = segmentCFrame * CFrame.new(0, 0, -rayDisplacement / 2)
+
+ QueueEvent(id, "LengthChanged", lastPoint, segmentDirection, rayDisplacement)
@@
- castStartCFrame = CFrame.new(lastPoint, lastPoint+rayDir),
+ castStartCFrame = segmentCFrame,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| casts_CFrame[id] = (if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)) * CFrame.new(0, 0, -rayDisplacement / 2) | |
| QueueEvent(id, "LengthChanged", lastPoint, rayDir.Unit, rayDisplacement) | |
| casts_DistanceCovered[id] += rayDisplacement | |
| if delta > 0 then | |
| QueueVisualize( | |
| id, | |
| { | |
| castStartCFrame = CFrame.new(lastPoint, lastPoint+rayDir), | |
| local segmentCFrame = if rayDir ~= Vector3.zero then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint) | |
| local segmentDirection = if rayDir ~= Vector3.zero then rayDir.Unit else Vector3.zero | |
| casts_CFrame[id] = segmentCFrame * CFrame.new(0, 0, -rayDisplacement / 2) | |
| QueueEvent(id, "LengthChanged", lastPoint, segmentDirection, rayDisplacement) | |
| casts_DistanceCovered[id] += rayDisplacement | |
| if delta > 0 then | |
| QueueVisualize( | |
| id, | |
| { | |
| castStartCFrame = segmentCFrame, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ParallelSimulation.luau` around lines 623 - 633, The code calls
rayDir.Unit and CFrame.new(lastPoint, lastPoint + rayDir) for QueueEvent and
QueueVisualize without guarding zero-length rayDir, causing invalid orientation
when the segment length is zero; update the block around casts_CFrame,
QueueEvent, and QueueVisualize to check rayDir.Magnitude (or rayDir ~=
Vector3.new()) before normalizing or building CFrames and handle the zero-length
case by using a safe fallback direction/orientation (e.g., keep previous
orientation or use a constant unit vector) so casts_CFrame, QueueEvent(id,
"LengthChanged", ... rayDir.Unit ...), and QueueVisualize's castStartCFrame are
only passed valid directions when delta == 0 or rayDir is zero.
| local subPosition = GetPositionAtTime( | ||
| totalDelta + (timeIncrement * segmentIndex), | ||
| origin, | ||
| initialVelocity, | ||
| acceleration | ||
| ) | ||
| local subVelocity = GetVelocityAtTime( | ||
| lastDelta + (timeIncrement * segmentIndex), | ||
| initialVelocity, | ||
| acceleration | ||
| ) | ||
| local subRayDir = subVelocity * delta | ||
| local subResult = castHandler(targetWorldRoot, subPosition, subRayDir, casts_RayInfo[id].Parameters) | ||
|
|
There was a problem hiding this comment.
Automatic subcasts use the wrong time base and lose shape parameters.
These substeps start from the already-advanced totalDelta, scale subRayDir by the full-frame delta, and omit casts_CastVariant[id] when calling the handler. That makes each subcast overshoot its segment, and block/sphere high-fidelity casts can crash on variant.Size/variant.Radius.
Suggested fix
local subPosition = GetPositionAtTime(
- totalDelta + (timeIncrement * segmentIndex),
+ lastDelta + (timeIncrement * segmentIndex),
origin,
initialVelocity,
acceleration
)
local subVelocity = GetVelocityAtTime(
lastDelta + (timeIncrement * segmentIndex),
initialVelocity,
acceleration
)
- local subRayDir = subVelocity * delta
- local subResult = castHandler(targetWorldRoot, subPosition, subRayDir, casts_RayInfo[id].Parameters)
+ local subRayDir = subVelocity * timeIncrement
+ local subResult = castHandler(
+ targetWorldRoot,
+ subPosition,
+ subRayDir,
+ casts_RayInfo[id].Parameters,
+ casts_CastVariant[id]
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| local subPosition = GetPositionAtTime( | |
| totalDelta + (timeIncrement * segmentIndex), | |
| origin, | |
| initialVelocity, | |
| acceleration | |
| ) | |
| local subVelocity = GetVelocityAtTime( | |
| lastDelta + (timeIncrement * segmentIndex), | |
| initialVelocity, | |
| acceleration | |
| ) | |
| local subRayDir = subVelocity * delta | |
| local subResult = castHandler(targetWorldRoot, subPosition, subRayDir, casts_RayInfo[id].Parameters) | |
| local subPosition = GetPositionAtTime( | |
| lastDelta + (timeIncrement * segmentIndex), | |
| origin, | |
| initialVelocity, | |
| acceleration | |
| ) | |
| local subVelocity = GetVelocityAtTime( | |
| lastDelta + (timeIncrement * segmentIndex), | |
| initialVelocity, | |
| acceleration | |
| ) | |
| local subRayDir = subVelocity * timeIncrement | |
| local subResult = castHandler( | |
| targetWorldRoot, | |
| subPosition, | |
| subRayDir, | |
| casts_RayInfo[id].Parameters, | |
| casts_CastVariant[id] | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ParallelSimulation.luau` around lines 692 - 705, Subcasts use the wrong
time base and drop variant data: when building each subcast replace subRayDir =
subVelocity * delta with subVelocity * timeIncrement so each substep spans only
its segment, and include the shape/variant info by passing casts_CastVariant[id]
(or merging it with casts_RayInfo[id].Parameters) into the castHandler call;
update the call sites around GetPositionAtTime, GetVelocityAtTime, castHandler
and the variables totalDelta, lastDelta, timeIncrement, delta, casts_RayInfo,
casts_CastVariant to ensure subcasts use the correct time offset and receive the
variant (Size/Radius) parameter.
| if casts_DistanceCovered[id] >= casts_RayInfo[id].MaxDistance then | ||
| if resultOfCast then | ||
| QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) | ||
| QueueVisualize(id, { | ||
| atCF = CFrame.new(point), | ||
| sub = false, | ||
| wasPierce = false | ||
| }:: any) | ||
| end | ||
| QueueEvent(id, "CastTerminating", castTerminatingfn) | ||
| end |
There was a problem hiding this comment.
Use the cached max-distance value here.
Register() already normalizes nil MaxDistance to DEFAULT_MAX_DISTANCE, but this branch reads casts_RayInfo[id].MaxDistance again. If that source field is nil, >= will throw instead of terminating the cast.
Suggested fix
- if casts_DistanceCovered[id] >= casts_RayInfo[id].MaxDistance then
+ if casts_DistanceCovered[id] >= casts_MaxDistance[id] then📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if casts_DistanceCovered[id] >= casts_RayInfo[id].MaxDistance then | |
| if resultOfCast then | |
| QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) | |
| QueueVisualize(id, { | |
| atCF = CFrame.new(point), | |
| sub = false, | |
| wasPierce = false | |
| }:: any) | |
| end | |
| QueueEvent(id, "CastTerminating", castTerminatingfn) | |
| end | |
| if casts_DistanceCovered[id] >= casts_MaxDistance[id] then | |
| if resultOfCast then | |
| QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) | |
| QueueVisualize(id, { | |
| atCF = CFrame.new(point), | |
| sub = false, | |
| wasPierce = false | |
| }:: any) | |
| end | |
| QueueEvent(id, "CastTerminating", castTerminatingfn) | |
| end |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ParallelSimulation.luau` around lines 777 - 787, The branch compares
casts_DistanceCovered[id] against casts_RayInfo[id].MaxDistance which can be
nil; instead use the normalized cached max-distance set by Register()
(DEFAULT_MAX_DISTANCE is applied there) so the comparison never reads a
potentially nil field—store the normalized value in a per-cast cache (e.g.
casts_MaxDistance[id] or a localMaxDistance captured when the cast is
registered) and replace casts_RayInfo[id].MaxDistance in this >= check with that
cached value.
Summary
Complete architectural rewrite from the original single-mode FastCast into two distinct modes with 350+ commits.
FastCast.new()— Serial caster (main thread, simple API)FastCast.newParallel()— Parallel caster (Actor VMs, high performance)0. Before (main branch — pre-PR state)
The
mainbranch contained a monolithic single-mode FastCast with files undersrc/FastCast2/.Architecture (pre-PR)
FastCast.new()returning one caster type — no serial/parallel splitHeartbeat/RenderSteppedper active cast)ActiveCast(808 lines) with metatable methods (__index)Trajectoriesarray (list of trajectory segments) on each castBehavior.MovementMethodfield for movement mode selectionxpcall/pcallwrapping on all user callbacksSignal.luauutility class for event handlingFastCastVMs/with Actor-based workersAPI (pre-PR)
FastCast.new()— returned single caster typecaster:Fire(origin, direction, velocity, behavior)— single fire method.RayHit,.RayPierced,.CanRayPierce,.LengthChanged,.CastTerminatingSafeCall()utility for error-handled callback invocationPauseCast()/ResumeCast()SetBulkMoveEnabled(bool)BetterLengthChangedModule/CanRayPierceModulepatternSource layout (pre-PR)
1. Architecture
New modules (5 new source files)
BaseCastSerial.luau— Serial-mode caster handler, owns ObjectCache/Motor6DCache, routes toSerialSimulationBaseCastParallel.luau— Parallel-mode caster handler (module-scoped state inside each Actor VM), routes toParallelSimulationSerialSimulation.luau— SoA physics engine on main-thread RunService (Heartbeat/PreSimulation)ParallelSimulation.luau— SoA physics engine viaConnectParallelinside Actor VMsMotor6DCache.luau— Motor6D instance pooling for Transform movement mode (initial pool 128, growth rate 2x)Rewritten modules (3)
ActiveCast.luau— OOP class (808 lines) → Cast data factory (149 lines, pure data, AoS exposed to users, SoA internally)ObjectCache.luau— Basic pooling → BulkMoveTo-based with auto-expand and Model template supportFastCastVMs/init.luau— VM Dispatcher: template Actor with controller script, clones N workers, round-robin dispatchSoA (Structure of Arrays) pattern
casts_TotalRunTime,casts_Trajectory,casts_RayInfo,casts_CFrame,casts_UserData,casts_CastType,casts_CastVariant,casts_MaxDistance,casts_ActiveMotor6Ds, etc.casts_IDarray for O(n) per-frame iteration (no table traversal overhead)casts_ID_Indexreverse lookupActiveCast refactor (OOP → pure data)
createCastData()factory function replaces class instantiationStateInfo.Trajectories(array) →Trajectory(single object — saves memory, simplifies access)StateInfo.UpdateConnectionremoved — no per-cast RunService connectionsUserDataattached frombehavior.UserDatawhen providedFastCastEventsModulereference stored inRayInfoCosmeticBulletObjectcreated on the main thread (critical for parallel mode — instance writes restricted in actors)AutoIgnoreContainersupport — automatically adds bullet container to filter listCloneCastParams— deep-clones RaycastParams withtable.unpackon FilterDescendantsInstancesRespectCanCollidenow propagated in cloned paramsInstance-local state (fixes multi-caster corruption)
BaseCastSerialmoved from module-scoped variables toself.*propertiesActives,ObjectCacheInstance,Motor6DCacheInstance,NextProjectileID,SerialSimulationVM Dispatcher improvements
Actor:SendMessage()for cross-context communication_nextIndex % #Threads + 1)DispatchAll()for broadcasting settings changes to all workersAllocate()for dynamic worker pool growthActor:SetAttribute("Tasks", ...)for tracking active cast count per worker2. New Features
Serial mode (
FastCast.new())caster:Init(movementMode, useObjectCache, template?, cacheSize?, cacheHolder?)caster.Hit = function(cast, result, velocity, cosmeticBullet) end__newindexmetamethod onFastCastSerialroutes event assignments toBaseCastSerial:_UpdateEvents()Parallel mode (
FastCast.newParallel())caster:Init(numWorkers, newParent, newName, ContainerParent, VMContainerName, VMname, movementMode, fastCastEventsModule?, useObjectCache, template?, cacheSize?, cacheHolder?)ConnectParallelfor truly parallel physics simulationOutput:BindableEvent+ Dispatcher callback for event routing back to main threadSyncChanges:BindableEventfor pushing trajectory/state modifications into Actor VMsActiveCastCleaner:BindableEventfor cleanup orchestrationSetFastCastEventsModule(moduleScript)— direct callback (no BindableEvent routing, better perf)FastCastEventsModuleConfigseparate fromFastCastEventsConfigfor dual event channel supporttask.synchronize()barrier between simulation and event firing for safe cross-context communicationMotor6D movement mode
Motor6D.Transformcaster:Init("Motor6D", ...)orcaster:SetMovementMode("Motor6D", true)Motor6DCachepools Motor6D instances (initial 128, grows 2x on exhaustion)motor6d.Transform = casts_CFrame[id]High-Fidelity sub-stepping
rayDisplacement / HighFidelitySegmentSizesegments, recasts each to find precise hit pointCancelHighResCastflag allows aborting in-progress high-res passtimeIncrementinstead ofdelta(was causing missed hits in Automatic mode)Debug visualization system
VisualizeCastSettings:Debug_SegmentColor,Debug_SegmentTransparency,Debug_SegmentSizeDebug_HitColor,Debug_HitTransparency,Debug_HitSizeDebug_RayPierceColor,Debug_RayPierceTransparency,Debug_RayPierceSizeDebug_RayLifetime,Debug_HitLifetimeworkspace.Terrain.FastCastVisualizationObjectswith debris cleanupObjectCache system
BulkMoveTofor moving parts to/from far-away CFrame(2^24, 2^24, 2^24)GetPart(CFrame?),ReturnPart(Part),ExpandCache(Amount),SetExpandAmount(Amount)IsInUse(Object)for introspectionPartToObjectmapping to prevent Model template orphaningEvent system
FastCastEventstable passed toBaseCastSerial.Init()— fired fromSerialSimulation:FireQueuedEvents()FastCastEventsConfig→Output:BindableEvent:Fire()→ Dispatcher callback → user handlerFastCastEventsModuleConfig→ directrequire()callbacks (no BindableEvent overhead)CastFire,Hit,Pierced,LengthChanged,CastTerminating,CanPierceUse*booleans in config tables{ [castID]: { QueuedEventData[] } }, sorted by cast ID, fired in order3. API Changes
Removed
FastCastParallel.new()FastCast2.newParallel()behavior.MovementMethodcaster:Init("Motor6D", ...)orcaster:SetMovementMode()SetBulkMoveEnabled()SetMovementMode(mode, enabled)PauseCast()/ResumeCast()UpdateConnectionon StateInfoxpcall/pcallfrom hot pathFastCastEventsModulefrom SerialTrajectories(array of segments)Trajectory(single object)SafeCall()BetterLengthChangedModuleCanRayPierceModuleObjectCache.Typefieldself.self.*patternsself.*TEST_LOGS,roblox.yml,mcp.jsonActiveCast.luau(OOP)BaseCast.luauBaseCastSerial+BaseCastParallelSignal.luauConfigs.luauConfig.luauBenchmarks/bench1.client.luauNew methods on
FastCastnew()→ FastCastSerial casternewParallel()→ FastCastParallel casternewBehavior()→ FastCastBehaviorGetPositionCast(cast)→ Vector3GetVelocityCast(cast)→ Vector3(now computes actual velocity including acceleration)GetAccelerationCast(cast)→ Vector3SetPositionCast(cast, pos)→ ()SetVelocityCast(cast, vel)→ ()SetAccelerationCast(cast, accel)→ ()AddPositionCast(cast, pos)→ ()AddVelocityCast(cast, vel)→ ()AddAccelerationCast(cast, accel)→ ()TerminateCast(cast)→ ()New methods on Caster
caster:SetMovementMode(mode, enabled)caster:SetObjectCacheEnabled(enabled, ...)caster:SetFastCastEventsModule(module)caster:SyncChangesToCast(cast)Changed signatures
caster:Init(true, true, ...)caster:Init("BulkMoveTo", true, ...)FastCast:GetVelocityCastreturnsInitialVelocityBehavior.MovementMethod = "Transform"caster:Init("Motor6D", ...)numWorkers > 1assertionnumWorkers > 1(was>= 1, reverted to> 1)Spawn(function)task.spawn(function).RayHit,.RayPierced,.CanRayPierceevents.Hit,.Pierced,.CanPiercetable.clear()queuedEvents[castID] = nil__indexmetatablecreateCastData()RaycastParamsreferenceCloneCastParams()Configuration changes
FastCastEventsConfignow includesUseCanPierce(was missing in DefaultConfigs)FastCastEventsModuleConfigadded for parallel module event gatingVisualizeCastsandVisualizeCastSettingsadded to behaviorUserDatafield for arbitrary data attachment4. Bug Fixes
Critical
local rayDir = totalDisplacement.Unit * segmentVelocity.Magnitude * delta→local rayDir = totalDisplacement(was using wrong delta causing missed hits)typeof(module) == "ModuleScript" or (typeof(module) == "Instance" and module:IsA("ModuleScript"))checkCast manipulation
cast.StateInfo.Trajectory.AccelerationcorrectlySetPositionCast/etcModifyTransformationCleanup & memory
ActivesRef→self.ActivesRefin SimulateCastif rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)Type system
@typedoc copying BlockCastRayInfo: Fixed copy-paste error@typeunnamed parameters: Added namesFastCastEventsModuletype exportedVisualization
Misc
> 1(correct)CloneCastParamswithtable.unpack5. Documentation
New files
AGENTS.md— Project overview, development commands, project structure, code style guidelinesdocs/api-reference.md— Full API reference with Init signatures, parameter tables, event docs, FastCastBehavior fieldsskills/architecture.md— Full architecture documentation (module layout, execution flow, SoA pattern, event system, data flow diagram)PR.md— This PR description documentUpdated files
README.md— Corrected Init examples, added Rojo installation guide, simplified serial/parallel code examples, fixed FastCastEventsModule section, fixedmodule.RayHit→module.Hitin events exampledocs/changelog.md— Updated changelog from 0.0.1 to 0.1.0docs/intro.md— Updated project introductiondocs/cheatsheet.md— Updated API reference with serial/parallel sectionsDoc comments
@class,@method,@param,@return,@withinannotations@typeand@withintagsTypeDefinitions.luau— All public types exported with documentation:FastCastBehavior,FastCastEvents,FastCastEventsConfig,FastCastEventsModuleConfigCastStateInfo,CastTrajectory,CastRayInfo,BlockCastRayInfo,SphereCastRayInfoActiveCastData,ActiveBlockcastData,ActiveSpherecastDataBaseCastData,CasterSerial,CasterParallelVisualizeCastSettingswith all debug visualization properties6. Chores & Infrastructure
Project setup
default.project.json,sourcemap.json— Rojo build configurationwally.toml— Wally package manager config (version bumped).gitignore— Addedsrc/*.legacy.luau,opencode.json,.aider,.luaurc,sourcemap.jsonCode cleanup
SafeCall,material, dead codetable.clear()→queuedEvents[castID] = nilTerminateCast, etc.)AutomaticPerformanceflagmcp.json,roblox.yml,TEST_LOGSSource files moved
src/FastCast2/→src/(flattened for Rojo compatibility)Summary by CodeRabbit
New Features
Documentation