Skip to content

Conversation

@dankelley2
Copy link
Owner

@dankelley2 dankelley2 commented Jan 24, 2026

See https://github.com/dankelley2/ProjectorSegmentation for second project in solution; Will merge them eventually

Summary by CodeRabbit

  • New Features

    • Added comprehensive game demo suite with multiple interactive games (Bubble Pop, Rain Catcher, Platformer, Sandbox)
    • Integrated pose/body tracking for motion-controlled gameplay
    • Added in-game settings menu for camera and detection configuration
    • Introduced UI system with interactive buttons, sliders, and menus
    • Added pause and time-scale controls to physics engine
  • Bug Fixes

    • Improved polygon-circle collision detection accuracy
    • Enhanced physics object sleeping and wake behavior
  • Refactor

    • Restructured physics system from static to instance-based architecture
    • Updated project configuration and framework targeting

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the SharpPhysics physics engine from a static, window-bound architecture to an instance-based, game-loop abstraction system, and introduces a comprehensive demo project with multiple playable games, pose-tracking integration, and a complete UI framework.

Changes

Cohort / File(s) Summary
Project Configuration
.gitignore, PhysicsEngine.sln, SharpPhysics.csproj, SharpPhysics.Tests.csproj, SharpPhysics.Demo.csproj
Added ONNX model ignore rule; updated solution for VS 18 with three new projects; converted SharpPhysics to library (net9.0) with SharpPhysics.Engine namespace; added new demo console project.
Core Game Loop Architecture
physics/Engine/Core/GameEngine.cs, physics/Engine/Core/IGame.cs
Introduced GameEngine orchestrating main loop and subsystems with game-switching support; defined IGame interface for lifecycle hooks (Initialize, Update, Render, Shutdown).
Physics System Refactoring
physics/Engine/PhysicsSystem.cs, physics/Engine/Classes/Templates/ObjectTemplates.cs, physics/Engine/Classes/Templates/ActionTemplates.cs
Converted from static to instance-based architecture with dependency injection; added TimeScale and IsPaused properties; reworked object creation and action methods to use instance context.
Collision and Constraint Improvements
physics/Engine/Classes/CollisionPair.cs, physics/Engine/Classes/CollisionPairPool.cs, physics/Engine/Collision.cs, physics/Engine/Constraints/Constraint.cs
Sealed CollisionPair with properties and Set/Reset methods; added object pool for collision pairs; implemented complete PolygonVsCircle collision algorithm; refactored WeldConstraint and AxisConstraint to use impulse-based Baumgarte stabilization.
Input System Overhaul
physics/Engine/Input/InputManager.cs, physics/Engine/Input/KeyState.cs
Replaced action-driven input with state-based buffering and edge detection; KeyState changed from record to struct with extensive key/mouse state fields; added key press consumption and mouse position tracking.
Rendering and UI Framework
physics/Engine/Rendering/Renderer.cs, physics/Engine/Rendering/UI/UiElement.cs, physics/Engine/Rendering/UI/UiManager.cs, physics/Engine/Rendering/UI/IUiClickable.cs, physics/Engine/Rendering/UI/IUiDraggable.cs
Major Renderer refactor adding debug UI manager, primitive drawing methods, and physics-backed rendering; introduced comprehensive UI system with clickable/draggable interfaces and UiManager orchestration.
UI Components
physics/Engine/Rendering/UI/UiButton.cs, physics/Engine/Rendering/UI/UiCheckbox.cs, physics/Engine/Rendering/UI/UiSlider.cs, physics/Engine/Rendering/UI/UiMenuButton.cs, physics/Engine/Rendering/UI/UiRoundedRectangle.cs
Added interactive UI component library: button with text labels, checkbox with state toggling, slider with value/drag handling, styled menu button with hover effects, and rounded rectangle shape.
Shader System
physics/Engine/Shaders/SFMLBallVelocityShader.cs, physics/Engine/Shaders/SFMLNoneShader.cs, physics/Engine/Shaders/SFMLPolyRainbowShader.cs, physics/Engine/Shaders/SFMLPolyShader.cs, physics/Engine/Shaders/ShaderHelpers.cs
Extracted HSV-to-RGB conversion to ShaderHelpers; added no-op shader; implemented rainbow-colored polygon shader with contact normal rendering; converted DrawNormals to static flag.
Physics Helpers and Cleanup
physics/Engine/Helpers/ConstraintHelper.cs, physics/Engine/Helpers/CollisionHelpers.cs, physics/Engine/Player/PlayerController.cs, physics/Engine/Structs/AABB.cs
Added constraint helper extension methods for WeldConstraint and AxisConstraint; removed ComputeSignedArea and IsInside helpers; added Slam action to PlayerController; converted AABB from record to struct.
Application Entry Points
physics/Program.cs
Removed legacy main entry point; physics engine now library-only.
Demo Project Games
SharpPhysics.Demo/MenuGame.cs, SharpPhysics.Demo/DemoGame.cs, SharpPhysics.Demo/BubblePopGame.cs, SharpPhysics.Demo/PlatformerGame.cs, SharpPhysics.Demo/RainCatcherGame.cs, SharpPhysics.Demo/SettingsGame.cs
Implemented six game modules: main menu with particle effects, physics sandbox with vehicle demo, bubble-pop with pose tracking, platformer with coins/enemies, ball-catching with power-ups, and in-game settings UI.
Pose Tracking Integration
SharpPhysics.Demo/Integration/PersonColliderBridge.cs, SharpPhysics.Demo/Integration/TrackedPerson.cs, SharpPhysics.Demo/Helpers/SkeletonRenderer.cs
Added multi-person pose detection bridge using YOLO with MJPEG/camera input; TrackedPerson models per-person skeleton data; SkeletonRenderer provides 17-keypoint visualization with color-coded body parts.
Configuration and Documentation
SharpPhysics.Demo/Settings/GameSettings.cs, SharpPhysics.Demo/gamesettings.json, SharpPhysics.Demo/Program.cs, SharpPhysics.Demo/README.md, README.md
Added JSON-based settings singleton for window, camera, pose detection, and game parameters; entry point bootstraps menu game; comprehensive demo README with IGame implementation guide and quick reference; root README documents January 2026 updates.

Sequence Diagram(s)

sequenceDiagram
    participant Main as Program.cs
    participant Engine as GameEngine
    participant Game as IGame
    participant Physics as PhysicsSystem
    participant Renderer

    Main->>Engine: new GameEngine(width, height, title, MenuGame)
    Main->>Engine: Run()
    
    Engine->>Game: Initialize(engine)
    activate Game
    Game->>Physics: Create objects/constraints
    Game->>Renderer: Setup UI
    deactivate Game
    
    loop Main Game Loop
        Engine->>Engine: Check pending game switches
        alt Game Switch Pending
            Engine->>Game: Shutdown()
            Engine->>Physics: Clear all objects
            Engine->>Game: Initialize(engine) [new game]
        end
        
        Engine->>Physics: Update(deltaTime * TimeScale)
        Engine->>Game: Update(deltaTime, keyState)
        Engine->>Renderer: Render physics objects
        Engine->>Game: Render(renderer) [overlays]
        Engine->>Renderer: Display()
    end
    
    Note over Engine,Renderer: On window close or ESC
    Engine->>Game: Shutdown()
    Engine->>Renderer: Cleanup
Loading
sequenceDiagram
    participant User as User Input
    participant InputMgr as InputManager
    participant KeyState as KeyState
    participant Game as IGame
    
    User->>InputMgr: Mouse/Keyboard events
    activate InputMgr
    InputMgr->>InputMgr: Buffer key presses with timer
    InputMgr->>InputMgr: Track edge states
    deactivate InputMgr
    
    Game->>InputMgr: GetKeyState()
    activate InputMgr
    InputMgr->>KeyState: Create snapshot
    KeyState-->>InputMgr: Held + Pressed states
    deactivate InputMgr
    
    InputMgr-->>Game: KeyState with buffered presses
    Game->>Game: Process input per buffered key
    Game->>InputMgr: ConsumePress(key) [optional]
    InputMgr->>InputMgr: Remove from buffer
Loading
sequenceDiagram
    participant PersonCollider as PersonColliderBridge
    participant YoloDetector as YOLO Model
    participant Camera as Camera/MJPEG
    participant Physics as PhysicsSystem
    participant MainThread as Main Thread
    
    PersonCollider->>Camera: Start background frame capture
    
    loop Background Detection Thread
        PersonCollider->>Camera: Grab frame
        PersonCollider->>YoloDetector: Detect pose (17 keypoints)
        PersonCollider->>PersonCollider: Enqueue detection result
    end
    
    MainThread->>PersonCollider: ProcessPendingUpdates()
    activate PersonCollider
    PersonCollider->>PersonCollider: Match detections to TrackedPerson
    loop Per Person
        PersonCollider->>PersonCollider: Apply smoothing to keypoints
        PersonCollider->>Physics: Update head/hand ball positions
    end
    PersonCollider-->>MainThread: OnPersonBodyUpdated event
    deactivate PersonCollider
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 The physics engine hops with glee,
From static chains now dancing free,
With games that pop, catch, and run,
A pose-tracked world of jumping fun!
Instance by instance, loop by loop,
The rabbit's engine joins the troupe! 🎮✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes in the PR: project refactoring, body-tracking integration, constraint fixes, and performance improvements are all evident in the changeset.

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

✨ Finishing touches
  • 📝 Generate docstrings

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
Contributor

@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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
physics/Engine/PhysicsSystem.cs (1)

229-239: Potential NullReferenceException in RemoveActiveObject.

ActiveObject could be null when this method is called, but it's passed to Contains() and Enqueue() without null checks. This will throw if called when no object is active.

🐛 Suggested fix
 public void RemoveActiveObject()
 {
+    if (ActiveObject == null)
+        return;
+
     if (ListGravityObjects.Contains(ActiveObject))
     {
         ListGravityObjects.Remove(ActiveObject);
     }

     // Add to removal queue for proper removal
     RemovalQueue.Enqueue(ActiveObject);
     ActiveObject = null;
 }
🤖 Fix all issues with AI agents
In `@physics/Engine/Classes/CollisionPair.cs`:
- Around line 33-37: CollisionPair.Reset() sets A and B to null but Equals() and
GetHashCode() call A.Equals(...) and A.GetHashCode() directly, which can throw
NullReferenceException; update CollisionPair.Equals(...) and
CollisionPair.GetHashCode() to defensively handle nulls by checking A and B for
null (use ReferenceEquals or explicit null checks) before calling
Equals/GetHashCode, return appropriate fallback values (e.g., false for
comparisons when the other operand isn't matching or 0/combined hash when fields
are null) and keep equality symmetrical and consistent with GetHashCode.

In `@physics/Engine/Classes/Objects/PhysicsObject.cs`:
- Line 29: The Friction property was made static which forces a global value and
prevents per-object tuning; revert to an instance property on PhysicsObject and
keep a static default constant for new instances. Replace the static Friction
with an instance property named Friction (get/set) initialized to a static
DefaultFriction (or DefaultFrictionValue) set to 0.5f, and update
constructors/usages that referenced PhysicsObject.Friction to use the instance
property (obj.Friction) or the static DefaultFriction when creating new objects.

In `@physics/Engine/Classes/Templates/ActionTemplates.cs`:
- Around line 35-46: PopAndMultiply iterates _physicsSystem.ListStaticObjects
while calling _physicsSystem.RemoveActiveObject(), which can mutate the
collection; fix by iterating a snapshot copy instead of the live collection:
capture the objects into a separate list/array (e.g., call ToList() or copy into
a new List<PhysicsObject>) and loop over that snapshot in PopAndMultiply,
keeping the existing ActivateAtPoint/RemoveActiveObject/SetVelocity calls
(referencing PopAndMultiply, _physicsSystem.ListStaticObjects,
RemoveActiveObject, ActivateAtPoint, SetVelocity, and
_objectTemplates.CreateSmallBall to locate the code).
- Around line 19-25: The Launch method's physObj parameter is unused; either
remove the parameter from Launch(PhysicsObject physObj, Vector2 StartPointF,
Vector2 EndPointF) if activation is meant to be location-only, or update the
method to activate and apply velocity to the provided PhysicsObject instead of
activating by StartPointF: replace the call to
_physicsSystem.ActivateAtPoint(StartPointF) with the appropriate activation call
for the object (e.g., activate/mark physObj in the physics system) and ensure
_physicsSystem.AddVelocityToActive applies the computed delta*2 to that
activated physObj; keep the existing delta calculation (from StartPointF to
EndPointF) and pass or associate the velocity with physObj so physObj is
actually used.

In `@physics/Engine/Collision.cs`:
- Around line 338-344: The penetration calculation for an inside-circle case is
wrong: in the block guarded by "if (!circleIsOutside)" (where you already flip
normal = -normal), change the penetration assignment from m.Penetration = radius
+ dist to m.Penetration = radius - dist so penetration equals radius minus
distance (same magnitude as the outside case) with the flipped normal; keep the
normal flip and variable names (circleIsOutside, normal, radius, dist,
m.Penetration) as-is.

In `@physics/Engine/Rendering/UI/UiCheckbox.cs`:
- Around line 34-44: The RectangleShape created in UiCheckbox.DrawSelf is not
disposed (same leak as in UiSlider); update DrawSelf to dispose the
RectangleShape after drawing (e.g., wrap the new RectangleShape(...) in a using
or call Dispose() after target.Draw(rect)) so the temporary
SFML.Graphics.RectangleShape is properly released.

In `@physics/Engine/Rendering/UI/UiElement.cs`:
- Around line 73-95: HandleClick currently forwards the world clickPos to
Children in insertion order, causing incorrect hit tests for positioned children
and clicks hitting elements under overlapping siblings; modify HandleClick to
iterate Children in reverse order and for each child convert the incoming
clickPos into the child's local coordinate space before calling
child.HandleClick (e.g. compute childLocal = clickPos - child.Position or call
child.WorldToLocal/Transform method if available), preserving the existing
IUiClickable check for this element after children are checked.

In `@physics/Engine/Rendering/UI/UiManager.cs`:
- Around line 58-75: UiManager.HandleClick currently treats any element in
_elements that returns true from UiElement.HandleClick as the clicked element,
which causes _draggedElement to be set to the container instead of the actual
child that handled the click; change the contract so UiElement.HandleClick
returns the actual UiElement that handled the click (or null) instead of bool,
update UiManager.HandleClick to call element.HandleClick(clickPos) and if it
returns a non-null handledElement use that handledElement (and check
handledElement is IUiDraggable) to set _draggedElement, and update any other
callers of UiElement.HandleClick to the new signature accordingly.

In `@physics/Engine/Rendering/UI/UiMenuButton.cs`:
- Around line 11-20: UiMenuButton currently holds SFML resources (_background,
_border, _titleText, _descriptionText) but does not implement IDisposable; add
the same disposal pattern used by UiTextLabel: implement IDisposable on the
UiMenuButton class, add a public void Dispose() that calls a protected virtual
void Dispose(bool disposing), in Dispose(bool) if disposing call Dispose() on
_background, _border, _titleText and _descriptionText (null-checking them and
then setting fields to null), call GC.SuppressFinalize(this) in the public
Dispose, and (optionally) add a finalizer that calls Dispose(false) if
UiTextLabel uses one — this ensures SFML shapes and texts are properly cleaned
up when the button is destroyed.

In `@physics/Engine/Rendering/UI/UiRoundedRectangle.cs`:
- Around line 136-140: The override of UiRoundedRectangle.HandleClick currently
returns false unconditionally which blocks child click traversal; change the
implementation to delegate to the base method (i.e., call
base.HandleClick(clickPos) and return its result) or remove the override
entirely so children can receive clicks—update the HandleClick method in class
UiRoundedRectangle to call base.HandleClick(clickPos) instead of returning
false.

In `@physics/Engine/Rendering/UI/UiSlider.cs`:
- Around line 48-51: GetNormalizedPosition() can divide by zero when MinValue ==
MaxValue; update the UiSlider class to either validate inputs in the constructor
(throw ArgumentException if MaxValue == MinValue) or make
GetNormalizedPosition() defensive by checking if (MaxValue - MinValue) == 0 and
returning a sensible default (e.g., 0f or 0.5f) instead of performing the
division; locate the GetNormalizedPosition method and the UiSlider constructor
to apply one of these fixes and ensure callers still get a well-defined
normalized value or a clear validation exception.
- Around line 109-142: In DrawSelf (UiSlider.DrawSelf) the three RectangleShape
instances (background, filled, handle) are created each frame but not disposed,
leaking native SFML resources; wrap each RectangleShape's lifetime in a using
(or call Dispose after target.Draw) so background, filled and handle are
disposed immediately after drawing (retain GetNormalizedPosition and target.Draw
calls but ensure Dispose/using around the RectangleShape creation).

In `@physics/SharpPhysics.csproj`:
- Around line 4-12: The project file SharpPhysics.csproj declares RootNamespace
and AssemblyName as SharpPhysics.Engine which conflicts with existing source
files using the physics.Engine namespace; fix by making them consistent: either
change all source file namespaces from physics.Engine to SharpPhysics.Engine
(update namespace declarations in classes across physics, Demo, and Tests) or
revert RootNamespace and AssemblyName in SharpPhysics.csproj to physics.Engine
so they match current code; additionally pin the SFML.Net package version in the
project file to a specific known-good version to ensure compatibility across
projects.

In `@PhysicsEngine.sln`:
- Around line 10-15: The solution contains external project references
(ProjectorSegmentation.Vision and ProjectorSegmentation.Demo) pointing to
sibling repo paths which break builds for contributors who only clone this repo;
update docs and/or solution so builds are reproducible: add clear setup
instructions in the README/Installation section describing that
ProjectorSegmentation must be cloned as a sibling directory (example structure
and git clone commands), or make the external projects optional by
unloading/guarding them in the solution (remove or conditionally include
ProjectorSegmentation.Vision and ProjectorSegmentation.Demo from
PhysicsEngine.sln and add a note how to re-add them), and include a brief
troubleshooting note about build errors referencing those project GUIDs
({58D568AA-7BA6-90BF-B217-BF1497DB529A}, {C3112F8F-13A2-AC9D-988A-397C4B3B3A44})
so contributors know how to proceed.

In `@SharpPhysics.Demo/DemoGame.cs`:
- Around line 493-499: The right-mouse deletion currently runs in the
OnMouseMoved path (when _isMousePressedRight && !_isCreatingBox) calling
_engine.PhysicsSystem.RemoveActiveObject() repeatedly; change this so deletion
happens only once per press: either remove the deletion logic from the
OnMouseMoved block and keep removal in OnMouseButtonPressed, or add a
press-cycle flag (e.g. _rightDeletedThisPress) that you set when
RemoveActiveObject() is called and check/reset on button release; update the
handlers OnMouseButtonPressed (where initial deletion currently occurs),
OnMouseMoved (remove or gate the call to
_engine.PhysicsSystem.RemoveActiveObject()), and OnMouseButtonReleased to reset
the flag if you choose the press-cycle approach.

In `@SharpPhysics.Demo/RainCatcherGame.cs`:
- Around line 494-504: The Space key handling in HandleDebugInput currently
spawns 5 balls every frame while keyState.Space is true, causing uncontrolled
spawning; change it to only trigger on a key-down transition or implement a
cooldown to respect MAX_BALLS: add state (e.g., a private bool lastSpaceDown or
a float spaceCooldown) to detect when Space goes from false to true (or
decrement cooldown each update and only spawn when <=0), call SpawnBall up to
remaining capacity (respecting MAX_BALLS) and update the state/cooldown after
spawning; reference HandleDebugInput, keyState.Space, SpawnBall and MAX_BALLS to
locate where to apply the change.

In `@SharpPhysics.Demo/SharpPhysics.Demo.csproj`:
- Around line 13-15: SharpPhysics.Demo.csproj currently contains a hard-coded
ProjectReference to
..\..\..\ProjectorSegmentation\src\ProjectorSegmentation.Vision\ProjectorSegmentation.Vision.csproj
which forces a specific sibling-repo layout; change this to either (A) make the
reference conditional by wrapping the ProjectReference with an MSBuild Condition
that only includes it when a property like ProjectorSegmentationPath is set
(e.g., use Condition="'$(ProjectorSegmentationPath)' != ''" and reference via
that property), or (B) replace the ProjectReference with a PackageReference to a
published ProjectorSegmentation.Vision NuGet package, and add clear README setup
instructions for contributors to either set ProjectorSegmentationPath or install
the package; update SharpPhysics.Demo.csproj and project documentation
accordingly.
🟡 Minor comments (16)
physics/Engine/Shaders/SFMLPolyShader.cs-16-17 (1)

16-17: Remove the unused DrawSleepSupports property.

DrawSleepSupports is declared in both SFMLPolyShader.cs and SFMLPolyRainbowShader.cs but is never read or referenced anywhere in the codebase. Unlike DrawNormals which is actively used in conditional rendering logic, this property serves no purpose and should be removed to avoid dead code.

physics/Engine/Collision.cs-276-309 (1)

276-309: Normal can be wrong when closest point ends up on a different edge.
When closestPoint is updated from an edge, closestIndex is not updated. In the dist < 0.0001f branch (Lines 323-327), the edge normal is derived from closestIndex, which may not correspond to the actual closest edge, producing an incorrect normal.

🔧 Suggested approach

Track the edge endpoints whenever closestPoint is updated from a segment, and use that edge to compute the normal in the near-zero-distance branch.

-            Vector2 closestPoint = Vector2.Zero;
-            int closestIndex = 0;
+            Vector2 closestPoint = Vector2.Zero;
+            int closestIndex = 0;
+            Vector2 closestEdgeA = Vector2.Zero;
+            Vector2 closestEdgeB = Vector2.Zero;
...
-                    closestPoint = ClosestPointOnSegment(va, vb, circleCenter);
+                    closestPoint = ClosestPointOnSegment(va, vb, circleCenter);
+                    closestEdgeA = va;
+                    closestEdgeB = vb;
...
-                    closestPoint = ClosestPointOnSegment(vb, vc, circleCenter);
+                    closestPoint = ClosestPointOnSegment(vb, vc, circleCenter);
+                    closestEdgeA = vb;
+                    closestEdgeB = vc;
...
-                    closestPoint = ClosestPointOnSegment(poly[i], poly[nextI], circleCenter);
+                    closestPoint = ClosestPointOnSegment(poly[i], poly[nextI], circleCenter);
+                    closestEdgeA = poly[i];
+                    closestEdgeB = poly[nextI];
...
-                Vector2 edge = poly[closestIndex] - poly[prevI];
+                Vector2 edge = closestEdgeB - closestEdgeA;

Also applies to: 323-327

physics/Engine/Collision.cs-372-375 (1)

372-375: Guard against zero-length segments in ClosestPointOnSegment.
If two consecutive vertices are identical, ab.LengthSquared() becomes zero and causes a divide-by-zero (NaN propagation).

✅ Proposed fix
             Vector2 ab = b - a;
-            float t = Vector2.Dot(p - a, ab) / ab.LengthSquared();
+            float abLenSq = ab.LengthSquared();
+            if (abLenSq < 1e-8f)
+                return a;
+            float t = Vector2.Dot(p - a, ab) / abLenSq;
SharpPhysics.Tests/SharpPhysics.Tests.csproj-3-3 (1)

3-3: Add global.json to enforce .NET 9.0 SDK version across development and CI environments.

All projects consistently target net9.0, but without a global.json file pinning the SDK version, developers and build systems could inadvertently use different SDK versions (e.g., .NET 8.0 or 10.0), causing environment inconsistencies. Create a global.json at the repository root with:

{
  "sdk": {
    "version": "9.0.0",
    "rollForward": "latestMinor"
  }
}

This enforces the SDK version for all developers. Consider also adding CI/CD configuration (e.g., GitHub Actions) to verify builds succeed automatically.

SharpPhysics.Demo/gamesettings.json-1-25 (1)

1-25: Avoid hard‑coded LAN camera URL in default settings.
Defaulting to a private LAN address with URL capture will fail for most users unless they edit the file. Consider a safer default (e.g., device capture or empty URL) and let users opt into URL mode.

💡 Suggested default tweak
-  "cameraSourceType": "url",
-  "cameraUrl": "http://192.168.1.161:8080",
+  "cameraSourceType": "device",
+  "cameraUrl": "",
physics/Engine/Rendering/UI/UiButton.cs-61-69 (1)

61-69: Pressed color never resets after click.

HandleClick sets _background.FillColor but nothing restores it, so the button stays in the pressed color indefinitely. If that isn’t intended, consider resetting after the click or tracking a short-lived pressed state.

SharpPhysics.Demo/PlatformerGame.cs-617-623 (1)

617-623: Reset debug UI visibility on shutdown.

Initialize disables debug UI; without restoring it, subsequent games may unexpectedly keep it hidden. Consider resetting in Shutdown (or ensure every game sets it explicitly).

🔧 Suggested fix
 public void Shutdown()
 {
+    _engine.Renderer.ShowDebugUI = true;
     _coins.Clear();
     _hazards.Clear();
     _enemies.Clear();
     Console.WriteLine($"🎮 Platformer ended. Final Score: {_score}");
 }
SharpPhysics.Demo/MenuGame.cs-145-151 (1)

145-151: The exit hint label is created but never added to the UI or rendered.

Similar to SettingsGame, the exitHint label is instantiated but not added to _uiManager, so it won't be displayed.

🐛 Suggested fix
         // Hint at bottom
         var exitHint = new UiTextLabel("Press ESC to return to menu from any game", font)
         {
             Position = new Vector2(centerX - 200, _engine.WindowHeight - 50),
             CharacterSize = 14
         };
+        _uiManager.Add(exitHint);
SharpPhysics.Demo/SettingsGame.cs-360-380 (1)

360-380: Fix inconsistent indentation in the Render method.

Lines 360-380 have broken indentation - the switch block and closing braces are misaligned. This hinders readability and may cause issues with future edits.

🔧 Suggested fix for indentation
-            // Category-specific hints
-            float hintY = startY + _currentSettings.Count * rowHeight + 35f;
-            switch (_currentCategory)
-            {
-                case "Camera":
-                    renderer.DrawText("Common URL formats:", labelX, hintY, 14, new Color(180, 180, 200));
-                    renderer.DrawText("- MJPEG Stream: http://<ip>:8080", labelX + 20f, hintY + 22f, 12, new Color(140, 140, 160));
-                    renderer.DrawText("- ESP32-CAM: http://<ip>:81/stream", labelX + 20f, hintY + 40f, 12, new Color(140, 140, 160));
-                    break;
-                case "Detection":
-                    renderer.DrawText("Larger ball radius = easier tracking, smaller = more precise", labelX, hintY, 14, new Color(180, 180, 200));
-                    break;
-                        case "Display":
-                                renderer.DrawText("WARNING: Display changes require application restart", labelX, hintY, 14, new Color(255, 200, 100));
-                                break;
-                        }
-
-                    // Draw UI elements (category buttons)
-                    renderer.Window.SetView(renderer.UiView);
-                    _uiManager.Draw(renderer.Window);
-                    }
+        // Category-specific hints
+        float hintY = startY + _currentSettings.Count * rowHeight + 35f;
+        switch (_currentCategory)
+        {
+            case "Camera":
+                renderer.DrawText("Common URL formats:", labelX, hintY, 14, new Color(180, 180, 200));
+                renderer.DrawText("- MJPEG Stream: http://<ip>:8080", labelX + 20f, hintY + 22f, 12, new Color(140, 140, 160));
+                renderer.DrawText("- ESP32-CAM: http://<ip>:81/stream", labelX + 20f, hintY + 40f, 12, new Color(140, 140, 160));
+                break;
+            case "Detection":
+                renderer.DrawText("Larger ball radius = easier tracking, smaller = more precise", labelX, hintY, 14, new Color(180, 180, 200));
+                break;
+            case "Display":
+                renderer.DrawText("WARNING: Display changes require application restart", labelX, hintY, 14, new Color(255, 200, 100));
+                break;
+        }
+
+        // Draw UI elements (category buttons)
+        renderer.Window.SetView(renderer.UiView);
+        _uiManager.Draw(renderer.Window);
+    }
SharpPhysics.Demo/MenuGame.cs-233-243 (1)

233-243: Fix inconsistent indentation in the Render method.

Lines 236-243 have misaligned indentation compared to the rest of the method.

🔧 Suggested fix
         // Draw animated background particles
         DrawParticles(renderer);

-            // Draw version/info at bottom
-            renderer.DrawText("Use your body to interact! • Body tracking powered by YOLO Pose", 
-                _engine.WindowWidth / 2f - 250, _engine.WindowHeight - 30, 14, new Color(120, 120, 140));
+        // Draw version/info at bottom
+        renderer.DrawText("Use your body to interact! • Body tracking powered by YOLO Pose", 
+            _engine.WindowWidth / 2f - 250, _engine.WindowHeight - 30, 14, new Color(120, 120, 140));

-            // Draw UI elements (menu buttons)
-            renderer.Window.SetView(renderer.UiView);
-            _uiManager.Draw(renderer.Window);
-        }
+        // Draw UI elements (menu buttons)
+        renderer.Window.SetView(renderer.UiView);
+        _uiManager.Draw(renderer.Window);
+    }
SharpPhysics.Demo/Helpers/SkeletonRenderer.cs-79-82 (1)

79-82: Missing bounds check on confidences in keypoint loop.

Similarly, accessing confidences[i] should verify i < confidences.Length.

🐛 Proposed fix
         for (int i = 0; i < keypoints.Length; i++)
         {
+            if (i >= confidences.Length)
+                continue;
+
             if (confidences[i] < confidenceThreshold)
                 continue;
SharpPhysics.Demo/Helpers/SkeletonRenderer.cs-61-67 (1)

61-67: Missing bounds check on confidences array.

The code checks if indices are within keypoints.Length but accesses confidences[idx1] and confidences[idx2] without verifying they're within bounds of the confidences array.

🐛 Proposed fix
         foreach (var (idx1, idx2) in connections)
         {
-            if (idx1 >= keypoints.Length || idx2 >= keypoints.Length)
+            if (idx1 >= keypoints.Length || idx2 >= keypoints.Length ||
+                idx1 >= confidences.Length || idx2 >= confidences.Length)
                 continue;
SharpPhysics.Demo/RainCatcherGame.cs-455-458 (1)

455-458: GravityFlip toggles on activation but always resets to false on deactivation.

When GravityFlip is activated, it toggles _isGravityReversed. But DeactivatePowerUp (line 482-484) always sets it to false. If gravity was already reversed before the power-up, this creates inconsistent behavior.

🔧 Proposed fix - store pre-powerup state
+    private bool _preGravityFlipState = false;
+
     case "GravityFlip":
+        _preGravityFlipState = _isGravityReversed;
         _isGravityReversed = !_isGravityReversed;
         _physics.Gravity = new Vector2(0, _isGravityReversed ? -6f : 6f);
         break;

And in deactivation:

     case "GravityFlip":
-        _isGravityReversed = false;
-        _physics.Gravity = new Vector2(0, 6f);
+        _isGravityReversed = _preGravityFlipState;
+        _physics.Gravity = new Vector2(0, _isGravityReversed ? -6f : 6f);
         break;
SharpPhysics.Demo/DemoGame.cs-345-366 (1)

345-366: Inconsistent indentation in Render and subsequent methods.

Lines 345-524 use 8-space indentation while the rest of the file uses 4-space. This appears to be a formatting inconsistency that should be corrected.

🔧 Fix indentation
-        public void Render(Renderer renderer)
-        {
+    public void Render(Renderer renderer)
+    {

Apply similar 4-space indentation to all methods from line 345 onwards.

SharpPhysics.Demo/RainCatcherGame.cs-135-146 (1)

135-146: Unused angle parameter in CreateAngledRamp.

The angle parameter is accepted but never applied to the ramp. The comment mentions "rotation would need polygon" - either remove the parameter or implement rotation with a polygon shape.

🔧 Remove unused parameter or add TODO
-    private void CreateAngledRamp(float x, float y, int width, int height, float angle)
+    private void CreateAngledRamp(float x, float y, int width, int height)
     {
-        // Create a simple angled platform using a box (rotation would need polygon)
+        // TODO: Implement rotation using polygon shapes for actual angled platforms
         var ramp = _physics.CreateStaticBox(

Also update the call sites on lines 119-120.

SharpPhysics.Demo/Integration/PersonColliderBridge.cs-216-217 (1)

216-217: The width, height, and fps parameters are not used for MJPEG streams.

The MjpegCameraFrameSource constructor at line 216 doesn't use the width, height, or fps parameters passed to Start(). This is inconsistent with the OpenCV camera version (line 257) where these parameters are used. Either remove the unused parameters from the MJPEG overload signature, or pass them to the frame source if supported.

🧹 Nitpick comments (40)
physics/Engine/Player/PlayerController.cs (3)

5-5: Unused import.

using System; does not appear to be used in this file. Consider removing it.


37-37: Remove commented-out code.

If this velocity reset is no longer needed, delete the line rather than leaving it commented. Version control preserves history if you need to restore it later.

Suggested diff
             if (normal.Y > 0.5f)
             {
                 _groundObjects.Add(obj);
-                //_player.Velocity = new Vector2(_player.Velocity.X, 0);
                 return;
             }

77-81: Extract magic number to a constant.

The value 2000 should be a named constant (e.g., _slamForce) for consistency with _speed and _jumpForce, and to make tuning easier.

Suggested refactor

Add a field near the other movement constants:

         private float _speed = 150.0f;
         private float _jumpForce = 180.0f;
+        private float _slamForce = 2000.0f;

Then update Slam():

         private void Slam()
         {
-            _player.Velocity = new Vector2(_player.Velocity.X, 2000);
+            _player.Velocity = new Vector2(_player.Velocity.X, _slamForce);
         }
physics/Engine/Shaders/SFMLPolyShader.cs (1)

57-65: Consider preallocating CircleShape to reduce GC pressure.

Given the PR's GC optimization goals, the CircleShape created on every PostDraw call could be preallocated as a class field (like vertexes and contactLines), then updated in place with the current position and color.

♻️ Suggested refactor
 public class SFMLPolyShader : SFMLShader
 {
     // Stores the polygon's outline points.
     private VertexArray vertexes = new VertexArray(PrimitiveType.LineStrip);
     // Preallocated VertexArray for drawing contact normals.
     private VertexArray contactLines = new VertexArray(PrimitiveType.Lines);
+    // Preallocated circle for drawing the object's center.
+    private CircleShape centerCircle = new CircleShape(5f)
+    {
+        Origin = new Vector2f(5f, 5f),
+        OutlineThickness = 1f,
+        FillColor = Color.Transparent
+    };

Then in PostDraw:

-            float radius = 5f;
-            CircleShape circle = new CircleShape(radius)
-            {
-                Origin = new Vector2f(radius, radius),
-                Position = obj.Center.ToSfml(),
-                OutlineThickness = 1f,
-                OutlineColor = obj.Sleeping ? Color.Blue : Color.Red,
-                FillColor = Color.Transparent
-            };
-            target.Draw(circle);
+            centerCircle.Position = obj.Center.ToSfml();
+            centerCircle.OutlineColor = obj.Sleeping ? Color.Blue : Color.Red;
+            target.Draw(centerCircle);
physics/Engine/Shaders/SFMLPolyRainbowShader.cs (5)

13-14: Misleading comment: TriangleFan renders a filled polygon, not an outline.

The comment states "polygon's outline points" but PrimitiveType.TriangleFan renders a filled polygon using the first vertex as the fan origin. If an outline is intended, use LineStrip like SFMLPolyShader. If filled rendering is correct, update the comment.

Suggested comment fix
-        // Stores the polygon's outline points.
+        // Stores the polygon's vertices for filled rendering.
         private VertexArray vertexes = new VertexArray(PrimitiveType.TriangleFan);

54-58: Unnecessary vertex append for TriangleFan.

With PrimitiveType.TriangleFan, the fan automatically connects back to the first vertex. Appending points[0] again creates a degenerate triangle. This pattern is needed for LineStrip (as in SFMLPolyShader) but not for TriangleFan.

Suggested fix
-            // Close the loop.
-            if (points.Length > 0)
-            {
-                vertexes.Append(new Vertex(points[0].ToSfml(), _color));
-            }

69-69: Stale comment: "3)" has no corresponding "1)" or "2)".

This appears to be a copy-paste artifact from SFMLPolyShader.PostDraw where steps 1, 2, and 3 exist. Here, only the normal-drawing step is present.

Suggested fix
-            // 3) Draw a line from each contact point along its normal.
+            // Draw a line from each contact point along its normal.

60-61: Misleading comment: TriangleFan draws a filled polygon, not an outline.

Update the comment to reflect the actual rendering behavior.

Suggested fix
-            // 1) Draw the polygon outline.
+            // Draw the filled polygon.
             target.Draw(vertexes);

18-18: Remove unused property DrawSleepSupports.

This static property is declared but never used anywhere in the codebase. An identical declaration also exists in SFMLPolyShader.cs. Remove this property unless there is a planned feature that requires it.

.gitignore (1)

241-243: Consider moving the *.onnx rule to a more appropriate section.

The *.onnx ignore rule is placed under the "FAKE - F# Make" section, which is misleading. Consider adding a dedicated section for ML/ONNX assets or placing it under "Others" (around line 179).

Also, since the demo project references ONNX models (e.g., models/yolo26s_pose.onnx per gamesettings.json), ensure the README or setup instructions document how users should obtain these model files.

Suggested reorganization
 # Others
 ClientBin/
 [Ss]tyle[Cc]op.*
 ~$*
 *~
 *.dbmdl
 *.dbproj.schemaview
 *.pfx
 *.publishsettings
 node_modules/
 orleans.codegen.cs
+
+# ML/ONNX Models (large binaries, download separately)
+*.onnx

And remove line 243 from under the FAKE section.

README.md (1)

12-12: Track or address the TODO to update the readme.

The TODO comment indicates pending documentation work. Consider creating an issue to track this or completing the documentation update as part of this PR.

Would you like me to help draft updated documentation sections based on the new features introduced in this PR?

PhysicsEngine.sln (1)

3-4: Visual Studio Version 18 header may cause compatibility warnings.

The solution format references Visual Studio Version 18, which corresponds to a preview/future version. Developers using VS 2022 (Version 17) may see compatibility warnings when opening the solution, though it should still load correctly.

physics/Engine/Rendering/UI/IUiClickable.cs (1)

8-9: Consider documenting the OnClick event parameter semantics.

The bool parameter in Action<bool> OnClick has different meanings across implementers:

  • UiCheckbox: passes the new IsChecked state after toggle
  • UiSlider: always passes true when clicked

This semantic inconsistency could confuse consumers. Consider either:

  1. Adding XML documentation to clarify the expected usage
  2. Using a more descriptive event signature (e.g., a custom ClickEventArgs)
Suggested documentation
 public interface IUiClickable
 {
-    // Move the event to the interface
+    /// <summary>
+    /// Raised when the element is clicked.
+    /// The bool parameter meaning is implementation-specific:
+    /// for toggleable controls it indicates the new state,
+    /// for simple buttons it indicates click occurred (always true).
+    /// </summary>
     event Action<bool> OnClick;
     
-    // Keep the click handling method
+    /// <summary>
+    /// Handles a click at the specified position.
+    /// </summary>
+    /// <param name="clickPos">The position of the click in UI coordinates.</param>
+    /// <returns>True if the click was handled by this element; otherwise, false.</returns>
     bool HandleClick(Vector2 clickPos);
 }
physics/Engine/Classes/Objects/PhysicsObject.cs (1)

47-47: Consider resetting sleep state when CanSleep is false.
If CanSleep is turned off at runtime, a sleeping object stays asleep and sleepTimer keeps accumulating, so re‑enabling can immediately put it back to sleep. Consider short‑circuiting UpdateSleepState to wake/reset when CanSleep is false.

💡 Optional guard in UpdateSleepState
 public void UpdateSleepState(float dt)
 {
+    if (!CanSleep)
+    {
+        if (Sleeping) Wake();
+        sleepTimer = 0f;
+        return;
+    }
     if (Locked)
     {
         sleepTimer = 0f;
         return;
     }

Also applies to: 203-204

physics/Engine/Classes/CollisionPairPool.cs (1)

21-25: Consider adding null guard in Return().

If a null CollisionPair is accidentally passed, pair.Reset() will throw. A simple guard prevents this edge case.

Suggested fix
 public void Return(CollisionPair pair)
 {
+    if (pair == null) return;
     pair.Reset();
     _pool.Push(pair);
 }
physics/Engine/Input/InputManager.cs (2)

25-26: prevKeyState appears unused.

The prevKeyState field is updated at lines 272-280 but never read. The "Pressed" states are derived from _pressTimers, not from comparing previous vs current state. This is dead code.

Suggested fix

Remove the unused field and the update block:

-        // Previous frame's key state for edge detection
-        private KeyState prevKeyState = new KeyState();

And remove lines 271-280 in GetKeyState():

-                    // Store current key state as previous for next frame (struct copy, no allocation)
-                    prevKeyState.Left = keyState.Left;
-                    prevKeyState.Right = keyState.Right;
-                    prevKeyState.Up = keyState.Up;
-                    prevKeyState.Down = keyState.Down;
-                    prevKeyState.Space = keyState.Space;
-                    prevKeyState.Escape = keyState.Escape;
-                    prevKeyState.Enter = keyState.Enter;
-                    prevKeyState.Tab = keyState.Tab;
-                    prevKeyState.Backspace = keyState.Backspace;

Also applies to: 272-280


222-283: Inconsistent indentation in GetKeyState() method.

The method body has excessive indentation (appears to be indented 16 spaces instead of 8). This harms readability.

physics/Engine/Helpers/ConstraintHelper.cs (1)

1-9: Unused imports.

System, System.Collections.Generic, and System.Linq appear unused in this file.

Suggested fix
-using System;
-using System.Collections.Generic;
 using physics.Engine.Classes;
 using physics.Engine.Objects;
 using physics.Engine.Shapes;
 using physics.Engine.Constraints;
-using System.Linq;
 using System.Numerics;
 using physics.Engine.Core;
physics/Engine/Rendering/UI/UiSlider.cs (1)

87-97: new modifier hides interface methods unexpectedly.

Using new on HandleDrag and StopDrag hides any base class definitions. Since these are interface implementations, use explicit interface implementation instead for clarity.

Suggested change
-        public new bool HandleDrag(Vector2 dragPos)
+        bool IUiDraggable.HandleDrag(Vector2 dragPos)
         {
             ...
         }
         
-        public new void StopDrag()
+        void IUiDraggable.StopDrag()
         {
             ...
         }

Also applies to: 99-107

physics/Engine/Constraints/Constraint.cs (1)

45-123: Clamp Baumgarte bias in WeldConstraint for stability.

AxisConstraint clamps bias; WeldConstraint doesn’t, so large errors or small dt can produce oversized impulses.

♻️ Proposed adjustment
 private const float BaumgarteBias = 0.1f;
 private const float AngularBias = 0.05f;
+private const float MaxBiasVelocity = 300f;
@@
-                float bias = BaumgarteBias * posError / dt;
+                float bias = BaumgarteBias * posError / dt;
+                bias = Math.Clamp(bias, -MaxBiasVelocity, MaxBiasVelocity);
physics/Engine/Rendering/Renderer.cs (1)

46-54: DrawText leaves the window in UI view.

Because DrawText switches to UiView without restoring the previous view, callers that render via Renderer.Window afterward may draw in the wrong coordinate space. Consider saving/restoring the view.

♻️ Suggested change
 public void DrawText(string text, float x, float y, uint size = 24, Color? color = null)
 {
-    Window.SetView(UiView);
+    var previousView = Window.GetView();
+    Window.SetView(UiView);
     _reusableText.DisplayedString = text;
     _reusableText.CharacterSize = size;
     _reusableText.Position = new Vector2f(x, y);
     _reusableText.FillColor = color ?? Color.White;
     Window.Draw(_reusableText);
+    Window.SetView(previousView);
 }
SharpPhysics.Demo/SettingsGame.cs (2)

112-146: Inconsistent boolean parsing logic.

Lines 113-114 use bool.TryParse() for parsing booleans, but lines 133 and 135 use string comparison (v.ToLower() == "true"). This inconsistency means "True", "TRUE", and "false" are handled differently across settings.

♻️ Suggested fix for consistent boolean parsing
             case "Detection":
                 _currentSettings.Add(new SettingItem("Model Path", _settings.ModelPath, 
                     v => _settings.ModelPath = v, "path to ONNX model"));
                 _currentSettings.Add(new SettingItem("Use GPU", _settings.UseGpu.ToString(), 
-                    v => _settings.UseGpu = v.ToLower() == "true", "true/false"));
+                    v => { if (bool.TryParse(v, out var b)) _settings.UseGpu = b; }, "true/false"));
                 _currentSettings.Add(new SettingItem("Mirror Mode (FlipX)", _settings.FlipX.ToString(), 
-                    v => _settings.FlipX = v.ToLower() == "true", "true/false"));
+                    v => { if (bool.TryParse(v, out var b)) _settings.FlipX = b; }, "true/false"));

386-393: Avoid empty catch blocks that silently swallow exceptions.

The empty catch block hides any errors during event unsubscription. At minimum, log exceptions for debugging purposes, or handle only the specific expected exception type.

♻️ Suggested fix
         try
         {
             _engine.Renderer.Window.TextEntered -= OnTextEntered;
         }
-        catch { }
+        catch (Exception ex)
+        {
+            Console.WriteLine($"Warning: Failed to unsubscribe TextEntered: {ex.Message}");
+        }
physics/Engine/PhysicsSystem.cs (1)

513-521: Consider pooling cell lists to reduce GC pressure.

A new List<PhysicsObject> is allocated for each spatial hash cell every frame. For scenes with many cells, this could generate allocation pressure.

Consider maintaining a pool of lists or reusing a single list per cell across frames if profiling shows this as a hotspot.

SharpPhysics.Demo/Program.cs (1)

19-23: Consider logging the full exception for debugging.

Only ex.Message is logged, which omits the stack trace. During development, the full exception details are valuable for diagnosing issues.

♻️ Suggested improvement
 catch (Exception ex)
 {
     Console.WriteLine("Fatal error starting the game engine:");
-    Console.WriteLine(ex.Message);
+    Console.WriteLine(ex);
 }
SharpPhysics.Demo/Integration/TrackedPerson.cs (1)

36-43: Consider caching or avoiding allocation in GetTrackingBalls().

This method allocates a new list on every call. If called frequently (e.g., per frame), consider caching the result or returning an array/span.

♻️ Alternative implementation to avoid per-call allocation
+    private PhysicsObject[]? _trackingBallsCache;
+    private int _trackingBallsCacheVersion = -1;
+    private int _ballsVersion = 0;
+
     public IReadOnlyList<PhysicsObject> GetTrackingBalls()
     {
-        var balls = new List<PhysicsObject>();
-        if (HeadBall != null) balls.Add(HeadBall);
-        if (LeftHandBall != null) balls.Add(LeftHandBall);
-        if (RightHandBall != null) balls.Add(RightHandBall);
-        return balls;
+        // Simple approach: return array with nulls filtered
+        var count = (HeadBall != null ? 1 : 0) + 
+                    (LeftHandBall != null ? 1 : 0) + 
+                    (RightHandBall != null ? 1 : 0);
+        var result = new PhysicsObject[count];
+        int idx = 0;
+        if (HeadBall != null) result[idx++] = HeadBall;
+        if (LeftHandBall != null) result[idx++] = LeftHandBall;
+        if (RightHandBall != null) result[idx++] = RightHandBall;
+        return result;
     }

Or if the balls rarely change, cache the result and invalidate when setters are called.

SharpPhysics.Demo/BubblePopGame.cs (2)

44-51: Verify spawn weight totals exactly 1.0.

The SpawnWeight values (0.4 + 0.3 + 0.2 + 0.08 + 0.02) sum to 1.0, which is correct. However, floating-point arithmetic could introduce precision issues. Consider adding a comment or normalizing at runtime for robustness.


180-180: Move RandomExtensions to a shared helpers file.

_random.NextFloat() relies on the extension method RandomExtensions defined in RainCatcherGame.cs (line 575-577), but it's also used in MenuGame.cs and BubblePopGame.cs. This creates an implicit cross-file dependency where multiple game files depend on an extension defined in an unrelated game class. The project already has a Helpers directory; moving RandomExtensions there would make this utility more discoverable and logically organized.

physics/Engine/Rendering/UI/UiMenuButton.cs (1)

111-131: Duplicate bounds-checking logic between HandleClick and ContainsPoint.

Both HandleClick and ContainsPoint perform identical bounds checking. Consider having HandleClick delegate to ContainsPoint for DRY compliance.

♻️ Proposed refactor
 bool IUiClickable.HandleClick(Vector2 clickPos)
 {
-    var bounds = _background.GetGlobalBounds();
-    if (clickPos.X >= bounds.Left && clickPos.X <= bounds.Left + bounds.Width &&
-        clickPos.Y >= bounds.Top && clickPos.Y <= bounds.Top + bounds.Height)
+    if (ContainsPoint(clickPos))
     {
         OnClick?.Invoke(true);
         return true;
     }
     return false;
 }
SharpPhysics.Demo/DemoGame.cs (2)

76-86: Remove or document commented-out code.

The commented ball grid creation code should either be removed if no longer needed or documented with a reason for keeping it (e.g., for future testing). Commented-out code adds noise and maintenance burden.


96-251: Consider breaking down CreateCarDemo into smaller focused methods.

At ~155 lines, this method handles car body, wheels, axis constraints, weld constraints, rope chains, and circle formations. Consider extracting logical groups into separate methods for improved readability and testability.

♻️ Suggested structure
private void CreateCarDemo(uint worldWidth, uint worldHeight)
{
    var carBody = CreateCarBody(worldWidth, worldHeight);
    AttachWheels(carBody, worldWidth, worldHeight);
    AttachBodyParts(carBody, worldWidth, worldHeight);
    CreateRopeChains();
    CreateCircleOfBalls();
}
SharpPhysics.Demo/RainCatcherGame.cs (2)

574-578: Move RandomExtensions to a shared helper file.

This extension method is used by both RainCatcherGame and BubblePopGame. Placing it in a shared location (e.g., SharpPhysics.Demo/Helpers/RandomExtensions.cs) would improve discoverability and prevent duplication.


231-267: UpdateTimers has side effects on physics system.

This method modifies _physics.Gravity during the gravity pulse effect. Consider renaming to UpdateTimersAndEffects or extracting the gravity modification to make the side effect explicit.

physics/Engine/Classes/Templates/ActionTemplates.cs (1)

21-24: Simplify Vector2 delta calculation.

The verbose Vector2 initialization can be simplified.

♻️ Simplified code
-    Vector2 delta = (new Vector2 { X = EndPointF.X, Y = EndPointF.Y } -
-                  new Vector2 { X = StartPointF.X, Y = StartPointF.Y });
+    Vector2 delta = EndPointF - StartPointF;
     _physicsSystem.AddVelocityToActive(delta * 2);
SharpPhysics.Demo/Settings/GameSettings.cs (2)

14-34: Consider marking _instance as volatile for thread-safe singleton.

The double-checked locking pattern requires the instance field to be volatile to ensure that when a thread sees a non-null _instance, it also sees a fully constructed object. Without volatile, there's a theoretical risk of another thread seeing a non-null but partially initialized object.

Suggested fix
-    private static GameSettings? _instance;
+    private static volatile GameSettings? _instance;

2-2: Unused import.

System.Numerics is imported but Vector2 doesn't appear to be used in this file.

 `#nullable` enable
-using System.Numerics;
 using System.Text.Json;
physics/Engine/Classes/Templates/ObjectTemplates.cs (2)

20-23: Consider adding null check for constructor parameter.

If physicsSystem is null, the error will manifest later when calling factory methods, making debugging harder. Adding an early guard clause improves error reporting.

Suggested fix
 public ObjectTemplates(PhysicsSystem physicsSystem)
 {
+    ArgumentNullException.ThrowIfNull(physicsSystem);
     _physicsSystem = physicsSystem;
 }

26-49: Minor: Consider using TryGetValue instead of ContainsKey + indexer.

The current pattern performs two dictionary lookups. Using TryGetValue reduces this to one lookup per access.

Suggested refactor
 private T GetShader<T>(int diameter) where T : SFMLShader
 {
     Type shaderType = typeof(T);
-    if (!shaderPool.ContainsKey(shaderType))
+    if (!shaderPool.TryGetValue(shaderType, out var innerDict))
     {
-        shaderPool[shaderType] = new Dictionary<int, SFMLShader>();
+        innerDict = new Dictionary<int, SFMLShader>();
+        shaderPool[shaderType] = innerDict;
     }
-    var innerDict = shaderPool[shaderType];
-    if (!innerDict.ContainsKey(diameter))
+    if (!innerDict.TryGetValue(diameter, out var shader))
     {
         // Try to get a constructor that takes an int parameter.
         var constructor = shaderType.GetConstructor(new Type[] { typeof(int) });
         if (constructor != null)
         {
-            innerDict[diameter] = (SFMLShader)Activator.CreateInstance(shaderType, diameter);
+            shader = (SFMLShader)Activator.CreateInstance(shaderType, diameter)!;
         }
         else
         {
             // If not available, use the default constructor.
-            innerDict[diameter] = (SFMLShader)Activator.CreateInstance(shaderType);
+            shader = (SFMLShader)Activator.CreateInstance(shaderType)!;
         }
+        innerDict[diameter] = shader;
     }
-    return (T)innerDict[diameter];
+    return (T)shader;
 }
SharpPhysics.Demo/Integration/PersonColliderBridge.cs (2)

202-279: Significant code duplication between Start overloads.

Both Start(string url, ...) and Start(int cameraIndex, ...) share nearly identical code for model loading, detector initialization, thread creation, and error handling. Only the frame source instantiation differs. Consider extracting the common logic.

Suggested refactor
+private void StartInternal(IFrameSource frameSource, string sourceDescription)
+{
+    try
+    {
+        Console.WriteLine($"[PersonBridge] Loading model from: {System.IO.Path.GetFullPath(_modelPath)}");
+
+        if (!System.IO.File.Exists(_modelPath))
+        {
+            throw new System.IO.FileNotFoundException($"Model file not found: {_modelPath}");
+        }
+
+        _poseDetector = new YoloV26PoseDetector(_modelPath, useGpu: true, maxPeople: _maxPeople);
+        Console.WriteLine("[PersonBridge] Yolo model loaded successfully");
+
+        _camera = frameSource;
+        Console.WriteLine($"[PersonBridge] {sourceDescription}");
+        Console.WriteLine($"[PersonBridge] Multi-person tracking enabled (max {_maxPeople} people)");
+
+        _cts = new CancellationTokenSource();
+        _isRunning = true;
+
+        _detectionThread = new Thread(() => DetectionLoop(_cts.Token))
+        {
+            Name = "PersonColliderBridge-Detection",
+            IsBackground = true
+        };
+        _detectionThread.Start();
+
+        Console.WriteLine($"[PersonBridge] Detection thread started");
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine($"[PersonBridge] Failed to start: {ex.Message}");
+        Console.WriteLine($"[PersonBridge] Stack trace: {ex.StackTrace}");
+        OnError?.Invoke(this, ex);
+    }
+}
+
 public void Start(string url, int width = 640, int height = 480, int fps = 30)
 {
-    // ... current implementation
+    var source = new MjpegCameraFrameSource(url, 5, true);
+    StartInternal(source, $"Stream {url} opened at {width}x{height} @ {fps}fps");
 }
 
 public void Start(int cameraIndex = 0, int width = 640, int height = 480, int fps = 30)
 {
-    // ... current implementation
+    var source = new OpenCvCameraFrameSource(cameraIndex, width, height, fps);
+    StartInternal(source, $"Camera {cameraIndex} opened at {width}x{height} @ {fps}fps");
 }

686-695: Consider whether setting velocity is necessary for locked objects.

MoveBallToPosition unlocks the ball, sets velocity and moves it, then re-locks. Since the ball is immediately locked again, the velocity assignment (line 692) may have no effect—the physics system likely ignores velocity for locked objects. If the velocity serves no purpose here, consider removing it for clarity.

Comment on lines +33 to +37
public void Reset()
{
A = null;
B = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential NullReferenceException after Reset() is called.

After Reset() sets A and B to null, both Equals() and GetHashCode() will throw NullReferenceException since they access this.A.Equals() and A.GetHashCode() without null checks.

While pooled pairs shouldn't typically be used in equality comparisons, defensive null-checks would prevent subtle runtime crashes.

Suggested fix
 public bool Equals(CollisionPair other)
 {
-    if (other == null)
+    if (other == null || A == null || B == null || other.A == null || other.B == null)
         return false;

     return (this.A.Equals(other.A) && this.B.Equals(other.B)) ||
            (this.A.Equals(other.B) && this.B.Equals(other.A));
 }

 public override int GetHashCode()
 {
+    if (A == null || B == null)
+        return 0;
     int hashA = A.GetHashCode();
     int hashB = B.GetHashCode();
     return hashA ^ hashB;
 }

Also applies to: 44-58

🤖 Prompt for AI Agents
In `@physics/Engine/Classes/CollisionPair.cs` around lines 33 - 37,
CollisionPair.Reset() sets A and B to null but Equals() and GetHashCode() call
A.Equals(...) and A.GetHashCode() directly, which can throw
NullReferenceException; update CollisionPair.Equals(...) and
CollisionPair.GetHashCode() to defensively handle nulls by checking A and B for
null (use ReferenceEquals or explicit null checks) before calling
Equals/GetHashCode, return appropriate fallback values (e.g., false for
comparisons when the other operand isn't matching or 0/combined hash when fields
are null) and keep equality symmetrical and consistent with GetHashCode.


// Friction, in newtons
public float Friction = 0.5f;
public static float Friction { get; set; }= 0.5f;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Static Friction makes per‑object tuning impossible.
If any code expects per‑object friction, this change silently globalizes it. Consider keeping an instance property with a static default to preserve configurability.

🛠️ Suggested adjustment
-        public static float Friction { get; set; }= 0.5f;
+        public static float DefaultFriction { get; set; } = 0.5f;
+        public float Friction { get; set; } = DefaultFriction;
🤖 Prompt for AI Agents
In `@physics/Engine/Classes/Objects/PhysicsObject.cs` at line 29, The Friction
property was made static which forces a global value and prevents per-object
tuning; revert to an instance property on PhysicsObject and keep a static
default constant for new instances. Replace the static Friction with an instance
property named Friction (get/set) initialized to a static DefaultFriction (or
DefaultFrictionValue) set to 0.5f, and update constructors/usages that
referenced PhysicsObject.Friction to use the instance property (obj.Friction) or
the static DefaultFriction when creating new objects.

Comment on lines +19 to 25
public void Launch(PhysicsObject physObj, Vector2 StartPointF, Vector2 EndPointF)
{
physSystem.ActivateAtPoint(StartPointF);
_physicsSystem.ActivateAtPoint(StartPointF);
Vector2 delta = (new Vector2 { X = EndPointF.X, Y = EndPointF.Y } -
new Vector2 { X = StartPointF.X, Y = StartPointF.Y });
physSystem.AddVelocityToActive(delta * 2);
_physicsSystem.AddVelocityToActive(delta * 2);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unused physObj parameter in Launch method.

The physObj parameter is passed but never used. The method activates an object at StartPointF instead of using the provided physObj. This appears to be a bug or incomplete refactoring.

🐛 Proposed fix - use the provided object

Option 1: Remove the unused parameter if activation by point is intended:

-public void Launch(PhysicsObject physObj, Vector2 StartPointF, Vector2 EndPointF)
+public void Launch(Vector2 StartPointF, Vector2 EndPointF)
 {
     _physicsSystem.ActivateAtPoint(StartPointF);

Option 2: Use the provided object directly:

 public void Launch(PhysicsObject physObj, Vector2 StartPointF, Vector2 EndPointF)
 {
-    _physicsSystem.ActivateAtPoint(StartPointF);
+    _physicsSystem.SetActiveObject(physObj);
     Vector2 delta = (new Vector2 { X = EndPointF.X, Y = EndPointF.Y } -
                   new Vector2 { X = StartPointF.X, Y = StartPointF.Y });
     _physicsSystem.AddVelocityToActive(delta * 2);
 }
🤖 Prompt for AI Agents
In `@physics/Engine/Classes/Templates/ActionTemplates.cs` around lines 19 - 25,
The Launch method's physObj parameter is unused; either remove the parameter
from Launch(PhysicsObject physObj, Vector2 StartPointF, Vector2 EndPointF) if
activation is meant to be location-only, or update the method to activate and
apply velocity to the provided PhysicsObject instead of activating by
StartPointF: replace the call to _physicsSystem.ActivateAtPoint(StartPointF)
with the appropriate activation call for the object (e.g., activate/mark physObj
in the physics system) and ensure _physicsSystem.AddVelocityToActive applies the
computed delta*2 to that activated physObj; keep the existing delta calculation
(from StartPointF to EndPointF) and pass or associate the velocity with physObj
so physObj is actually used.

Comment on lines +35 to 46
public void PopAndMultiply()
{
foreach(PhysicsObject obj in PhysicsSystem.ListStaticObjects)
foreach(PhysicsObject obj in _physicsSystem.ListStaticObjects)
{
physSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
_physicsSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
var velocity = obj.Velocity;
var origin = obj.Center;
physSystem.RemoveActiveObject();
physSystem.SetVelocity(ObjectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
physSystem.SetVelocity(ObjectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
_physicsSystem.RemoveActiveObject();
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Iterating over collection while potentially modifying it.

PopAndMultiply iterates over _physicsSystem.ListStaticObjects while calling RemoveActiveObject(), which may modify the underlying collection. This could cause a InvalidOperationException or skip elements.

🐛 Proposed fix - snapshot the collection first
 public void PopAndMultiply()
 {
-    foreach(PhysicsObject obj in _physicsSystem.ListStaticObjects)
+    var objectsToProcess = _physicsSystem.ListStaticObjects.ToList();
+    foreach(PhysicsObject obj in objectsToProcess)
     {
         _physicsSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
         var velocity = obj.Velocity;
         var origin = obj.Center;
         _physicsSystem.RemoveActiveObject();
         _physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
         _physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void PopAndMultiply()
{
foreach(PhysicsObject obj in PhysicsSystem.ListStaticObjects)
foreach(PhysicsObject obj in _physicsSystem.ListStaticObjects)
{
physSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
_physicsSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
var velocity = obj.Velocity;
var origin = obj.Center;
physSystem.RemoveActiveObject();
physSystem.SetVelocity(ObjectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
physSystem.SetVelocity(ObjectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
_physicsSystem.RemoveActiveObject();
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
}
}
public void PopAndMultiply()
{
var objectsToProcess = _physicsSystem.ListStaticObjects.ToList();
foreach(PhysicsObject obj in objectsToProcess)
{
_physicsSystem.ActivateAtPoint(new Vector2(obj.Center.X, obj.Center.Y));
var velocity = obj.Velocity;
var origin = obj.Center;
_physicsSystem.RemoveActiveObject();
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
_physicsSystem.SetVelocity(_objectTemplates.CreateSmallBall(origin.X, origin.Y), velocity);
}
}
🤖 Prompt for AI Agents
In `@physics/Engine/Classes/Templates/ActionTemplates.cs` around lines 35 - 46,
PopAndMultiply iterates _physicsSystem.ListStaticObjects while calling
_physicsSystem.RemoveActiveObject(), which can mutate the collection; fix by
iterating a snapshot copy instead of the live collection: capture the objects
into a separate list/array (e.g., call ToList() or copy into a new
List<PhysicsObject>) and loop over that snapshot in PopAndMultiply, keeping the
existing ActivateAtPoint/RemoveActiveObject/SetVelocity calls (referencing
PopAndMultiply, _physicsSystem.ListStaticObjects, RemoveActiveObject,
ActivateAtPoint, SetVelocity, and _objectTemplates.CreateSmallBall to locate the
code).

Comment on lines +338 to +344
// If circle is inside, reverse the normal
if (!circleIsOutside)
{
normal = -normal;
// Penetration is radius plus distance to closest edge
m.Penetration = radius + dist;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inside-circle penetration uses the wrong formula.
Line 343 adds the distance to the radius, which over-penetrates when the circle center is inside the polygon. Penetration should be radius - dist (same as outside, but with flipped normal).

✅ Proposed fix
-                    m.Penetration = radius + dist;
+                    m.Penetration = Math.Max(radius - dist, 0f);
🤖 Prompt for AI Agents
In `@physics/Engine/Collision.cs` around lines 338 - 344, The penetration
calculation for an inside-circle case is wrong: in the block guarded by "if
(!circleIsOutside)" (where you already flip normal = -normal), change the
penetration assignment from m.Penetration = radius + dist to m.Penetration =
radius - dist so penetration equals radius minus distance (same magnitude as the
outside case) with the flipped normal; keep the normal flip and variable names
(circleIsOutside, normal, radius, dist, m.Penetration) as-is.

Comment on lines +4 to +12
<OutputType>Library</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>SharpPhysics.Engine</RootNamespace>
<AssemblyName>SharpPhysics.Engine</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>false</UseWindowsForms>
<Platforms>AnyCPU</Platforms>
<PlatformTarget>AnyCPU</PlatformTarget>
<Nullable>enable</Nullable>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find all .csproj files to identify project structure
find . -name "*.csproj" -type f | head -20

Repository: dankelley2/sharpPhysics

Length of output: 188


🏁 Script executed:

# Check for any usage of old "physics" namespace in C# files
rg -n 'using\s+physics\b|namespace\s+physics\b' --type cs

Repository: dankelley2/sharpPhysics

Length of output: 12540


🏁 Script executed:

# Search for SFML.Net references and versions
rg -n 'SFML\.Net' --type csproj --type props

Repository: dankelley2/sharpPhysics

Length of output: 97


🏁 Script executed:

# Check all target frameworks in the repository
rg -n 'TargetFramework' --type csproj | head -20

Repository: dankelley2/sharpPhysics

Length of output: 97


🏁 Script executed:

# Look at the actual SharpPhysics.csproj file in full
cat physics/SharpPhysics.csproj

Repository: dankelley2/sharpPhysics

Length of output: 819


🏁 Script executed:

cat SharpPhysics.Tests/SharpPhysics.Tests.csproj

Repository: dankelley2/sharpPhysics

Length of output: 1517


🏁 Script executed:

cat SharpPhysics.Demo/SharpPhysics.Demo.csproj

Repository: dankelley2/sharpPhysics

Length of output: 898


🏁 Script executed:

# Check for any namespace changes in git (if this is a git repo)
git log --oneline -n 20 -- physics/SharpPhysics.csproj 2>/dev/null || echo "Not a git repo or limited history"

Repository: dankelley2/sharpPhysics

Length of output: 106


Fix the RootNamespace/AssemblyName mismatch—code still uses physics.Engine namespace, not SharpPhysics.Engine

The csproj declares RootNamespace and AssemblyName as SharpPhysics.Engine, but all C# files in the physics, Demo, and Tests projects still use the physics.Engine namespace. This creates a mismatch where the assembly name doesn't match its contained code namespaces. Either update all source files to use the new SharpPhysics.Engine namespace, or revert the csproj changes to keep the existing physics root namespace. Additionally, consider pinning the SFML.Net version to ensure compatibility across projects.

🤖 Prompt for AI Agents
In `@physics/SharpPhysics.csproj` around lines 4 - 12, The project file
SharpPhysics.csproj declares RootNamespace and AssemblyName as
SharpPhysics.Engine which conflicts with existing source files using the
physics.Engine namespace; fix by making them consistent: either change all
source file namespaces from physics.Engine to SharpPhysics.Engine (update
namespace declarations in classes across physics, Demo, and Tests) or revert
RootNamespace and AssemblyName in SharpPhysics.csproj to physics.Engine so they
match current code; additionally pin the SFML.Net package version in the project
file to a specific known-good version to ensure compatibility across projects.

Comment on lines +10 to +15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectorSegmentation.Vision", "..\..\ProjectorSegmentation\src\ProjectorSegmentation.Vision\ProjectorSegmentation.Vision.csproj", "{58D568AA-7BA6-90BF-B217-BF1497DB529A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectorSegmentation.Demo", "..\..\ProjectorSegmentation\src\ProjectorSegmentation.Demo\ProjectorSegmentation.Demo.csproj", "{C3112F8F-13A2-AC9D-988A-397C4B3B3A44}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpPhysics.Demo", "SharpPhysics.Demo\SharpPhysics.Demo.csproj", "{DC24BC1D-93C6-4062-B20C-A7C34BF60C88}"
EndProject
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if README.md exists and search for documentation about external dependencies
if [ -f "README.md" ]; then
  echo "=== README.md exists ===" 
  echo "Searching for ProjectorSegmentation mentions:"
  rg -i "ProjectorSegmentation" README.md
  echo -e "\n=== Searching for clone instructions ==="
  rg -i "clone" README.md
else
  echo "README.md not found"
fi

# Check actual project structure to understand dependencies
echo -e "\n=== Repository structure ==="
git ls-files | grep -E "(\.sln|\.csproj)" | head -20

Repository: dankelley2/sharpPhysics

Length of output: 455


🏁 Script executed:

# Get the full context around ProjectorSegmentation mentions in README
echo "=== Full context around ProjectorSegmentation in README ==="
rg -B 3 -A 3 "ProjectorSegmentation" README.md

# Also check for setup/build/installation sections
echo -e "\n=== Looking for setup/build/installation sections ==="
rg -i "(setup|installation|build|requirements|dependencies)" README.md | head -20

# Get full README to understand its structure
echo -e "\n=== README.md full content ==="
wc -l README.md
head -100 README.md

Repository: dankelley2/sharpPhysics

Length of output: 4677


External project references lack setup documentation.

The ProjectorSegmentation.Vision and ProjectorSegmentation.Demo projects reference paths outside this repository (..\..\ProjectorSegmentation\...), which assumes both repositories are cloned as siblings. While the README mentions the ProjectorSegmentation project exists, the Installation and Setup section provides no guidance on this multi-repository structure. Contributors cloning only sharpPhysics will encounter build failures.

Add explicit setup instructions documenting how to organize both repositories, or make these projects optional/conditionally included in the solution.

🤖 Prompt for AI Agents
In `@PhysicsEngine.sln` around lines 10 - 15, The solution contains external
project references (ProjectorSegmentation.Vision and ProjectorSegmentation.Demo)
pointing to sibling repo paths which break builds for contributors who only
clone this repo; update docs and/or solution so builds are reproducible: add
clear setup instructions in the README/Installation section describing that
ProjectorSegmentation must be cloned as a sibling directory (example structure
and git clone commands), or make the external projects optional by
unloading/guarding them in the solution (remove or conditionally include
ProjectorSegmentation.Vision and ProjectorSegmentation.Demo from
PhysicsEngine.sln and add a note how to re-add them), and include a brief
troubleshooting note about build errors referencing those project GUIDs
({58D568AA-7BA6-90BF-B217-BF1497DB529A}, {C3112F8F-13A2-AC9D-988A-397C4B3B3A44})
so contributors know how to proceed.

Comment on lines +493 to +499
else if (_isMousePressedRight && !_isCreatingBox)
{
if (_engine.PhysicsSystem.ActivateAtPoint(_mousePosition))
{
_engine.PhysicsSystem.RemoveActiveObject();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's search for RemoveActiveObject usage as suggested
rg -n "RemoveActiveObject" --type=cs -B3 -A3

Repository: dankelley2/sharpPhysics

Length of output: 2225


🏁 Script executed:

# Also search for the file and understand the broader context
find . -name "DemoGame.cs" -type f

Repository: dankelley2/sharpPhysics

Length of output: 98


🏁 Script executed:

# Get the full context of the problematic code section
rg -n "_isMousePressedRight" --type=cs -B5 -A5

Repository: dankelley2/sharpPhysics

Length of output: 3139


🏁 Script executed:

# Get more context around line 424
sed -n '410,435p' SharpPhysics.Demo/DemoGame.cs

Repository: dankelley2/sharpPhysics

Length of output: 948


🏁 Script executed:

# Get the full OnMouseMoved method and surrounding context
sed -n '474,510p' SharpPhysics.Demo/DemoGame.cs

Repository: dankelley2/sharpPhysics

Length of output: 1478


🏁 Script executed:

# Check the ActivateAtPoint method to understand what it does
rg -n "ActivateAtPoint" --type=cs -B2 -A8

Repository: dankelley2/sharpPhysics

Length of output: 5301


Right-mouse deletion logic causes unintended continuous object removal during mouse drag.

When right-clicking on an object, RemoveActiveObject() is called immediately in OnMouseButtonPressed (line 424). However, _isMousePressedRight remains true throughout the drag, causing OnMouseMoved to repeatedly attempt deletion on any objects under the cursor. This differs from left-click behavior, which only grabs the initially targeted object and doesn't repeat during drag motion. If the cursor moves over other objects while dragging, they will be deleted as well.

Consider either: (1) removing the deletion logic from OnMouseMoved, (2) tracking whether an object was already deleted in this press cycle, or (3) only deleting on button release instead of press.

🤖 Prompt for AI Agents
In `@SharpPhysics.Demo/DemoGame.cs` around lines 493 - 499, The right-mouse
deletion currently runs in the OnMouseMoved path (when _isMousePressedRight &&
!_isCreatingBox) calling _engine.PhysicsSystem.RemoveActiveObject() repeatedly;
change this so deletion happens only once per press: either remove the deletion
logic from the OnMouseMoved block and keep removal in OnMouseButtonPressed, or
add a press-cycle flag (e.g. _rightDeletedThisPress) that you set when
RemoveActiveObject() is called and check/reset on button release; update the
handlers OnMouseButtonPressed (where initial deletion currently occurs),
OnMouseMoved (remove or gate the call to
_engine.PhysicsSystem.RemoveActiveObject()), and OnMouseButtonReleased to reset
the flag if you choose the press-cycle approach.

Comment on lines +494 to +504
private void HandleDebugInput(KeyState keyState)
{
// Space to spawn extra balls (for testing)
if (keyState.Space)
{
for (int i = 0; i < 5; i++)
{
SpawnBall();
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Space key spawns balls continuously every frame while held.

keyState.Space is true for every frame the key is held, spawning 5 balls per frame. This could quickly exceed MAX_BALLS. Consider adding a cooldown or checking for key-down transition only.

🐛 Proposed fix with cooldown
+    private bool _spaceWasPressed = false;
+
     private void HandleDebugInput(KeyState keyState)
     {
         // Space to spawn extra balls (for testing)
-        if (keyState.Space)
+        if (keyState.Space && !_spaceWasPressed)
         {
             for (int i = 0; i < 5; i++)
             {
                 SpawnBall();
             }
         }
+        _spaceWasPressed = keyState.Space;
     }
🤖 Prompt for AI Agents
In `@SharpPhysics.Demo/RainCatcherGame.cs` around lines 494 - 504, The Space key
handling in HandleDebugInput currently spawns 5 balls every frame while
keyState.Space is true, causing uncontrolled spawning; change it to only trigger
on a key-down transition or implement a cooldown to respect MAX_BALLS: add state
(e.g., a private bool lastSpaceDown or a float spaceCooldown) to detect when
Space goes from false to true (or decrement cooldown each update and only spawn
when <=0), call SpawnBall up to remaining capacity (respecting MAX_BALLS) and
update the state/cooldown after spawning; reference HandleDebugInput,
keyState.Space, SpawnBall and MAX_BALLS to locate where to apply the change.

Comment on lines +13 to +15
<ProjectReference Include="..\physics\SharpPhysics.csproj" />
<ProjectReference Include="..\..\..\ProjectorSegmentation\src\ProjectorSegmentation.Vision\ProjectorSegmentation.Vision.csproj" />
</ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

External project reference depends on specific directory structure.

The reference to ProjectorSegmentation.Vision.csproj uses a relative path that navigates outside this repository. This creates a hard dependency on a sibling repository being present at a specific location.

As noted in the solution file review, this will cause build failures for contributors who only clone this repository. Consider documenting the required setup or making this dependency optional.

🤖 Prompt for AI Agents
In `@SharpPhysics.Demo/SharpPhysics.Demo.csproj` around lines 13 - 15,
SharpPhysics.Demo.csproj currently contains a hard-coded ProjectReference to
..\..\..\ProjectorSegmentation\src\ProjectorSegmentation.Vision\ProjectorSegmentation.Vision.csproj
which forces a specific sibling-repo layout; change this to either (A) make the
reference conditional by wrapping the ProjectReference with an MSBuild Condition
that only includes it when a property like ProjectorSegmentationPath is set
(e.g., use Condition="'$(ProjectorSegmentationPath)' != ''" and reference via
that property), or (B) replace the ProjectReference with a PackageReference to a
published ProjectorSegmentation.Vision NuGet package, and add clear README setup
instructions for contributors to either set ProjectorSegmentationPath or install
the package; update SharpPhysics.Demo.csproj and project documentation
accordingly.

@dankelley2 dankelley2 merged commit 814e7a7 into master Jan 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants