Golf

Play a golf round on a course described as data: holes, surfaces, a bag of clubs, wind, a two-step swing, one to four local players and bots, a scorecard, hooks and events.

Source: module-kits:kits/sport_golf/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs sport-golfMCP tool: gripforge_docs { "topic": "sport-golf" }

A golf round on a course written as data (sport.golf) #

What it is for #

sport.golf plays a golf round. The course is data (golf_courses): tees, cups, greens, fairway strips, bunkers, water and out-of-bounds. One to four local seats, humans and bots, take turns. Each player owns a ball entity; the kit moves it through flight, bounce and roll using the surface it lands on, the wind and the slope. A shot goes through the phases aim, power (a gauge that rises and falls), accuracy (a needle, only if you ask for it), flight until the ball stops, rest, then the next turn, hole_end and finished. It keeps the scorecard, applies penalties and decides who plays next. It draws only a HUD (hole, par, distance, player, strokes, club buttons, wind, gauge, card); the course props come from your assets. For team ball games, see the Sport guide, which covers sport.ball and gives a short overview of this kit.

Turn it on #

gripforge_gamekit_install   { project_id: "gkp_…", id: "sport.golf" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "sport.golf",
                              config: { "holes": 9, "players": { "humans": 1, "bots": 1 }, "shot": { "accuracy": true } } }

It requires input.actions, world.ground and game.engine. Pair it with a camera kit that frames the ball. With no golf_courses document a built-in nine-hole course is played. The default controls are golf.swing (click, Space, tap: aim, then stop the gauge), golf.cancel, golf.club_next, golf.club_prev and golf.aim.

First example #

<!-- test: kits/game_engine/host/tests/parity.rs::rust_host_matches_the_typescript_traces (fixture sport.golf.round) --> A two-hole round with one human and one bot, played through the service:

sport.golf.setClub("iron_6")          // true; an unknown club id answers false
sport.golf.aimAt([-260, 150])         // turn toward a point on the ground [x, z]
sport.golf.swing(0.9)                 // power 0 to 1 (the second argument is the accuracy, -1 to 1)
// the ball flies, then rests: events golf.swing, golf.landed, then golf.turn for the next player
sport.golf.distanceToCup()            // metres left for the player at the ball

Settings #

SettingTypeDefaultMeaning
coursestring""id of the golf_courses document; empty: the first one, else the built-in nine holes
holesinteger 0 to 360play only the first N holes (0: all)
players.humansinteger 1 to 41local players taking turns on one screen
players.botsinteger 0 to 30automatic golfers
names, colors (in players)lists[]names and hex ball colours by seat
players.orderfarthest or rotationfarthestwho plays next: the ball farthest from the cup (honours on the tee), or seat after seat
players.maxStrokesinteger 0 to 3012a player reaching it picks up the ball and scores it (0: no limit)
layout.fairwayWidthnumber30width of the fairway of a hole without strips
layout.boundsnumber45half-width of the in-play rough; beyond it, out of bounds
layout.teeSpacingnumber0.6metres between balls on the tee
ball.radiusnumber0.1ball radius, metres
ball.gravity, ball.dragnumber9.81, 0.01flight physics (drag: share of speed lost per second)
ball.restSpeednumber0.5a landing or roll slower than this rests
ball.maxSpeed, ball.slopenumber120, 1speed cap; how much slopes pull a rolling ball (0: none)
hole.cupRadiusnumber0.18a ball this close to the cup drops in
hole.captureSpeednumber2.5faster than this (m/s) the ball lips out
shot.gaugeSecondsnumber1.1seconds for the gauge to rise from 0 to 1
shot.accuracybooleanfalsea third click stops an accuracy needle; off: shots are straight
shot.maxErrornumber8degrees of hook or slice at the worst accuracy
shot.minPowernumber0.05the power played when the gauge is stopped lower
shot.aimRatenumber60degrees per second the aim turns
shot.autoClubbooleantruepick the club that fits the distance at the start of a turn
shot.clubslist of ids[]the bag (up to 16 golf_clubs ids); empty: every club
wind.enabledbooleantrueuse wind
wind.minSpeed, wind.maxSpeednumber0, 8m/s range drawn for a hole, in a random direction
wind.effectnumber0.12how fast the wind carries the ball in flight
perHole (in wind)booleantruea new wind at each hole; off: one for the round
flow.shotPause, flow.holePausenumber1, 3seconds after a ball stops, and after the last ball is holed
flow.endGamebooleantruethe end of the round ends the game
ai.thinkSeconds, ai.error, ai.powerErrornumber1.2, 3, 0.08bot delay, aiming error in degrees, share of power missed
hidePlayerbooleantruehide the character and park it at the ball so a follow camera frames the ball

