colregs 0.2.3 → 0.2.4

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.
package/PROVENANCE.md CHANGED
@@ -19,6 +19,10 @@ publications. Nothing here is authoritative: consult the published rules.
19
19
  reconstruct it from memory, `rules.json` records it under `gaps` and the
20
20
  paragraph is omitted. 24(g) covers inconspicuous, partly submerged tows and
21
21
  is outside the applicability table in this release.
22
+ - **Withheld text is not a gap.** A `gaps` entry is a paragraph we could not
23
+ obtain. A paragraph with `text_status: withheld` is one we hold but may not
24
+ publish, under a licence bar recorded in `withheld_reason` — see ADR 0010.
25
+ None exist yet; `intl` is entirely verbatim.
22
26
  - **Known quirks kept verbatim:** the source's own text of Rule 1(c) repeats
23
27
  "special rules ... special rules made" and Rule 13(b) reads "coming up with
24
28
  a another vessel" (sic), and Rule 10(c) reads "A vessel, shall so far as
package/README.md CHANGED
@@ -1,17 +1,32 @@
1
1
  # colregs
2
2
 
3
- The COLREGS 72 navigation *light* rules as language-neutral JSON, plus the
4
- USCG's own diagrams and enough geometry to draw the lights yourself. Built as
5
- the base other countries' national amalgamations hang off of, not scoped to
6
- one country's rulebook.
3
+ Two vessels are closing. *What must they do? Who gives way? What must they display?*
7
4
 
