Skip to content

v0.1.0 — Major refactor: Serial & Parallel casters, SoA simulation, Motor6D movement, visualization, benchmarks#39

Open
weenachuangkud wants to merge 361 commits into
mainfrom
major
Open

v0.1.0 — Major refactor: Serial & Parallel casters, SoA simulation, Motor6D movement, visualization, benchmarks#39
weenachuangkud wants to merge 361 commits into
mainfrom
major

Conversation

@weenachuangkud
Copy link
Copy Markdown
Owner

@weenachuangkud weenachuangkud commented May 8, 2026

Summary

Complete architectural rewrite from the single FastCastParallel into two distinct modes with 350+ commits.

  • FastCast.new() — Serial caster (main thread, simple API)
  • FastCast.newParallel() — Parallel caster (Actor VMs, high performance)

1. Architecture

New modules (8 new source files)

  • BaseCastSerial.luau — Serial-mode caster handler, owns ObjectCache/Motor6DCache, routes to SerialSimulation
  • BaseCastParallel.luau — Parallel-mode caster handler (module-scoped state inside each Actor VM), routes to ParallelSimulation
  • SerialSimulation.luau — SoA physics engine on main-thread RunService (Heartbeat/PreSimulation)
  • ParallelSimulation.luau — SoA physics engine via ConnectParallel inside Actor VMs
  • ActiveCast.luau — Cast data factory (pure data structure, AoS exposed to users, SoA internally)
  • ObjectCache.luau — Cosmetic bullet part pooling using BulkMoveTo (pre-allocates, auto-expands)
  • Motor6DCache.luau — Motor6D instance pooling for Transform movement mode (initial pool 128, growth rate 2x)
  • FastCastVMs/init.luau — VM Dispatcher: template Actor with controller script, clones N workers, round-robin dispatch

SoA (Structure of Arrays) pattern

  • ~25 parallel arrays per simulation: casts_TotalRunTime, casts_Trajectory, casts_RayInfo, casts_CFrame, casts_UserData, casts_CastType, casts_CastVariant, casts_MaxDistance, casts_ActiveMotor6Ds, etc.
  • Dense casts_ID array for O(n) per-frame iteration (no table traversal overhead)
  • O(1) unregistration via swap-and-pop with casts_ID_Index reverse lookup
  • Eliminates per-cast RunService connections entirely — one connection per simulation instance
  • Queued event/visualization system: events are collected during simulation step, sorted by cast ID, and fired after physics

ActiveCast refactor (OOP → pure data)

  • createCastData() factory function replaces class instantiation
  • StateInfo.Trajectories (array) → Trajectory (single object — saves memory, simplifies access)
  • StateInfo.UpdateConnection removed — no per-cast RunService connections
  • UserData attached from behavior.UserData when provided
  • FastCastEventsModule reference stored in RayInfo
  • CosmeticBulletObject created on the main thread (critical for parallel mode — instance writes restricted in actors)
  • AutoIgnoreContainer support — automatically adds bullet container to filter list
  • CloneCastParams — deep-clones RaycastParams with table.unpack on FilterDescendantsInstances
  • RespectCanCollide now propagated in cloned params

Instance-local state (fixes multi-caster corruption)

  • BaseCastSerial moved from module-scoped variables to self.* properties
  • Each caster instance now owns its own Actives, ObjectCacheInstance, Motor6DCacheInstance, NextProjectileID, SerialSimulation
  • Prevents cross-instance corruption when multiple casters exist

VM Dispatcher improvements

  • Actor-based worker VMs with Actor:SendMessage() for cross-context communication
  • Round-robin dispatch for load balancing (_nextIndex % #Threads + 1)
  • DispatchAll() for broadcasting settings changes to all workers
  • Allocate() for dynamic worker pool growth
  • ObjectCache moved locally into each Actor VM (eliminates BindableFunction cross-thread calls)
  • Actor:SetAttribute("Tasks", ...) for tracking active cast count per worker

2. New Features

Serial mode (FastCast.new())

  • caster:Init(movementMode, useObjectCache, template?, cacheSize?, cacheHolder?)
  • Events set directly: caster.Hit = function(cast, result, velocity, cosmeticBullet) end
  • __newindex metamethod on FastCastSerial routes event assignments to BaseCastSerial:_UpdateEvents()
  • TerminateCast support with proper cleanup (cosmetic bullet return, simulation unregister)
  • Single RunService connection (Heartbeat on server, PreSimulation on client)

Parallel mode (FastCast.newParallel())

  • caster:Init(numWorkers, newParent, newName, ContainerParent, VMContainerName, VMname, movementMode, fastCastEventsModule?, useObjectCache, template?, cacheSize?, cacheHolder?)
  • Actor VM workers with ConnectParallel for truly parallel physics simulation
  • Output:BindableEvent + Dispatcher callback for event routing back to main thread
  • SyncChanges:BindableEvent for pushing trajectory/state modifications into Actor VMs
  • ActiveCastCleaner:BindableEvent for cleanup orchestration
  • SetFastCastEventsModule(moduleScript) — direct callback (no BindableEvent routing, better perf)
  • FastCastEventsModuleConfig separate from FastCastEventsConfig for dual event channel support
  • task.synchronize() barrier between simulation and event firing for safe cross-context communication

Motor6D movement mode

  • Alternative to BulkMoveTo using Motor6D.Transform
  • Set via caster:Init("Motor6D", ...) or caster:SetMovementMode("Motor6D", true)
  • Motor6DCache pools Motor6D instances (initial 128, grows 2x on exhaustion)
  • Invisible anchored anchor part as Part0, cosmetic bullet as Part1
  • Per-frame transform update: motor6d.Transform = casts_CFrame[id]
  • Smooth visual interpolation via engine Motor6D physics
  • Automatic Motor6D connection on cast registration, disconnection on unregistration
  • Mode switching at runtime: converts all active casts between BulkMoveTo and Motor6D

High-Fidelity sub-stepping

  • Default (1) — single cast per frame (fastest)
  • Automatic (2) — on hit, subdivides displacement into rayDisplacement / HighFidelitySegmentSize segments, recasts each to find precise hit point
  • Always (3) — always subdivides every frame (most accurate)
  • CancelHighResCast flag allows aborting in-progress high-res pass
  • Cascading cast lag detection with warning
  • Fixed: subRayDir now uses timeIncrement instead of delta (was causing missed hits in Automatic mode)
  • Fixed: Always-mode sub-step delta calculation

Debug visualization system

  • ConeHandleAdornment for raycast segments
  • BoxHandleAdornment for blockcast segments (not stretched by cast length)
  • SphereHandleAdornment for spherecast segments
  • Distinct hit vs. pierce visualization (different colors, sizes, transparency)
  • Configurable via VisualizeCastSettings:
    • Debug_SegmentColor, Debug_SegmentTransparency, Debug_SegmentSize
    • Debug_HitColor, Debug_HitTransparency, Debug_HitSize
    • Debug_RayPierceColor, Debug_RayPierceTransparency, Debug_RayPierceSize
    • Debug_RayLifetime, Debug_HitLifetime
  • Sub-cast visualization for high-fidelity segments
  • Adornments parented to workspace.Terrain.FastCastVisualizationObjects with debris cleanup
  • Queued visualization system: collected during simulation, fired after physics

ObjectCache system

  • Bullet part/model pooling with pre-allocation (default 500, expand by 50)
  • BulkMoveTo for moving parts to/from far-away CFrame (2^24, 2^24, 2^24)
  • Model support (uses PrimaryPart as root)
  • GetPart(CFrame?), ReturnPart(Part), ExpandCache(Amount), SetExpandAmount(Amount)
  • IsInUse(Object) for introspection
  • PartToObject mapping to prevent Model template orphaning
  • Memory leak fix: stores full objects in free pool

Event system

  • Serial mode: Direct callbacks via FastCastEvents table passed to BaseCastSerial.Init() — fired from SerialSimulation:FireQueuedEvents()
  • Parallel mode: Two channels:
    1. FastCastEventsConfigOutput:BindableEvent:Fire() → Dispatcher callback → user handler
    2. FastCastEventsModuleConfig → direct require() callbacks (no BindableEvent overhead)
  • Events: CastFire, Hit, Pierced, LengthChanged, CastTerminating, CanPierce
  • Event gating via Use* booleans in config tables
  • Queued event system: events collected in { [castID]: { QueuedEventData[] } }, sorted by cast ID, fired in order

3. API Changes

Removed

API Replacement / Reason
FastCastParallel.new() FastCast2.newParallel()
behavior.MovementMethod Set via caster:Init("Motor6D", ...) or caster:SetMovementMode()
SetBulkMoveEnabled() SetMovementMode(mode, enabled)
PauseCast() / ResumeCast() Removed entirely (unused, cross-context complexity)
UpdateConnection on StateInfo No per-cast RunService connections
xpcall/pcall from hot path Direct calls for performance
FastCastEventsModule from Serial Parallel only
Trajectories (array of segments) Trajectory (single object)
SafeCall() Removed
BetterLengthChangedModule Replaced by FastCastEventsModule
CanRayPierceModule Replaced by FastCastEventsModule
ObjectCache.Type field Internal refactor
self.self.* patterns Fixed to self.*
_mini variant Removed
TEST_LOGS, roblox.yml, mcp.json Cleanup
Legacy ActiveCast.luau (OOP) Replaced by data factory

New methods on FastCast

Method Signature
new() → FastCastSerial caster
newParallel() → FastCastParallel caster
newBehavior() → FastCastBehavior
GetPositionCast(cast) → Vector3
GetVelocityCast(cast) → Vector3 (now computes actual velocity including acceleration)
GetAccelerationCast(cast) → Vector3
SetPositionCast(cast, pos) → ()
SetVelocityCast(cast, vel) → ()
SetAccelerationCast(cast, accel) → ()
AddPositionCast(cast, pos) → ()
AddVelocityCast(cast, vel) → ()
AddAccelerationCast(cast, accel) → ()
TerminateCast(cast) → ()

New methods on Caster

Method Mode Purpose
caster:SetMovementMode(mode, enabled) Both Switch between BulkMoveTo/Motor6D
caster:SetObjectCacheEnabled(enabled, ...) Both Toggle part pooling
caster:SetFastCastEventsModule(module) Parallel Register events module
caster:SyncChangesToCast(cast) Parallel Push state to worker VM

Changed signatures

Old New
caster:Init(true, true, ...) caster:Init("BulkMoveTo", true, ...)
FastCast:GetVelocityCast returns InitialVelocity Returns actual current velocity with acceleration
Behavior.MovementMethod = "Transform" caster:Init("Motor6D", ...)
numWorkers > 1 assertion numWorkers > 1 (was >= 1, reverted to > 1)
Spawn(function) task.spawn(function)
.RayHit, .RayPierced, .CanRayPierce events .Hit, .Pierced, .CanPierce
table.clear() queuedEvents[castID] = nil
Cast data with __index metatable Plain table from createCastData()
RaycastParams reference Deep clone via CloneCastParams()

Configuration changes

  • FastCastEventsConfig now includes UseCanPierce (was missing in DefaultConfigs)
  • FastCastEventsModuleConfig added for parallel module event gating
  • VisualizeCasts and VisualizeCastSettings added to behavior
  • UserData field for arbitrary data attachment

4. Bug Fixes

Critical

  • HighFidelityBehavior.Automatic ray direction: local rayDir = totalDisplacement.Unit * segmentVelocity.Magnitude * deltalocal rayDir = totalDisplacement (was using wrong delta causing missed hits)
  • HighFidelityBehavior.Always sub-step: Fixed sub-step delta calculation (was accumulating incorrectly)
  • Pierce-stuck bug: Reset parametric trajectory after pierce so next frame continues from hit position
  • Double Destroy regression: Stash no-op Destroy before removing metatable to prevent re-entry
  • CastTerminating not firing for serial: Fixed TerminateCast serial branch isolation
  • Missing return after cascading cast warn: Added return to prevent further execution
  • EndTime early-return: Prevent double-termination when EndTime already set
  • TerminateCast nil-state guard: Isolate Actives cleanup to serial branch
  • Cosmetic bullet creation in parallel: Move to main thread (pre-dispatch) — instance writes restricted in Actor threads
  • ObjectCache cross-thread BindableFunction: Moved ObjectCache locally into each Actor VM
  • ModuleScript typeof returning 'Instance': Added typeof(module) == "ModuleScript" or (typeof(module) == "Instance" and module:IsA("ModuleScript")) check

Cast manipulation

  • GetAccelerationCast returning velocity: Now returns cast.StateInfo.Trajectory.Acceleration correctly
  • AddPositionCast/AddVelocityCast/AddAccelerationCast: Were calling nonexistent methods, now use SetPositionCast/etc
  • SetPositionCast missing: Was not implemented, now added via ModifyTransformation
  • GetVelocityCast returning InitialVelocity: Now computes velocity at current runtime using acceleration
  • Trajectory rebase in setters: Before modifying Origin/InitialVelocity/Acceleration, forward-integrate trajectory to current TotalRuntime and set StartTime = TotalRuntime

Cleanup & memory

  • Destroy referencing RayHit/RayPierced: Changed to Hit/Pierced
  • Motor6DCache memory leak: Destroy pooled Motor6D instances on Destroy
  • ObjectCache Model template orphaning: Store full objects in free pool, add PartToObject mapping
  • SyncChanges nested merge: Deep merge StateInfo/Trajectory to preserve nested properties
  • SerialSimulation ActivesRef: ActivesRefself.ActivesRef in SimulateCast
  • BaseCastParallel safe iteration: Collect active list before iterating in Destroy
  • SyncChanges connection leak: Disconnect SyncChanges event in Destroy
  • Zero-direction CFrame guard: if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)
  • DebrisAdd return after Destroy: Add return statement
  • IsActivelyResimulating reset on early exit: Reset flag when cast is nil

