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.
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 #
| 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.wonandgame.lostwithflow.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; readgolf.surfaceandwind(). - Putts never drop: the ball crosses the cup faster than
hole.captureSpeedand lips out. - The round never ends: a player is still out; use
golf_next_hole, or setplayers.maxStrokes. - The camera ignores the ball:
hidePlayeris off, or the camera kit has no target to follow. setClubanswersfalse: 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.