8
- Data only. No runtime, no dependencies, no inference.
5
+ The COLREGS, the International Regulations for Preventing Collisions at Sea, answer all three. This project
6
+ transforms the colregs so that a machine can evaluate and reason about them *deterministically*:
7
+ the whole of the rules as structured data, so the same situation always yields the same result,
8
+ and every result can be traced back to the rule that produced it. Going further, the engine
9
+ and the rules are then *checked with formal methods*, which means mathematically proving the rule set
10
+ is consistent and complete rather than just testing it a bunch and hoping it all works out.
11
+
12
+ This package is the data: the rules as language-neutral JSON, the USCG's own diagrams, and
13
+ enough geometry to draw the lights yourself. Jurisdictions are deltas on the international
14
+ base, so national amalgamations hang off it rather than forking it.
15
+
16
+ Related packages:
17
+ * [colregs](https://github.com/mark-brannan/colregs) - the data and JSON Schema
18
+ * [colregs-engine](https://github.com/mark-brannan/colregs-engine) - the engine that evaluates rules
19
+ * [colregs-mcp](https://github.com/mark-brannan/colregs-mcp) - an MCP server so AI can use the engine
20
+ * [nav-wright](https://github.com/mark-brannan/nav-wright) - draws vessels and displays (stub)
21
+ * [searoom](https://github.com/mark-brannan/searoom) - a study tool demo
22
+
23
+ See a [live demo](https://mark-brannan.github.io/searoom/) of searoom and the colregs data/engine.
9
24
 
10
25
  > **Status: pre-release.** Not complete, and not fit for navigation. Navigate
11
26
  > by the published rules.
12
27
 
13
28
  ```text
14
- data/rules.json verbatim rule text, keyed by paragraph path and jurisdiction
29
+ data/rules.json rule text, keyed by paragraph path and jurisdiction
15
30
  data/lights.json the six Rule 21 lights: colour, arc, Rule 22 range
16
31
  data/facts.json the fact record, and how to decode SignalK navigation.state
17
32
  data/applicability.json predicate -> lights, each entry also carrying modality, citation, jurisdiction
@@ -22,107 +37,82 @@ images/ 38 USCG diagrams + 5 arc GIFs
22
37
  fixtures/ fact records and the entries that apply to them
23
38
  ```
24
39
 
25
- ## Coverage
40
+ ## What this package does not do
26
41
 
27
- Part C lights (Rules 20-31), `intl` jurisdiction, night only. Day shapes,
28
- Part D signals, and every other jurisdiction (US Inland, Canada, CEVNI) are
29
- on the roadmap in [`docs/requirements.md`](docs/requirements.md) but not
30
- present here.
42
+ It has no runtime and no dependencies. It does not infer anything: it is a
43
+ pure function of the fact record, and deciding *that* a vessel is fishing, or
44
+ aground, or making way is the caller's job. Nothing here reads a sensor.
31
45
 
32
- One exception, not a claim to model the Inland Rules: `us/inland` carries
33
- two entries, `30a-buoy` and `30b-buoy`, for a vessel made fast to a mooring
34
- buoy — 33 CFR 90.5 deems her at anchor and the Convention does not, so the
35
- case cannot be stated under `intl` at all
46
+ It does not select a single display. Where the rules permit a choice, every
47
+ lawful option comes back and none is picked. Selection belongs to the consumer.
48
+
49
+ ## Coverage
50
+
51
+ Every rule with a machine-checkable consequence: Part B conduct, Part C
52
+ lights and shapes, Part D sound and light signals, and Annex I geometry.
53
+ `intl` is the base; `us/inland`, `ca/inland` and `eu/cevni` are deltas on it.
54
+ [`docs/requirements.md`](docs/requirements.md) is the numbered contract for
55
+ all of it. One case the Convention cannot state, a vessel made fast to a
56
+ mooring buoy, lives only under `us/inland`
36
57
  ([ADR 0008](docs/adr/0008-mooring-buoy-modifier.md)).
37
58
 
38
- ## The four layers
59
+ ## The layers
39
60
 
40
- **Rule text.** Keyed by *paragraph path*, like `27(a)(i)` or `25(d)(ii)`,
41
- because the paragraph is the unit you actually cite. The text is verbatim
42
- International text; Inland-only inserts were stripped rather than paraphrased.
61
+ **Rule text.** Verbatim, keyed by *paragraph path*, like `27(a)(i)`, because
62
+ the paragraph is the unit you actually cite. Where a licence bars republishing
63
+ a jurisdiction's words, the paragraph is still modelled and its text withheld
64
+ rather than paraphrased — evaluation never reads the text.
43
65
 
44
- **Light definitions.** This layer is Rule 21. Each light carries its colour,
45
- its arc as a bearing range, and its minimum visible range by length band from
46
- Rule 22. Bearings run in degrees clockwise from right ahead, and an arc whose
47
- `from_deg` exceeds its `to_deg` wraps through the bow. So the masthead light
48
- is 247.5° to 112.5°, which is 225° of arc.
66
+ **Light definitions.** Rule 21's lights with colour, arc as a bearing range,
67
+ and Rule 22 range by length band. Bearings run clockwise from right ahead; an
68
+ arc whose `from_deg` exceeds its `to_deg` wraps through the bow.
49
69
 
50
70
  **Facts.** Three orthogonal axes (`fact:propulsion`, `fact:activity`,
51
- `fact:position`), a `fact:making_way` modifier that refines
52
- `position:underway`, and scalars such as `fact:length_m` and
53
- `fact:tow_length_m`. There is deliberately no vessel-class field. Under COLREGS
54
- what a vessel *is* follows from what it is *doing*, so classification falls
55
- out of the axes on its own.
56
-
57
- SignalK's `navigation.state` flattens all of that into one enum. `facts.json`
58
- therefore carries a decode table, the enum values it can't decode, and the
59
- five places where the flattening loses information. Fishing at anchor and
60
- making-way are the two of those that matter in practice.
61
-
62
- **Applicability entries.** Each entry is a predicate over facts, a set of
63
- lights or references to other entries, a modality, a citation, and a
64
- jurisdiction. Every entry has an id (`25b`, `27a-mw`) that both consumers can
65
- point at.
66
-
67
- **Identifiers.** Two classes, with opposite requirements. Citation-derived ids
68
- — paragraph paths and the entry ids built from them — carry no prefix, because
69
- the path *is* the citation. Vocabulary ids do: `light:masthead`,
70
- `fact:activity`, `activity:nuc`, `rel:in_lieu_of`. See
71
- [`docs/identifiers.md`](docs/identifiers.md); coming from a 0.1.x release, see
72
- [Migrating from 0.1.x](#migrating-from-01x).
71
+ `fact:position`), a `fact:making_way` modifier, and scalars such as
72
+ `fact:length_m`. There is deliberately no vessel-class field: under COLREGS
73
+ what a vessel *is* follows from what it is *doing*. A decode table maps
74
+ SignalK's `navigation.state` onto the axes and names what the flattening
75
+ loses.
76
+
77
+ **Applicability entries.** Each is a predicate over facts, a set of lights or
78
+ references to other entries, a modality, a citation, and a jurisdiction. Every
79
+ entry has an id (`25b`, `27a-mw`) a consumer can point at.
80
+
81
+ **Identifiers.** Citation-derived ids (paragraph paths, entry ids) carry no
82
+ prefix, because the path *is* the citation. Vocabulary ids do:
83
+ `light:masthead`, `fact:activity`, `activity:nuc`, `rel:in_lieu_of`. Every
84
+ identifier is immutable once published; retirements go through
85
+ [`data/deprecated-identifiers.json`](data/deprecated-identifiers.json). See
86
+ [`docs/identifiers.md`](docs/identifiers.md).
73
87
 
74
88
  ## Design
75
89
 
76
- This repo is requirements-first. Coding sessions work against numbered
77
- requirements and cite them; decisions that shaped the design are recorded as
78
- ADRs rather than argued again.
79
-
80
- - [`docs/requirements.md`](docs/requirements.md): the source of truth
81
- - [`docs/adr/`](docs/adr/): decisions, with the reasoning that produced them
90
+ Requirements-first: sessions work against the numbered requirements in
91
+ [`docs/requirements.md`](docs/requirements.md) and decisions live in
92
+ [`docs/adr/`](docs/adr/) rather than being argued again. Four ideas carry
93
+ most of it.
82
94
 
83
- Three ideas carry most of the design:
95
+ **The paragraph is the unit.** Rule text, citations and composition all key
96
+ on the paragraph path. Citation unit and composition unit turn out to be the
97
+ same thing.
84
98
 
85
- **The paragraph is the unit.** Rule text, citations and composition all key on
86
- the paragraph path (`27(a)(i)`, not "Rule 27"). Citation unit and composition
87
- unit turn out to be the same thing.
99
+ **Jurisdiction is a dimension, not a fork.** Every record carries a
100
+ `jurisdiction`: `intl`, or `<country-or-body>/<waters>` as a delta on it.
101
+ Entries a jurisdiction doesn't override are inherited, not restated.
88
102
 
89
- **Jurisdiction is a dimension, not a fork.** Every rule-text record and every
90
- applicability entry carries a `jurisdiction`: `intl` (the reserved base value)
91
- or `<country-or-body>/<waters>` (`us/inland`, `eu/cevni`). A jurisdiction is a
92
- delta on `intl`; entries it doesn't override are inherited, not restated
93
- (REQ-SCOPE-2/3). Geography that merely gates a rule inside one jurisdiction
94
- (Great Lakes, Western Rivers) is an ordinary fact a predicate reads, not a
95
- jurisdiction of its own (REQ-SCOPE-5).
103
+ **Predicates, not enumerations.** Gates are `fact:length_m < 7`, never a
104
+ pre-built list of configurations. Enumerated tables are where prior art
105
+ silently loses rules; a predicate cannot omit a case it was never asked about.
96
106
 
97
- **Predicates, not enumerations.** Gates are `fact:length_m < 7`, never a pre-built
98
- list of configurations. Enumerated tables are where prior art silently loses
99
- rules; a predicate cannot omit a case it was never asked about.
100
-
101
- **Alternatives are first-class.** COLREGS routinely permits a choice: a
102
- tricolor *in lieu of* separate sidelights, a torch *in lieu of* either. The data
103
- carries every lawful option with its modality and gate, and picks none of them.
104
- Selection belongs to the consumer.
107
+ **Alternatives are first-class.** A tricolor *in lieu of* separate sidelights,
108
+ a torch *in lieu of* either. The data carries every lawful option with its
109
+ modality and gate, and picks none of them.
105
110
 
106
111
  ## Predicate semantics
107
112
 
108
113
  An entry applies when **every** constraint in its `when` is satisfied. An
109
- absent fact never satisfies a constraint. Numeric constraints are
110
- `{gte, gt, lte, lt}`; a list means membership; anything else is equality.
111
- `fact:activity: "activity:ram"` also matches `activity:ram_underwater`, which
112
- is a refinement of it.
113
-
114
- A `when` is a conjunction, and two constructs open it up. `{"not": C}` is a
115
- constraint satisfied when the fact does *not* satisfy `C`; `any_of` is
116
- disjunction, holding sub-predicates as a key of a `when` and constraints as the
117
- value of a fact. **`not` over an absent fact is unsatisfied**, like every other
118
- constraint over an absent fact — so `{"not": C}` and `C` are both false on a
119
- record that never mentions the fact, and the two are not complements there. That
120
- is deliberate: predicates stay conservative, and a duty is never laid on a vessel
121
- because a consumer left a field out. It also means `not` is a constraint on one
122
- fact and never a key of a `when` — a predicate-level negation would be satisfied
123
- by silence, which is the one thing the absent-fact rule is there to forbid. Where
124
- a paragraph really does mean "any vessel other than …", the negation goes on the
125
- fact the paragraph names, and the fact has to be present for the entry to apply.
114
+ absent fact never satisfies a constraint, including `not`: a duty is never
115
+ laid on a vessel because a consumer left a field out.
126
116
 
127
117
  | form | where | means |
128
118
  |---|---|---|
@@ -133,94 +123,34 @@ fact the paragraph names, and the fact has to be present for the entry to apply.
133
123
  | `{"any_of": [C, …]}` | a fact's constraint | the fact satisfies at least one `C` |
134
124
  | `"any_of": [W, …]` | a key of a `when` | at least one sub-predicate `W` holds |
135
125
 
136
- The `ram`/`ram_underwater` refinement belongs to the *value*, not to one
137
- constraint form: it applies to equality, to list membership and to each
138
- `any_of` disjunct alike, and `{"not": C}` negates the refined reading rather
139
- than sneaking underneath it. It used to fire on a scalar constraint only, so a
140
- list quietly missed it; that is fixed, and a list and `{"any_of": […]}` are now
141
- interchangeable wherever both are legal.
142
-
143
- Some facts are **derived** rather than supplied. `facts.json`'s `derived`
144
- section holds them, each with a decode table that is its definition — an ordered
145
- list of predicate/value rows, first match wins — read the same way as the
146
- SignalK `navigation.state` table. `fact:rule18_class` is the one that exists: a
147
- vessel's rank under Rule 18, decoded from her propulsion, her activity and the
148
- 27(c) and WIG booleans, because Rule 18 ranks vessels by the Rule 3 terms of art
149
- and `fact:activity` answers a different question — what she *shows*. A consumer
150
- never supplies a derived fact.
151
-
152
- Entries **compose**: several apply to one fact record, and Rule 28 or Rule 26
153
- add to Rule 23 rather than replacing it. Relations between them:
126
+ Entries **compose**: several apply to one fact record, and Rule 26 adds to
127
+ Rule 23 rather than replacing it. A condition on whether a paragraph applies
128
+ at all goes in the predicate; a condition on which of two applicable
129
+ paragraphs prevails is a relation.
154
130
 
155
131
  | relation | meaning |
156
132
  |---|---|
157
133
  | `rel:includes` | import the referenced entry's **lights only**, never its predicate |
158
134
  | `rel:conditional_includes` | import lights when the stated `when` holds; `one_of` is a set of legal alternatives |
159
135
  | `rel:in_lieu_of` | this entry's lights replace the referenced entries' lights |
160
- | `rel:excludes` | must not be shown together: a pick-one between alternatives (25(c) and the tricolor), never one obligation vetoing another |
136
+ | `rel:excludes` | must not be shown together: a pick-one between alternatives, never one obligation vetoing another |
161
137
  | `rel:exempts` | the referenced requirement does not apply (30(e)) |
162
- | `rel:overrides` | the superiority relation: this paragraph's requirement prevails over the referenced one's when both apply (Rule 18's "except where Rules 9, 10 and 13 otherwise require"); for lights, Rule 26(a)'s "only the lights prescribed in this Rule" displacing Rule 30's anchor lights |
163
-
164
- A condition on whether a paragraph applies to the vessel at all goes in the
165
- predicate; a condition on which of two applicable paragraphs prevails is a
166
- relation. Delete the other paragraph: if this one is still true of the vessel,
167
- it is a relation. Rule 28 at anchor is a predicate; Rule 18 displacing Rule 15's
168
- role is `rel:overrides` (REQ-MODEL-13, ADR 0005 §4).
169
-
170
- Where the rules permit a choice, the data keeps every lawful option instead of
171
- picking one. A 12 m sloop under sail has three legal displays: 25(a), the
172
- 25(b) tricolor, or 25(a) plus the 25(c) red-over-green. Which one a given boat
173
- shows depends on what's installed, and that decision belongs to the consumer.
138
+ | `rel:overrides` | this paragraph's requirement prevails over the referenced one's when both apply (Rule 18; Rule 26(a) over Rule 30's anchor lights) |
174
139
 
175
140
  Modality is `shall`, `may`, `shall-if-practicable`, `shall-not`,
176
141
  `shall-not-impede`, or `conditional` with a `modality_by` table when it turns
177
- on a fact (23(a)(ii) is `shall` at 50 m and above, `may` below).
178
-
179
- Most entries read one vessel and produce lights. A few read **two** — a
180
- situation, not a fact record — and produce an `effect` instead. Rules 4, 11
181
- and 19(a) say which section of Part B governs; Rules 18, 9, 10, 12 and 15 say
182
- which vessel gives way — Rule 18 reading `fact:rule18_class` rather than
183
- re-listing the activity axis, Rules 12 and 15 reading the propulsion their own
184
- words name and leaving the rank to Rule 18's `rel:overrides`; and Rules 7(d),
185
- 13, 14 and 15 say what kind of encounter it is —
186
- `head-on`, `crossing` or `overtaking` — or that risk of collision exists.
187
- Those carry `category` and `subjects: 2`, and address each vessel
188
- through a subject segment: `own:fact:activity`, `other:fact:propulsion`,
189
- `pair:geo:in_sight`. A key with no subject means `own:`, so nothing above
190
- changes. `docs/identifiers.md` has the namespace and the effect vocabulary;
191
- `fixtures/situation-fixtures.json` is their contract.
192
-
193
- The three encounter types **partition** relative bearing. 13(b)'s overtaking
194
- sector is written once, as one constraint; Rule 15's crossing is `not` over
195
- that same constraint and `not` over Rule 14's head-on cone, so no crossing
196
- sector is enumerated anywhere and none can drift out of step. The suite sweeps
197
- both vessels' bearings in half-degree steps and asserts that exactly one
198
- encounter applies at every point, that 13(b)'s 22.5°-abaft-the-beam edge is
199
- exclusive on both sides, and that Rule 13(d)'s latch holds the classification
200
- at `overtaking` however far the bearing afterwards draws out. The numbers the
201
- Rules do not state — what counts as an appreciable bearing change, how wide
202
- "nearly reciprocal" is — are declared once in `facts.json` under
203
- `situation.constants`, marked pencil, and read from there by every entry.
204
-
205
- A situation can state geometry no two vessels can occupy, so the suite checks
206
- every fixture that states its kinematics against them (REQ-VERIFY-8): the two
207
- relative bearings must be two readings of one line of sight, positions must
208
- reproduce range and bearing, and CPA, TCPA and bearing rate must be the ones
209
- the headings and speeds give. The equations and tolerances are declared once in
210
- `facts.json` under `situation.geometry.consistency`. The sweeps construct
211
- situations that pass the same check, and the property that no two vessels are
212
- both give-way is asserted over a sweep of steady-bearing geometries — where it
213
- is a theorem — with the both-starboard geometry that breaks it pinned as one
214
- the check rejects.
215
-
216
- ## What this package does not do
217
-
218
- It does not infer anything. It is a pure function of the fact record. Deciding
219
- *that* a vessel is making way, or fishing, or aground is somebody else's job.
220
- Nothing here reads a sensor.
221
-
222
- It does not select a single display. Where the rules offer alternatives, all of
223
- them come back.
142
+ on a fact.
143
+
144
+ Most entries read one vessel and produce lights. The Part B entries read
145
+ **two**, a situation rather than a fact record, and produce an `effect`:
146
+ which section governs, which vessel gives way, whether the encounter is
147
+ `head-on`, `crossing` or `overtaking`. They address each vessel through a
148
+ subject segment (`own:fact:activity`, `other:fact:propulsion`,
149
+ `pair:geo:in_sight`); a key with no subject means `own:`. The encounter
150
+ sectors partition relative bearing, so no crossing sector is enumerated and
151
+ none can drift. [`docs/identifiers.md`](docs/identifiers.md) has the
152
+ vocabulary, [`docs/part-b-invariants.md`](docs/part-b-invariants.md) the
153
+ invariants, and `fixtures/situation-fixtures.json` is the contract.
224
154
 
225
155
  ## Verifying
226
156
 
@@ -230,124 +160,17 @@ npm test
230
160
 
231
161
  Every fixture reproduces exactly; every citation, cross-reference, light and
232
162
  geometry reference resolves; every image is on disk with its SHA-256 recorded;
233
- every decoded `navigation.state` value and every fact a predicate reads is
234
- declared.
235
-
236
- A **drift test** (REQ-VERIFY-2) cross-checks two directions: forward, fact
237
- record to lights, and reverse, lights already shown to which other entries
238
- could explain them. It fails on any collision the data doesn't already
239
- declare through `rel:includes`/`rel:in_lieu_of`/`rel:excludes`/
240
- `rel:exempts`/`rel:conditional_includes`.
241
-
242
- Every numeric gate that affects *which entries apply* has fixtures immediately
243
- either side of its threshold (REQ-VERIFY-5), and every entry is exercised by
244
- at least one fixture and absent from at least one other (REQ-VERIFY-3). Three
245
- gates that affect only *modality*, not which entries apply, are flagged as an
246
- open question rather than fixtured against a schema that can't express the
247
- distinction; see [Q-5](docs/requirements.md#9-open-questions).
248
-
249
- `fixtures/applicability-fixtures.json` is the cross-implementation contract: an
250
- implementation in any language should reproduce those entry sets exactly.
251
-
252
- ## Migrating from 0.1.x
253
-
254
- 0.2.0 is the first release whose vocabulary identifiers carry a type prefix.
255
- `colregs@0.1.1`, the last version on npm, has the bare names; every 0.1.x
256
- consumer holds strings that no longer resolve. Renaming an identifier is a
257
- breaking change under REQ-PKG-4, and the rename is the one exception
258
- REQ-MODEL-10 records: 0.1.1 predates the identifier audit, sits outside the
259
- immutability baseline, and nothing after it may be renamed this way again.
260
-
261
- **What did not change.** Paragraph paths (`27(a)(i)`) and entry ids (`25b`,
262
- `27a-mw`) are citation-derived and stay bare. No entry id present in 0.1.1
263
- is removed — the 40 entries of 0.1.1 are all in 0.2.0, with 30 more.
264
-
265
- **What did.** Every light id, fact key, fact value and relation verb, in
266
- every place it appears: `lights.json` keys, `facts.json` keys and `values`,
267
- the `light` field of an entry's `lights[]`, the keys and values of an entry's
268
- `when` and `modality_by[].when`, the relation keys on an entry
269
- (`includes` → `rel:includes`, and so on), the `relations` map in
270
- `applicability.json`, the `light` field in `geometry.json`, the
271
- `signalk_navigation_state.decode` table, `actuable_subset.fields`, and the
272
- `facts` record of every fixture. The rule is in
273
- [`docs/identifiers.md`](docs/identifiers.md): a light is `light:<id>`, a
274
- fact key is `fact:<key>`, a value of an enumerated fact is `<fact>:<value>`,
275
- a relation is `rel:<name>`. The full table, generated from the two data
276
- sets rather than from memory:
277
-
278
- | kind | 0.1.1 | 0.2.0 |
279
- |---|---|---|
280
- | light id | `masthead` | `light:masthead` |
281
- | light id | `sidelights` | `light:sidelights` |
282
- | light id | `sidelight_starboard` | `light:sidelight_starboard` |
283
- | light id | `sidelight_port` | `light:sidelight_port` |
284
- | light id | `sternlight` | `light:sternlight` |
285
- | light id | `towing` | `light:towing` |
286
- | light id | `all_round` | `light:all_round` |
287
- | light id | `flashing` | `light:flashing` |
288
- | light id | `torch` | `light:torch` |
289
- | light id | `deck_lights` | `light:deck_lights` |
290
- | fact key | `propulsion` | `fact:propulsion` |
291
- | value of propulsion | `power` | `propulsion:power` |
292
- | value of propulsion | `sail` | `propulsion:sail` |
293
- | value of propulsion | `oars` | `propulsion:oars` |
294
- | fact key | `activity` | `fact:activity` |
295
- | value of activity | `none` | `activity:none` |
296
- | value of activity | `fishing` | `activity:fishing` |
297
- | value of activity | `trawling` | `activity:trawling` |
298
- | value of activity | `towing` | `activity:towing` |
299
- | value of activity | `pushing` | `activity:pushing` |
300
- | value of activity | `being_towed` | `activity:being_towed` |
301
- | value of activity | `nuc` | `activity:nuc` |
302
- | value of activity | `ram` | `activity:ram` |
303
- | value of activity | `ram_underwater` | `activity:ram_underwater` |
304
- | value of activity | `cbd` | `activity:cbd` |
305
- | value of activity | `mine` | `activity:mine` |
306
- | value of activity | `pilot` | `activity:pilot` |
307
- | value of activity | `diving` | `activity:diving` |
308
- | fact key | `position` | `fact:position` |
309
- | value of position | `underway` | `position:underway` |
310
- | value of position | `anchored` | `position:anchored` |
311
- | value of position | `aground` | `position:aground` |
312
- | value of position | `moored` | `position:moored` |
313
- | fact key | `making_way` | `fact:making_way` |
314
- | fact key | `length_m` | `fact:length_m` |
315
- | fact key | `tow_length_m` | `fact:tow_length_m` |
316
- | fact key | `max_speed_kn` | `fact:max_speed_kn` |
317
- | fact key | `gear_extent_m` | `fact:gear_extent_m` |
318
- | fact key | `beam_m` | `fact:beam_m` |
319
- | fact key | `composite_unit` | `fact:composite_unit` |
320
- | fact key | `non_displacement` | `fact:non_displacement` |
321
- | fact key | `wig` | `fact:wig` |
322
- | fact key | `wig_near_surface` | `fact:wig_near_surface` |
323
- | fact key | `near_channel` | `fact:near_channel` |
324
- | fact key | `inconspicuous_partly_submerged_tow` | `fact:inconspicuous_partly_submerged_tow` |
325
- | fact key | `towed_alongside` | `fact:towed_alongside` |
326
- | fact key | `obstruction_exists` | `fact:obstruction_exists` |
327
- | fact key | `obstruction_side` | `fact:obstruction_side` |
328
- | value of obstruction_side | `port` | `obstruction_side:port` |
329
- | value of obstruction_side | `starboard` | `obstruction_side:starboard` |
330
- | relation | `includes` | `rel:includes` |
331
- | relation | `conditional_includes` | `rel:conditional_includes` |
332
- | relation | `in_lieu_of` | `rel:in_lieu_of` |
333
- | relation | `excludes` | `rel:excludes` |
334
- | relation | `exempts` | `rel:exempts` |
335
-
336
- The mapping is mechanical and total: strip nothing, prepend the namespace.
337
- The one string that needed the prefix to disambiguate is `towing`, which in
338
- 0.1.1 was both a light id and an `activity` value; it is now `light:towing`
339
- or `activity:towing` depending on which it was.
340
-
341
- A consumer that builds a fact record from SignalK `navigation.state` gets
342
- the new keys and values from the decode table for free. One that stored a
343
- 0.1.1 fact record, light id or relation name must rewrite it by the table
344
- above; `fixtures/applicability-fixtures.json` is the check that the rewrite
345
- came out right.
346
-
347
- From 0.2.0 on, every identifier in the package is immutable once published
348
- (REQ-MODEL-10). [`data/deprecated-identifiers.json`](data/deprecated-identifiers.json)
349
- is the REQ-MODEL-11 registry a retired identifier goes into, and `npm test`
350
- refuses a removal that is not recorded there. [`data/version.json`](data/version.json) mirrors `package.json`; release-please keeps it in sync (see [ADR 0009](docs/adr/0009-data-version-stamp.md)).
163
+ every fact a predicate reads is declared. A drift test cross-checks fact
164
+ record to lights and lights back to entries, and fails on any collision the
165
+ data doesn't declare through a relation. Every numeric gate has fixtures
166
+ either side of its threshold, and every entry is exercised by at least one
167
+ fixture and absent from another. The encounter sweeps assert that exactly one
168
+ encounter applies at every bearing, and that no steady-bearing geometry makes
169
+ both vessels give-way.
170
+
171
+ `fixtures/applicability-fixtures.json` and `fixtures/situation-fixtures.json`
172
+ are the cross-implementation contract: an implementation in any language
173
+ should reproduce those exactly.
351
174
 
352
175
  ## Provenance and licence
353
176
 
@@ -356,5 +179,5 @@ publications, public domain; see [PROVENANCE.md](PROVENANCE.md). The
356
179
  compilation is Apache-2.0.
357
180
 
358
181
  Not authoritative, not endorsed by the Coast Guard. Navigate by the published
359
- rules. When the `ca/inland` delta lands, it will carry the same condition
360
- its licence does: it must not be represented as an official version.
182
+ rules. The `ca/inland` delta carries the same condition its licence does: it
183
+ must not be represented as an official version.
@@ -196,6 +196,7 @@
196
196
  }
197
197
  ],
198
198
  "images": [
199
+ "NRHB_23_a.png",
199
200
  "NRHB_23_aii.png"
200
201
  ]
201
202
  },
@@ -393,7 +394,7 @@
393
394
  ],
394
395
  "modality": "shall",
395
396
  "images": [
396
- "NRHB_24_a.png"
397
+ "NRHB_24_av.png"
397
398
  ]
398
399
  },
399
400
  {
@@ -952,7 +953,8 @@
952
953
  ],
953
954
  "modality": "shall",
954
955
  "images": [
955
- "NRHB_27_d.png"
956
+ "NRHB_27_d.png",
957
+ "NRHB_27_diii.png"
956
958
  ],
957
959
  "notes": "27(d)(iii): when at anchor these lights are shown instead of Rule 30's."
958
960
  },
@@ -987,9 +989,6 @@
987
989
  "27d"
988
990
  ],
989
991
  "modality": "shall",
990
- "images": [
991
- "NRHB_27_ei.png"
992
- ],
993
992
  "notes": "Only when the vessel's size makes the full 27(d) display impracticable."
994
993
  },
995
994
  {
@@ -1134,7 +1133,7 @@
1134
1133
  ],
1135
1134
  "modality": "shall",
1136
1135
  "images": [
1137
- "NRHB_30_a.png"
1136
+ "NRHB_30_d.png"
1138
1137
  ]
1139
1138
  },
