# 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](guide:sport), which covers `sport.ball` and gives a short overview of this kit. ## Turn it on ```text 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 A two-hole round with one human and one bot, played through the service: ```text 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 | Setting | Type | Default | Meaning | |---|---|---|---| | `course` | string | `""` | id of the `golf_courses` document; empty: the first one, else the built-in nine holes | | `holes` | integer 0 to 36 | `0` | play only the first N holes (0: all) | | `players.humans` | integer 1 to 4 | `1` | local players taking turns on one screen | | `players.bots` | integer 0 to 3 | `0` | automatic golfers | | `names`, `colors` (in `players`) | lists | `[]` | names and hex ball colours by seat | | `players.order` | `farthest` or `rotation` | `farthest` | who plays next: the ball farthest from the cup (honours on the tee), or seat after seat | | `players.maxStrokes` | integer 0 to 30 | `12` | a player reaching it picks up the ball and scores it (0: no limit) | | `layout.fairwayWidth` | number | `30` | width of the fairway of a hole without strips | | `layout.bounds` | number | `45` | half-width of the in-play rough; beyond it, out of bounds | | `layout.teeSpacing` | number | `0.6` | metres between balls on the tee | | `ball.radius` | number | `0.1` | ball radius, metres | | `ball.gravity`, `ball.drag` | number | `9.81`, `0.01` | flight physics (drag: share of speed lost per second) | | `ball.restSpeed` | number | `0.5` | a landing or roll slower than this rests | | `ball.maxSpeed`, `ball.slope` | number | `120`, `1` | speed cap; how much slopes pull a rolling ball (0: none) | | `hole.cupRadius` | number | `0.18` | a ball this close to the cup drops in | | `hole.captureSpeed` | number | `2.5` | faster than this (m/s) the ball lips out | | `shot.gaugeSeconds` | number | `1.1` | seconds for the gauge to rise from 0 to 1 | | `shot.accuracy` | boolean | `false` | a third click stops an accuracy needle; off: shots are straight | | `shot.maxError` | number | `8` | degrees of hook or slice at the worst accuracy | | `shot.minPower` | number | `0.05` | the power played when the gauge is stopped lower | | `shot.aimRate` | number | `60` | degrees per second the aim turns | | `shot.autoClub` | boolean | `true` | pick the club that fits the distance at the start of a turn | | `shot.clubs` | list of ids | `[]` | the bag (up to 16 `golf_clubs` ids); empty: every club | | `wind.enabled` | boolean | `true` | use wind | | `wind.minSpeed`, `wind.maxSpeed` | number | `0`, `8` | m/s range drawn for a hole, in a random direction | | `wind.effect` | number | `0.12` | how fast the wind carries the ball in flight | | `perHole` (in `wind`) | boolean | `true` | a new wind at each hole; off: one for the round | | `flow.shotPause`, `flow.holePause` | number | `1`, `3` | seconds after a ball stops, and after the last ball is holed | | `flow.endGame` | boolean | `true` | the end of the round ends the game | | `ai.thinkSeconds`, `ai.error`, `ai.powerError` | number | `1.2`, `3`, `0.08` | bot delay, aiming error in degrees, share of power missed | | `hidePlayer` | boolean | `true` | hide 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. | Hook | Payload | What a tap can do | |---|---|---| | `golf.wind` | `hole`, `index`, `direction`, `speed`, `fixed` | change `direction` and `speed`, cancel | | `golf.turn` | `player`, `hole`, `stroke`, `club`, `distance`, `surface` | change `player` and `club` | | `golf.swing` | `player`, `human`, `hole`, `stroke`, `club`, `surface`, `power`, `accuracy`, `speed`, `loft`, `yaw` | change `speed`, `loft`, `yaw`; cancel | | `golf.lie` | `player`, `hole`, `surface`, `penalty`, `drop`, `at` | change `penalty` and `drop`, cancel | | `golf.hole_out` | `player`, `hole`, `par`, `strokes`, `pickedUp` | change `strokes` (not cancellable) | | `golf.finish` | `course`, `winner`, `player`, `won`, `totals` | change `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.