Course data #

A golf_courses document is { id, name, holes } with 1 to 36 holes. A hole has id, name, par (2 to 7), tee and cup ([x, z]), green ({ radius, at? }), fairway strips, bunkers and water (a circle { at, radius } or a strip { from, to, width }), bounds, and an optional fixed wind ({ direction, speed }). A dogleg is two fairway strips.

The registry golf_clubs holds { id, name, carry, loft, putter? } (carry: metres of a full shot; a putter rolls the ball). The built-in bag is driver, wood_3, iron_4, iron_6, iron_8, wedge, sand_wedge and putter. The registry golf_surfaces holds { id, name, friction, bounce, grip, power, penalty, drop, lost? } for fairway, green, rough, bunker, water and out; power scales a shot played from it, penalty adds strokes when the ball rests there, drop is lie, last or replay.

Services #

sport.golf (service) methods:

  • Round: state(), course(), hole(), players(), scorecard(), nextHole(), restart(), endStats(lang).
  • Shot: clubs(), club(), setClub(id), aimAt(point), aimYaw(yaw?), swing(power, accuracy?), cancel() (back to aiming).
  • Ball: ball(player?), ballPosition(player?), placeBall(point), distanceToCup(player?), surfaceAt(x, z).
  • Wind: wind(), setWind(direction, speed).

Verbs: golf_next_hole, golf_restart, golf_set_wind { direction, speed }; conditions golf_hole_is { hole } and golf_leading { seat? }.

Variables: golf.hole, golf.holes, golf.par, golf.stroke, golf.player, golf.phase, golf.gauge, golf.accuracy, golf.club, golf.wind_speed, golf.wind_dir, golf.distance, golf.to_par, golf.surface, golf.aim.

Events #

  • golf.round_started (course, holes, players), golf.hole_started (hole, index, par, length, player), golf.wind (the wind of the hole), golf.turn (player, hole, stroke, distance, surface), golf.swing (player, hole, club, power, accuracy, speed, stroke).
  • golf.landed (player, hole, surface, at, distance, flew), golf.penalty (player, hole, surface, strokes, drop), golf.holed (player, hole, strokes, par, score, term, pickedUp), golf.hole_ended (hole, index, par, strokes), golf.finished (course, winner, totals, toPar).
  • game.won and game.lost with flow.endGame: the first local player won (or shares first place), or did not.

Hooks and modding #

Each hook is asked once, when the decision is taken, never while the ball flies.

HookPayloadWhat a tap can do
golf.windhole, index, direction, speed, fixedchange direction and speed, cancel
golf.turnplayer, hole, stroke, club, distance, surfacechange player and club
golf.swingplayer, human, hole, stroke, club, surface, power, accuracy, speed, loft, yawchange speed, loft, yaw; cancel
golf.lieplayer, hole, surface, penalty, drop, atchange penalty and drop, cancel
golf.hole_outplayer, hole, par, strokes, pickedUpchange strokes (not cancellable)
golf.finishcourse, winner, player, won, totalschange winner and won, cancel

A mod can change holes, players, layout, ball, hole, shot, wind, flow, ai and hidePlayer, and add or patch documents of golf_courses, golf_clubs and golf_surfaces (for example a harder rough, or forgiving water).

Assets #

Optional props: golf_ball (one variant per seat), golf_flag, golf_tee, golf_green, golf_bunker and golf_water.

Engines #

The same Rust logic runs on the web and in the native player, with a web UI fragment for the HUD. The course position, cards, balls, gauge and wind are part of the save.

When it does not do what you expect #

  • A shot goes short: the surface power (rough 0.8, bunker 0.6) or a headwind; read golf.surface and wind().
  • Putts never drop: the ball crosses the cup faster than hole.captureSpeed and lips out.
  • The round never ends: a player is still out; use golf_next_hole, or set players.maxStrokes.
  • The camera ignores the ball: hidePlayer is off, or the camera kit has no target to follow.
  • setClub answers false: the club id is not in the bag (shot.clubs).

Limits #

  • Local play only, up to four seats; no online matches.
  • The swing is a gauge, not a free-form motion.
  • It does not generate courses: write them as data.

Versions #

Kit version 1.0.0. It relies on game.engine 1.x and input.actions 1.x.