The objects that describe the state of a pool game before and after simulation.
System is the single object passed into every API function. It owns the balls, cue, table, simulation clock, and the complete event log produced by simulation.
pooltool/system/datatypes.py:30
| Attribute | Type | Description |
|---|---|---|
| cue | Cue | The cue stick with strike parameters. |
| table | Table | Table geometry — cushions, pockets, surface. |
| balls | dict[str, Ball] | Ball ID → Ball object. Key "cue" is the cue ball. |
| t | float | Elapsed simulation time (seconds). |
| events | list[Event] | Ordered event log produced by simulate(). |
# strike() sets cue parameters before simulation system.strike( V0=8.0, # m/s impact speed phi=45.0, # azimuth angle (degrees) theta=20.0, # cue elevation (degrees) a=0.0, # side offset (-1 .. +1) b=0.5, # top/bottom offset (-1 .. +1) ) # reset_balls() restores all balls to their initial states system.reset_balls() # copy() produces a deep-copied system (used by simulate inplace=False) clone = system.copy()
# MultiSystem wraps a list[System] for multi-shot games # pooltool/system/datatypes.py:587 class MultiSystem: multisystem: list[System] def append(self, system: System) -> None: ... def active(self) -> System: ... # last system def __len__(self) -> int: ...
A Ball wraps the current kinematic state, physical parameters, and two trajectory histories — one sparse (event-timestamped) and one dense (continuous, for animation).
pooltool/objects/ball/datatypes.py:324
| Attribute | Type | Description |
|---|---|---|
| id | str | Unique identifier: "cue", "1" … "15". |
| state | BallState | Current position, velocity, spin, and motion state. |
| params | BallParams | Mass, radius, friction coefficients. |
| history | BallHistory | Sparse trajectory — one state per event. |
| history_cts | BallHistory | Dense trajectory — uniform dt, filled by continuize(). |
| ballset | BallSet | None | Texture / rendering metadata. |
# Convenience properties delegate to state.rvw: ball.xyz # → state.rvw[0] — position (x, y, z) ball.vel # → state.rvw[1] — velocity (vx, vy, vz) ball.avel # → state.rvw[2] — angular velocity (wx, wy, wz) # Factory: ball = Ball.create("cue", xy=(0.5, 1.0))
# pooltool/objects/ball/datatypes.py:70 @dataclass class BallState: rvw: NDArray[np.float64] # shape (3, 3) s: int # motion state label t: float # time of this state snapshot # Motion state labels (pooltool/objects/ball/constants.py): # 0 = stationary # 1 = spinning (ball is rotating in-place) # 2 = sliding (linear velocity ≠ surface velocity) # 3 = rolling (no slip between ball and cloth) # 4 = pocketed # 5 = airborne (3D mode only) # The rvw rows: state.rvw[0] # [x, y, z] — position state.rvw[1] # [vx, vy, vz] — linear velocity state.rvw[2] # [wx, wy, wz] — angular velocity
# pooltool/objects/ball/datatypes.py:142 class BallHistory: """Ordered list of BallState snapshots.""" states: list[BallState] def add(self, state: BallState) -> None: ... def vectorize(self) -> dict[str, NDArray]: ... # vectorize() returns arrays of shape (N,) for each field # Used internally by rendering and interpolation. # After simulate() — ball.history has one entry per event # After continuize() — ball.history_cts has uniform dt entries len(ball.history.states) # e.g. 14 (one per event) len(ball.history_cts.states) # e.g. 350 (dt=0.01 over 3.5s)
Cue carries all parameters of the intended shot. These are consumed once by the stick-ball resolver at t=0 to determine the initial ball velocity and spin.
pooltool/objects/cue/datatypes.py:137
| Attribute | Type | Description |
|---|---|---|
| V0 | float | Impact speed (m/s). Controls how hard the ball is hit. |
| phi | float | Azimuth angle (degrees). Direction the cue ball will travel. |
| theta | float | Cue elevation (degrees). 0 = parallel to table, 90 = massé. |
| a | float | Side offset of tip contact (−1 to +1). Adds left/right english. |
| b | float | Vertical offset of tip contact (−1 to +1). Top/bottom spin. |
| cue_ball_id | str | Which ball is struck. Default "cue". |
| specs | CueSpecs | Physical properties of the stick (mass, tip radius, …). |
# pooltool/objects/cue/datatypes.py:12 @dataclass(frozen=True) class CueSpecs: brand: str = "Predator" M: float = 0.567 # kg — cue mass length:float = 1.4732 # m — cue length tip_radius: float = 0.007 # m — tip radius end_mass: float = 0.02 # kg — butt mass
Table provides the geometric constraints: cushion rail segments (linear and circular) that balls can bounce off, and pockets that swallow balls.
pooltool/objects/table/datatypes.py:35
| Attribute | Type | Description |
|---|---|---|
| cushion_segments | CushionSegments | Collection of linear rails and circular corner curves. |
| pockets | dict[str, Pocket] | Six pockets: "tl", "tr", "bl", "br", "lc", "rc". |
| table_type | TableType | Enum — BILLIARD, SNOOKER, POCKET, … |
| height | float | Height of playing surface above floor. |
# Two cushion segment types: # LinearCushionSegment — a straight rail section # Defined by two endpoints; represented as an infinite vertical plane # for collision math # CircularCushionSegment — the curved rail at corner/side pockets # Defined by center point and radius # Added in #343 with configurable nose radius
pooltool/events/datatypes.py:21
| EventType | Class | Description |
|---|---|---|
| STICK_BALL | collision | Cue tip contacts ball at t=0. |
| BALL_BALL | collision | Two balls touch. |
| BALL_LINEAR_CUSHION | collision | Ball hits a straight rail. |
| BALL_CIRCULAR_CUSHION | collision | Ball hits a curved corner rail. |
| BALL_POCKET | collision | Ball enters pocket radius. |
| BALL_TABLE | collision | Airborne ball lands on table (3D only). |
| SLIDING_ROLLING | transition | Ball decelerates to rolling (no-slip) condition. |
| ROLLING_SPINNING | transition | Ball loses forward motion, only spin remains. |
| ROLLING_STATIONARY | transition | Slow ball comes to rest directly. |
| SPINNING_STATIONARY | transition | Residual spin stops. |
# Event dataclass: @dataclass class Event: event_type: EventType agents: list[Agent] # balls (and cushion/pocket) involved time: float # absolute simulation time # After simulation: for event in system.events: print(event.event_type, event.time, [a.id for a in event.agents])