milsymbol-sidc 0.3.0 → 0.5.0

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/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # milsymbol-sidc
2
2
 
3
+ [![CI](https://github.com/psylsph/milsymbol-sidc/actions/workflows/ci.yml/badge.svg)](https://github.com/psylsph/milsymbol-sidc/actions/workflows/ci.yml)
3
4
  [![npm version](https://img.shields.io/npm/v/milsymbol-sidc.svg)](https://www.npmjs.com/package/milsymbol-sidc)
4
5
  [![npm downloads](https://img.shields.io/npm/dm/milsymbol-sidc.svg)](https://www.npmjs.com/package/milsymbol-sidc)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-%E2%89%A55.7-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
8
 
8
- A fluent TypeScript builder for **20-character numeric SIDC strings** — the
9
- symbol identification code format used by [MIL-STD-2525E](https://en.wikipedia.org/wiki/MIL-STD-2525)
10
- and [APP-6](https://en.wikipedia.org/wiki/NATO_Joint_Military_Symbology) — made
11
- for constructing symbols with the
9
+ A fluent TypeScript builder for **numeric SIDC strings** — 20 characters by
10
+ default, with opt-in positions 21–23 (extended modifiers and frame shape) for
11
+ [MIL-STD-2525E](https://en.wikipedia.org/wiki/MIL-STD-2525) and
12
+ [APP-6](https://en.wikipedia.org/wiki/NATO_Joint_Military_Symbology) symbols —
13
+ made for constructing symbols with the
12
14
  [milsymbol](https://github.com/spatialillusions/milsymbol) library.
13
15
 
14
16
  ```ts
@@ -29,14 +31,16 @@ new ms.Symbol(sidc).asSVG(); // friendly land unit icon
29
31
  - **Validated output** — invalid values throw; inconsistent combinations warn
30
32
  (or throw in `strict` mode), using the same rules milsymbol applies when it
31
33
  parses a SIDC.
32
- - **milsymbol-ready** — every generated 20-character string is accepted by
34
+ - **milsymbol-ready** — every generated string is accepted by
33
35
  milsymbol's numeric parser. Full rendering depends on the entity and
34
36
  modifier codes you supply, exactly as it does for any raw SIDC.
35
37
 
36
38
  > **Coverage:** complete structural encoding for positions 1–20 of the numeric
37
- > SIDC. Positions 8–10 have named universal codes; positions 11–20 accept
38
- > validated raw entity and modifier codes. Symbol-set-specific entity and
39
- > modifier catalogs remain future work.
39
+ > SIDC, plus opt-in positions 21–23 (extended modifiers and frame shape) that
40
+ > milsymbol consumes for 2525E/APP-6 E symbols. Positions 8–10 have named
41
+ > universal codes; positions 11–20 accept validated raw entity and modifier
42
+ > codes. A membership-only code catalog ships behind the `milsymbol-sidc/catalogs`
43
+ > subpath; semantic names remain future work.
40
44
 
41
45
  ## Installation
42
46
 
@@ -77,11 +81,11 @@ import {
77
81
 
78
82
  // A hostile planned air missile track under MIL-STD-2525E:
79
83
  const sidc = new Sidc()
80
- .version(Version.MilStd2525E) // pos 1-2 → "13"
81
- .context(Context.Reality) // pos 3 → "0"
84
+ .version(Version.MilStd2525E) // pos 1-2 → "13"
85
+ .context(Context.Reality) // pos 3 → "0"
82
86
  .identity(StandardIdentity.SuspectJoker) // pos 4 → "5"
83
- .symbolSet(SymbolSet.AirMissile) // pos 5-6 → "02"
84
- .status(Status.Planned) // pos 7 → "1"
87
+ .symbolSet(SymbolSet.AirMissile) // pos 5-6 → "02"
88
+ .status(Status.Planned) // pos 7 → "1"
85
89
  .toString();
86
90
 
87
91
  console.log(sidc); // "13050210000000000000"
@@ -103,32 +107,43 @@ const hostile = base.identity(StandardIdentity.SuspectJoker).toString();
103
107
  ## Anatomy of the generated SIDC
104
108
 
105
109
  ```text
106
- 13 0 3 10 0 2 16 123456 78 90
107
- │ │ │ │ │ │ │ │ │ │
108
- │ │ │ │ │ │ │ │ │ └ modifier 2 (19–20)
109
- │ │ │ │ │ │ │ │ └ │ modifier 1 (17–18)
110
- │ │ │ │ │ │ │ └ │ │ entity code (11–16)
111
- │ │ │ │ │ │ └ │ │ │ amplifier (9–10)
112
- │ │ │ │ │ └ │ │ │ │ HQ/task force/feint-dummy (8)
113
- │ │ │ │ └ │ │ │ │ │ status (7)
114
- │ │ │ └ │ │ │ │ │ │ symbol set (5–6)
115
- │ │ └ │ │ │ │ │ │ │ standard identity (4)
116
- │ └ │ │ │ │ │ │ │ │ context (3)
117
- └ │ │ │ │ │ │ │ │ │ version / edition (1–2)
110
+ 13 0 3 10 0 2 16 123456 78 90 0 0 A
111
+ │ │ │ │ │ │ │ │ │ │ │ │ │
112
+ │ │ │ │ │ │ │ │ │ │ │ │ └ frame shape (23) — `A` = no frame
113
+ │ │ │ │ │ │ │ │ │ │ │ └ │ modifier 2 extension (22)
114
+ │ │ │ │ │ │ │ │ │ │ └ │ │ modifier 1 extension (21)
115
+ │ │ │ │ │ │ │ │ │ └ │ │ │ modifier 2 (19–20)
116
+ │ │ │ │ │ │ │ │ └ │ │ │ │ modifier 1 (17–18)
117
+ │ │ │ │ │ │ │ └ │ │ │ │ │ entity code (11–16)
118
+ │ │ │ │ │ │ └ │ │ │ │ │ │ amplifier (9–10)
119
+ │ │ │ │ │ └ │ │ │ │ │ │ │ HQ/task force/feint-dummy (8)
120
+ │ │ │ │ └ │ │ │ │ │ │ │ │ status (7)
121
+ │ │ │ └ │ │ │ │ │ │ │ │ │ symbol set (5–6)
122
+ │ │ └ │ │ │ │ │ │ │ │ │ │ standard identity (4)
123
+ │ └ │ │ │ │ │ │ │ │ │ │ │ context (3)
124
+ └ │ │ │ │ │ │ │ │ │ │ │ │ version / edition (1–2)
118
125
  ```
119
126
 
120
- | Position | Field | API |
121
- | -------- | ------------------ | ----------------- |
122
- | 1–2 | Version / edition | `Version`, `version()` |
123
- | 3 | Context | `Context`, `context()` |
124
- | 4 | Standard identity | `StandardIdentity`, `identity()` |
125
- | 5–6 | Symbol set | `SymbolSet`, `symbolSet()` |
126
- | 7 | Status / condition | `Status`, `status()` |
127
- | 8 | HQ/task force/feint-dummy | `HqTaskForceDummy`, `hqTaskForceDummy()` |
128
- | 9–10 | Amplifier | `Amplifier`, `amplifier()` |
129
- | 11–16 | Entity code | `entity()` — six raw digits |
130
- | 17–18 | Modifier 1 | `modifier1()` — two raw digits |
131
- | 19–20 | Modifier 2 | `modifier2()` — two raw digits |
127
+ Positions 21–23 are emitted only when an extension field is set explicitly;
128
+ otherwise `toString()` produces the classic 20-character SIDC.
129
+
130
+ | Position | Field | API |
131
+ | -------- | ------------------------- | ------------------------------------------------------------ |
132
+ | 1–2 | Version / edition | `Version`, `version()` |
133
+ | 3 | Context | `Context`, `context()` |
134
+ | 4 | Standard identity | `StandardIdentity`, `identity()` |
135
+ | 5–6 | Symbol set | `SymbolSet`, `symbolSet()` |
136
+ | 7 | Status / condition | `Status`, `status()` |
137
+ | 8 | HQ/task force/feint-dummy | `HqTaskForceDummy`, `hqTaskForceDummy()` |
138
+ | 9–10 | Amplifier | `Amplifier`, `amplifier()` |
139
+ | 11–16 | Entity code | `entity()` — six raw digits |
140
+ | 17–18 | Modifier 1 | `modifier1()` — two raw digits, or `extendedModifier1()` |
141
+ | 19–20 | Modifier 2 | `modifier2()` — two raw digits, or `extendedModifier2()` |
142
+ | 21 | Modifier 1 extension | `extendedModifier1()` — hundreds digit of a three-digit code |
143
+ | 22 | Modifier 2 extension | `extendedModifier2()` — hundreds digit of a three-digit code |
144
+ | 23 | Frame shape | `frameShape()` — `0`–`9`, or `A` (no frame) |
145
+ | 17–18 | Modifier 1 | `modifier1()` — two raw digits |
146
+ | 19–20 | Modifier 2 | `modifier2()` — two raw digits |
132
147
 
133
148
  ## API
134
149
 
@@ -138,29 +153,42 @@ Creates a builder preconfigured to 2525E / Reality / Unknown / Unknown set /
138
153
  Present. This remains the default for backward compatibility; configure
139
154
  `Standard.App6` to default to APP-6 E instead.
140
155
 
141
- | Option | Type | Default | Description |
142
- | ------ | ---- | ------- | ----------- |
143
- | `strict` | `boolean` | `false` | Throw on invalid field combinations during `toString()` instead of warning. Invalid values always throw immediately regardless of this flag. |
144
- | `standard` | `Standard` | Not configured (2525E behavior) | Select a standard family. `App6` defaults the version to APP-6 E (`"14"`); `MilStd2525` defaults it to MIL-STD-2525E (`"13"`). Explicit configuration also checks that later version choices belong to the selected family. |
156
+ | Option | Type | Default | Description |
157
+ | ----------- | -------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `strict` | `boolean` | `false` | Throw on invalid field combinations during `toString()` instead of warning. Invalid values always throw immediately regardless of this flag. |
159
+ | `standard` | `Standard` | Not configured (2525E behavior) | Select a standard family. `App6` defaults the version to APP-6 E (`"14"`); `MilStd2525` defaults it to MIL-STD-2525E (`"13"`). Explicit configuration also checks that later version choices belong to the selected family. |
160
+ | `onWarning` | `(problem: SidcProblem) => void` | `console.warn` | Receive non-fatal problems instead of writing to the console. `strict: true` still throws instead of reporting. |
145
161
 
146
162
  ### Methods
147
163
 
148
164
  All setters validate their argument and return a new immutable `Sidc`.
149
165
 
150
- | Method | Field | Accepts |
151
- | ------ | ----- | ------- |
152
- | `standard(s)` | Version default + validation rules | A `Standard` constant. It retains a compatible current version; otherwise it selects that family's latest edition. |
153
- | `version(v)` | Positions 1–2 | A `Version` constant or any two-digit string (escape hatch for future editions) |
154
- | `context(c)` | Position 3 | A `Context` constant |
155
- | `identity(i)` | Position 4 | A `StandardIdentity` constant |
156
- | `symbolSet(s)` | Positions 5–6 | A `SymbolSet` constant or any two-digit string |
157
- | `status(s)` | Position 7 | A `Status` constant |
158
- | `hqTaskForceDummy(v)` | Position 8 | An `HqTaskForceDummy` constant |
159
- | `amplifier(v)` | Positions 9–10 | An `Amplifier` constant |
160
- | `entity(v)` | Positions 11–16 | Any six-digit string; symbol-set-specific catalogs are not included |
161
- | `modifier1(v)` | Positions 17–18 | Any two-digit string; symbol-set-specific catalogs are not included |
162
- | `modifier2(v)` | Positions 19–20 | Any two-digit string; symbol-set-specific catalogs are not included |
163
- | `toString()` | — | Validates combinations and renders the 20-character SIDC |
166
+ | Method | Field | Accepts |
167
+ | ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
168
+ | `standard(s)` | Version default + validation rules | A `Standard` constant. It retains a compatible current version; otherwise it selects that family's latest edition. |
169
+ | `version(v)` | Positions 1–2 | A `Version` constant or any two-digit string (escape hatch for future editions) |
170
+ | `context(c)` | Position 3 | A `Context` constant |
171
+ | `identity(i)` | Position 4 | A `StandardIdentity` constant |
172
+ | `symbolSet(s)` | Positions 5–6 | A `SymbolSet` constant or any two-digit string |
173
+ | `status(s)` | Position 7 | A `Status` constant |
174
+ | `hqTaskForceDummy(v)` | Position 8 | An `HqTaskForceDummy` constant |
175
+ | `amplifier(v)` | Positions 9–10 | An `Amplifier` constant |
176
+ | `entity(v)` | Positions 11–16 | Any six-digit string; symbol-set-specific catalogs are not included |
177
+ | `modifier1(v)` | Positions 17–18 | Any two-digit string; resets the modifier-1 extension digit to `0` when a tail exists |
178
+ | `modifier2(v)` | Positions 19–20 | Any two-digit string; resets the modifier-2 extension digit to `0` when a tail exists |
179
+ | `extendedModifier1(v)` | Positions 17–18 + 21 | Any three-digit string; hundreds digit lands at position 21 |
180
+ | `extendedModifier2(v)` | Positions 19–20 + 22 | Any three-digit string; hundreds digit lands at position 22 |
181
+ | `frameShape(v)` | Position 23 | A `FrameShape` constant |
182
+ | `withoutExtension()` | Positions 21–23 | Drops the tail, keeping the two-digit modifier slots (may change rendering) |
183
+ | `toString()` | — | Validates combinations and renders the 20- or 23-character SIDC |
184
+ | `problems()` | — | Structured list of non-fatal problems; never throws |
185
+ | `isValid()` | — | `true` when `problems()` is empty |
186
+ | `with(fields)` | — | Applies a partial field record immutably; validates like the setters |
187
+ | `clone()` | — | Copies the fields into an independent builder |
188
+ | `equals(other)` | — | Compares encoded fields, ignoring `strict` and `standard` |
189
+ | `toObject()` / `toJSON()` | — | Plain, serializable snapshot of every encoded field |
190
+ | `Sidc.parse(s, options?)` | — | Parses a 20- or 23-character numeric SIDC; throws when invalid |
191
+ | `Sidc.tryParse(s, options?)` | — | Parses a SIDC or returns `undefined` |
164
192
 
165
193
  ### Enum reference
166
194
 
@@ -169,24 +197,24 @@ All setters validate their argument and return a new immutable `Sidc`.
169
197
  Values match milsymbol's `standard` option and `ms.setStandard()` API, so the
170
198
  same constant can configure both libraries.
171
199
 
172
- | Constant | Value | Standard family |
173
- | -------- | ----- | --------------- |
200
+ | Constant | Value | Standard family |
201
+ | ------------ | -------- | --------------------------------------------------- |
174
202
  | `MilStd2525` | `"2525"` | US MIL-STD-2525 (**default behavior when omitted**) |
175
- | `App6` | `"APP6"` | NATO APP-6 |
203
+ | `App6` | `"APP6"` | NATO APP-6 |
176
204
 
177
205
  #### `HqTaskForceDummy`
178
206
 
179
207
  Position 8 values identify headquarters, task force, and feint/dummy variants.
180
208
 
181
- | Constant | Code | Meaning |
182
- | -------- | ---- | ------- |
183
- | `None` | `"0"` | None / not applicable |
184
- | `FeintDummy` | `"1"` | Feint/dummy |
185
- | `Headquarters` | `"2"` | Headquarters |
186
- | `FeintDummyHeadquarters` | `"3"` | Feint/dummy headquarters |
187
- | `TaskForce` | `"4"` | Task force |
188
- | `FeintDummyTaskForce` | `"5"` | Feint/dummy task force |
189
- | `TaskForceHeadquarters` | `"6"` | Task-force headquarters |
209
+ | Constant | Code | Meaning |
210
+ | --------------------------------- | ----- | ----------------------------------- |
211
+ | `None` | `"0"` | None / not applicable |
212
+ | `FeintDummy` | `"1"` | Feint/dummy |
213
+ | `Headquarters` | `"2"` | Headquarters |
214
+ | `FeintDummyHeadquarters` | `"3"` | Feint/dummy headquarters |
215
+ | `TaskForce` | `"4"` | Task force |
216
+ | `FeintDummyTaskForce` | `"5"` | Feint/dummy task force |
217
+ | `TaskForceHeadquarters` | `"6"` | Task-force headquarters |
190
218
  | `FeintDummyTaskForceHeadquarters` | `"7"` | Feint/dummy task-force headquarters |
191
219
 
192
220
  #### `Amplifier`
@@ -194,113 +222,134 @@ Position 8 values identify headquarters, task force, and feint/dummy variants.
194
222
  Position 9–10 values identify echelon, mobility, leadership, or auxiliary
195
223
  amplifiers. `None` writes the zero/no-amplifier code `"00"`.
196
224
 
197
- | Constant | Code | Meaning |
198
- | -------- | ---- | ------- |
199
- | `None` | `"00"` | None / not specified |
200
- | `TeamCrew` | `"11"` | Team/crew |
201
- | `Squad` | `"12"` | Squad |
202
- | `Section` | `"13"` | Section |
203
- | `PlatoonDetachment` | `"14"` | Platoon/detachment |
204
- | `CompanyBatteryTroop` | `"15"` | Company/battery/troop |
205
- | `BattalionSquadron` | `"16"` | Battalion/squadron |
206
- | `RegimentGroup` | `"17"` | Regiment/group |
207
- | `Brigade` | `"18"` | Brigade |
208
- | `Division` | `"21"` | Division |
209
- | `CorpsMef` | `"22"` | Corps/MEF |
210
- | `Army` | `"23"` | Army |
211
- | `ArmyGroupFront` | `"24"` | Army group/front |
212
- | `RegionTheater` | `"25"` | Region/theater |
213
- | `Command` | `"26"` | Command |
214
- | `WheeledLimitedCrossCountry` | `"31"` | Wheeled, limited cross-country |
215
- | `WheeledCrossCountry` | `"32"` | Wheeled, cross-country |
216
- | `Tracked` | `"33"` | Tracked |
217
- | `WheeledTrackedCombination` | `"34"` | Wheeled and tracked combination |
218
- | `Towed` | `"35"` | Towed |
219
- | `Rail` | `"36"` | Rail |
220
- | `PackAnimals` | `"37"` | Pack animals |
221
- | `OverSnowPrimeMover` | `"41"` | Over snow, prime mover |
222
- | `Sled` | `"42"` | Sled |
223
- | `Barge` | `"51"` | Barge |
224
- | `Amphibious` | `"52"` | Amphibious |
225
- | `ShortTowedArray` | `"61"` | Short towed array |
226
- | `LongTowedArray` | `"62"` | Long towed array |
227
- | `LeaderIndividual` | `"71"` | Leader individual |
228
- | `DeputyIndividual` | `"72"` | Deputy individual |
225
+ | Constant | Code | Meaning |
226
+ | ---------------------------- | ------ | ------------------------------- |
227
+ | `None` | `"00"` | None / not specified |
228
+ | `TeamCrew` | `"11"` | Team/crew |
229
+ | `Squad` | `"12"` | Squad |
230
+ | `Section` | `"13"` | Section |
231
+ | `PlatoonDetachment` | `"14"` | Platoon/detachment |
232
+ | `CompanyBatteryTroop` | `"15"` | Company/battery/troop |
233
+ | `BattalionSquadron` | `"16"` | Battalion/squadron |
234
+ | `RegimentGroup` | `"17"` | Regiment/group |
235
+ | `Brigade` | `"18"` | Brigade |
236
+ | `Division` | `"21"` | Division |
237
+ | `CorpsMef` | `"22"` | Corps/MEF |
238
+ | `Army` | `"23"` | Army |
239
+ | `ArmyGroupFront` | `"24"` | Army group/front |
240
+ | `RegionTheater` | `"25"` | Region/theater |
241
+ | `Command` | `"26"` | Command |
242
+ | `WheeledLimitedCrossCountry` | `"31"` | Wheeled, limited cross-country |
243
+ | `WheeledCrossCountry` | `"32"` | Wheeled, cross-country |
244
+ | `Tracked` | `"33"` | Tracked |
245
+ | `WheeledTrackedCombination` | `"34"` | Wheeled and tracked combination |
246
+ | `Towed` | `"35"` | Towed |
247
+ | `Rail` | `"36"` | Rail |
248
+ | `PackAnimals` | `"37"` | Pack animals |
249
+ | `OverSnowPrimeMover` | `"41"` | Over snow, prime mover |
250
+ | `Sled` | `"42"` | Sled |
251
+ | `Barge` | `"51"` | Barge |
252
+ | `Amphibious` | `"52"` | Amphibious |
253
+ | `ShortTowedArray` | `"61"` | Short towed array |
254
+ | `LongTowedArray` | `"62"` | Long towed array |
255
+ | `LeaderIndividual` | `"71"` | Leader individual |
256
+ | `DeputyIndividual` | `"72"` | Deputy individual |
229
257
 
230
258
  Entity and modifier setters intentionally accept raw digit strings so callers
231
259
  can use codes specific to their symbol set and edition. They validate width and
232
260
  ASCII digits but do not validate catalog membership.
233
261
 
262
+ #### `FrameShape`
263
+
264
+ Position 23, consumed by milsymbol for E-edition shape overrides (`1`–`9` are
265
+ ignored upstream unless the version is E; `A` applies on any edition).
266
+ Selectors are named after the frame they select; `NoFrame` (`A`) is the only
267
+ letter-valued code in the SIDC.
268
+
269
+ | Constant | Code | Meaning |
270
+ | -------------------------- | ----- | -------------------------------------------------- |
271
+ | `Default` | `"0"` | No override (**implied when no extension is set**) |
272
+ | `Space` | `"1"` | Space frame |
273
+ | `Air` | `"2"` | Air frame |
274
+ | `LandUnit` | `"3"` | Land unit frame |
275
+ | `LandEquipmentSeaSurface` | `"4"` | Land equipment / sea surface frame |
276
+ | `Installation` | `"5"` | Installation frame |
277
+ | `LandDismountedIndividual` | `"6"` | Dismounted individual frame |
278
+ | `SeaSubsurface` | `"7"` | Sea subsurface frame |
279
+ | `ActivityEvent` | `"8"` | Activity / event frame |
280
+ | `Cyberspace` | `"9"` | Cyberspace frame |
281
+ | `NoFrame` | `"A"` | Suppress the frame entirely |
282
+
234
283
  #### `Version`
235
284
 
236
- | Constant | Code | Standard |
237
- | -------- | ---- | -------- |
238
- | `MilStd2525D` | `"10"` | MIL-STD-2525D |
239
- | `App6D` | `"11"` | APP-6 D |
285
+ | Constant | Code | Standard |
286
+ | ------------- | ------ | --------------------------- |
287
+ | `MilStd2525D` | `"10"` | MIL-STD-2525D |
288
+ | `App6D` | `"11"` | APP-6 D |
240
289
  | `MilStd2525E` | `"13"` | MIL-STD-2525E (**default**) |
241
- | `App6E` | `"14"` | APP-6 E |
290
+ | `App6E` | `"14"` | APP-6 E |
242
291
 
243
292
  #### `Context`
244
293
 
245
- | Constant | Code | Meaning |
246
- | -------- | ---- | ------- |
247
- | `Reality` | `"0"` | Real-world operation (**default**) |
248
- | `Exercise` | `"1"` | Training/exercise |
249
- | `Simulation` | `"2"` | Simulation |
294
+ | Constant | Code | Meaning |
295
+ | ------------ | ----- | ---------------------------------- |
296
+ | `Reality` | `"0"` | Real-world operation (**default**) |
297
+ | `Exercise` | `"1"` | Training/exercise |
298
+ | `Simulation` | `"2"` | Simulation |
250
299
 
251
300
  #### `StandardIdentity`
252
301
 
253
- | Constant | Code | Frame drawn |
254
- | -------- | ---- | ----------- |
255
- | `Pending` | `"0"` | Unknown shape, dashed |
256
- | `Unknown` | `"1"` | Yellow octagonal frame (**default**) |
257
- | `AssumedFriend` | `"2"` | Blue frame, dashed |
258
- | `Friend` | `"3"` | Blue frame |
259
- | `Neutral` | `"4"` | Green frame |
260
- | `SuspectJoker` | `"5"` | Red frame, dashed — **Suspect** in reality, **Joker** in exercises |
261
- | `HostileFaker` | `"6"` | Red frame — **Hostile** in reality, **Faker** in exercises |
302
+ | Constant | Code | Frame drawn |
303
+ | --------------- | ----- | ------------------------------------------------------------------ |
304
+ | `Pending` | `"0"` | Unknown shape, dashed |
305
+ | `Unknown` | `"1"` | Yellow octagonal frame (**default**) |
306
+ | `AssumedFriend` | `"2"` | Blue frame, dashed |
307
+ | `Friend` | `"3"` | Blue frame |
308
+ | `Neutral` | `"4"` | Green frame |
309
+ | `SuspectJoker` | `"5"` | Red frame, dashed — **Suspect** in reality, **Joker** in exercises |
310
+ | `HostileFaker` | `"6"` | Red frame — **Hostile** in reality, **Faker** in exercises |
262
311
 
263
312
  #### `SymbolSet`
264
313
 
265
314
  Only sets that milsymbol can render are listed.
266
315
 
267
- | Constant | Code | Domain |
268
- | -------- | ---- | ------ |
269
- | `Unknown` | `"00"` | Unknown |
270
- | `Air` | `"01"` | Air tracks |
271
- | `AirMissile` | `"02"` | Air missiles |
272
- | `Space` | `"05"` | Space |
273
- | `SpaceMissile` | `"06"` | Space missiles |
274
- | `LandUnit` | `"10"` | Land units |
275
- | `LandCivilianUnit` | `"11"` | Land civilian units |
276
- | `LandEquipment` | `"15"` | Land equipment |
277
- | `Installation` | `"20"` | Installations |
278
- | `ControlMeasure` | `"25"` | Tactical graphics / control measures |
279
- | `LandDismountedIndividual` | `"27"` | Dismounted individuals |
280
- | `SeaSurface` | `"30"` | Sea surface tracks |
281
- | `SeaSubsurface` | `"35"` | Subsurface tracks |
282
- | `MineWarfare` | `"36"` | Sea mines |
283
- | `Activity` | `"40"` | Activities/events |
284
- | `SignalsIntelligenceSpace` | `"50"` | SIGINT space |
285
- | `SignalsIntelligenceAir` | `"51"` | SIGINT air |
286
- | `SignalsIntelligenceLand` | `"52"` | SIGINT land |
287
- | `SignalsIntelligenceSeaSurface` | `"53"` | SIGINT sea surface |
288
- | `SignalsIntelligenceSubsurface` | `"54"` | SIGINT subsurface |
289
- | `Cyberspace` | `"60"` | Cyberspace |
316
+ | Constant | Code | Domain |
317
+ | ------------------------------- | ------ | ------------------------------------ |
318
+ | `Unknown` | `"00"` | Unknown |
319
+ | `Air` | `"01"` | Air tracks |
320
+ | `AirMissile` | `"02"` | Air missiles |
321
+ | `Space` | `"05"` | Space |
322
+ | `SpaceMissile` | `"06"` | Space missiles |
323
+ | `LandUnit` | `"10"` | Land units |
324
+ | `LandCivilianUnit` | `"11"` | Land civilian units |
325
+ | `LandEquipment` | `"15"` | Land equipment |
326
+ | `Installation` | `"20"` | Installations |
327
+ | `ControlMeasure` | `"25"` | Tactical graphics / control measures |
328
+ | `LandDismountedIndividual` | `"27"` | Dismounted individuals |
329
+ | `SeaSurface` | `"30"` | Sea surface tracks |
330
+ | `SeaSubsurface` | `"35"` | Subsurface tracks |
331
+ | `MineWarfare` | `"36"` | Sea mines |
332
+ | `Activity` | `"40"` | Activities/events |
333
+ | `SignalsIntelligenceSpace` | `"50"` | SIGINT space |
334
+ | `SignalsIntelligenceAir` | `"51"` | SIGINT air |
335
+ | `SignalsIntelligenceLand` | `"52"` | SIGINT land |
336
+ | `SignalsIntelligenceSeaSurface` | `"53"` | SIGINT sea surface |
337
+ | `SignalsIntelligenceSubsurface` | `"54"` | SIGINT subsurface |
338
+ | `Cyberspace` | `"60"` | Cyberspace |
290
339
 
291
340
  Codes `"12"` and `"39"` have no named constant but are recognized via the raw
292
341
  string escape hatch.
293
342
 
294
343
  #### `Status`
295
344
 
296
- | Constant | Code | Meaning |
297
- | -------- | ---- | ------- |
298
- | `Present` | `"0"` | Present / actual (**default**) |
299
- | `Planned` | `"1"` | Planned / anticipated (dashed frame) |
300
- | `FullyCapable` | `"2"` | Condition bar: fully capable |
301
- | `Damaged` | `"3"` | Condition bar: damaged |
302
- | `Destroyed` | `"4"` | Condition bar: destroyed |
303
- | `FullToCapacity` | `"5"` | Condition bar: full to capacity |
345
+ | Constant | Code | Meaning |
346
+ | ---------------- | ----- | ------------------------------------ |
347
+ | `Present` | `"0"` | Present / actual (**default**) |
348
+ | `Planned` | `"1"` | Planned / anticipated (dashed frame) |
349
+ | `FullyCapable` | `"2"` | Condition bar: fully capable |
350
+ | `Damaged` | `"3"` | Condition bar: damaged |
351
+ | `Destroyed` | `"4"` | Condition bar: destroyed |
352
+ | `FullToCapacity` | `"5"` | Condition bar: full to capacity |
304
353
 
305
354
  ## Validation and error handling
306
355
 
@@ -323,6 +372,12 @@ Active combination rules:
323
372
  identity is pending/unknown.
324
373
  - Symbol set 27 is unsupported in MIL-STD-2525D; symbol set 60 is unsupported
325
374
  in APP-6 D.
375
+ - Non-default extension values (nonzero modifier extension digits or a frame
376
+ shape other than `Default`) are reported for D-edition versions `"10"`,
377
+ `"11"`, and `"12"`. This is the library's support policy, not a milsymbol
378
+ restriction: upstream consumes positions 21–22 and `A` on any edition, while
379
+ shape selectors `1`–`9` only take effect for E editions. Explicit all-zero
380
+ tails never warn.
326
381
  - When a `Standard` is explicitly configured, official version codes from the
327
382
  other standard family are reported.
328
383
  - Raw version/symbol-set codes outside milsymbol's known tables are reported.
@@ -390,6 +445,87 @@ try {
390
445
  Error classes: `SidcError` (base) → `SidcValidationError`,
391
446
  `SidcCombinationError`.
392
447
 
448
+ ### Reading and reporting problems
449
+
450
+ `console.warn` is only the default. Inspect problems without side effects with
451
+ `problems()` and `isValid()`, or route them anywhere with `onWarning`:
452
+
453
+ ```ts
454
+ import { Sidc, SymbolSet, Status } from "milsymbol-sidc";
455
+
456
+ const sidc = new Sidc()
457
+ .symbolSet(SymbolSet.ControlMeasure)
458
+ .status(Status.Destroyed);
459
+
460
+ sidc.problems();
461
+ // [{ code: "condition-status-control-measure", message: "…" }]
462
+ sidc.isValid(); // false
463
+
464
+ new Sidc({ onWarning: (problem) => log.warn(problem.code) })
465
+ .symbolSet(SymbolSet.ControlMeasure)
466
+ .status(Status.Destroyed)
467
+ .toString(); // no console output; onWarning is called once
468
+ ```
469
+
470
+ Every problem carries a stable `code` and the exact legacy warning `message`.
471
+ `problems()` never throws, even for builders created with `{ strict: true }`;
472
+ `toString()` still throws `SidcCombinationError` in strict mode.
473
+
474
+ ## Parsing a SIDC
475
+
476
+ `Sidc.parse()` turns a 20- or 23-character numeric SIDC back into a builder so
477
+ you can
478
+ validate, edit, and re-render existing codes. `Sidc.tryParse()` returns
479
+ `undefined` instead of throwing.
480
+
481
+ ```ts
482
+ import { Sidc } from "milsymbol-sidc";
483
+
484
+ const sidc = Sidc.parse("14031002161234560109");
485
+ sidc.toObject().entity; // "123456"
486
+
487
+ sidc.with({ modifier1: "02" }).toString(); // "14031002161234560209"
488
+ Sidc.tryParse("not-a-sidc"); // undefined
489
+ ```
490
+
491
+ Round-tripping is guaranteed: `Sidc.parse(s).toString() === s` for any string
492
+ this library produces, including explicit zero tails (`…000` stays 23
493
+ characters, because tail presence is data). A 20-character SIDC and the same
494
+ code padded with a zero tail render identically in milsymbol but compare as
495
+ unequal builders, mirroring the different strings. Only the 20- and
496
+ 23-character numeric forms are supported; letter-based SIDCs, 21/22-character
497
+ input, and positions 24–30 are not.
498
+
499
+ ## Catalogs (optional)
500
+
501
+ The `milsymbol-sidc/catalogs` subpath exposes a membership catalog generated from
502
+ milsymbol's numeric symbol data: which entity and modifier codes milsymbol
503
+ registers for each symbol set. It is a separate entry point, so the core builder
504
+ stays data-free unless you import it.
505
+
506
+ ```ts
507
+ import { entityCodes, isKnownEntityCode } from "milsymbol-sidc/catalogs";
508
+
509
+ isKnownEntityCode("10", "121100"); // true — a registered land-unit entity
510
+ isKnownEntityCode("10", "999999"); // false
511
+ entityCodes("10").length; // number of registered land-unit codes
512
+
513
+ // Three-digit (extended) modifiers are exposed separately:
514
+ import {
515
+ extendedModifier1Codes,
516
+ isKnownExtendedModifier1Code,
517
+ } from "milsymbol-sidc/catalogs";
518
+
519
+ isKnownExtendedModifier1Code("10", "100"); // true — the common UAV modifier
520
+ isKnownExtendedModifier1Code("10", "199"); // false
521
+ extendedModifier1Codes("10").length; // registered three-digit modifier 1 codes
522
+ ```
523
+
524
+ The catalog is **membership only**: it carries no semantic names and is never
525
+ consulted by `toString()`, so raw entity and modifier values still encode
526
+ without recognition warnings. Regenerate it with `npm run generate:catalogs`;
527
+ the milsymbol version it was derived from is exported as `CATALOG_SOURCE`.
528
+
393
529
  ## Using with milsymbol
394
530
 
395
531
  milsymbol routes any SIDC whose first two characters are digits to its numeric
@@ -426,9 +562,8 @@ const svg = symbol.asSVG();
426
562
  const ms = require("milsymbol");
427
563
 
428
564
  async function render() {
429
- const { Sidc, Standard, StandardIdentity, SymbolSet } = await import(
430
- "milsymbol-sidc"
431
- );
565
+ const { Sidc, Standard, StandardIdentity, SymbolSet } =
566
+ await import("milsymbol-sidc");
432
567
 
433
568
  const sidc = new Sidc({ standard: Standard.App6 })
434
569
  .identity(StandardIdentity.Friend)
@@ -447,7 +582,12 @@ Node.js 20.19+ and 22.12+ can also `require("milsymbol-sidc")` directly via
447
582
  ```html
448
583
  <script src="https://unpkg.com/milsymbol@3/dist/milsymbol.js"></script>
449
584
  <script type="module">
450
- import { Sidc, Standard, StandardIdentity, SymbolSet } from "https://unpkg.com/milsymbol-sidc/dist/src/index.js";
585
+ import {
586
+ Sidc,
587
+ Standard,
588
+ StandardIdentity,
589
+ SymbolSet,
590
+ } from "https://unpkg.com/milsymbol-sidc/dist/src/index.js";
451
591
 
452
592
  const sidc = new Sidc({ standard: Standard.App6 })
453
593
  .identity(StandardIdentity.Neutral)
@@ -466,6 +606,7 @@ Node.js 20.19+ and 22.12+ can also `require("milsymbol-sidc")` directly via
466
606
  import {
467
607
  Amplifier,
468
608
  Context,
609
+ FrameShape,
469
610
  HqTaskForceDummy,
470
611
  Sidc,
471
612
  Standard,
@@ -512,6 +653,22 @@ new Sidc({ standard: Standard.App6, strict: true })
512
653
  .modifier2("09")
513
654
  .toString(); // "14031000001234560109"
514
655
 
656
+ // 2525E UAV modifier (extended modifier 1 "100") plus the airborne modifier
657
+ new Sidc({ strict: true })
658
+ .identity(StandardIdentity.Friend)
659
+ .symbolSet(SymbolSet.LandUnit)
660
+ .entity("110000")
661
+ .extendedModifier1("100")
662
+ .extendedModifier2("100")
663
+ .toString(); // "13031000001100000000110"
664
+
665
+ // Frame shape "A" suppresses the frame for E-edition symbols
666
+ new Sidc({ strict: true })
667
+ .identity(StandardIdentity.Friend)
668
+ .symbolSet(SymbolSet.LandUnit)
669
+ .frameShape(FrameShape.NoFrame)
670
+ .toString(); // "1303100000000000000000A"
671
+
515
672
  // APP-6 D configuration retains an explicitly selected compatible edition
516
673
  new Sidc({ standard: Standard.App6, strict: true })
517
674
  .version(Version.App6D)
@@ -524,10 +681,16 @@ new Sidc({ standard: Standard.App6, strict: true })
524
681
 
525
682
  ```bash
526
683
  npm install
527
- npm test # compiles with tsc, then runs node --test against dist/
684
+ npm test # compiles with tsc, then runs node --test against dist/
528
685
  npm run build
686
+ npm run lint # eslint
687
+ npm run format:check # prettier
688
+ npm run generate:catalogs # regenerate src/catalogs.generated.ts from milsymbol
529
689
  ```
530
690
 
691
+ CI runs the test suite on Node 18/20/22/24, plus lint, formatting, markdownlint,
692
+ coverage, and a check that the generated catalog is current.
693
+
531
694
  Test coverage includes per-field offset encoding for every enum member,
532
695
  defaults, immutability, setter validation errors, extended-field offsets,
533
696
  all combination rules in both warn and strict modes, raw-code escape hatches,
@@ -535,10 +698,10 @@ and error class hierarchy.
535
698
 
536
699
  ## Roadmap
537
700
 
538
- - Symbol-set-specific named entity and modifier catalogs with semantic
539
- validation.
540
- - Official positions 21–30 / Set C extension data.
541
- - Parsing/decoding SIDC strings back into structured fields.
701
+ - Semantic entity and modifier names, layered on the membership catalog.
702
+ - Official positions 24–30 / Set C extension data (country codes and beyond).
703
+ Positions 21–23 shipped: see `extendedModifier1()`, `extendedModifier2()`,
704
+ and `frameShape()`.
542
705
 
543
706
  ## License
544
707