Type system

  • Spherecast not working: Fixed type errors in variant handling and radius extraction
  • Unnamed parameters in all callback function types: Added parameter names
  • Incorrect union types on Caster signal fields: Removed RBXScriptSignal/RBXScriptConnection
  • SphereCastRayInfo @type doc copying BlockCastRayInfo: Fixed copy-paste error
  • OnCastFireFunction @type unnamed parameters: Added names
  • GetVelocityCast signature: Fixed return type
  • AddAccelerationCast signature: Fixed parameter type
  • Lua-ModuleScript union typing: FastCastEventsModule type exported
  • Type errors in ParallelSimulation: Silenced with proper casts

Visualization

  • DBG_SEGMENT_SUB_COLOR2 identical to DBG_SEGMENT_SUB_COLOR: Distinguished
  • Blockcast visualization stretched: No longer stretched by cast length
  • Complete visualization in ParallelSimulation: Was missing, fully wired
  • Complete visualization in SerialSimulation: Was missing, fully wired

Misc

  • BulkMoveTo double-connection guard: Fixed in BindBulkMoveTo
  • numWorkers assertion: > 1 (correct)
  • FastCastEventsModule nil guard: Added guarding in ActiveCast
  • CanPierce logic: Fixed pierce decision flow in both simulations
  • BaseCastSerial cross-instance corruption: Made state instance-local
  • Acceleration validation: Guard before kinematic read
  • RaycastParams deep copy: CloneCastParams with table.unpack
  • DefaultConfigs missing UseCanPierce: Added to FastCastEventsConfig
  • Fire Hit when max distance reached after pierce: Fixed termination logic
  • Preserve StateInfo/RayInfo in SeriaSimulation TerminateCast: Fixed cleanup order
  • init.luau redundant cloning in parallel fire methods: Removed

5. Documentation

New files (7)

  • docs/api-reference.md — Full API reference with Init signatures, parameter tables, event docs, FastCastBehavior fields
  • docs/changelog.md — Complete changelog from 0.0.1 to 0.1.0
  • docs/intro.md — Project introduction and benefits
  • docs/cheatsheet.md — Quick API reference with serial/parallel sections
  • skills/architecture.md — Full architecture documentation (module layout, execution flow, SoA pattern, event system, data flow diagram)
  • skills/edge-case-considerations.md — Edge cases and considerations (from earlier version)
  • PR.md — This PR description document

Updated files

  • README.md — Corrected Init examples, added Rojo installation guide, simplified serial/parallel code examples, fixed FastCastEventsModule section, fixed module.RayHitmodule.Hit in events example
  • AGENTS.md — Project overview, development commands, project structure, code style guidelines

Doc comments

  • Moonwave-compatible documentation comments throughout all source files:
    • @class, @method, @param, @return, @within annotations
    • Type definitions with @type and @within tags
    • Cross-references to TypeDefinitions
  • TypeDefinitions.luau — All public types exported with documentation:
    • FastCastBehavior, FastCastEvents, FastCastEventsConfig, FastCastEventsModuleConfig
    • CastStateInfo, CastTrajectory, CastRayInfo, BlockCastRayInfo, SphereCastRayInfo
    • ActiveCastData, ActiveBlockcastData, ActiveSpherecastData
    • BaseCastData, CasterSerial, CasterParallel
    • All callback function types with named parameters
    • VisualizeCastSettings with all debug visualization properties

6. Benchmarks & Testing

Benchmark scripts

  • Benchmarks/benchSerial.client.luau — Serial mode benchmark (press E to fire 500 casts)
    • FPS sampling with delta/average/max/min tracking
    • Active cast counter display
    • Configurable fire amount, bench time, and cast speed
    • Gravity acceleration for realistic trajectories
  • Benchmarks/benchParallel.client.luau — Parallel mode benchmark (press P to fire 500 casts)
    • Same features as serial + worker count configuration
    • Can fire up to 1000+ casts with minimal frame drop

Test infrastructure

  • Test.project.json — Rojo test project configuration
  • pipelineTest.client.luau — CI pipeline test (raycast, blockcast, spherecast, CanPierce, max distance, acceleration)
  • TEST_LOGS.md — Detailed test logs and results
  • TEST.md — Test documentation

Pipeline / CI

  • .github/workflows/roblox.yml — GitHub Actions CI (later removed)
  • rokit.toml — Toolchain configuration
  • .luaurc — Luau language server configuration

7. Chores & Infrastructure

