Notices
Normative.
The engine knows things nobody else does: that two clubs are out and the next shot will have no club; that a measured source just became an inferred one; that the clock cannot enforce a deadline. A notice is the engine telling the people who can act on that.
Schema: notice.schema.json.
1. Transient is normal. Persistent is a problem.#
The obvious design is to notify whenever the engine detects something wrong. It is also the wrong one, and the club case shows why.
Two clubs out of tracked holders is constant during ordinary play — a player deciding between two, someone carrying a spare, a group changing over. Warning every time would fire dozens of times a round about nothing, and everyone would learn to dismiss the channel long before it said something that mattered.
What matters is not that ambiguity exists. It is that ambiguity survived a shot, and especially that it keeps doing so:
One swing without club data is an oops. A whole round of them, because somebody stood the 5-iron against the wall, is a problem worth three seconds to fix.
So the engine watches rather than reports:
| event | what happens |
|---|---|
| ambiguity comes and goes between shots | silence. Journaled, never announced |
| one shot lands unmatched | silence, but now armed |
| the monitor arms while still ambiguous | warn — the player is over the ball and can still fix it |
| a second shot lands unmatched | warn — this is no longer a transition |
| the run continues | nothing further. Said once |
| a shot resolves cleanly | cleared, and the counter resets |
The best moment to speak#
Once one shot has been lost, the engine warns when the launch monitor arms for the next one. That is the ideal instant: the player is standing over the ball, the problem is still in front of them, and returning a club costs three seconds. Better than any message after the fact.
Where a monitor does not report readiness, the second lost shot triggers it instead — later, but still early enough to save the round.
And the counter resets#
A run that is fixed and later recurs is reported again. Silence after the first warning applies to that run, not to the rest of the round.
2. Audience#
| audience | reaches | for |
|---|---|---|
round | everyone playing | anyone in the bay can resolve it |
slot | one player | their device, their binding, their problem |
operator | staff surfaces | something a player cannot fix |
A slot-scoped notice is addressed by slot, never by name. The engine says "slot 2"; the member's app resolves that to a person. The bay stays ignorant of who anyone is, per 00 §6, and the notice channel does not become the hole in that.
3. Delivery reuses the subscription model#
A participant receives notices by wanting the pseudo-field sga.notice:
"wants": [ { "field": "sga.notice", "reason_code": "display" } ]No separate transport, no new mechanism, and a device that does not care never sees one. A phone app connected to the bay gets them live; a spectator board can show the round-scoped ones; a launch monitor ignores the lot.
A participant joining mid-round can ask for the standing notices — what is already wrong — rather than only learning about problems that happen to recur after it arrived.
4. Raise once, and say when it is over#
Two rules that decide whether anyone still reads this channel in six months.
A condition is announced once, not on every shot. A notice carries a fingerprint of the specific situation; while that is unchanged, it is not repeated. Ten shots into a two-club ambiguity produces one notice, not ten.
And it is cleared explicitly when it resolves. A club goes back, the engine says so. This is half the value of the channel and the half most systems forget: a warning that never resolves teaches people to ignore warnings.
A changed condition — a different pair of clubs — is a new fingerprint and a new notice, because it is genuinely a new situation.
5. Actionable, and what it is allowed to interrupt#
actionable distinguishes put a club back and this clears from your firmware is two versions old.
- Actionable notices may interrupt play. They are worth it: three seconds now saves a hole's worth of missing data.
- Everything else waits for a natural break or the round summary.
Severity is separate: info, warning, blocked. blocked is reserved for pre-round settings divergence — the one place the standard stops a round, because before the first stroke is the only moment refusing costs nobody anything (08 §2.1). Nothing mid-round blocks.
6. Urgency is not a channel#
A notice says how much of someone's attention it deserves. It does not say whether to push, text, email or wait — the engine has no idea what a member prefers, and a bay should never be deciding to send somebody an SMS.
| urgency | means |
|---|---|
interrupt | they are over the ball and can fix it now |
attention | they should see it before the next hole |
background | the round summary is soon enough |
record | journal only, no human involved |
The platform maps urgency to channel using the member's own preferences — push, in-app, silent, SMS, email, or nothing. That split is what lets a player who wants total quiet still have everything waiting in their round summary, without the engine knowing anything about them.
Only the engine may use interrupt. Interrupting a golfer mid-round is a privilege, and it is not one a launch monitor vendor gets to award itself.
7. Devices may speak, within limits#
A participant can raise a notice, not only receive one. The uses are real: a swing monitor asking how that one felt, a tournament system asking whether a player is ready to start, an accessory reporting it needs attention.
It is also the obvious way to ruin this channel, so it is fenced:
| engine | a participant | |
|---|---|---|
round and operator audience | yes | no |
| a slot it is bound to, or the active slot | yes | yes |
interrupt urgency | yes | no |
blocked severity | yes | no |
| codes | all | device_message, device_question only |
And the control that actually matters: a player can turn notifications off per paired device, in the app, next to the sharing switch.
The device is always told what happened#
A notice that is not delivered is not an error — the device did nothing wrong — but it is told, with a reason:
{ "delivered": false, "reason": "player_disabled" }
{ "delivered": false, "reason": "vendor_blocked",
"detail": "promotional content in device_message" }The device is told. Hiding it, on the theory that a device which cannot tell will not build a retry loop, fails in both directions. A badly behaved device retries anyway — it cannot distinguish success from silence, so it may retry more. And a well behaved one is prevented from doing the sensible thing: showing the prompt on its own display, skipping it, or simply not asking again.
Every device message is recorded#
Each device-originated notice is journaled with the sending device, its vendor, model and firmware, the code, the urgency, and the text — whether it was delivered or not.
That record is the evidence base. Aggregated across bays it answers which vendors message, how often, and about what without anyone having to go looking, and it is what makes the next section fair rather than arbitrary: a block rests on what a device actually sent, not on a complaint.
Blocking, without ending a partnership#
The bundle carries a notification blocklist, keyed on vendor, model and firmware range.
{ "vendor": "acme", "firmware_below": "4.2.0",
"action": "block", "reason": "promotional content in device_message",
"since": "2027-01-15" }Why it belongs in the bundle: it is signed, so it cannot be forged or edited away at a bay, and it is already distributed everywhere — including through the offending vendor's own installer, which is a pleasing symmetry.
Three properties make this proportionate rather than punitive:
- It blocks a behavior, not a partnership. A vendor misusing the channel does not lose their certification, their listing, or their data integration. Their messages stop.
warnbeforeblock. A rule can deliver and record, so a first offense is a conversation rather than a ban.- A fixed build clears itself. Because the block is a range, a vendor who corrects the behavior and ships 4.2.0 is unblocked the moment they do — no republished bundle, no negotiation, no waiting on us. That last property is the one worth keeping: enforcement that requires our attention to lift is enforcement we will be slow to lift.
A device that will not state its firmware counts as older than the fix, so staying quiet is not a way out.
A rule may also name exact builds with firmware_in, for a behavior that appeared in one release and stopped in the next — the same field, with the same semantics, as the admission list in 13 §2.
Certification still carries the rule licensing cannot: the channel is not for promotion. The blocklist is what makes that enforceable in days rather than quarters.
8. Questions#
A notice carrying choices is a question. The response routes back to whoever asked.
{ "type": "notice.post", "code": "device_question",
"from": "sga:device:swingcam", "audience": "slot", "slot": 2,
"urgency": "background",
"text": "How did that one feel?",
"choices": [ {"id":"1","label":"Poor"}, {"id":"3","label":"Fine"},
{"id":"5","label":"Great"} ],
"expires_ms": 120000 }Three rules keep this from becoming an interruption engine:
Answering is always optional. An unanswered question expires and nothing waits on it. A round never blocks on a prompt, including a tournament readiness check — that is a convenience for the director, not a gate on the golfer.
Questions queue rather than interrupt. expires_ms says how long an answer is still worth having: a swing rating is worthless three holes later, a readiness check may hold for minutes. The app decides when to surface it, and "at the next natural break" is usually right.
The asker learns a slot, never a name. A response carries the slot that answered. The swing camera knows player 2 said the shot felt poor; it does not know who player 2 is, and nothing at the bay does.
Subjective ratings are worth having for a reason beyond novelty: a shot that felt terrible and scored well is exactly the sort of thing a rating model cannot see, and no sensor will ever measure it.
The registered codes#
| code | audience | urgency | raised by |
|---|---|---|---|
club_ambiguous | round | interrupt | engine |
source_degraded | round | attention | engine |
device_left | round | attention | engine |
unmeasured_stroke | slot | background | engine |
settings_divergent | operator | interrupt (pre-round) | engine |
calibration_expired | operator | attention | engine |
unbound_wearable | slot | attention | engine |
time_unverified | operator | attention | engine |
media_capture_suppressed | round | background | engine |
round_ending | round | interrupt | engine |
bay_offline | operator | attention | engine |
update_available | operator | background | engine |
device_denied | operator | interrupt | engine |
certification_shortfall | operator | interrupt | engine |
calibration_expiring | operator | rising | engine |
certification_expiring | operator | rising | engine |
bay_ineligible_for_event | operator | interrupt | engine |
device_message | slot | ≤ attention | a participant |
device_question | slot | ≤ attention | a participant |
media_capture_suppressed is worth its place: when someone opts out of video and the session's capture stops, the others are entitled to know the cameras are off, and they are not entitled to know who opted out.
Note it says media. Shot and round data have no bay-side opt-out — they go to quarantine and are stripped of identifiers when the claim window closes, so there is nothing for a bay control to protect. Suppressing them would break the course-difficulty corpus for everyone else in the round and buy nothing.
9. Reaching the operator through the players' phones#
An operator notice is about the bay, not about a round: a software update is due, calibration has expired, the machine has not seen the internet in nine days.
The last of those is the awkward one — the bay that most needs to tell somebody is the bay that cannot.
But the players' phones are right there, and they already carry the journal out when a bay is offline (07 §1). An operator notice rides the same courier: the engine journals it, a phone carries it, and the platform resolves bay → registered owner and reaches them however they asked to be reached — email, SMS, push.
offline bay ──BLE──> a player's phone ──> SGA ──> "Bay 7 at Diamond Golf
Austin has been offline
for 9 days"Three things make this safe rather than clever:
- The courier carries an opaque, signed payload. The phone cannot read the notice, alter it, or forge one — the same properties that already make it a safe carrier for the journal.
- It contains nothing about anybody. An operator notice describes equipment and a bay. That is why it can travel on a stranger's phone at all.
- It is already agreed. Carrying is the member's choice (07 §1.2), and this is the same choice — one courier role, not a second thing to agree to.
The nice part is that it costs nothing. Every mechanism it needs was built for another reason, and a bay that has been dark for a week gets reported by the first member who plays there and goes home.
10. What this must not become#
- Not a chat channel. The engine reports conditions it observes. Anything a human wants to say to another human belongs in the app.
- Not a marketing surface. The one channel allowed to interrupt a golfer mid-round is worth protecting absolutely.
- Not a substitute for the journal. Every notice is journaled; the journal is the record, and this is the courtesy.