Data Models

The objects that describe the state of a pool game before and after simulation.

System — the root container

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

System — fields and key methods
AttributeTypeDescription
cueCueThe cue stick with strike parameters.
tableTableTable geometry — cushions, pockets, surface.
ballsdict[str, Ball]Ball ID → Ball object. Key "cue" is the cue ball.
tfloatElapsed simulation time (seconds).
eventslist[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 — multiple sequential shots
# 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: ...

Ball — kinematic state and history

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

Ball — fields and convenience properties
AttributeTypeDescription
idstrUnique identifier: "cue", "1""15".
stateBallStateCurrent position, velocity, spin, and motion state.
paramsBallParamsMass, radius, friction coefficients.
historyBallHistorySparse trajectory — one state per event.
history_ctsBallHistoryDense trajectory — uniform dt, filled by continuize().
ballsetBallSet | NoneTexture / 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))
BallState — rvw tensor and motion state
# 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
BallHistory — sparse and dense trajectories
# 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 — the cue stick

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

Cue — strike parameters
AttributeTypeDescription
V0floatImpact speed (m/s). Controls how hard the ball is hit.
phifloatAzimuth angle (degrees). Direction the cue ball will travel.
thetafloatCue elevation (degrees). 0 = parallel to table, 90 = massé.
afloatSide offset of tip contact (−1 to +1). Adds left/right english.
bfloatVertical offset of tip contact (−1 to +1). Top/bottom spin.
cue_ball_idstrWhich ball is struck. Default "cue".
specsCueSpecsPhysical properties of the stick (mass, tip radius, …).
CueSpecs — physical constants of the cue
# 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 — geometry and playing surface

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

Table — cushion segments and pockets
AttributeTypeDescription
cushion_segmentsCushionSegmentsCollection of linear rails and circular corner curves.
pocketsdict[str, Pocket]Six pockets: "tl", "tr", "bl", "br", "lc", "rc".
table_typeTableTypeEnum — BILLIARD, SNOOKER, POCKET, …
heightfloatHeight 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

Event — simulation log entries

pooltool/events/datatypes.py:21

EventType enum — all physics events
EventTypeClassDescription
STICK_BALLcollisionCue tip contacts ball at t=0.
BALL_BALLcollisionTwo balls touch.
BALL_LINEAR_CUSHIONcollisionBall hits a straight rail.
BALL_CIRCULAR_CUSHIONcollisionBall hits a curved corner rail.
BALL_POCKETcollisionBall enters pocket radius.
BALL_TABLEcollisionAirborne ball lands on table (3D only).
SLIDING_ROLLINGtransitionBall decelerates to rolling (no-slip) condition.
ROLLING_SPINNINGtransitionBall loses forward motion, only spin remains.
ROLLING_STATIONARYtransitionSlow ball comes to rest directly.
SPINNING_STATIONARYtransitionResidual 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])