const RollSpeed, RollDrag, keepBonus, maxKeep
The calibration of Rolling, fitted by least squares to the course's strokes: keep = min((Friction+keepBonus)·Scale, maxKeep), then Crr = ((1−keep)·RollSpeed + RollDrag/Scale) / G.
Package physics is a 2D rolling-ball engine: it answers where a ball goes, never what that means.
Package physics is a 2D rolling-ball engine: it answers where a ball goes, never what that means.
+X is right, +Y is down, 1.0 is one board unit, angles are in radians. Deterministic: float64 only, no map iteration, no clock, no randomness.
Inputs must be finite: a NaN or an infinity gives no panic and no endless loop, but no meaningful answer either. Callers check them.
The calibration of Rolling, fitted by least squares to the course's strokes: keep = min((Friction+keepBonus)·Scale, maxKeep), then Crr = ((1−keep)·RollSpeed + RollDrag/Scale) / G.
1const (
2 // WallFriction is the Coulomb µ between the ball and a wall or post:
3 // the tangential impulse is at most µ times the normal one.
4 WallFriction = 0.05
5 // TangentMass caps the tangential impulse, per unit mass and speed:
6 // 2/7 is what stops a solid ball's contact point from slipping.
7 TangentMass = 2.0 / 7
8 // RestSpeed is the speed into a surface, per substep, under which a
9 // contact is resting: no restitution, no bounce counted. At least what
10 // the steepest hill adds in a substep, so a ball a hill presses into a
11 // rail slides along it instead of jittering.
12 RestSpeed = 0.35
13 // MaxBounce is the most restitution a passive piece plays: a Bounce
14 // above it is played at it.
15 MaxBounce = 0.75
16 // MaxKick is the most restitution a bumper (a Bounce above 1 on a skin
17 // that Kicks) plays, and the most course.Decode accepts. The ball stays
18 // under SpeedCap.
19 MaxKick = 1.5
20)The contact rules for walls and posts.
1const (
2 // CrestRadius is the radius of a hill's lip, in board units: a ball
3 // needs sqrt(G·CrestRadius), 0.71 per substep, to take off.
4 CrestRadius = 0.5
5 // MinRamp is the gentlest hill (|Vec|) that launches.
6 MinRamp = 0.12
7 // GroundBounce is the restitution of the ground when a ball lands.
8 GroundBounce = 0.4
9 // LandFriction is the Coulomb µ between a landing ball and the ground.
10 LandFriction = 0.3
11 // HopSpeed is the vertical speed, per substep, under which a landing
12 // ball stays down.
13 HopSpeed = 0.25
14 // MaxSin is the steepest grade a hill plays, sin θ (about 72°), so tan θ
15 // and a flight stay bounded; course.Decode holds |Vec| to G·MaxSin.
16 MaxSin = 0.95
17)The flight rules: a ball takes off over a hill's crest (its uphill edge) when v² > G·CrestRadius, v its speed up the hill, flies a ballistic arc and lands at the height it took off from.
G is gravity, in board units per substep squared: a hill's Vec is G·sin θ.
JumpRun is the share of a hill's depth a ball must have climbed to take off at its top: one that only clipped the hill near its crest has not ridden it.
LoopKeep is the share of its speed a ball keeps once round a loop.
MaxMove is the longest single move inside a substep. Zones are sampled once per move, so a zone must be at least this deep along the way a ball crosses it, or a fast ball can step over it unseen.
MaxRollOn caps the substeps a ball may roll on past its stroke's own while a slope keeps it moving or it is still in the air.
MaxWork caps what one stroke may cost, whatever the field, in work units of about a thousand gas each (Shot.Work): a stroke that reaches it ends where the ball is. A Field's Cap lowers it for one stroke.
MaxWorkStep is the most a stroke's Work passes its limit (MaxWork, or a lower Cap) by on a Decode-limited field: the most between two checks. Golf holds a shot to its limit plus this (shotBound).
SpeedCap is the fastest a ball may go, in board units per substep, held at the start of every substep and after every bounce: it bounds a substep to 6 moves, even between two bumpers or down a slope on ice.
1const (
2 Surface ZoneKind = iota // Scale sets the rolling resistance (Rolling): sand holds, ice frees
3 Slope // a hill: Vec, gravity along it, is added to velocity each substep
4 Tunnel // the ball is moved to Vec, keeping its velocity
5 Hazard // the ball goes back where the stroke was played from: water, a pit, the void
6 // Loop is a tube's mouth (Min..Max): a ball heading in within 25° at
7 // Scale or faster comes out at Vec, else it falls back or off the side.
8 // The mouth must be at least MaxMove deep, or a fast ball steps over it.
9 Loop
10)The zone kinds, each named in its own line.
Kicks reports whether a piece of this skin may kick the ball back faster than it came (a Bounce above 1). Every other piece is passive.
Lengths is the three square roots Prepare takes for a wall and a ball of radius r: the wall's length, then its plus and minus offset lines'.
Prepare works out the field's wall offsets once, for every shot to reuse. Call it once the walls are where they stay: Step works out again any wall that no longer matches its entry.
PrepareWith is Prepare with each wall's Lengths given, three per wall in wall order: the same prep, bit for bit, when they are the walls' own. It does not check them; whoever stored them must have. With the wrong count it is Prepare.
Prepared is a copy of the field's prep, to compare bit for bit.
Rolling is the rolling-resistance coefficient Crr of a green of the given Friction under a Surface of the given Scale (1 is plain grass). A Scale of 0 or less is an infinite Crr: the ball stays where it is.
There is the timing rule walls, zones and course pulses share (sign-safe mod, so a negative phase works): on at i when (i+phase) mod every is below on; every <= 0 is always there.
WithTick is the field with its clock moved on by tick substeps. It may return f itself (tick 0): do not mutate what it returns.
FromPolar builds a vector of length r pointing at angle (radians).
UnstickIn moves a ball of radius r out of pieces that appeared on top of it. walls are read in bars of four (build.Bar): a ball inside one leaves through the nearest side it can; one closer than r to a bar or a post is pushed off it. No push crosses an untimed wall of stays: out of a bar the ball tries the next side, else it stays; any other such push is not made.
V is Vec2{x, y}, short so a course's coordinates read as coordinates.
Circle is a round barrier: a post, a bumper, the rim of a pond.
1type Field struct {
2 Walls []Wall
3 Posts []Post
4 Zones []Zone
5 Friction float64 // the green's rolling resistance, as Rolling maps it: 0.87 is Crr 0.224
6 Bounce float64 // default restitution of barriers
7 // Radius of the ball (0 plays it as a point).
8 Radius float64
9 // Tick is where the stroke starts on the clock of timed walls and zones.
10 Tick int
11 // Cap, above 0 and under MaxWork, replaces MaxWork for this stroke.
12 Cap int
13 // prep is the walls' offsets, worked out once (Prepare).
14 prep []float64
15}Field is everything a ball rolls through: walls, posts and zones.
Step rolls a ball through the field until it comes to rest or runs out of substeps (and past them while a slope keeps it rolling or it is still in the air, see MaxRollOn). With a Radius, the ball meets walls and posts with its edge. Cost is O(moves * (walls + posts + zones)), paid on every shot.
Post is a round obstacle. Bounce is the contact's restitution, played at MaxBounce at most; above 1 with a bumper's skin (Kicks) it kicks the ball back faster than it came (at most MaxKick times).
Segment is a straight barrier from A to B.
Closest is the point of the segment nearest p.
Crosses reports whether the motion p0->p1 crosses the segment, without working out the normal (and its square root).
Normal is the unit left-normal of the segment.
1type Shot struct {
2 Path []Vec2
3 Bounces int
4 // Air marks the path points where the ball is off the ground (no zone
5 // acts, no cup takes it). Same length as Path.
6 Air []bool
7 // Cause says, per path point, what most acted on the ball in the substep
8 // that ended there: 'b' a bounce, 's' a hill, 'w' wind or a gust, 'i' a
9 // slippery surface, '-' nothing but friction. Same length as Path.
10 Cause []byte
11 why byte // this substep's cause so far
12 // Work is what the stroke cost, in MaxWork's units.
13 Work int
14 // start is where the stroke was played from, where a hazard sends it.
15 start Vec2
16}Shot is everything a stroke did: where the ball was at every substep, and how many walls and posts it met. A renderer replays Path and never simulates; two consecutive points far apart are a tunnel, not a line to interpolate.
Vec2 is a point or a displacement on the board, in board units.
Add is a+b.
Dot is the dot product of a and b.
Len is the length of a.
LenCmp compares a.Len() with r: -1 shorter, 0 equal, 1 longer, exactly as comparing a.Len() would. It takes the costly square root only within 1e-12 of r², where a.Dot(a) alone could round the other way. For board-scale r.
Scale is a times s.
Sub is a-b.
Wall is a straight barrier. Bounce overrides the field's restitution (0 keeps it); Mark and Skin are for whoever draws it, except that only a bumper's skin kicks (Kicks).
1type Zone struct {
2 Kind ZoneKind
3 Min, Max Vec2
4 Vec Vec2 // Slope: acceleration (G·sin θ downhill). Tunnel: destination. Loop: where it comes down. Hazard: unused.
5 Scale float64 // Surface: its rolling resistance (Rolling). Loop: the speed to go round.
6 Mark rune
7 Skin string
8 // Round makes the zone the ellipse inscribed in Min..Max.
9 Round bool
10 // Poly (3 points or more) makes the zone that polygon, within Min..Max;
11 // Outside makes it everything in Min..Max but the polygon.
12 Poly []Vec2
13 Outside bool
14 // Every, On, Phase time the zone within a stroke, as for a Wall.
15 Every, On, Phase int
16 // Air makes a Slope moving air: it pushes the ball but is no hill, and
17 // hides none under it. Capped air never speeds the ball up.
18 Air, Capped bool
19}Zone is an area of the board that changes how the ball moves: the rectangle Min..Max, or the ellipse inside it (Round), or a polygon (Poly), or all of it but a polygon (Outside). Vec and Scale mean what the kind says.
ZoneKind is what a zone does to a ball inside it.