colregs 0.1.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +194 -26
  2. package/PROVENANCE.md +45 -12
  3. package/README.md +222 -23
  4. package/data/applicability.json +1252 -190
  5. package/data/deprecated-identifiers.json +7 -0
  6. package/data/facts.json +556 -113
  7. package/data/geometry.json +33 -33
  8. package/data/images.json +51 -25
  9. package/data/lights.json +35 -35
  10. package/data/rules.json +787 -3
  11. package/docs/adr/0001-name-and-jurisdiction-model.md +119 -1
  12. package/docs/adr/0003-language-as-a-dimension.md +220 -0
  13. package/docs/adr/0004-licence-layering.md +78 -0
  14. package/docs/adr/0005-rule-categories-and-the-situation-record.md +323 -0
  15. package/docs/adr/0006-json-schema-and-identifier-diff.md +95 -0
  16. package/docs/adr/0007-rule26-overrides-and-aground.md +68 -0
  17. package/docs/adr/0008-mooring-buoy-modifier.md +87 -0
  18. package/docs/budgets.json +162 -0
  19. package/docs/conventions.md +27 -0
  20. package/docs/gates.json +84 -0
  21. package/docs/identifiers.md +435 -0
  22. package/docs/part-b-invariants.md +922 -0
  23. package/docs/requirements.md +1251 -23
  24. package/docs/verification/2026-08-30-q6-q8.md +278 -0
  25. package/docs/verification/2026-09-05-rule27f-rule28-anchor-scenarios.md +330 -0
  26. package/fixtures/applicability-fixtures.json +361 -227
  27. package/fixtures/situation-fixtures.json +3673 -0
  28. package/package.json +16 -4
  29. package/schema/applicability-fixtures.schema.json +49 -0
  30. package/schema/applicability.schema.json +401 -0
  31. package/schema/deprecated-identifiers.schema.json +29 -0
  32. package/schema/facts.schema.json +793 -0
  33. package/schema/geometry.schema.json +239 -0
  34. package/schema/images.schema.json +53 -0
  35. package/schema/lights.schema.json +131 -0
  36. package/schema/rules.schema.json +50 -0
  37. package/schema/situation-fixtures.schema.json +325 -0
@@ -0,0 +1,435 @@
1
+ # Identifiers
2
+
3
+ Every name the data is addressed by — paragraph path, entry id, light id,
4
+ fact key, fact value, relation name — is an identifier, and REQ-MODEL-10
5
+ makes identifiers immutable from the version stated there onward. This file
6
+ records what the identifiers are and why they are shaped the way they are,
7
+ so that the shape is a decision on file rather than an accident nobody can
8
+ now change.
9
+
10
+ Identifiers are schema keywords, not display strings. They are never
11
+ localized (REQ-LANG-2); translations attach to them.
12
+
13
+ ## Two classes, opposite requirements
14
+
15
+ **Citation-derived identifiers carry no prefix.** A paragraph path *is* the
16
+ citation: `27(a)(i)` is what a mariner, a lawyer and a court all write, and
17
+ what a consumer stores when it records why a light was shown. Prefixing it
18
+ would put a package-local token in front of a reference that belongs to the
19
+ Convention rather than to this repository, and would make a stored citation
20
+ unreadable outside the tool that stored it. Entry ids are derived from
21
+ paragraph paths (below) and inherit the same transparency for the same
22
+ reason. Paragraph-keying is argued in ADR 0001 and required by
23
+ REQ-MODEL-4; nothing here reopens either.
24
+
25
+ **Vocabulary identifiers carry a type prefix.** These names are this
26
+ package's own — nothing in COLREGS calls anything `masthead` or `nuc`. They
27
+ share one flat string space across five files, and before the prefix they
28
+ collided in it: `towing` was simultaneously a light id (Rule 21(d)) and an
29
+ `activity` value (Rule 24(a)), so a consumer holding the string `towing`
30
+ could not say what it was a name *for* without knowing which field it came
31
+ out of. The prefix makes the namespace part of the identifier, which
32
+ resolves that collision by construction rather than by convention.
33
+
34
+ ## The scheme
35
+
36
+ | form | class | examples |
37
+ |---|---|---|
38
+ | `light:<id>` | light definitions (`data/lights.json`) | `light:masthead`, `light:sidelight_starboard`, `light:all_round` |
39
+ | `fact:<key>` | fact keys — the input vocabulary (`data/facts.json`) | `fact:activity`, `fact:length_m`, `fact:making_way`, `fact:on_mooring_buoy` |
40
+ | `<fact>:<value>` | values of an enumerated fact | `activity:nuc`, `position:anchored`, `propulsion:sail`, `obstruction_side:port` |
41
+ | `rel:<name>` | the five relation verbs (`data/applicability.json`) | `rel:includes`, `rel:in_lieu_of`, `rel:exempts` |
42
+
43
+ The prefix names the namespace the identifier lives in. For a fact *value*
44
+ that namespace is the fact itself, written bare: `activity:nuc`, not
45
+ `fact:activity:nuc`. A value is only ever meaningful against its own fact,
46
+ so naming the fact is what disambiguates it; naming the class as well would
47
+ add a segment that never varies.
48
+
49
+ **There is no version segment.** No `colregs.v1:activity:nuc`. A version in
50
+ the identifier churns every id at a major bump — including the ones that
51
+ did not change — which destroys exactly the stability REQ-MODEL-10 exists
52
+ to provide, and forces every consumer to rewrite stored references for
53
+ changes that did not affect them. Breaking changes are signalled by the
54
+ package version, which is where a consumer already looks.
55
+
56
+ Only enumerated facts have a value namespace. Numeric facts
57
+ (`fact:length_m`) take numbers and booleans (`fact:composite_unit`) take
58
+ `true`/`false`; there is nothing to prefix.
59
+
60
+ A two-subject predicate prefixes a **subject** segment onto the forms above
61
+ and adds three fact classes of its own (`kin:`, `geo:`, `hist:`). That is
62
+ pencil and is the next section.
63
+
64
+ ## Two subjects `✎`
65
+
66
+ **Pencil** (`docs/conventions.md`): ADR 0005 puts the whole two-subject shape
67
+ in pencil and v0.x allows the break, so any session may change this section
68
+ for a better idea, logging the change. What would settle it: the first
69
+ two-subject entry — Rule 18 — actually being written against it. This
70
+ section answers `Q-28`.
71
+
72
+ A `display` entry reads one vessel. A `classification` or `precedence`
73
+ entry reads two, and needs to say *whose* `fact:activity` it means. The form
74
+ is three segments:
75
+
76
+ ```
77
+ <subject>:<class>:<key>
78
+ ```
79
+
80
+ | segment | values |
81
+ |---|---|
82
+ | subject | `own`, `other`, `pair` |
83
+ | class | `fact`, `kin`, `geo`, `hist` |
84
+ | key | the identifier as it already exists, or a new one in a new class |
85
+
86
+ `own:fact:activity`, `other:kin:heading_deg`, `pair:geo:in_sight`,
87
+ `own:hist:was_overtaking`.
88
+
89
+ **A key with no subject segment means `own:`.** This is the whole of the
90
+ backward-compatibility story and it is why the subject is a *prefix* rather
91
+ than a change to the fact keys. `fact:activity` still spells `fact:activity`
92
+ and still denotes what it always denoted, so every predicate in
93
+ `data/applicability.json`, every fixture in
94
+ `fixtures/applicability-fixtures.json` and every stored citation a consumer
95
+ holds stays correct unedited — `REQ-MODEL-10` is satisfied by construction
96
+ rather than by a migration. The alternative shapes were a suffix
97
+ (`fact:activity:own`), which buries the thing you are scanning for at the
98
+ end of a variable-length name, and per-subject fact keys
99
+ (`fact:own_activity`), which would double the fact vocabulary and repoint
100
+ nothing but would leave two names for one concept forever. Prefixing is the
101
+ only one of the three where the existing vocabulary is a strict subset of
102
+ the new one.
103
+
104
+ The cost, stated so nobody rediscovers it: `own`, `other` and `pair` are now
105
+ reserved at the head of the identifier space, and no fact, light or relation
106
+ may ever be named one of them. That is the price of a subject segment that
107
+ is not itself prefixed, and it is cheap — the three words are not candidate
108
+ names for anything this package models.
109
+
110
+ ### The three subjects
111
+
112
+ `own` is the vessel the rule addresses; `other` is the vessel it is in an
113
+ encounter with. **`pair` is the encounter itself**, and it exists because
114
+ some facts belong to neither vessel: range is one number, not own's number
115
+ and the other's. Putting `geo:range_m` under both subjects would create two
116
+ identifiers for one quantity and a class of bug — the two disagreeing —
117
+ that has no meaning.
118
+
119
+ ### Relative geometry, and why aspect is not an identifier
120
+
121
+ Geometry splits on whether the quantity is symmetric between the vessels:
122
+
123
+ | fact | subject | |
124
+ |---|---|---|
125
+ | `geo:rel_bearing_deg` | `own` / `other` | bearing of the *other* subject, clockwise from this subject's heading |
126
+ | `geo:range_m` | `pair` | |
127
+ | `geo:bearing_change_deg_min` | `pair` | Rule 7(d)(i)'s steady bearing |
128
+ | `geo:cpa_m`, `geo:tcpa_s` | `pair` | |
129
+ | `geo:in_sight` | `pair` | Rule 3(k), symmetric because the rule defines it that way |
130
+
131
+ The directional row is where the namespace earns its keep.
132
+ `own:geo:rel_bearing_deg` is relative bearing — where the other vessel is
133
+ off own's bow. `other:geo:rel_bearing_deg` is the same fact read from the
134
+ other side, which is **aspect**. So aspect gets no identifier of its own: it
135
+ is a subject swap, not a second fact. Rule 13(b)'s overtaking sector is then
136
+ `other:geo:rel_bearing_deg` in (112.5, 247.5) — own more than 22.5° abaft
137
+ the other vessel's beam — written once, in the units the rule itself uses.
138
+ Swapping `own` and `other` throughout a predicate reverses the encounter,
139
+ which is exactly the operation a `precedence` rule needs and the reason to
140
+ prefer a subject namespace over two parallel vocabularies.
141
+
142
+ The directional and the pair geometry are redundant with `kin:` wherever both
143
+ are stated, and a record can state a set no two vessels can occupy — two
144
+ bearings no pair of headings produces, a CPA the speeds do not give.
145
+ `facts.json` declares the equations that relate them under
146
+ `situation.geometry.consistency`, and the suite enforces them on every fixture
147
+ and on every situation it constructs (`REQ-VERIFY-8`, `Q-48`). That makes them
148
+ checked, not derived: a consumer with an ARPA solution still supplies them,
149
+ and a record that omits the kinematics is unchecked rather than wrong.
150
+
151
+ `kin:` is the kinematic class ADR 0005 introduces — `kin:position`,
152
+ `kin:heading_deg`, `kin:sog_kn`, `kin:rot_deg_min`, `kin:dynamics`. It takes
153
+ `own`/`other` only; there is no kinematic state of a pair. `kin:dynamics`
154
+ is an enumerated fact, so its values follow the bare-fact-name rule above:
155
+ `dynamics:tanker`, not `kin:dynamics:tanker`.
156
+
157
+ ### History
158
+
159
+ Rule 13(d) is the reason history is a class and not a note. Once a vessel is
160
+ overtaking, a subsequent alteration of the bearing does not make her a
161
+ crossing vessel; the instantaneous geometry, read alone, says otherwise and
162
+ hands the duty to the wrong vessel. So the latch is a fact:
163
+
164
+ - `own:hist:was_overtaking` — this subject was, earlier in this encounter,
165
+ an overtaking vessel with respect to the other.
166
+ - `own:hist:latched_at_s` — how long ago that attached, for a `conduct`
167
+ monitor. A predicate at a point does not read it.
168
+
169
+ History is directional — it is *own* that was overtaking — so it takes a
170
+ subject segment like the fact record does, and never `pair`.
171
+
172
+ ### What this does not do
173
+
174
+ It does not version an identifier, and it does not repoint one. Everything
175
+ above is additive: new segments, new classes, new keys. No existing
176
+ identifier changes its spelling or its meaning, which is the property
177
+ `REQ-MODEL-10` protects and the one an alternative that renamed the fact
178
+ keys would have broken.
179
+
180
+ ## Effects `✎`
181
+
182
+ **Pencil** (`docs/conventions.md`): ADR 0005 puts the whole two-subject shape
183
+ there. What would settle it: a second family of `precedence` paragraphs —
184
+ Rules 12, 14 and 15 — written against it. **Written, and it held with one
185
+ addition**: Rules 7(d) and 13–15 are the first `classification` entries, and a
186
+ classification produces neither a role nor a section, so the table below grows
187
+ a third row. Rule 12 turned out to be `precedence` and not `classification`
188
+ (below). This section answers the data half of `Q-27` and is required by
189
+ `REQ-CAT-8`.
190
+
191
+ A `display` entry produces `lights`. A `scope` or `precedence` entry produces
192
+ an **effect**, and the shape of the effect is fixed by the category:
193
+
194
+ | category | effect |
195
+ |---|---|
196
+ | `scope` | `{"part", "section", "applies_rules"}` — which section of which Part governs, and the rules it contains |
197
+ | `precedence` | `{"own": <role>, "other": <role>}` — one role per subject |
198
+ | `classification` | `{"encounter": <encounter>}` **or** `{"risk_of_collision": true}` — exactly one key |
199
+
200
+ Five roles, a closed set: `give-way`, `stand-on`, `shall-not-impede`,
201
+ `keep-clear`, `none`. They are declared in `data/applicability.json` under
202
+ `effects`, and they are **not identifiers** — like modality and jurisdiction
203
+ values they are a closed vocabulary of their own, outside what `REQ-MODEL-10`
204
+ binds.
205
+
206
+ ### Encounters, and why a classification effect has two shapes
207
+
208
+ Four encounters, a closed set like the roles: `head-on` (Rule 14), `crossing`
209
+ (Rule 15), `overtaking` (Rule 13) and `none`. They are declared in
210
+ `data/applicability.json` under `effects.encounters` and, like the roles, they
211
+ are not identifiers.
212
+
213
+ A `classification` effect carries **exactly one key**, and which key it is
214
+ depends on which question the paragraph answers. Rule 7(d)(i) answers *does
215
+ risk of collision exist* and produces `{"risk_of_collision": true}`; Rules 13,
216
+ 14 and 15 answer *what kind of encounter is this* and produce an `encounter`.
217
+ ADR 0005 gives both questions to `classification` — "relative geometry,
218
+ history → encounter type, risk of collision" — and the two do not merge. A
219
+ single shape would have made every encounter entry state a risk it does not
220
+ decide, and 15(a)'s crossing test reads `pair:geo:risk_of_collision` as an
221
+ input rather than producing it.
222
+
223
+ There is no `{"risk_of_collision": false}` and there never will be. 7(a) makes
224
+ risk a judgement on all available means and deems it to exist in any doubt, so
225
+ an entry can add a ground for risk and nothing in this package can deny one.
226
+ `none` is declared as an encounter for the completeness of the vocabulary and
227
+ no entry produces it: an encounter type is asserted by a paragraph, and the
228
+ absence of one is the absence of an entry rather than an entry with a null
229
+ value.
230
+
231
+ **The three encounter types partition relative bearing, and the data is
232
+ written so that they cannot stop.** 13(b)'s sector is one constraint object,
233
+ `{"gt": 112.5, "lt": 247.5}`; 15(a)'s residual is `not` over that same object,
234
+ and 14(b)'s cone is negated the same way inside an `any_of`. Nothing writes a
235
+ crossing sector. The consequence is that the only way to put a bearing in two
236
+ encounters or in none is to edit one of two constraints and not the other, and
237
+ there is exactly one of each to edit. `test/data.test.mjs` sweeps both
238
+ subjects' bearings in half-degree steps and asserts exactly one encounter at
239
+ each of the 518 400 points; the Alloy version of the same property lives in
240
+ `colregs-engine`.
241
+
242
+ ### Rule 12 is `precedence`, not `classification`
243
+
244
+ ADR 0005 §1 and the proposal's first-cut table file Rule 12 under
245
+ `classification`. It is `precedence` here, for the reason `Q-37` gives for
246
+ 13(a): **12(a) produces a role, and a classification effect has nowhere to put
247
+ one.** "One of them shall keep out of the way of the other" is give-way and
248
+ stand-on in the effect vocabulary that already exists, and it is not an
249
+ encounter type — two sailing vessels meeting are still in a head-on, a
250
+ crossing or an overtaking, and Rule 12 says which of them gives way rather
251
+ than which kind of meeting it is. Rule 12 has no deeming paragraph at all:
252
+ 12(b) defines the windward side and is a `definition`, so it is the cite on the
253
+ `kin:wind_side` fact rather than an entry.
254
+
255
+ The category is `Q-14`'s to settle paragraph by paragraph and this is two more
256
+ of them; the departure from the table is recorded in ADR 0005's pencil log and
257
+ in `Q-40`.
258
+
259
+ **Who governs over Rule 12.** 12(a)'s subjects are "two sailing vessels",
260
+ which is 3(c), so `12a1`–`12a3` gate on `fact:propulsion` and not on the Rule
261
+ 18 rank — a fishing vessel under sail is a sailing vessel. Where Rule 18 also
262
+ ranks the pair, its entry displaces Rule 12's: `18b1`–`18b3` and `18c1`–`18c2`
263
+ carry `rel:overrides` against all three, because Rule 18's opening words
264
+ except Rules 9, 10 and 13 and nothing else. `13a` overrides them for the same
265
+ reason it overrides Rule 18 — 13(a) is "notwithstanding" the rest of Sections
266
+ I and II. The test that pins the relation asserts both reasons from
267
+ `rules.json`, so the data cannot keep an override after losing the words.
268
+
269
+ **And over Rule 15, the same way.** 15(a)'s subjects are "two power-driven
270
+ vessels", which is 3(b), so `15a-give-way` gates on `fact:propulsion` and on
271
+ no Rule 18 rank either — a vessel engaged in fishing, or not under command,
272
+ whose machinery is in use is a power-driven vessel. It used to negate the four
273
+ ranks in its own predicate, which said the same thing in the one place a test
274
+ could not see the reason; `18a1`–`18a3`, `18c1`–`18c2` and `18f1` now carry
275
+ `rel:overrides` against it instead. The derived half of the test is what makes
276
+ that checkable: a Rule 18 entry meets Rule 15 when it assigns a helm role and
277
+ neither subject is gated to a sailing vessel, and every such entry must carry
278
+ the override, so a Rule 18 paragraph added later cannot join Rule 15 silently.
279
+
280
+ **The effect names both subjects, and that is the point.** A `precedence`
281
+ entry is evaluated from own's side, so 18(a)(i) says own gives way *and* the
282
+ other vessel stands on. Writing only own's half would lose Rule 17, which
283
+ attaches to the counterpart of a give-way duty and to nothing else. So
284
+ `stand-on` appears only opposite `give-way`, and the counterpart of
285
+ `shall-not-impede` is always `none` — that is 8(f)(iii) in the data: a vessel
286
+ whose passage is not to be impeded acquires no privilege by it. `none` is
287
+ written rather than omitted, because a norm that confers nothing on a subject
288
+ is a finding and not an absence: NUC against RAM is `none` on both sides, and
289
+ that is Rule 18's partial order rather than a gap in the table.
290
+
291
+ `keep-clear` is one role for the two duties 18(e) and 18(f)(i) impose together
292
+ — keep well clear, and avoid impeding navigation. The vocabulary cannot
293
+ separate them and does not pretend to.
294
+
295
+ ### Two-subject entry ids
296
+
297
+ Entry ids stay citation-derived, exactly as below: `18a1` is 18(a)(i), `9c` is
298
+ 9(c), `8f3` is 8(f)(iii). Where a paragraph's subject is disjunctive — 9(b) is
299
+ "a vessel of less than 20 metres in length **or** a sailing vessel", and a
300
+ `when` is a conjunction — the paragraph takes two entries and the suffix names
301
+ the half: `9b-small` and `9b-sail`, `10j-small` and `10j-sail`. That is the
302
+ same rule the `-m2`/`-mw`/`-anc` suffixes below follow: name what
303
+ distinguishes them, in terms a reader with the rule text in front of them can
304
+ find.
305
+
306
+ ## Entry ids
307
+
308
+ An entry id is derived from the paragraph path its entry cites, lowercased
309
+ with the parentheses dropped and roman sub-paragraph numerals written as
310
+ arabic digits:
311
+
312
+ | paragraph path | entry id |
313
+ |---|---|
314
+ | Rule 28 (one paragraph) | `28` |
315
+ | `23(b)` | `23b` |
316
+ | `25(d)(ii)` | `25d2` |
317
+ | `23(a)(iii)`–`(iv)`, one entry | `23a34` |
318
+
319
+ Where one paragraph produces more than one entry, a hyphenated suffix names
320
+ what distinguishes them. The suffixes are **not** drawn from a single
321
+ scheme, because the paragraphs they split do not divide on a single axis:
322
+
323
+ | suffix | means | example |
324
+ |---|---|---|
325
+ | `-m2` / `-m3` | two or three masthead lights | `24a-m2`, `24a-m3` |
326
+ | `-rest` | the remainder of the rule's requirements once the split ones are taken out | `24a-rest` |
327
+ | `-id` | the identity lights: the all-round group that says *what the vessel is* | `26b-id`, `27a-id`, `27b-id` |
328
+ | `-mast` | the masthead light the paragraph adds on top of the identity lights | `26b-mast` |
329
+ | `-mw` | the making-way half of a rule that lights differently when moving through the water | `26b-mw`, `27a-mw`, `27b-mw` |
330
+ | `-gear` | the light indicating the direction of outlying gear | `26c-gear` |
331
+ | `-anc` | the at-anchor branch | `27b-anc` |
332
+ | `-anchor` / `-red` | 30(d)'s two halves: the anchor lights it requires, and the two red all-round lights of a vessel aground | `30d-anchor`, `30d-red` |
333
+
334
+ This was reviewed and kept as it stands. The alternative — a uniform
335
+ ordinal suffix, `27a-1` / `27a-2` — would be self-consistent and completely
336
+ opaque: it tells a reader with the rule text in front of them nothing, in
337
+ exchange for no gain to a machine, which only ever compares entry ids for
338
+ equality. `24a-m2` / `24a-m3` are worth calling out in particular, because
339
+ the two-or-three masthead split is stated in 24(a)(i) itself — the
340
+ cardinality is in the law, not a modelling convenience of this package, and
341
+ the suffix names something the reader can go and find.
342
+
343
+ ## Derived facts
344
+
345
+ A derived fact is one this package computes from the fact record rather than
346
+ asking a consumer for. It is an identifier like any other and takes the same
347
+ two forms as everything above: `fact:<key>` for the key, `<key>:<value>` for
348
+ its values. `fact:rule18_class` therefore takes `rule18_class:nuc`,
349
+ `rule18_class:sail` and so on — **not** `class:nuc`. The bare-fact-name rule
350
+ is what makes a value readable on its own: `class:nuc` would say which
351
+ namespace a reader is in only if they already knew, and `class` is exactly the
352
+ kind of word that a second derived fact would want too. The verbosity is the
353
+ price of the value being self-identifying, which is the same trade the whole
354
+ scheme makes.
355
+
356
+ Being derived is a property of the fact, not of its name. There is no `derived:`
357
+ prefix and no naming convention that marks one, because whether a consumer
358
+ supplies a fact or an evaluator computes it is a question about the pipeline
359
+ rather than about what the name denotes — and a fact that becomes derivable
360
+ later must not have to be renamed for it, which is exactly what `REQ-MODEL-10`
361
+ forbids. `facts.json` says so in a field instead: `derived: true`, beside a
362
+ decode table that is the definition.
363
+
364
+ ## Terms of art kept unspelled
365
+
366
+ Four `activity` values are abbreviations rather than words, and stay that
367
+ way:
368
+
369
+ | value | expansion | Rule |
370
+ |---|---|---|
371
+ | `activity:nuc` | not under command | 3(f) |
372
+ | `activity:ram` | restricted in her ability to manoeuvre | 3(g) |
373
+ | `activity:ram_underwater` | restricted in her ability to manoeuvre, dredging or engaged in underwater operations | 27(d) |
374
+ | `activity:cbd` | constrained by her draught | 3(h) |
375
+
376
+ These are the standard abbreviations in the field: they are what appears on
377
+ an AIS display, in a SignalK `navigation.state` value, and in a mariner's
378
+ own speech. Spelling them out would produce `activity:not_under_command`,
379
+ which is longer, no clearer to the audience that reads them, and further
380
+ from the vocabulary the consumers already use.
381
+
382
+ **`ram` is a trap and is named here so nobody has to discover it.** In this
383
+ dataset `ram` is *restricted in her ability to manoeuvre*. It is not the
384
+ English verb, and this is a dataset about vessels colliding. Anyone reading
385
+ `activity:ram` as a collision is reading a rule about a dredger as a rule
386
+ about an impact. The prefix helps — `activity:ram` reads as a status, where
387
+ bare `ram` read as an event — but the expansion is written down here
388
+ because a prefix cannot carry a definition.
389
+
390
+ `activity:ram_underwater` is a **refinement** of `activity:ram`, not a peer
391
+ of it: a predicate written for `activity:ram` also matches it. That is
392
+ implemented in the reference evaluator and asserted by the fixtures.
393
+
394
+ ## What is not an identifier
395
+
396
+ - **Modality values** (`shall`, `may`, `shall-if-practicable`,
397
+ `conditional`, `exempt`) and **jurisdiction values** (`intl`,
398
+ `us/inland`) are their own closed vocabularies, defined in §2 of the
399
+ requirements and not part of the identifier space REQ-MODEL-10 binds. So are
400
+ the **role** and **encounter** values of an effect.
401
+ - **Constants** — `situation.constants` in `data/facts.json`: the numbers a
402
+ Part B predicate needs and the Rules do not always give
403
+ (`appreciable_bearing_change_deg_min`, `head_on_half_angle_deg`, the two
404
+ `overtaking_sector_*_deg`). They are values, like a modality, and they are
405
+ named rather than written into a predicate so that the number appears once
406
+ and a test can assert that every entry reads it. Each carries its status
407
+ under `docs/conventions.md`, and a pencilled one carries what would settle
408
+ it.
409
+ - **Shape keys** — `when`, `one_of`, `cite`, `lights`, the predicate
410
+ language's `not` and `any_of`, and the SignalK decode table's
411
+ `also_activity` and `annex_ii_signal` — are JSON structure, not names the
412
+ data is addressed by. Only the values inside them can be identifiers, and
413
+ where they are (`also_activity` holds an activity value) they are prefixed.
414
+ `not` and `any_of` are the second pair of words reserved at the head of an
415
+ identifier space, after `own`/`other`/`pair`: they appear where a fact key
416
+ appears, so no fact may ever be named either. The cost is the same and as
417
+ cheap — every fact key carries a class prefix (`fact:`, `geo:`, `kin:`,
418
+ `hist:`, `env:`) and neither word could be one.
419
+ - **Prose fields** — `geometry.json`'s `datum` ("hull", "gunwale",
420
+ "forward masthead light") describes where a measurement is taken from in
421
+ words. It is deliberately not a light reference and does not resolve to
422
+ one.
423
+
424
+ ## This does not reverse GATE-5
425
+
426
+ GATE-5 declined a CI-enforced terminology glossary, permanently, for the
427
+ **legal corpora**: rule text is verbatim (REQ-MODEL-1), so a glossary that
428
+ disagreed with the source would be a defect in the glossary, and enforcing
429
+ consistent terminology across a transcription means corrupting it.
430
+
431
+ Nothing in this file is transcribed from a source. `light:masthead`,
432
+ `activity:nuc` and `rel:in_lieu_of` are names this package invented for its
433
+ own structures; COLREGS contains none of them. Documenting a vocabulary you
434
+ authored is not the same act as imposing one on a text you did not, and the
435
+ reasoning that closed GATE-5 does not reach it. GATE-5 stays declined.