SpaceGolfHUD
The score across the top, and everything else a space golfer reads, as a DOM overlay.
What it does
SpaceGolfHUD builds one overlay of plain DOM over the page and draws nothing in 3D. It only listens to the bus, and its buttons only emit requests. Nothing in the game calls it, so the overlay can be dropped or replaced without touching the game.
A click never changes how a button looks. SIDE asks the camera for the side view, and the button lights when the camera answers with axisview.view.changed. + ADD greys out when the director reports that the level's black holes are all out.
The mass readout and its − / + buttons follow the last hole hovered or grabbed. A mouse has to leave the hole to reach a button, and a touch screen has no hover and no wheel. A new level clears the choice, since that hole may be back in its pool.
The score rolls up to each new total instead of swapping the number, so a delivery reads as a tally ticking over. A score reset drops it straight to 0.
Use it in a scene
A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.
Build it from scratch with the bjs CLI:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add SpaceGolfHUD
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
Score keeps the totals. The rest of what the HUD shows comes from the director, the camera, the aim line and the black holes in a SpaceGolf scene.
Props
owner(string, default'player') — whose score to show, theScorebucket the director pays into.showHelp(boolean, defaulttrue) — show the controls line along the bottom.bannerSeconds(number, default2.4) — seconds a level banner stays up.
Events
Listens, and what each one changes:
score.changed/score.reset: the big score, read forowner.spacegolf.level.started: the level name and number, a banner with the hint and quota, and whether SOLVE shows.spacegolf.progress.changed: delivered out of the quota, a pip per allowed miss, and black holes out of the limit.spacegolf.countdown.changed: 3, 2, 1, GO! in the middle of the screen.spacegolf.cadence.changed: ROCK EVERY 2.2 s, with SLOWER or FASTER greyed at the ends.spacegolf.rock.delivered/spacegolf.rock.lost/spacegolf.blackhole.refused/spacegolf.level.solved: a toast such as +100, CRASHED, MAX 3 BLACK HOLES or SOLUTION PLACED.spacegolf.level.cleared/spacegolf.level.failed/spacegolf.game.completed: a banner.axisview.view.changed: which view button is lit.trajectory.outcome.changed: the aim verdict under the score, such as ON TARGET or COLLISION AHEAD.blackhole.mass.changed/pointerdrag.hover.changed/pointerdrag.started: the mass readout, and which hole − / + act on.
Emits, one per button:
axisview.view.set{ view }from TOP and SIDE.spacegolf.blackhole.add,spacegolf.level.restartandspacegolf.level.solvefrom + ADD, RESTART and SOLVE.spacegolf.level.goto{ step }from ◀ and ▶, as −1 and +1.spacegolf.cadence.adjust{ steps }from SLOWER as +1 and FASTER as −1.blackhole.mass.adjust{ entityId, steps }from − and +.
Dependencies
None. The HUD shows whatever the bus carries. With no director in the scene its readouts never fill in, and with no Score the score stays at 0.
Notes
- The overlay is fixed to the whole window, not sized to the canvas. It suits a full-window game like SpaceGolf.
- It is built once there is a HUD entity and a
document, and taken down on shutdown. A stale overlay from an earlier run is replaced, not stacked. With nodocument, as in a headless test, it builds nothing and every event is harmless. - It keeps running while the world is paused, so banners and the countdown still time out.
- The help line names keys (V, R, [ ], - and =), but the HUD handles none of them. The director and the camera do.
