Project setup

  • default.project.json, sourcemap.json — Rojo build configuration
  • wally.toml — Wally package manager config (version bumped)
  • .gitignore — Added src/*.legacy.luau, opencode.json, .aider, .luaurc, sourcemap.json
  • .luau-lsp.json — Luau LSP integration

Code cleanup

  • Removed _mini variant directory
  • Updated _debug variant to match main
  • Removed SafeCall, material, dead code
  • Removed unused variables and type warnings
  • Cleaned up comments, spacing, boilerplate
  • table.clear()queuedEvents[castID] = nil
  • Signal → function where appropriate
  • DOT → COLON for method calls (TerminateCast, etc.)
  • Fixed AI-introduced nonsense edits
  • Removed AutomaticPerformance flag
  • Removed mcp.json, roblox.yml, TEST_LOGS

Source files moved

  • src/src/FastCast2/ (restructured for Rojo compatibility)

Version variants

  • Main FastCast2 directory
  • FastCast2_debug variant (with debug logging enabled)
  • FastCast2_mini variant (later removed)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

FastCast2 is refactored into distinct Serial and Parallel casters with SoA-driven SerialSimulation and ParallelSimulation. Trajectory storage consolidates to a single Trajectory. Motor6D pooling and ActiveCastSerial are added. Public API adds FastCast.new() (serial) and FastCast.newParallel() (parallel). Docs, README, benchmarks, and changelog updated.

Changes

Serial and Parallel Casting Architecture Refactor with SoA Simulation

Layer / File(s) Summary
Type Contracts & Trajectory Consolidation
src/FastCast2/TypeDefinitions.luau, src/FastCast2/DefaultConfigs.luau, src/FastCast2/Configs.luau, src/FastCast2/FastCastEnums.luau
CastStateInfo.TrajectoriesCastStateInfo.Trajectory; add default MovementMethod = "BulkMoveTo" and minor header/comment edits.
Main API & Constructors
src/FastCast2/init.luau
Split into FastCastSerial and FastCastParallel metatables; add FastCast.new() (serial) and FastCast.newParallel() (parallel); split behavior factories and per-mode method sets and lifecycles.
ActiveCast Core
src/FastCast2/ActiveCast.luau
ActiveCast now uses singular Trajectory; createCastData initializes StateInfo.Trajectory, sets RayInfo.FastCastEventsModule and RayInfo.MovementMethod, returns cast; fixes to DebrisAdd and high-fidelity substep timing.
ActiveCastSerial
src/FastCast2/ActiveCastSerial.luau
New serial cast object with kinematics helpers, RaycastParams cloning, debug visualization, and cast handlers.
BaseCastSerial Entrypoints
src/FastCast2/BaseCastSerial.luau
New serial base implementing Init, Raycast/Blockcast/Spherecast, builds castData, constructs ActiveCastSerial, registers with SerialSimulation, fires CastFire, and implements Bind/Terminate/Destroy and ObjectCache stub.
BaseCast Parallel Integration
src/FastCast2/BaseCast.luau
Parallel path updated to register created casts with ParallelSimulation.Register(cast), store actives by cast.ID, and fire CastFire using the local cast object.
ParallelSimulation (SoA)
src/FastCast2/ParallelSimulation.luau
New SoA-based ParallelSimulation: Register/Unregister, swap-compaction, per-step UpdateCasts deriving step ray directions, handling queries, updating cosmetics/Motor6D, queuing events, and Start/Stop wiring to RunService.
SerialSimulation (SoA)
src/FastCast2/SerialSimulation.luau
New SoA-driven SerialSimulation on Heartbeat: Register/Unregister, per-frame UpdateCasts advancing time, performing queries, updating cosmetic bullets (Motor6D supported), queuing events, and Start/Stop lifecycle.
Motor6D Pooling
src/FastCast2/Motor6DPool.luau
New Motor6D pooling module exposing Initialize, Get, Return, Connect, Disconnect and managing a single anchored pool for Transform movement.
Configs & Docs
src/FastCast2/Configs.luau, docs/*, README.md, TODO.md, skills/*
Add DebugLogging.AutomaticPerformance flag; add API reference, changelog 0.1.0, README Rojo/install and simplified examples, TODO updates, architecture/comparison/edge-case docs, and benchmark clients.
Benchmarks (Serial & Parallel)
Benchmarks/benchSerial.client.luau, Benchmarks/benchParallel.client.luau
Add benchmark clients sampling FPS and firing large numbers of casts (E for serial, P for parallel), tracking active casts and printing delta/average/max/min FPS summaries.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Bench as Benchmark Script
  participant Caster as FastCast Caster
  participant Sim as Serial/Parallel Simulation
  User->>Bench: press E or P
  Bench->>Caster: Fire AMOUNT casts
  Caster->>Sim: Register casts
  Sim->>Sim: process casts per frame
  Bench->>Caster: Terminate casts after BENCH_TIME
  Bench->>User: print FPS stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Last major update plans #38 — Implements the refactor items described (Trajectories → Trajectory, serial/parallel casters, Motor6D pooling, HighFidelity fix, and API/docs changes).

Poem

🐰
I hopped the repo, snipped and sewn,
Split casters, made trajectories one,
Motor6Ds queued in tidy store,
Serial hums while Parallel soars,
FastCast leaps — I chew some more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: introducing dual caster modes (serial and parallel) with a structure-of-arrays simulation architecture, which aligns with the extensive refactoring across multiple core modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch major

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

Casts registered with ParallelSimulation but not stored in Actives table breaks cleanup and sync systems.

The cast created at lines 175-191 is registered with ParallelSimulation.Register(cast) but never added to the Actives table. However, Actives is still expected by:

  • HandleBulkMoveTo (line 56): Iterates Actives to batch-update cosmetic bullet positions
  • ActiveCastCleaner.Event (line 127): Expects Actives[activeCastID] to remove cleaned casts
  • SyncChanges.Event (line 139): Expects Actives[ID] to sync cast state
  • Destroy (line 368): Iterates Actives to terminate remaining casts

Additionally, ParallelSimulation.Terminate() does not fire the ActiveCastCleaner event 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) and Spherecast (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 win

Finish the Trajectory rename inside SimulateCast().

These lines switch to local trajectory = cast.StateInfo.Trajectory, but the function still reads latestTrajectory later on. That variable is undefined here, so the first simulation step will nil-index on StartTime / 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()
 	end

Also 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 win

Use Hit, not RayHit, in the events module example.

The runtime lookup uses FastCastEvents.Hit; module.RayHit will 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 lift

Unify the StateInfo.Trajectory shape before using it here.

This file currently treats Trajectory as both an array (trajectories[index], table.insert(...)) and a single segment (trajectory.StartTime). It also writes through lastTrajectory, 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 win

Return after warning when the parallel caster is not initialized.

This guard only logs, then immediately calls self.Dispatcher:DispatchAll(...) anyway. If self.Dispatcher is 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 value

Consider simplifying Get() to avoid recursion.

The recursive call after GrowPool works 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 value

Consider adding ProjectileContainer to filter exclusions.

The FilterDescendantsInstances is empty, but castBehavior.AutoIgnoreContainer = true should 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

📥 Commits

Reviewing files that changed from the base of the PR and between b6ae671 and 82519af.

📒 Files selected for processing (18)
  • Benchmarks/benchParallel.client.luau
  • Benchmarks/benchSerial.client.luau
  • README.md
  • TODO.md
  • docs/api-reference.md
  • docs/changelog.md
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
  • src/FastCast2/BaseCast.luau
  • src/FastCast2/BaseCastSerial.luau
  • src/FastCast2/Configs.luau
  • src/FastCast2/DefaultConfigs.luau
  • src/FastCast2/FastCastEnums.luau
  • src/FastCast2/Motor6DPool.luau
  • src/FastCast2/ParallelSimulation.luau
  • src/FastCast2/SerialSimulation.luau
  • src/FastCast2/TypeDefinitions.luau
  • src/FastCast2/init.luau

Comment thread Benchmarks/benchSerial.client.luau Outdated
Comment thread README.md
Comment thread src/ActiveCast.luau Outdated
Comment thread src/ActiveCastSerial.luau Outdated
Comment thread src/FastCast2/BaseCastSerial.luau Outdated
Comment thread src/FastCast2/ParallelSimulation.luau Outdated
Comment thread src/ParallelSimulation.luau
Comment thread src/SerialSimulation.luau Outdated
Comment thread src/SerialSimulation.luau Outdated
Comment thread src/FastCast2/SerialSimulation.luau Outdated
@weenachuangkud weenachuangkud self-assigned this May 8, 2026
@weenachuangkud weenachuangkud added documentation Improvements or additions to documentation enhancement New feature or request labels May 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
README.md (1)

117-121: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Define FastCastEnums before using HighFidelityBehavior.

FastCastEnums is 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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 24f3ffa6-adb6-4982-9067-55b293c3e80b

📥 Commits

Reviewing files that changed from the base of the PR and between 82519af and 0e2fa47.

📒 Files selected for processing (1)
  • README.md

Comment thread README.md
Comment thread README.md Outdated
weenachuangkud added a commit that referenced this pull request May 8, 2026
- 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
@weenachuangkud weenachuangkud changed the title Final major updates Introduce serial and parallel caster modes with SoA simulation architecture May 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

DebrisAdd schedules destruction even after immediate destroy.

Same issue as in ActiveCastSerial.luau: when Lifetime <= 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 win

Undefined variable latestTrajectory will cause runtime error.

Line 380 references latestTrajectory which is never defined in SimulateCast. The function defines trajectory at 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 win

Use the same speed variable name as defined in the example.

Line 178 uses speed, but the example defines SPEED (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 win

Fix the .git removal path in Rojo setup commands.

After cd FastCast2, the command targets FastCast2/.git which is incorrect. The .git folder is in the current directory after the cd.

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 win

Define FastCastEnums before using it in the example.

Line 121 references FastCastEnums.HighFidelityBehavior.Default, but the example never requires FastCastEnums. 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 win

Pass raw shape argument to block/sphere handlers instead of variant table.

castHandlers[Blockcast] expects size: Vector3 and castHandlers[Spherecast] expects radius: number, but line 274 passes the whole variant table. 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 lift

Swap-delete cleanup still has multiple bugs.

The Unregister function has several issues:

  1. 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.

  2. casts array not updated during swap: The individual SoA arrays are copied, but casts[id] is never reassigned to casts[lastId], leaving the old reference.

  3. Line 212 clears wrong index: castCFrame[id] = nil should be castCFrame[lastId] = nil.

  4. Line 224 clears wrong index: casts[id] = nil should be casts[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

📥 Commits

Reviewing files that changed from the base of the PR and between 0e2fa47 and 5b2dad1.

📒 Files selected for processing (9)
  • Benchmarks/benchSerial.client.luau
  • README.md
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
  • src/FastCast2/BaseCast.luau
  • src/FastCast2/Motor6DPool.luau
  • src/FastCast2/ParallelSimulation.luau
  • src/FastCast2/SerialSimulation.luau
  • src/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

Comment thread src/ActiveCastSerial.luau Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️ Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/FastCast2/ActiveCast.luau (1)

379-382: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Replace stale latestTrajectory reference in SimulateCast.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b2dad1 and 51225b1.

📒 Files selected for processing (3)
  • README.md
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
✅ Files skipped from review due to trivial changes (1)
  • README.md

Comment thread src/ActiveCast.luau Outdated
Comment thread src/ActiveCastSerial.luau Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Critical bug: latestTrajectory is undefined.

Line 381 references latestTrajectory.StartTime, but latestTrajectory is never declared in SimulateCast. This will cause a runtime error. It should reference the local trajectory variable 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 value

Redundant double-clone of RaycastParams.

cast.RayInfo.Parameters is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 51225b1 and 5fb543f.

📒 Files selected for processing (2)
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
✅ Files skipped from review due to trivial changes (1)
  • src/FastCast2/ActiveCastSerial.luau

@weenachuangkud
Copy link
Copy Markdown
Owner Author

@coderabbitai

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

@weenachuangkud, it looks like your message only contains a mention without any specific request. How can I help you? Here are some things I can assist with:

  • 🔍 Review the PR – Trigger a full or focused review of the changes
  • 🐛 Investigate an issue – Dig into specific code, logic, or potential bugs
  • 📝 Generate a summary – Summarize specific files or the overall PR changes
  • 🧪 Generate tests – Create unit or integration tests for new code
  • 🛠️ Plan code changes – Help design or implement modifications
  • 📋 Create a GitHub issue – Open a follow-up issue for something found in this PR

Just let me know what you'd like! 😊

@weenachuangkud
Copy link
Copy Markdown
Owner Author

@coderabbitai Could you answers all my replies?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me check the full PR for any other comments or replies from you that need responses.


🏁 Script executed:

#!/bin/bash
# Fetch all issue comments on the PR
echo "=== Issue Comments ==="
gh api repos/weenachuangkud/FastCast2/issues/39/comments --jq '.[] | {user: .user.login, body: .body, created_at: .created_at}'

echo ""
echo "=== Review Comments ==="
gh api repos/weenachuangkud/FastCast2/pulls/39/comments --jq '.[] | {user: .user.login, body: .body, path: .path, line: .line, created_at: .created_at}'

echo ""
echo "=== Reviews ==="
gh api repos/weenachuangkud/FastCast2/pulls/39/reviews --jq '.[] | {user: .user.login, body: .body, state: .state, submitted_at: .submitted_at}'

Length of output: 183542


@weenachuangkud, all of your previous replies have already been answered in the comment threads! Here's a quick recap of what was covered:

  1. BaseCastSerial.luau shared-state bug – I provided a full diff fix moving Output, ParentCaster, ObjectCache, BulkMoveToConnection, and NextProjectileID from module-level to instance-level (self.*).
  2. AI agent prompt for that fix – I provided a copy-paste-ready prompt detailing the exact changes for your AI agent.

For reference, here are the remaining open issues in this PR that still need attention (not yet marked ✅):

File Issue
README.md FastCastEnums never required before use in code example
README.md module.RayHit should be module.Hit in the events module example
src/FastCast2/ActiveCast.luau Undefined latestTrajectory in SimulateCast() at line 381 — should be trajectory.StartTime
src/FastCast2/BaseCastSerial.luau Module-level shared state bug (fix provided above)
src/FastCast2/BaseCast.luau Casts registered with ParallelSimulation but never added to Actives table
src/FastCast2/init.luau Serial BindableOutput not forwarded to public signals (CastFire, CastTerminating)
src/FastCast2/init.luau GetVelocityCast() returns InitialVelocity instead of time-integrated velocity
src/FastCast2/init.luau Trajectory setters/adders don't rebase StartTime, causing retroactive path changes
src/FastCast2/init.luau StateInfo.Trajectory treated as both array and single segment inconsistently
src/FastCast2/init.luau SetBulkMoveEnabled() doesn't return after warning, crashes if Dispatcher is nil
src/FastCast2/ParallelSimulation.luau CastHandler passed a table instead of raw Vector3/number for block/sphere casts
src/FastCast2/ParallelSimulation.luau PreRender is client-only; needs guard for server-side usage
src/FastCast2/SerialSimulation.luau Same block/sphere CastHandler argument bug as parallel

Would you like me to generate a combined AI agent prompt for all of these remaining issues at once?

@weenachuangkud
Copy link
Copy Markdown
Owner Author

weenachuangkud commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain
@weenachuangkud, all of your previous replies have already been answered in the comment threads! Here's a quick recap of what was covered:

  1. BaseCastSerial.luau shared-state bug – I provided a full diff fix moving Output, ParentCaster, ObjectCache, BulkMoveToConnection, and NextProjectileID from module-level to instance-level (self.*).
  2. AI agent prompt for that fix – I provided a copy-paste-ready prompt detailing the exact changes for your AI agent.

For reference, here are the remaining open issues in this PR that still need attention (not yet marked ✅):

File Issue
README.md FastCastEnums never required before use in code example
README.md module.RayHit should be module.Hit in the events module example
src/FastCast2/ActiveCast.luau Undefined latestTrajectory in SimulateCast() at line 381 — should be trajectory.StartTime
src/FastCast2/BaseCastSerial.luau Module-level shared state bug (fix provided above)
src/FastCast2/BaseCast.luau Casts registered with ParallelSimulation but never added to Actives table
src/FastCast2/init.luau Serial BindableOutput not forwarded to public signals (CastFire, CastTerminating)
src/FastCast2/init.luau GetVelocityCast() returns InitialVelocity instead of time-integrated velocity
src/FastCast2/init.luau Trajectory setters/adders don't rebase StartTime, causing retroactive path changes
src/FastCast2/init.luau StateInfo.Trajectory treated as both array and single segment inconsistently
src/FastCast2/init.luau SetBulkMoveEnabled() doesn't return after warning, crashes if Dispatcher is nil
src/FastCast2/ParallelSimulation.luau CastHandler passed a table instead of raw Vector3/number for block/sphere casts
src/FastCast2/ParallelSimulation.luau PreRender is client-only; needs guard for server-side usage
src/FastCast2/SerialSimulation.luau Same block/sphere CastHandler argument bug as parallel
Would you like me to generate a combined AI agent prompt for all of these remaining issues at once?

Sure @coderabbitai

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me read the current state of all affected files before generating the combined prompt.


🏁 Script executed:

#!/bin/bash
echo "=== README.md ==="
cat README.md

echo ""
echo "=== src/FastCast2/ActiveCast.luau ==="
cat src/FastCast2/ActiveCast.luau

echo ""
echo "=== src/FastCast2/BaseCastSerial.luau ==="
cat src/FastCast2/BaseCastSerial.luau

echo ""
echo "=== src/FastCast2/BaseCast.luau ==="
cat src/FastCast2/BaseCast.luau

echo ""
echo "=== src/FastCast2/init.luau ==="
cat src/FastCast2/init.luau

echo ""
echo "=== src/FastCast2/ParallelSimulation.luau ==="
cat src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== src/FastCast2/SerialSimulation.luau ==="
cat src/FastCast2/SerialSimulation.luau

Length of output: 109272


@weenachuangkud, here is the combined AI agent prompt covering all 10 confirmed remaining bugs. You can copy-paste this directly into your AI agent:


Combined AI Agent Prompt — FastCast2 Bug Fixes

Fix the following 10 bugs across 5 files in the FastCast2 Roblox Luau library. Apply every change exactly as shown.


Fix 1 — README.md: Add missing FastCastEnums require

In the serial code example, FastCastEnums is used but never required. Add the require after the FastCast2 require:

 -- Requires
 local FastCast2 = require(Rep:WaitForChild("FastCast2"))
+local FastCastEnums = require(Rep:WaitForChild("FastCast2"):WaitForChild("FastCastEnums"))

Fix 2 — src/FastCast2/ActiveCast.luau: Undefined latestTrajectory in SimulateCast

In SimulateCast, after cast.StateInfo.TotalRuntime += delta, the variable latestTrajectory is used but was never declared. Replace with trajectory (which is already declared earlier in the same function):

 	cast.StateInfo.TotalRuntime += delta

-	totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
+	totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime

Fix 3 — src/FastCast2/BaseCastSerial.luau: Broken Init, self.self.* patterns, wrong method name

The Init function has broken parameter name (Bindableself.Output), uses self.self.* for all fields, and there is a method named Bindself.ObjectCache. Blockcast and Spherecast use bare self.* correctly already.

Replace the entire Init function and rename the Bindself.ObjectCache method:

-function BaseCastSerial.Init(Bindableself.Output: BindableEvent, Data: any, parentCaster: any)
+function BaseCastSerial.Init(BindableOutput: BindableEvent, Data: any, parentCaster: any)
 	local self = setmetatable({}, BaseCastSerial)
-	self.self.Output = Bindableself.Output
-	self.self.ParentCaster = parentCaster
-	self.self.ObjectCache = nil
-	self.self.BulkMoveToConnection = nil
-	self.self.NextProjectileID = 0
+	self.Output = BindableOutput
+	self.ParentCaster = parentCaster
+	self.ObjectCache = nil
+	self.BulkMoveToConnection = nil
+	self.NextProjectileID = 0

 	if Data.useBulkMoveTo then
 		-- BulkMoveTo is handled by SerialSimulation
 	end

 	return self
 end

Fix the Raycast method which still uses self.self.*:

 function BaseCastSerial:Raycast(...)
-	self.self.NextProjectileID += 1
+	self.NextProjectileID += 1
 	...
-	local castData = {
-		ID = self.self.NextProjectileID,
+	local castData = {
+		ID = self.NextProjectileID,
 		...
 	}

-	local cast = ActiveCastSerial.new(self.self.ParentCaster, castData)
+	local cast = ActiveCastSerial.new(self.ParentCaster, castData)
 	SerialSimulation.Register(cast)

-	if self.self.Output then
-		self.self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
+	if self.Output then
+		self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
 	end
 end

Rename the broken method and fix its body:

---[=[
-	`@method` Bindself.ObjectCache
-	`@within` BaseCastSerial
-]=]
-function BaseCastSerial:Bindself.ObjectCache(bool: boolean)
+--[=[
+	`@method` BindObjectCache
+	`@within` BaseCastSerial
+]=]
+function BaseCastSerial:BindObjectCache(bool: boolean)
 	if bool then
 		if self.ObjectCache then return end
 		self.ObjectCache = Instance.new("BindableFunction")
-		self.ObjectCache.Name = "self.ObjectCache"
+		self.ObjectCache.Name = "ObjectCache"
 	else
 		if self.ObjectCache then
 			self.ObjectCache:Destroy()
 			self.ObjectCache = nil
 		end
 	end
 end

Fix 4 — src/FastCast2/init.luau: Forward serial Output BindableEvent to public Signal objects

In FastCastSerial:Init, after self.Output = BindableOutput, add a connection that routes all fired events to the corresponding public Signal:

 	self.BaseCast = BaseCastSerial.Init(BindableOutput, data, self)
 	self.Output = BindableOutput

+	-- Forward BindableEvent output to public-facing Signal objects
+	BindableOutput.Event:Connect(function(eventName: string, ...)
+		local signal = self[eventName]
+		if signal and type(signal) == "table" and type(signal.Fire) == "function" then
+			signal:Fire(...)
+		end
+	end)
+
 	if useObjectCache then

Fix 5 — src/FastCast2/init.luau: GetVelocityCast returns InitialVelocity instead of time-integrated velocity

 function FastCastSerial:GetVelocityCast(cast: vaildcast): Vector3
 	local latestTrajectory = cast.StateInfo.Trajectory
-	return latestTrajectory.InitialVelocity
+	return GetVelocityAtTime(
+		cast.StateInfo.TotalRuntime - latestTrajectory.StartTime,
+		latestTrajectory.InitialVelocity,
+		latestTrajectory.Acceleration
+	)
 end

Fix 6 — src/FastCast2/init.luau: Serial trajectory setters must rebase StartTime before mutating

Direct mutation of Origin/InitialVelocity/Acceleration without rebasing retroactively changes the entire past trajectory. Replace all serial setter/adder methods:

 function FastCastSerial:SetVelocityCast(cast: vaildcast, velocity: Vector3)
 	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.InitialVelocity = velocity
+	local t = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
+	latestTrajectory.Origin = GetPositionAtTime(t, latestTrajectory.Origin, latestTrajectory.InitialVelocity, latestTrajectory.Acceleration)
+	latestTrajectory.StartTime = cast.StateInfo.TotalRuntime
+	latestTrajectory.InitialVelocity = velocity
 end

 function FastCastSerial:SetAccelerationCast(cast: vaildcast, acceleration: Vector3)
 	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.Acceleration = acceleration
+	local t = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
+	latestTrajectory.Origin = GetPositionAtTime(t, latestTrajectory.Origin, latestTrajectory.InitialVelocity, latestTrajectory.Acceleration)
+	latestTrajectory.InitialVelocity = GetVelocityAtTime(t, latestTrajectory.InitialVelocity, latestTrajectory.Acceleration)
+	latestTrajectory.StartTime = cast.StateInfo.TotalRuntime
+	latestTrajectory.Acceleration = acceleration
 end

 function FastCastSerial:SetPositionCast(cast: vaildcast, position: Vector3)
 	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.Origin = position
+	local t = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
+	latestTrajectory.InitialVelocity = GetVelocityAtTime(t, latestTrajectory.InitialVelocity, latestTrajectory.Acceleration)
+	latestTrajectory.StartTime = cast.StateInfo.TotalRuntime
+	latestTrajectory.Origin = position
 end

 function FastCastSerial:AddPositionCast(cast: vaildcast, position: Vector3)
-	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.Origin += position
+	self:SetPositionCast(cast, self:GetPositionCast(cast) + position)
 end

 function FastCastSerial:AddVelocityCast(cast: vaildcast, velocity: Vector3)
-	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.InitialVelocity += velocity
+	self:SetVelocityCast(cast, self:GetVelocityCast(cast) + velocity)
 end

 function FastCastSerial:AddAccelerationCast(cast: vaildcast, acceleration: Vector3)
-	local latestTrajectory = cast.StateInfo.Trajectory
-	latestTrajectory.Acceleration += acceleration
+	self:SetAccelerationCast(cast, self:GetAccelerationCast(cast) + acceleration)
 end

Fix 7 — src/FastCast2/init.luau: GetTrajectoryInfo and ModifyTransformation treat Trajectory as array

cast.StateInfo.Trajectory is now a single object, not an array. These functions index it with [index] and call table.insert on it, which is wrong.

Replace GetTrajectoryInfo and GetLatestTrajectoryEndInfo:

 local function GetTrajectoryInfo(
 	cast: vaildcast,
 	index: number
 ): { [number]: Vector3 }
-	assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED")
-	local trajectories = cast.StateInfo.Trajectory
-	local trajectory = trajectories[index]
-	local duration = trajectory.EndTime - trajectory.StartTime
+	local trajectory = cast.StateInfo.Trajectory
+	local duration = trajectory.EndTime ~= -1
+		and (trajectory.EndTime - trajectory.StartTime)
+		or (cast.StateInfo.TotalRuntime - trajectory.StartTime)

 	local origin = trajectory.Origin
 	local vel = trajectory.InitialVelocity
 	local accel = trajectory.Acceleration

 	return { GetPositionAtTime(duration, origin, vel, accel), GetVelocityAtTime(duration, vel, accel) }
 end

 local function GetLatestTrajectoryEndInfo(cast: vaildcast): { [number]: Vector3 }
-	return GetTrajectoryInfo(cast, 1)
+	return GetTrajectoryInfo(cast, 1) -- index unused now; Trajectory is a single segment
 end

Replace ModifyTransformation to stop using table.insert and treat Trajectory as a single mutable segment:

 local function ModifyTransformation(
 	cast: vaildcast,
 	velocity: Vector3?,
 	acceleration: Vector3?,
 	position: Vector3?
 )
 	local trajectory = cast.StateInfo.Trajectory

-	if trajectory.StartTime == cast.StateInfo.TotalRuntime then
-		if velocity == nil then
-			velocity = trajectory.InitialVelocity
-		end
-		if acceleration == nil then
-			acceleration = trajectory.Acceleration
-		end
-		if position == nil then
-			position = lastTrajectory.Origin
-		end
-
-		lastTrajectory.Origin = position
-		lastTrajectory.InitialVelocity = velocity
-		lastTrajectory.Acceleration = acceleration
-	else
-		lastTrajectory.EndTime = cast.StateInfo.TotalRuntime
-
-		local point, velAtPoint = unpack(GetLatestTrajectoryEndInfo(cast))
-
-		if velocity == nil then
-			velocity = velAtPoint
-		end
-		if acceleration == nil then
-			acceleration = lastTrajectory.Acceleration
-		end
-		if position == nil then
-			position = point
-		end
-		table.insert(cast.StateInfo.Trajectory, {
-			StartTime = cast.StateInfo.TotalRuntime,
-			EndTime = -1,
-			Origin = position,
-			InitialVelocity = velocity,
-			Acceleration = acceleration,
-		})
-		cast.StateInfo.CancelHighResCast = true
-	end
+	local t = cast.StateInfo.TotalRuntime - trajectory.StartTime
+	local currentPosition = GetPositionAtTime(t, trajectory.Origin, trajectory.InitialVelocity, trajectory.Acceleration)
+	local currentVelocity = GetVelocityAtTime(t, trajectory.InitialVelocity, trajectory.Acceleration)
+
+	-- Rebase the single trajectory segment to current time
+	trajectory.Origin = position or currentPosition
+	trajectory.InitialVelocity = velocity or currentVelocity
+	trajectory.Acceleration = acceleration or trajectory.Acceleration
+	trajectory.StartTime = cast.StateInfo.TotalRuntime
+	cast.StateInfo.CancelHighResCast = true
 end

Fix 8 — src/FastCast2/ParallelSimulation.luau: Start() checks wrong variable

In ParallelSimulation.Start(), the early-return guard checks SerialSimulation.StepConnection (which doesn't exist in scope) instead of ParallelSimulation.StepConnection:

 function ParallelSimulation.Start()
-	if SerialSimulation.StepConnection then return end
+	if ParallelSimulation.StepConnection then return end
 	if RS:IsClient() then

Fix 9 — src/FastCast2/ParallelSimulation.luau: CastHandler receives a table instead of raw Vector3/number

In UpdateCasts, variant is built as {Size = ...} or {Radius = ...} and passed as the 5th argument, but castHandlers[Blockcast] expects a raw Vector3 and castHandlers[Spherecast] expects a raw number. Pass the values directly:

-		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 sizeOrRadius: (Vector3 | number)?
+		if castType == EnumCastTypes.Blockcast then
+			sizeOrRadius = castSize[i]
+		elseif castType == EnumCastTypes.Spherecast then
+			sizeOrRadius = castRadius[i]
+		end
+
+		local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

Fix 10 — src/FastCast2/SerialSimulation.luau: Same CastHandler table argument bug

Apply the identical fix as Fix 9 to SerialSimulation.luau's UpdateCasts function:

-		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 sizeOrRadius: (Vector3 | number)?
+		if castType == EnumCastTypes.Blockcast then
+			sizeOrRadius = castSize[i]
+		elseif castType == EnumCastTypes.Spherecast then
+			sizeOrRadius = castRadius[i]
+		end
+
+		local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

That covers all 10 remaining bugs. Three issues from the original list (README.md module.RayHit, BaseCast.luau Actives table, init.luau SetBulkMoveEnabled return) were already correctly addressed in the current code.

@weenachuangkud
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Guard FastCastParallel:Destroy() when Init() never completed.

Line 1010 assumes self.Dispatcher exists, but new parallel casters start with Dispatcher = nil. Calling Destroy() before a successful Init() 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 win

Pass raw shape values to the block/sphere cast handlers.

Lines 264-271 always pass a table as the fifth argument, but Blockcast expects a Vector3 and Spherecast expects a number. 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 win

Preserve the swapped-in cast during Unregister().

This swap-delete path still tears down the destination slot after moving lastId into id: the moved cast loses its CFrame, casts[id] is nilled, and its Motor6D is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 51225b1 and 58c9792.

📒 Files selected for processing (6)
  • README.md
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
  • src/FastCast2/BaseCastSerial.luau
  • src/FastCast2/ParallelSimulation.luau
  • src/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

Comment thread src/FastCast2/BaseCastSerial.luau Outdated
Comment thread src/init.luau Outdated
@weenachuangkud
Copy link
Copy Markdown
Owner Author

@coderabbitai Write me a single prompt for ai agent

  • FastCastSerial having feature conflict with FastCastParallel
  • ActiveCastSerial: 123, why castData? No explicit parameters like in ActiveCast.luau .createCastData(...)
  • BaseCastSerial: 36 -> 46, unused parameters: Data
  • SerialSimulation.luau: 21 -> 32, If you do not use these constants, consider removing it.
  • ParallelSimulation: 19 -> 30, Are these unused constants? If so, remove it
  • Lints more if there any issues

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me read the relevant files first before generating the prompt.


🏁 Script executed:

#!/bin/bash
echo "=== src/FastCast2/ActiveCastSerial.luau (around line 123) ==="
sed -n '110,140p' src/FastCast2/ActiveCastSerial.luau

echo ""
echo "=== src/FastCast2/BaseCastSerial.luau (lines 30-60) ==="
sed -n '30,60p' src/FastCast2/BaseCastSerial.luau

echo ""
echo "=== src/FastCast2/SerialSimulation.luau (lines 15-45) ==="
sed -n '15,45p' src/FastCast2/SerialSimulation.luau

echo ""
echo "=== src/FastCast2/ParallelSimulation.luau (lines 15-40) ==="
sed -n '15,40p' src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== src/FastCast2/init.luau (FastCastSerial vs FastCastParallel method tables) ==="
grep -n "FastCastSerial\.\|FastCastParallel\." src/FastCast2/init.luau | head -80

echo ""
echo "=== src/FastCast2/ActiveCast.luau createCastData ==="
grep -n "createCastData\|function.*castData" src/FastCast2/ActiveCast.luau | head -20

echo ""
echo "=== Full ActiveCastSerial.luau ==="
cat src/FastCast2/ActiveCastSerial.luau

echo ""
echo "=== Full SerialSimulation.luau lines 1-50 ==="
sed -n '1,55p' src/FastCast2/SerialSimulation.luau

echo ""
echo "=== Full ParallelSimulation.luau lines 1-50 ==="
sed -n '1,50p' src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== Full BaseCastSerial.luau ==="
cat src/FastCast2/BaseCastSerial.luau

echo ""
echo "=== Full init.luau ==="
cat src/FastCast2/init.luau

Length of output: 54706


I need to check the full body of both simulation files to confirm which constants are actually used.


🏁 Script executed:

#!/bin/bash
echo "=== SerialSimulation.luau full ==="
cat src/FastCast2/SerialSimulation.luau

echo ""
echo "=== ParallelSimulation.luau full ==="
cat src/FastCast2/ParallelSimulation.luau

Length of output: 22229


@weenachuangkud Here is the comprehensive AI agent prompt:


FastCast2 AI Agent Prompt — Cleanup & Bug Fixes

Fix all of the following issues across src/FastCast2. Apply every change exactly as described.


Fix 1 — src/FastCast2/init.luau: FastCastSerial uses wrong newBehavior()

FastCastSerial:RaycastFire, BlockcastFire, and SpherecastFire all fall back to FastCastParallel.newBehavior() when BehaviorData is nil. Change all three to use FastCastSerial.newBehavior():

 function FastCastSerial:RaycastFire(...)
   if BehaviorData == nil then
-    BehaviorData = FastCastParallel.newBehavior()
+    BehaviorData = FastCastSerial.newBehavior()
   end
   ...
 end

 function FastCastSerial:BlockcastFire(...)
   if BehaviorData == nil then
-    BehaviorData = FastCastParallel.newBehavior()
+    BehaviorData = FastCastSerial.newBehavior()
   end
   ...
 end

 function FastCastSerial:SpherecastFire(...)
   if BehaviorData == nil then
-    BehaviorData = FastCastParallel.newBehavior()
+    BehaviorData = FastCastSerial.newBehavior()
   end
   ...
 end

Fix 2 — src/FastCast2/init.luau: Remove stale UpdateConnection guards from FastCastParallel methods

The SoA parallel simulation never sets UpdateConnection on any cast. Every assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") in FastCastParallel will always error. Remove that assert from every method that has it: GetVelocityCast, GetAccelerationCast, GetPositionCast, SetVelocityCast, SetAccelerationCast, SetPositionCast, PauseCast, AddPositionCast, AddVelocityCast, AddAccelerationCast, SyncChangesToCast:

 function FastCastParallel:GetVelocityCast(cast: vaildcast)
-  assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED")
   local currentTrajectory = cast.StateInfo.Trajectory
   ...
 end
 -- (repeat for every other method listed above)

Fix 3 — src/FastCast2/init.luau: FastCastSerial:TerminateCast double-fires and double-calls

FastCastSerial:TerminateCast calls self.BaseCast:TerminateCast(cast, castTerminatingFunction), which already fires Output:Fire("CastTerminating", cast) and calls castTerminatingFunction(cast). Then it calls both again directly, causing double emission and a double callback. Remove the redundant direct calls:

 function FastCastSerial:TerminateCast(cast: vaildcast, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?)
   if cast.StateInfo.UpdateConnection ~= nil then
     cast.StateInfo.UpdateConnection:Disconnect()
     cast.StateInfo.UpdateConnection = nil
   end

   if cast.RayInfo.CosmeticBulletObject then
     cast.RayInfo.CosmeticBulletObject:Destroy()
     cast.RayInfo.CosmeticBulletObject = nil
   end

   if self.BaseCast then
     self.BaseCast:TerminateCast(cast, castTerminatingFunction)
   end

-  if castTerminatingFunction then
-    castTerminatingFunction(cast)
-  end
-
-  self.Output:Fire("CastTerminating", cast)
 end

Fix 4 — src/FastCast2/init.luau: FastCastSerial:SyncChangesToCast references nonexistent field

BaseCastSerial has no SyncChange field. For serial mode, state mutations are immediate and no sync step is needed. Replace with a no-op:

 function FastCastSerial:SyncChangesToCast(cast: vaildcast)
-  if self.BaseCast.SyncChange then
-    self.BaseCast.SyncChange:Fire(cast)
-  end
+  -- Serial simulation applies mutations directly; no sync dispatch needed.
 end

Fix 5 — src/FastCast2/BaseCastSerial.luau: Remove unused Data parameter

Data is only read in a dead no-op block. Remove it and update the call site in init.luau.

In BaseCastSerial.luau:

-function BaseCastSerial.Init(BindableOutput: BindableEvent, Data: any, parentCaster: any)
+function BaseCastSerial.Init(BindableOutput: BindableEvent, parentCaster: any)
   local self = setmetatable({}, BaseCastSerial)
   self.Output = BindableOutput
   self.ParentCaster = parentCaster
   self.ObjectCache = nil
   self.BulkMoveToConnection = nil
   self.NextProjectileID = 0

-  if Data.useBulkMoveTo then
-    -- BulkMoveTo is handled by SerialSimulation
-  end

   return self
 end

In init.luau, update the Init call:

-self.BaseCast = BaseCastSerial.Init(BindableOutput, data, self)
+self.BaseCast = BaseCastSerial.Init(BindableOutput, self)

Fix 6 — src/FastCast2/BaseCastSerial.luau: Raycast method uses self.self.*

BaseCastSerial:Raycast still references self.self.ParentCaster and self.self.OutputBlockcast and Spherecast are already correct. Fix Raycast:

-  local cast = ActiveCastSerial.new(self.self.ParentCaster, castData)
+  local cast = ActiveCastSerial.new(self.ParentCaster, castData)
   SerialSimulation.Register(cast)

-  if self.self.Output then
-    self.self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
+  if self.Output then
+    self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
   end

Fix 7 — src/FastCast2/ActiveCastSerial.luau: Replace opaque castData table with explicit parameters

ActiveCastSerial.new(caster, castData) should use explicit named parameters like ActiveCast.createCastData does, not a raw untyped table. Also remove all the dead code defined in this file that belongs in SerialSimulation (the simulation functions, debug helpers, and constants are all unused here).

Replace the entire ActiveCastSerial.luau content with:

--[[
	- Author : Mawin CK
	- Date : 2025

	ActiveCastSerial - Constructs the cast data object for serial simulation.
]]

local FastCastModule = script.Parent
local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums"))

local CastVariantTypes = {
	[FastCastEnums.CastType.Raycast] = "Raycast",
	[FastCastEnums.CastType.Blockcast] = "Blockcast",
	[FastCastEnums.CastType.Spherecast] = "Spherecast",
}

local DEFAULT_MAX_DISTANCE = 1000

local function CloneCastParams(params: RaycastParams): RaycastParams
	local clone: RaycastParams = RaycastParams.new()
	clone.CollisionGroup = params.CollisionGroup
	clone.FilterType = params.FilterType
	clone.FilterDescendantsInstances = params.FilterDescendantsInstances
	clone.IgnoreWater = params.IgnoreWater
	return clone
end

local ActiveCastSerial = {}

--[=[
	`@function` new
	`@within` ActiveCastSerial

	`@param` caster any -- The parent Caster instance.
	`@param` id number -- The unique projectile ID.
	`@param` origin Vector3 -- Starting position.
	`@param` velocity Vector3 -- Initial velocity vector.
	`@param` acceleration Vector3 -- Acceleration vector.
	`@param` castType number -- FastCastEnums.CastType value.
	`@param` raycastParams RaycastParams? -- Optional raycast params (cloned if provided).
	`@param` maxDistance number? -- Max travel distance (defaults to 1000).
	`@param` cosmeticBullet Instance? -- Optional cosmetic bullet object.
	`@param` movementMethod string? -- "BulkMoveTo" or "Transform" (defaults to "BulkMoveTo").
	`@param` size Vector3? -- Block cast size (Blockcast only).
	`@param` radius number? -- Sphere cast radius (Spherecast only).
	`@param` highFidelitySegmentSize number? -- High-fidelity segment size.
	`@param` highFidelityBehavior number? -- High-fidelity behavior enum.
	`@param` visualizeCasts boolean? -- Whether to visualize casts.
	`@param` visualizeCastSettings any? -- Settings for cast visualization.
]=]
function ActiveCastSerial.new(
	caster: any,
	id: number,
	origin: Vector3,
	velocity: Vector3,
	acceleration: Vector3,
	castType: number,
	raycastParams: RaycastParams?,
	maxDistance: number?,
	cosmeticBullet: Instance?,
	movementMethod: string?,
	size: Vector3?,
	radius: number?,
	highFidelitySegmentSize: number?,
	highFidelityBehavior: number?,
	visualizeCasts: boolean?,
	visualizeCastSettings: any?
): any
	return {
		Caster = caster,
		StateInfo = {
			Paused = false,
			TotalRuntime = 0,
			DistanceCovered = 0,
			HighFidelitySegmentSize = highFidelitySegmentSize,
			HighFidelityBehavior = highFidelityBehavior,
			IsActivelyResimulating = false,
			CancelHighResCast = false,
			Trajectory = {
				StartTime = 0,
				EndTime = -1,
				Origin = origin,
				InitialVelocity = velocity,
				Acceleration = acceleration,
			},
			VisualizeCasts = visualizeCasts,
			VisualizeCastSettings = visualizeCastSettings,
		},
		RayInfo = {
			Parameters = raycastParams and CloneCastParams(raycastParams) or RaycastParams.new(),
			WorldRoot = workspace,
			MaxDistance = maxDistance or DEFAULT_MAX_DISTANCE,
			CosmeticBulletObject = cosmeticBullet,
			MovementMethod = movementMethod or "BulkMoveTo",
			Size = size,
			Radius = radius,
		},
		Type = CastVariantTypes[castType],
		CFrame = CFrame.new(origin),
		ID = id,
	}
end

return ActiveCastSerial

Fix 8 — src/FastCast2/BaseCastSerial.luau: Update all three cast methods to use explicit ActiveCastSerial.new parameters

After Fix 7, all three Raycast, Blockcast, and Spherecast methods must pass explicit arguments instead of a castData table. Replace the ActiveCastSerial.new(...) call in each:

In Raycast:

-  local castData = {
-    ID = self.NextProjectileID,
-    Origin = Origin,
-    Velocity = Velocity,
-    Acceleration = Behavior.Acceleration,
-    RaycastParams = raycastParams,
-    MaxDistance = Behavior.MaxDistance or 1000,
-    CosmeticBulletObject = cosmeticBullet,
-    CastType = EnumCastTypes.Raycast,
-    VisualizeCasts = Behavior.VisualizeCasts,
-    VisualizeCastSettings = Behavior.VisualizeCastSettings,
-    HighFidelitySegmentSize = Behavior.HighFidelitySegmentSize,
-    HighFidelityBehavior = Behavior.HighFidelityBehavior,
-    MovementMethod = Behavior.MovementMethod or "BulkMoveTo"
-  }
-  local cast = ActiveCastSerial.new(self.ParentCaster, castData)
+  local cast = ActiveCastSerial.new(
+    self.ParentCaster,
+    self.NextProjectileID,
+    Origin, Velocity,
+    Behavior.Acceleration or Vector3.zero,
+    EnumCastTypes.Raycast,
+    raycastParams,
+    Behavior.MaxDistance,
+    cosmeticBullet,
+    Behavior.MovementMethod,
+    nil, nil,
+    Behavior.HighFidelitySegmentSize,
+    Behavior.HighFidelityBehavior,
+    Behavior.VisualizeCasts,
+    Behavior.VisualizeCastSettings
+  )

In Blockcast:

-  local cast = ActiveCastSerial.new(self.ParentCaster, castData)
+  local cast = ActiveCastSerial.new(
+    self.ParentCaster,
+    self.NextProjectileID,
+    Origin, Velocity,
+    Behavior.Acceleration or Vector3.zero,
+    EnumCastTypes.Blockcast,
+    raycastParams,
+    Behavior.MaxDistance,
+    cosmeticBullet,
+    Behavior.MovementMethod,
+    Size, nil,
+    Behavior.HighFidelitySegmentSize,
+    Behavior.HighFidelityBehavior,
+    Behavior.VisualizeCasts,
+    Behavior.VisualizeCastSettings
+  )

In Spherecast:

-  local cast = ActiveCastSerial.new(self.ParentCaster, castData)
+  local cast = ActiveCastSerial.new(
+    self.ParentCaster,
+    self.NextProjectileID,
+    Origin, Velocity,
+    Behavior.Acceleration or Vector3.zero,
+    EnumCastTypes.Spherecast,
+    raycastParams,
+    Behavior.MaxDistance,
+    cosmeticBullet,
+    Behavior.MovementMethod,
+    nil, Radius,
+    Behavior.HighFidelitySegmentSize,
+    Behavior.HighFidelityBehavior,
+    Behavior.VisualizeCasts,
+    Behavior.VisualizeCastSettings
+  )

Fix 9 — src/FastCast2/SerialSimulation.luau: Remove all unused constants and dead code

Remove the following lines entirely (lines 21–32 and related unused requires):

-local TypeDef = require(FastCastModule:WaitForChild("TypeDefinitions"))
-local Configs = require(FastCastModule:WaitForChild("Configs"))
-local DebugLogging = Configs.DebugLogging
-local ActiveCastSerial = require(FastCastModule:WaitForChild("ActiveCastSerial"))

-local HIGH_FIDE_INCREASE_SIZE = 0.5
-local MAX_SEGMENT_CAL_TIME = 0.016 * 5
-local MAX_CASTING_TIME = 0.2
-local DEFAULT_MAX_DISTANCE = 1000
-local EPSILON = 1e-6
-local RAY_SEARCH_OFFSET = 0.001

-local DBG_SEGMENT_SUB_COLOR = Color3.new(0.286275, 0.329412, 0.247059)
-local DBG_SEGMENT_SUB_COLOR2 = Color3.new(0.454902, 0.933333, 0.011765)
-local DBG_HIT_SUB_COLOR = Color3.new(0.0588235, 0.87451, 1)
-local DBG_RAYPIERCE_SUB_COLOR = Color3.new(1, 0.113725, 0.588235)

Remove the unused QueueEvent function:

-local function QueueEvent(callback: any, ...)
-  if callback then
-    table.insert(QueuedEvents, { Callback = callback, Args = { ... } })
-  end
-end

Fix 10 — src/FastCast2/ParallelSimulation.luau: Same removals as Fix 9

Apply identical removals: TypeDef, Configs/DebugLogging, and all unused constants (lines 19–30), and the unused QueueEvent function.


Fix 11 — src/FastCast2/SerialSimulation.luau & ParallelSimulation.luau: Unregister clears wrong castCFrame slot

In both Unregister functions, after swapping slot idlastId, the cleanup incorrectly clears castCFrame[id] instead of castCFrame[lastId]:

-  castCFrame[id] = nil   -- wrong: id now holds the swapped-in cast
+  castCFrame[lastId] = nil

Fix 12 — src/FastCast2/SerialSimulation.luau: Motor6D disconnect happens after swap

After the swap-with-last in Unregister, castMotor6D[id] already holds the swapped-in (kept) motor6D, not the one belonging to the removed cast. Save it before swapping:

 function SerialSimulation.Unregister(id: number)
   if not casts[id] then return end
+  local motorToDisconnect = castMotor6D[id]
   local lastId = castCount

   if id ~= lastId then
     ...
     castMotor6D[id] = castMotor6D[lastId]
     ...
   end

-  if castMotor6D[id] then
-    Motor6DPool.Disconnect(castMotor6D[id])
-  end
+  if motorToDisconnect then
+    Motor6DPool.Disconnect(motorToDisconnect)
+  end
   ...
 end

Apply the same pattern to ParallelSimulation.Unregister, replacing its current incorrect guard:

-  if id ~= lastId and castMotor6D[lastId] then
-    Motor6DPool.Disconnect(castMotor6D[lastId])
-  end

with the same save-before-swap approach.


Fix 13 — Both simulation files: CastHandler receives a table instead of raw value

In UpdateCasts in both SerialSimulation.luau and ParallelSimulation.luau, variant is a table { Size = ... } or { Radius = ... } but the castHandlers expect a raw Vector3 or number. Pass the value directly:

-  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 sizeOrRadius: (Vector3 | number)?
+  if castType == EnumCastTypes.Blockcast then
+    sizeOrRadius = castSize[i]
+  elseif castType == EnumCastTypes.Spherecast then
+    sizeOrRadius = castRadius[i]
+  end
+  local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

@weenachuangkud
Copy link
Copy Markdown
Owner Author

FastCast2 AI Agent Prompt — Cleanup & Bug Fixes

Fix all of the following issues across src/FastCast2. Apply every change exactly as described.

Fix 1 — src/FastCast2/init.luau: FastCastSerial.Behavior and FastCastParallel.newBehavior should simpify to FastCast.newBehavior

- function FastCastSerial.newBehavior() ... end
- function FastCastParallel.newBehavior ... end
+ function FastCast.newBehavior() ... end

Fix 2 — src/FastCast2/init.luau: Remove stale UpdateConnection guards from FastCastParallel methods

The SoA parallel simulation never sets UpdateConnection on any cast. Every assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") in FastCastParallel will always error. Remove that assert from every method that has it: GetVelocityCast, GetAccelerationCast, GetPositionCast, SetVelocityCast, SetAccelerationCast, SetPositionCast, PauseCast, AddPositionCast, AddVelocityCast, AddAccelerationCast, SyncChangesToCast:

 function FastCastParallel:GetVelocityCast(cast: vaildcast)
-  assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED")
   local currentTrajectory = cast.StateInfo.Trajectory
   ...
 end
 -- (repeat for every other method listed above)

Fix 3 — src/FastCast2/init.luau: FastCastSerial:TerminateCast double-fires and double-calls

FastCastSerial:TerminateCast calls self.BaseCast:TerminateCast(cast, castTerminatingFunction), which already fires Output:Fire("CastTerminating", cast) and calls castTerminatingFunction(cast). Then it calls both again directly, causing double emission and a double callback. Remove the redundant direct calls:

 function FastCastSerial:TerminateCast(cast: vaildcast, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?)
   if cast.StateInfo.UpdateConnection ~= nil then
     cast.StateInfo.UpdateConnection:Disconnect()
     cast.StateInfo.UpdateConnection = nil
   end

   if cast.RayInfo.CosmeticBulletObject then
     cast.RayInfo.CosmeticBulletObject:Destroy()
     cast.RayInfo.CosmeticBulletObject = nil
   end

   if self.BaseCast then
     self.BaseCast:TerminateCast(cast, castTerminatingFunction)
   end

-  if castTerminatingFunction then
-    castTerminatingFunction(cast)
-  end
-
-  self.Output:Fire("CastTerminating", cast)
 end

Fix 4 — src/FastCast2/init.luau: FastCastSerial:SyncChangesToCast references nonexistent field

BaseCastSerial has no SyncChange field. For serial mode, state mutations are immediate and no sync step is needed. Replace with a no-op:

 function FastCastSerial:SyncChangesToCast(cast: vaildcast)
-  if self.BaseCast.SyncChange then
-    self.BaseCast.SyncChange:Fire(cast)
-  end
+  -- Serial simulation applies mutations directly; no sync dispatch needed.
 end

Fix 5 — src/FastCast2/BaseCastSerial.luau: Remove unused Data parameter

Data is only read in a dead no-op block. Remove it and update the call site in init.luau.

In BaseCastSerial.luau:

-function BaseCastSerial.Init(BindableOutput: BindableEvent, Data: any, parentCaster: any)
+function BaseCastSerial.Init(BindableOutput: BindableEvent, parentCaster: any)
   local self = setmetatable({}, BaseCastSerial)
   self.Output = BindableOutput
   self.ParentCaster = parentCaster
   self.ObjectCache = nil
   self.BulkMoveToConnection = nil
   self.NextProjectileID = 0

-  if Data.useBulkMoveTo then
-    -- BulkMoveTo is handled by SerialSimulation
-  end

   return self
 end

In init.luau, update the Init call:

-self.BaseCast = BaseCastSerial.Init(BindableOutput, data, self)
+self.BaseCast = BaseCastSerial.Init(BindableOutput, self)

Fix 6 — src/FastCast2/BaseCastSerial.luau: Raycast method uses self.self.*

BaseCastSerial:Raycast still references self.self.ParentCaster and self.self.OutputBlockcast and Spherecast are already correct. Fix Raycast:

-  local cast = ActiveCastSerial.new(self.self.ParentCaster, castData)
+  local cast = ActiveCastSerial.new(self.ParentCaster, castData)
   SerialSimulation.Register(cast)

-  if self.self.Output then
-    self.self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
+  if self.Output then
+    self.Output:Fire("CastFire", cast, Origin, Direction, Velocity, Behavior)
   end

Fix 7 — src/FastCast2/ActiveCastSerial.luau: Replace opaque castData table with explicit parameters

ActiveCastSerial.new(caster, castData) should use explicit named parameters like ActiveCast.createCastData does, not a raw untyped table. Also remove all the dead code defined in this file that belongs in SerialSimulation (the simulation functions, debug helpers, and constants are all unused here).

Replace the entire ActiveCastSerial.luau content with:

--[[
	- Author : Mawin CK
	- Date : 2025

	ActiveCastSerial - Constructs the cast data object for serial simulation.
]]

local FastCastModule = script.Parent
local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums"))

local CastVariantTypes = {
	[FastCastEnums.CastType.Raycast] = "Raycast",
	[FastCastEnums.CastType.Blockcast] = "Blockcast",
	[FastCastEnums.CastType.Spherecast] = "Spherecast",
}

local DEFAULT_MAX_DISTANCE = 1000

local function CloneCastParams(params: RaycastParams): RaycastParams
	local clone: RaycastParams = RaycastParams.new()
	clone.CollisionGroup = params.CollisionGroup
	clone.FilterType = params.FilterType
	clone.FilterDescendantsInstances = params.FilterDescendantsInstances
	clone.IgnoreWater = params.IgnoreWater
	return clone
end

local ActiveCastSerial = {}

function ActiveCastSerial.new(
	BaseCast: TypeDef.BaseCastData,
  activeCastID: number,
  origin: Vector3,
  direction: Vector3,
  velocity: Vector3 | number,
  behavior: TypeDef.FastCastBehavior,
  eventModule: TypeDef.FastCastEventsModule?,
  variant: CastVariants
): any
	...
end

Fix 8 — src/FastCast2/BaseCastSerial.luau: Update all three cast methods to use explicit ActiveCastSerial.new parameters

After Fix 7, all three Raycast, Blockcast, and Spherecast methods must pass explicit arguments instead of a castData table. Replace the ActiveCastSerial.new(...) call in each:

Fix 9 — src/FastCast2/SerialSimulation.luau: Remove all unused constants and dead code

Remove the following lines entirely (lines 21–32 and related unused requires):

-local TypeDef = require(FastCastModule:WaitForChild("TypeDefinitions"))
-local Configs = require(FastCastModule:WaitForChild("Configs"))
-local DebugLogging = Configs.DebugLogging
-local ActiveCastSerial = require(FastCastModule:WaitForChild("ActiveCastSerial"))

-local HIGH_FIDE_INCREASE_SIZE = 0.5
-local MAX_SEGMENT_CAL_TIME = 0.016 * 5
-local MAX_CASTING_TIME = 0.2
-local DEFAULT_MAX_DISTANCE = 1000
-local EPSILON = 1e-6
-local RAY_SEARCH_OFFSET = 0.001

-local DBG_SEGMENT_SUB_COLOR = Color3.new(0.286275, 0.329412, 0.247059)
-local DBG_SEGMENT_SUB_COLOR2 = Color3.new(0.454902, 0.933333, 0.011765)
-local DBG_HIT_SUB_COLOR = Color3.new(0.0588235, 0.87451, 1)
-local DBG_RAYPIERCE_SUB_COLOR = Color3.new(1, 0.113725, 0.588235)

Remove the unused QueueEvent function:

-local function QueueEvent(callback: any, ...)
-  if callback then
-    table.insert(QueuedEvents, { Callback = callback, Args = { ... } })
-  end
-end

Fix 10 — src/FastCast2/ParallelSimulation.luau: Same removals as Fix 9

Apply identical removals: TypeDef, Configs/DebugLogging, and all unused constants (lines 19–30), and the unused QueueEvent function.

Fix 11 — src/FastCast2/SerialSimulation.luau & ParallelSimulation.luau: Unregister clears wrong castCFrame slot

In both Unregister functions, after swapping slot idlastId, the cleanup incorrectly clears castCFrame[id] instead of castCFrame[lastId]:

-  castCFrame[id] = nil   -- wrong: id now holds the swapped-in cast
+  castCFrame[lastId] = nil

Fix 12 — src/FastCast2/SerialSimulation.luau: Motor6D disconnect happens after swap

After the swap-with-last in Unregister, castMotor6D[id] already holds the swapped-in (kept) motor6D, not the one belonging to the removed cast. Save it before swapping:

 function SerialSimulation.Unregister(id: number)
   if not casts[id] then return end
+  local motorToDisconnect = castMotor6D[id]
   local lastId = castCount

   if id ~= lastId then
     ...
     castMotor6D[id] = castMotor6D[lastId]
     ...
   end

-  if castMotor6D[id] then
-    Motor6DPool.Disconnect(castMotor6D[id])
-  end
+  if motorToDisconnect then
+    Motor6DPool.Disconnect(motorToDisconnect)
+  end
   ...
 end

Apply the same pattern to ParallelSimulation.Unregister, replacing its current incorrect guard:

-  if id ~= lastId and castMotor6D[lastId] then
-    Motor6DPool.Disconnect(castMotor6D[lastId])
-  end

with the same save-before-swap approach.

Fix 13 — Both simulation files: CastHandler receives a table instead of raw value

In UpdateCasts in both SerialSimulation.luau and ParallelSimulation.luau, variant is a table { Size = ... } or { Radius = ... } but the castHandlers expect a raw Vector3 or number. Pass the value directly:

-  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 sizeOrRadius: (Vector3 | number)?
+  if castType == EnumCastTypes.Blockcast then
+    sizeOrRadius = castSize[i]
+  elseif castType == EnumCastTypes.Spherecast then
+    sizeOrRadius = castRadius[i]
+  end
+  local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

Repository owner deleted a comment from coderabbitai Bot May 8, 2026
Repository owner deleted a comment from coderabbitai Bot May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== ActiveCast.luau - full SimulateCast and high fidelity section ==="
cat src/FastCast2/ActiveCast.luau

echo ""
echo "=== SerialSimulation.luau UpdateCasts ==="
grep -n "UpdateCasts\|HighFidelity\|highFidelity\|HighFide\|subcast\|SubCast\|subRay\|Stepped\|Always\|Once\|SegmentSize\|fidelity\|hifi\|resim\|CancelHighRes\|IsActivelyResim" src/FastCast2/SerialSimulation.luau

echo ""
echo "=== ParallelSimulation.luau UpdateCasts ==="
grep -n "UpdateCasts\|HighFidelity\|highFidelity\|HighFide\|subcast\|SubCast\|subRay\|Stepped\|Always\|Once\|SegmentSize\|fidelity\|hifi\|resim\|CancelHighRes\|IsActivelyResim" src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== SerialSimulation.luau full UpdateCasts function ==="
awk '/function.*UpdateCasts/,/^end$/' src/FastCast2/SerialSimulation.luau

echo ""
echo "=== ParallelSimulation.luau full UpdateCasts function ==="
awk '/function.*UpdateCasts/,/^end$/' src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== FastCastEnums.luau HighFidelityBehavior values ==="
cat src/FastCast2/FastCastEnums.luau

echo ""
echo "=== TypeDefinitions.luau CastStateInfo ==="
grep -n -A 20 "CastStateInfo\|HighFidelity" src/FastCast2/TypeDefinitions.luau

Length of output: 45265


@weenachuangkud Here is the focused AI agent prompt:


FastCast2 AI Agent Prompt — Implement HighFidelitySegmentSize & HighFidelityBehavior in SoA Simulations

HighFidelitySegmentSize and HighFidelityBehavior are already stored in SoA arrays in both SerialSimulation.luau and ParallelSimulation.luau (populated in Register/Unregister) but are never read in UpdateCasts. Implement the two high-fidelity modes (Automatic = 2, Always = 3) by replacing the inner loop body in UpdateCasts in both files identically (except the final Terminate call uses SerialSimulation.Terminate vs ParallelSimulation.Terminate).

⚠️ Do not touch ActiveCast.luau — it is the parallel per-cast simulation and already has its own high-fidelity implementation.


Background: how high-fidelity modes work (from ActiveCast.luau)

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. by SyncChangesToCast).

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()
end

Summary 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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me read the simulation files and the existing high-fidelity logic in ActiveCast.luau first.


🏁 Script executed:

#!/bin/bash
echo "=== src/FastCast2/SerialSimulation.luau ==="
cat src/FastCast2/SerialSimulation.luau

echo ""
echo "=== src/FastCast2/ParallelSimulation.luau ==="
cat src/FastCast2/ParallelSimulation.luau

echo ""
echo "=== src/FastCast2/ActiveCast.luau (high-fidelity section) ==="
cat src/FastCast2/ActiveCast.luau

echo ""
echo "=== FastCastEnums.luau ==="
cat src/FastCast2/FastCastEnums.luau

Length of output: 54537


@weenachuangkud Here is the AI agent prompt:


FastCast2 AI Agent Prompt — Implement HighFidelitySegmentSize & HighFidelityBehavior in SerialSimulation and ParallelSimulation

Both src/FastCast2/SerialSimulation.luau and src/FastCast2/ParallelSimulation.luau already store castHighFidelitySegmentSize[id] and castHighFidelityBehavior[id] in their SoA arrays but never use them in UpdateCasts. Apply all changes below identically to both files.

Reference the existing implementation in src/FastCast2/ActiveCast.luau for the correct sub-cast math. The three modes from FastCastEnums.HighFidelityBehavior are:

  • Default = 1 — single cast per frame (current behavior, no change).
  • Automatic = 2 — single cast per frame; on hit, subdivide the segment to find the exact impact point.
  • Always = 3 — every frame subdivide into N sub-casts regardless of hit.

Step 1 — Add the PerformHighFidelitySubcasts helper

Add this function before UpdateCasts in both files:

-- Performs N sub-casts over [runtimeBefore, runtimeBefore + deltaTime].
-- Returns true and schedules termination via destroyedIds if a hit is found.
-- Fires LengthChanged for every sub-segment.
local function PerformHighFidelitySubcasts(
	i: number,
	runtimeBefore: number,
	deltaTime: number,
	numSegments: number,
	origin: Vector3,
	velocity: Vector3,
	acceleration: Vector3,
	CastHandler: any,
	sizeOrRadius: (Vector3 | number)?,
	destroyedIds: { number }
): boolean
	local timeIncrement = deltaTime / numSegments
	local caster = castCaster[i]
	local bullet = castCosmeticBullet[i]
	local motor6d = castMotor6D[i]
	local hitFound = false

	for segIdx = 1, numSegments do
		if castCancelHighResCast[i] then
			castCancelHighResCast[i] = false
			break
		end

		local segT0 = runtimeBefore + timeIncrement * (segIdx - 1)
		local segT1 = runtimeBefore + timeIncrement * segIdx

		local segLastPos   = GetPositionAtTime(segT0, origin, velocity, acceleration)
		local segCurrPos   = GetPositionAtTime(segT1, origin, velocity, acceleration)
		local segVelocity  = GetVelocityAtTime(segT1, velocity, acceleration)

		-- Use velocity * timeIncrement to match ActiveCast.luau subRayDir fix
		local segRayDir = segVelocity * timeIncrement

		local segResult = CastHandler(castWorldRoot[i], segLastPos, segRayDir, castRaycastParams[i], sizeOrRadius)

		local segHitPoint = segCurrPos
		if segResult then
			segHitPoint = segResult.Position
		end

		local segDisplacement = (segHitPoint - segLastPos).Magnitude
		castDistanceCovered[i] += segDisplacement
		castTotalRuntime[i] = segT1

		local newCFrame = CFrame.new(segLastPos, segLastPos + segRayDir) * CFrame.new(0, 0, -segDisplacement / 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], segLastPos, segRayDir.Unit, segDisplacement, segVelocity, bullet)

		if segResult and segResult.Instance ~= bullet then
			QueueFire(caster, "Hit", casts[i], segResult, segVelocity, bullet)
			table.insert(destroyedIds, i)
			hitFound = true
			break
		end

		if castDistanceCovered[i] >= castMaxDistance[i] then
			table.insert(destroyedIds, i)
			hitFound = true
			break
		end
	end

	return hitFound
end

Step 2 — Replace the UpdateCasts per-cast body

In UpdateCasts, replace everything inside the for i = 1, castCount do loop (after the continue guard and local variable declarations) with the following. The structure branches on castHighFidelityBehavior[i]:

	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]

		local sizeOrRadius: (Vector3 | number)?
		if castType == EnumCastTypes.Blockcast then
			sizeOrRadius = castSize[i]
		elseif castType == EnumCastTypes.Spherecast then
			sizeOrRadius = castRadius[i]
		end

		local hfBehavior    = castHighFidelityBehavior[i] or 1
		local hfSegmentSize = castHighFidelitySegmentSize[i] or 1

		local bullet = castCosmeticBullet[i]
		local motor6d = castMotor6D[i]

		-- ── Always mode (3): subdivide every frame ──────────────────────────────
		if hfBehavior == FastCastEnums.HighFidelityBehavior.Always and hfSegmentSize > 0 then
			if castIsActivelyResimulating[i] then
				-- Cascading lag guard: terminate to prevent exponential cost
				warn("FastCast2: Cascading cast lag on cast " .. tostring(castIDs[i]) .. ". Increase HighFidelitySegmentSize.")
				table.insert(destroyedIds, i)
				continue
			end

			castIsActivelyResimulating[i] = true

			local runtimeBefore = castTotalRuntime[i]

			-- Estimate displacement to determine segment count
			local prePos  = GetPositionAtTime(runtimeBefore, origin, velocity, acceleration)
			local postPos = GetPositionAtTime(runtimeBefore + deltaTime, origin, velocity, acceleration)
			local estDisplacement = (postPos - prePos).Magnitude

			local numSegments = math.max(1, math.floor(estDisplacement / hfSegmentSize))

			PerformHighFidelitySubcasts(
				i, runtimeBefore, deltaTime, numSegments,
				origin, velocity, acceleration,
				CastHandler, sizeOrRadius, destroyedIds
			)

			-- Ensure runtime is advanced even if no hit was found
			if castTotalRuntime[i] == runtimeBefore then
				castTotalRuntime[i] = runtimeBefore + deltaTime
			end

			castIsActivelyResimulating[i] = false

		-- ── Automatic mode (2): single cast, sub-cast only on hit ────────────────
		elseif hfBehavior == FastCastEnums.HighFidelityBehavior.Automatic and hfSegmentSize > 0 then
			local runtimeBefore = castTotalRuntime[i]
			local totalDelta    = runtimeBefore

			local lastPosition = GetPositionAtTime(totalDelta, origin, velocity, acceleration)
			castTotalRuntime[i] += deltaTime
			totalDelta = castTotalRuntime[i]

			local currentPosition = GetPositionAtTime(totalDelta, origin, velocity, acceleration)
			local currentVelocity = GetVelocityAtTime(totalDelta, velocity, acceleration)
			local displacement    = currentPosition - lastPosition
			local rayDir = displacement.Unit * currentVelocity.Magnitude * deltaTime

			local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

			if result and result.Instance ~= bullet then
				-- Hit detected — resimulate to find exact point
				if castIsActivelyResimulating[i] then
					warn("FastCast2: Cascading cast lag on cast " .. tostring(castIDs[i]) .. ". Increase HighFidelitySegmentSize.")
					table.insert(destroyedIds, i)
					continue
				end

				castIsActivelyResimulating[i] = true

				local rayDisplacement = (result.Position - lastPosition).Magnitude
				local numSegments = math.max(1, math.floor(rayDisplacement / hfSegmentSize))

				-- Rewind time and let PerformHighFidelitySubcasts advance it
				castTotalRuntime[i] = runtimeBefore
				castDistanceCovered[i] -= 0  -- distance not yet added in this branch

				local hitFound = PerformHighFidelitySubcasts(
					i, runtimeBefore, deltaTime, numSegments,
					origin, velocity, acceleration,
					CastHandler, sizeOrRadius, destroyedIds
				)

				if not hitFound then
					-- Sub-cast found no hit (can happen with curved paths); fire normal LengthChanged
					local hitPoint = result.Position
					local rayDisp2 = (hitPoint - lastPosition).Magnitude
					castDistanceCovered[i] += rayDisp2
					local newCFrame = CFrame.new(lastPosition, lastPosition + rayDir) * CFrame.new(0, 0, -rayDisp2 / 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], lastPosition, rayDir.Unit, rayDisp2, currentVelocity, bullet)
				end

				castIsActivelyResimulating[i] = false
			else
				-- No hit — normal update
				local hitPoint = currentPosition
				if result then hitPoint = result.Position end
				local rayDisplacement = (hitPoint - lastPosition).Magnitude
				castDistanceCovered[i] += rayDisplacement

				local newCFrame = CFrame.new(lastPosition, lastPosition + 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], lastPosition, rayDir.Unit, rayDisplacement, currentVelocity, bullet)

				if castDistanceCovered[i] >= castMaxDistance[i] then
					table.insert(destroyedIds, i)
				end
			end

		-- ── Default mode (1): single cast per frame (existing behavior) ──────────
		else
			local totalDelta = castTotalRuntime[i]

			local lastPosition = GetPositionAtTime(totalDelta, origin, velocity, acceleration)
			castTotalRuntime[i] += deltaTime
			totalDelta = castTotalRuntime[i]

			local currentPosition = GetPositionAtTime(totalDelta, origin, velocity, acceleration)
			local currentVelocity = GetVelocityAtTime(totalDelta, velocity, acceleration)
			local displacement    = currentPosition - lastPosition
			local rayDir = displacement.Unit * currentVelocity.Magnitude * deltaTime

			local result = CastHandler(castWorldRoot[i], lastPosition, rayDir, castRaycastParams[i], sizeOrRadius)

			local hitPoint = currentPosition
			local hitPart  = nil
			if result then
				hitPoint = result.Position
				hitPart  = result.Instance
			end

			local rayDisplacement = (hitPoint - lastPosition).Magnitude
			castDistanceCovered[i] += rayDisplacement

			local newCFrame = CFrame.new(lastPosition, lastPosition + 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], lastPosition, rayDir.Unit, rayDisplacement, currentVelocity, bullet)

			if result and hitPart ~= bullet then
				QueueFire(caster, "Hit", casts[i], result, currentVelocity, bullet)
				table.insert(destroyedIds, i)
			end

			if castDistanceCovered[i] >= castMaxDistance[i] then
				table.insert(destroyedIds, i)
			end
		end
	end

Step 3 — Add FastCastEnums require to both files (if not already present)

FastCastEnums is already required in both files under local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums")). No change needed here, but confirm EnumCastTypes and FastCastEnums.HighFidelityBehavior are both accessible. They are — EnumCastTypes = FastCastEnums.CastType is already defined, and FastCastEnums.HighFidelityBehavior is used directly in the new code above.


Notes on correctness

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) ⚠️ Add a guard: if not table.find(destroyedIds, i) then table.insert(destroyedIds, i) end in both the hit and maxDistance checks inside PerformHighFidelitySubcasts

@weenachuangkud weenachuangkud marked this pull request as draft May 8, 2026 11:25
- Remove trajectory Origin/StartTime reset after pierce in serial path
  (matching legacy behavior — trajectory is unchanged on pierce)
- Fix Always-mode sub-cast loop: pass timeIncrement instead of delta
  to SimulateCast in both serial and parallel paths
typeof() in parallel context returns 'Instance' for ModuleScript
instances from outside the Actor. The Register function was only
checking for 'ModuleScript', causing the per-cast require() to
always be skipped and CanPierce to never fire.
@weenachuangkud weenachuangkud changed the title Final update v0.1.0 — Major refactor: Serial & Parallel casters, SoA simulation, Motor6D movement, visualization, benchmarks May 23, 2026
@weenachuangkud weenachuangkud marked this pull request as ready for review May 23, 2026 14:16
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants