Skip to content

3D stick-ball resolver + a partially functional 3D playground#300

Merged
ekiefl merged 4 commits into
mainfrom
ek/3d-stick-ball-2
May 18, 2026
Merged

3D stick-ball resolver + a partially functional 3D playground#300
ekiefl merged 4 commits into
mainfrom
ek/3d-stick-ball-2

Conversation

@ekiefl
Copy link
Copy Markdown
Owner

@ekiefl ekiefl commented May 18, 2026

Summary

This PR splits the stick-ball resolver into 2D and 3D variants and stands up
enough of the surrounding infrastructure that a 3D-mode SimulationEngine
can actually be constructed and exercised — at least for trajectories where
the only event types that need to fire are BALL_TABLE and transitions.

It does not vendor real 3D detection algorithms (those land per event type
in future PRs). Instead, the *_3d_event detector stubs that previously
raised NotImplementedError now return null_event(np.inf). The result is
a 3D engine that runs without exceptions, with the math-ready event types
firing correctly and the not-yet-ported ones simply contributing no
candidates. Enough to playtest airborne motion end-to-end.

What changed

Stick-ball resolver — 2D and 3D variants. The old InstantaneousPoint
became InstantaneousPoint3D: the canonical math, with the previously-
zeroed v·sin(θ) line uncommented so cue elevation produces real vertical
velocity. InstantaneousPoint2D is a thin subclass that calls
super().solve() and then clamps vz = 0 (and re-classifies state via
final_ball_motion_state, so the state label stays consistent with the
clamped velocity). The StickBallModel enum splits into
INSTANTANEOUS_POINT_2D and INSTANTANEOUS_POINT_3D; default_resolver()
ships the 2D variant; resolver VERSION bumps 10 → 11.

New sandbox: sandbox/airborne_demos.py. Three demos:

  • drop — handcrafted airborne ball at z=0.3 m with a small +x nudge.
  • impulse_into — handcrafted airborne ball at table height with a
    strong downward strike plus +y nudge.
  • jump — a genuine cue strike with theta=60°, V0=2, phi=90°,
    a=b=0. No handcrafted state: InstantaneousPoint3D produces the
    airborne motion from cue elevation.

Summary by CodeRabbit

  • New Features

    • Added 3D trajectory simulation capabilities with airborne ball support.
    • Added demonstration scenarios for 3D ball physics.
  • Changes

    • Improved cue strike mechanics with separate 2D and 3D modes.
    • Updated 3D collision detection to gracefully handle unsupported scenarios.
    • Updated default physics resolver configuration.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Walkthrough

This PR activates 3D ball physics simulation by disabling unimplemented 3D collision detectors, splitting the stick-ball impact model into 2D and 3D variants handling vertical velocity differently, implementing full 3D velocity calculation using cue-strike angles, and demonstrating the capabilities with airborne ball scenarios.

Changes

3D Ball Physics Activation

Layer / File(s) Summary
3D Collision Detection Stubs
pooltool/evolution/event_based/detect/ball_ball.py, pooltool/evolution/event_based/detect/ball_cushion.py, pooltool/evolution/event_based/detect/ball_pocket.py
Replace NotImplementedError with null_event(np.inf) in get_next_ball_ball_3d_event, get_next_ball_linear_cushion_3d_event, get_next_ball_circular_cushion_3d_event, and get_next_ball_pocket_3d_event to signal "no collision found" instead of halting.
Stick-Ball Model Contract
pooltool/physics/resolve/models.py
Split StickBallModel enum: INSTANTANEOUS_POINT_3D preserves vertical velocity from 3D cue-strike math; INSTANTANEOUS_POINT_2D applies same math but clamps vertical velocity to zero for table-surface simulations.
Resolver Configuration & Registry
pooltool/physics/resolve/resolver.py, pooltool/physics/resolve/stick_ball/__init__.py
Bump resolver version to 11, update default resolver to use InstantaneousPoint2D, and register both InstantaneousPoint2D and InstantaneousPoint3D in the stick-ball strategy registry.
Ball Motion State Classification
pooltool/physics/resolve/stick_ball/core.py
Add final_ball_motion_state(rvw, R) helper that returns airborne when z-velocity is nonzero, otherwise sliding.
3D Stick-Ball Physics Implementation
pooltool/physics/resolve/stick_ball/instantaneous_point/__init__.py
Compute full 3D ball velocity including sin(theta) vertical component, rename InstantaneousPoint to InstantaneousPoint3D with Dim.THREE default, use final_ball_motion_state() for spin classification, and add InstantaneousPoint2D subclass that zeroes vertical velocity after parent solve.
Demo Scenarios for 3D Trajectories
sandbox/airborne_demos.py
Add CLI tool with three demo scenarios (drop: handcrafted airborne state, impulse_into: downward-velocity airborne, jump: cue strike at elevation) that use a patched 3D resolver and show 3D trajectory visualization.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • ekiefl/pooltool#299: Introduces the 3D event detector functions and refactors detection strategy that this PR completes by replacing stub NotImplementedError raises with null-event returns.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: introducing a 3D stick-ball resolver and providing a playground/demo infrastructure for 3D simulations.
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 ek/3d-stick-ball-2

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.

@ekiefl
Copy link
Copy Markdown
Owner Author

ekiefl commented May 18, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.68%. Comparing base (3da1247) to head (8b99660).

Files with missing lines Patch % Lines
...ltool/evolution/event_based/detect/ball_cushion.py 0.00% 2 Missing ⚠️
pooltool/evolution/event_based/detect/ball_ball.py 0.00% 1 Missing ⚠️
...oltool/evolution/event_based/detect/ball_pocket.py 0.00% 1 Missing ⚠️
pooltool/physics/resolve/stick_ball/core.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #300      +/-   ##
==========================================
+ Coverage   47.61%   47.68%   +0.06%     
==========================================
  Files         158      158              
  Lines       10626    10642      +16     
==========================================
+ Hits         5060     5075      +15     
- Misses       5566     5567       +1     
Flag Coverage Δ
service 47.68% <83.33%> (+0.06%) ⬆️
service-no-ani 58.12% <83.33%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

🧹 Nitpick comments (1)
pooltool/evolution/event_based/detect/ball_cushion.py (1)

122-125: ⚡ Quick win

Add docstring for consistency with the linear cushion 3D stub.

The get_next_ball_linear_cushion_3d_event stub at line 73 includes a helpful docstring explaining the placeholder behavior, but this circular-cushion variant does not. Adding a similar docstring improves code clarity and maintains consistency within the file.

📝 Proposed docstring addition
 def get_next_ball_circular_cushion_3d_event(
     shot: System, collision_cache: CollisionCache
 ) -> Event:
+    """3D ball-circular-cushion detection — not vendored yet; emits no event."""
     return null_event(np.inf)
🤖 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 `@pooltool/evolution/event_based/detect/ball_cushion.py` around lines 122 -
125, Add a docstring to the get_next_ball_circular_cushion_3d_event function
mirroring the style and content used in get_next_ball_linear_cushion_3d_event:
describe that this is a stub/placeholder for circular cushion collision
detection in 3D, explain that it currently returns a null_event(np.inf) (no
collision) and note it should be implemented later; place the docstring
immediately below the def line for get_next_ball_circular_cushion_3d_event.
🤖 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 `@pooltool/physics/resolve/stick_ball/core.py`:
- Around line 13-25: final_ball_motion_state currently uses a direct float
equality check rvw[1, 2] != 0.0 which misclassifies near-zero z-velocities;
change this to a tolerance-based test (e.g., use numpy.isclose or compare
abs(rvw[1,2]) > EPS where EPS is a small constant or derived from
np.finfo(float).eps) and keep returning const.airborne / const.sliding
accordingly; also address the unused parameter R by either removing it from
final_ball_motion_state or adding a brief comment explaining it is reserved for
future use.

---

Nitpick comments:
In `@pooltool/evolution/event_based/detect/ball_cushion.py`:
- Around line 122-125: Add a docstring to the
get_next_ball_circular_cushion_3d_event function mirroring the style and content
used in get_next_ball_linear_cushion_3d_event: describe that this is a
stub/placeholder for circular cushion collision detection in 3D, explain that it
currently returns a null_event(np.inf) (no collision) and note it should be
implemented later; place the docstring immediately below the def line for
get_next_ball_circular_cushion_3d_event.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a9c7ca03-e4a9-4a7f-bb31-44fd07259e13

📥 Commits

Reviewing files that changed from the base of the PR and between 3da1247 and 8b99660.

📒 Files selected for processing (9)
  • pooltool/evolution/event_based/detect/ball_ball.py
  • pooltool/evolution/event_based/detect/ball_cushion.py
  • pooltool/evolution/event_based/detect/ball_pocket.py
  • pooltool/physics/resolve/models.py
  • pooltool/physics/resolve/resolver.py
  • pooltool/physics/resolve/stick_ball/__init__.py
  • pooltool/physics/resolve/stick_ball/core.py
  • pooltool/physics/resolve/stick_ball/instantaneous_point/__init__.py
  • sandbox/airborne_demos.py

Comment on lines +13 to +25
def final_ball_motion_state(rvw: NDArray[np.float64], R: float) -> int:
"""Return the final (post-strike) motion state label.

If the z-velocity is non-zero the ball is considered airborne, otherwise
it is sliding (a struck ball is always kinetic).

Notes:
- A universal ``final_ball_motion_state`` fn could be a good idea.
"""
if rvw[1, 2] != 0.0:
return const.airborne

return const.sliding
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Floating-point equality check is unreliable for classifying motion state.

rvw[1, 2] != 0.0 will fail for near-zero values arising from floating-point arithmetic (e.g., sin(0) producing ~1e-16). Balls intended to be sliding could be misclassified as airborne.

Use a tolerance-based check instead:

Proposed fix
-    if rvw[1, 2] != 0.0:
+    if not np.isclose(rvw[1, 2], 0.0, atol=1e-12):
         return const.airborne

Additionally, the parameter R is declared but never used in the function body. If it's reserved for future use (e.g., checking if z-position > R), consider removing it until needed, or add a comment explaining the intent.

🤖 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 `@pooltool/physics/resolve/stick_ball/core.py` around lines 13 - 25,
final_ball_motion_state currently uses a direct float equality check rvw[1, 2]
!= 0.0 which misclassifies near-zero z-velocities; change this to a
tolerance-based test (e.g., use numpy.isclose or compare abs(rvw[1,2]) > EPS
where EPS is a small constant or derived from np.finfo(float).eps) and keep
returning const.airborne / const.sliding accordingly; also address the unused
parameter R by either removing it from final_ball_motion_state or adding a brief
comment explaining it is reserved for future use.

@ekiefl ekiefl merged commit 0ace05c into main May 18, 2026
12 checks passed
@ekiefl ekiefl deleted the ek/3d-stick-ball-2 branch May 18, 2026 08:07
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.

1 participant