1140
1139
  {
@@ -1158,7 +1157,10 @@
1158
1157
  "rel:in_lieu_of": [
1159
1158
  "30a"
1160
1159
  ],
1161
- "modality": "may"
1160
+ "modality": "may",
1161
+ "images": [
1162
+ "NRHB_30_a.png"
1163
+ ]
1162
1164
  },
1163
1165
  {
1164
1166
  "id": "30c",
@@ -1232,7 +1234,7 @@
1232
1234
  ],
1233
1235
  "modality": "shall-if-practicable",
1234
1236
  "images": [
1235
- "NRHB_30_d.png"
1237
+ "NRHB_30_dii.png"
1236
1238
  ],
1237
1239
  "notes": "The two red lights are 'shall, if practicable'. 30(f) exempts vessels under 12 m from them; it does not exempt the 30(a)/(b) anchor lights, see 30d-anchor."
1238
1240
  },
@@ -1654,7 +1656,8 @@
1654
1656
  },
1655
1657
  "pair:geo:tcpa_s": {
1656
1658
  "gt": 0
1657
- }
1659
+ },
1660
+ "other:hist:was_overtaking": false
1658
1661
  },
1659
1662
  {
1660
1663
  "own:hist:was_overtaking": true
@@ -1683,7 +1686,7 @@
1683
1686
  "12a2",
1684
1687
  "12a3"
1685
1688
  ],
1686
- "note": "Categorised `precedence`, not `classification` as ADR 0005 sec. 1 and the proposal's first-cut table have it. 13(a) is the one paragraph of Rule 13 that assigns a role, and a `classification` entry has no field to put a role in; 13(b)'s sector test is the classification and is what sets the latch. Q-14 settles membership paragraph by paragraph and this is one of them. The predicate now carries both halves of 'any vessel overtaking any other': the 13(b) sector, which 13b-overtaking classifies, and the 13(d) latch, which 13d classifies. They are a disjunction, which is what the entry could not write before the predicate language grew `any_of` (Q-33). Now also overrides the three Rule 12 entries: 'notwithstanding anything contained in' the Rules of Part B Sections I and II includes Rule 12, and two sailing vessels in an overtaking would otherwise hold 13(a)'s and 12(a)'s roles at once (Q-40).",
1689
+ "note": "Categorised `precedence`, not `classification` as ADR 0005 sec. 1 and the proposal's first-cut table have it. 13(a) is the one paragraph of Rule 13 that assigns a role, and a `classification` entry has no field to put a role in; 13(b)'s sector test is the classification and is what sets the latch. Q-14 settles membership paragraph by paragraph and this is one of them. The predicate now carries both halves of 'any vessel overtaking any other': the 13(b) sector, which 13b-overtaking classifies, and the 13(d) latch, which 13d classifies. They are a disjunction, which is what the entry could not write before the predicate language grew `any_of` (Q-33). Now also overrides the three Rule 12 entries: 'notwithstanding anything contained in' the Rules of Part B Sections I and II includes Rule 12, and two sailing vessels in an overtaking would otherwise hold 13(a)'s and 12(a)'s roles at once (Q-40). The sector branch now also reads `other:hist:was_overtaking: false`, so it cannot newly name a second give-way vessel while the other side already holds 13(d)'s latch -- once overtaking, always overtaking, on whichever vessel history says holds the role (Q-47).",
1687
1690
  "gap": "Only own's side. 13(a) lays the duty on the overtaking vessel, so this entry is directional by design and the overtaken vessel's stand-on role comes from the same entry read on the swapped situation. What it still cannot say is 13(c) -- a vessel in any doubt as to whether she is overtaking shall assume that she is -- because doubt is a state of the observer and no fact carries it; and it inherits 13b-overtaking's speed-comparison gap. Q-41, Q-46."
1688
1691
  },
1689
1692
  {