@meri-imperiumi/signalk-dead-reckoning 0.1.0 → 0.3.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/CHANGELOG.md +155 -0
- package/README.md +1 -1
- package/SPEC.md +11 -3
- package/package.json +3 -2
- package/plugin/hysteresis.js +82 -0
- package/plugin/index.js +481 -93
- package/plugin/polar.js +85 -0
- package/plugin/uncertainty.js +12 -1
- package/public/dr-app.js +51 -29
- package/public/dr-map-view.js +29 -19
- package/public/dr-viewmodel.js +101 -14
- package/tests/dr-current.test.js +2 -1
- package/tests/dr-viewmodel.test.js +90 -2
- package/tests/hysteresis.test.js +62 -0
- package/tests/plugin.test.js +739 -48
- package/tests/polar.test.js +106 -0
- package/tests/tile-paths.test.js +258 -0
- package/tests/uncertainty.test.js +25 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,161 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`inertial-polar` DR speed fallback (SPEC §3.1, work doc #18)** —
|
|
12
|
+
when the paddlewheel is unusable (`navigation.speedThroughWater`
|
|
13
|
+
missing, or the debounced §6.3 fouling verdict active), DR integrates
|
|
14
|
+
speed from the polar performance plugin's `performance.polarSpeed`
|
|
15
|
+
delta instead of freezing: requires `signalk-polar-performance-plugin`
|
|
16
|
+
installed and configured with its polar speed output enabled; the
|
|
17
|
+
super-jittery raw delta is running-averaged (default 60 s window,
|
|
18
|
+
30 s staleness cutoff) before integration. Gated to underway+sailing
|
|
19
|
+
(no wind-on-mast drift at the dock, no meaningless polar under
|
|
20
|
+
power); no matrix corrections while on polar (bins were trained on
|
|
21
|
+
real STW — a model estimate is circular input); uncertainty grows at
|
|
22
|
+
the fallback rate; Training Mode and maneuver detection suspended;
|
|
23
|
+
the divergence advisory keeps watching. `navigation.speedThroughWater`
|
|
24
|
+
stays silent while on polar (a model estimate is not a measurement).
|
|
25
|
+
The DR state value gains `speedSource: "paddlewheel"|"polar"`, and a
|
|
26
|
+
§3.1 sensor-health alert names the switch (paddlewheel
|
|
27
|
+
unavailable/fouled — DR on polar-derived speed).
|
|
28
|
+
- Scalar sibling paths `navigation.deadReckoning.uncertainty.radius`
|
|
29
|
+
and `navigation.deadReckoning.divergence.distance` (metres, with
|
|
30
|
+
nautical-mile display-unit meta) for rule/display engines
|
|
31
|
+
(signalk-status-tiles threshold checks) that cannot read subfields of
|
|
32
|
+
object-valued paths — subscribing to a subfield path never sees a
|
|
33
|
+
delta.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **All published deltas now follow the Signal K SI unit conventions**
|
|
37
|
+
(breaking):
|
|
38
|
+
- `navigation.deadReckoning.log` / `trip.log` publish metres (was
|
|
39
|
+
nautical miles), with `value/1852` NM display-unit meta.
|
|
40
|
+
- The `environment.current` object is replaced by the standard
|
|
41
|
+
`environment.current.setTrue` (radians) and
|
|
42
|
+
`environment.current.drift` (m/s) paths; the DR-specific
|
|
43
|
+
tier/source enrichment rides REST `/status` instead of the bus.
|
|
44
|
+
- The uncertainty object field `radius_nm` becomes `radius_m`; the
|
|
45
|
+
divergence object fields `distance_nm`/`bearing_true` (deg) become
|
|
46
|
+
`distance_m`/`bearing_true` (rad).
|
|
47
|
+
- `navigation.deadReckoning.elapsedSinceFix` gains duration
|
|
48
|
+
display-unit meta so glance consumers render "3h 05m", not
|
|
49
|
+
seconds.
|
|
50
|
+
- All display-unit meta declares `category: "custom"` — the
|
|
51
|
+
server's unit-preference system rewrites category-less
|
|
52
|
+
`displayUnits` to the user's global preference (a seconds path
|
|
53
|
+
rendered as "0.0 hour"); custom keeps the nautical styling.
|
|
54
|
+
- **Inbound sensor deltas are now interpreted per the Signal K unit
|
|
55
|
+
conventions** (breaking for feeds that were publishing non-SI):
|
|
56
|
+
`speedThroughWater`/`speedApparent` are read as m/s and heading paths
|
|
57
|
+
as radians, converting to the engine's internal knots/degrees at the
|
|
58
|
+
boundary. Previously m/s and radian values were treated as knots and
|
|
59
|
+
degrees — DR under-travelled ~5× and mis-steered on standard feeds.
|
|
60
|
+
The standard-path passthroughs (`navigation.speedThroughWater`,
|
|
61
|
+
`navigation.headingTrue`) publish what they received, unchanged.
|
|
62
|
+
- Logbook fix/tack entries convert SOG/COG/heading at the boundary
|
|
63
|
+
(`_kn`/`_deg` REST fields previously received raw m/s/radians).
|
|
64
|
+
- The webapp converts SI bus values (m, m/s, rad) to nautical displays
|
|
65
|
+
centrally in the view model (`metresToNm`/`msToKn`/`radToDeg`).
|
|
66
|
+
- REST `/status` and `/current/manual` keep the plugin's internal
|
|
67
|
+
nautical units (`logNm`, `current.setTrue` deg, `drift` kn) — they
|
|
68
|
+
are the plugin's own API, not the Signal K bus.
|
|
69
|
+
- `navigation.deadReckoning.method` now reflects the actual speed
|
|
70
|
+
source every tick, completing the SPEC §3.1 enum: the idle branch
|
|
71
|
+
(no usable speed at all) publishes `fallback-zero` instead of
|
|
72
|
+
inheriting the constructor's `inertial-paddlewheel` — the `Polar`/
|
|
73
|
+
`Zero` headline labels added earlier are now driven by real values.
|
|
74
|
+
SPEC §3.1/§3.2/§6.1 aligned with the implemented fallback hierarchy
|
|
75
|
+
(polar first, fault-based selection, speed output silent on polar).
|
|
76
|
+
- The "Active method" headline shows a short watchkeeper-sized label
|
|
77
|
+
(`STW` for `inertial-paddlewheel`, `Polar`/`Zero` for the spec's
|
|
78
|
+
reserved methods) with the full token on hover — one of five figures,
|
|
79
|
+
it previously gave a full spec token permanent large-type real estate
|
|
80
|
+
despite having exactly one possible value today.
|
|
81
|
+
- The DR status no longer claims the vessel is `underway` while
|
|
82
|
+
moored: `navigation.deadReckoning.state.status` gains a `"warm"`
|
|
83
|
+
value (engine integrating on a tied-up boat, SPEC §5 runs it warm for
|
|
84
|
+
instant OVERRIDE handoff — that is not being under way) alongside
|
|
85
|
+
`"underway"` and `"idle"`. The webapp subscribes to the vessel's own
|
|
86
|
+
`navigation.state` (already in the system — nothing is republished
|
|
87
|
+
inside our deltas) and words the line accordingly:
|
|
88
|
+
`DR warm — moored/anchored, integrating sensors` vs
|
|
89
|
+
`Dead reckoning active`. Text logic moved to the pure `drStatusText()`
|
|
90
|
+
view-model helper.
|
|
91
|
+
|
|
92
|
+
## [0.2.0] - 2026-08-27
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
- **Map no longer opens blank — defaults to the first chart provider.**
|
|
96
|
+
The webapp previously kept its tile-less offline-first default unless
|
|
97
|
+
the server had *configured* charts, so on servers without any (the
|
|
98
|
+
common case: the layers control showed only "OpenStreetMap (online)",
|
|
99
|
+
unselected) the plot opened on an empty dark canvas. The first entry
|
|
100
|
+
of the chart list — first configured chart when the server has them,
|
|
101
|
+
otherwise the OSM online fallback — is now auto-selected on load, and
|
|
102
|
+
duplicate chart names no longer clobber each other's entry in the
|
|
103
|
+
layers control.
|
|
104
|
+
- **Dockside false positives while moored/anchored** — three alerts
|
|
105
|
+
that misfire on a tied-up boat are now suppressed in the moored/
|
|
106
|
+
anchored regime (consistent with the divergence monitor's existing
|
|
107
|
+
§7.1–2 suppression):
|
|
108
|
+
- *Divergence advisory* no longer raises on GPS wander at the dock:
|
|
109
|
+
the uncertainty radius previously collapsed to exactly 0.0 nm at
|
|
110
|
+
zero distance run, so a few metres of marina multipath "exceeded
|
|
111
|
+
expected uncertainty × 1.5" and sustained for 30 s raised
|
|
112
|
+
`DR-GPS divergence 0.00 nm exceeds expected 0.00 nm`. The radius
|
|
113
|
+
now floors at `MIN_RADIUS_NM` (0.02 nm ≈ 37 m, GNSS noise scale)
|
|
114
|
+
— DR error can never honestly be smaller than the GPS it was
|
|
115
|
+
seeded from.
|
|
116
|
+
- *Phantom "paddlewheel fouled" alert* — the fouling detector saw
|
|
117
|
+
STW≈0 (a moored paddlewheel's honest reading) plus wind on the
|
|
118
|
+
mast or GPS jitter and concluded the boat was making way. The
|
|
119
|
+
detector (and the whole training/matrix path, incl. auto tack
|
|
120
|
+
logging) is skipped while moored/anchored, and any earlier verdict
|
|
121
|
+
is withdrawn.
|
|
122
|
+
- *Sticky advisories* — a divergence advisory raised just before
|
|
123
|
+
mooring could never clear (the monitor isn't fed while suppressed).
|
|
124
|
+
It is now withdrawn with a `DR monitoring suspended while
|
|
125
|
+
moored/anchored` notification, and the divergence readout publishes
|
|
126
|
+
an explicit `null` while suppressed so the UI drops the stale
|
|
127
|
+
figure instead of showing a frozen `0.00 nm / 306°`.
|
|
128
|
+
- **Sensor-health alerts no longer flap** — the fouling and
|
|
129
|
+
idle-but-making-way verdicts are raw per-tick threshold comparisons
|
|
130
|
+
(STW vs 0.3 kn, wind vs 3 kn, SOG vs 1 kn); a sensor hovering at a
|
|
131
|
+
threshold toggled the alert on and off every tick. Both now pass
|
|
132
|
+
through a reusable symmetric hysteresis
|
|
133
|
+
(`plugin/hysteresis.js`, `sensorHealth.sustainS`/`clearS`, default
|
|
134
|
+
10 s/10 s): a real fault still surfaces after the sustain window,
|
|
135
|
+
threshold hover raises nothing.
|
|
136
|
+
- **Water-track log survives restart** — `navigation.deadReckoning.log`
|
|
137
|
+
and the trip log were persisted every flush but never restored at
|
|
138
|
+
start, resetting the headline to 0.00 nm on every server restart
|
|
139
|
+
mid-passage. Both are now restored from the `dr_state_store`.
|
|
140
|
+
- **Map layers control usable again** — the divergence chip overlay sat
|
|
141
|
+
at the top-right corner on top of Leaflet's layers control (equal
|
|
142
|
+
z-index, later sibling wins), covering its toggle: broken-looking
|
|
143
|
+
styling and swallowed clicks meant selecting a chart provider did
|
|
144
|
+
nothing. The chip now lives at the bottom-right (attribution is off,
|
|
145
|
+
so the corner is free).
|
|
146
|
+
- The DR vs GPS headline no longer shows a bearing below display
|
|
147
|
+
resolution (`0.00 nm / 306°` → `0.00 nm`): the bearing of a
|
|
148
|
+
zero-length noise vector is meaningless.
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
- When the server has no charts configured, the online OpenStreetMap
|
|
152
|
+
fallback layer is offered in the layers control but no longer
|
|
153
|
+
auto-added to the map (it was documented as opt-in; the tile-less
|
|
154
|
+
offline-first plot is the default again until selected).
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
- `plugin/hysteresis.js` — generic symmetric sustain/clear debounce for
|
|
158
|
+
boolean conditions, extracted from the divergence advisory's shape
|
|
159
|
+
and unit-tested; also used for the sensor-health alerts.
|
|
160
|
+
- Work doc #17 on the rngit board tracks this batch (dockside false
|
|
161
|
+
positives: divergence advisory, fouled paddlewheel, layers control).
|
|
162
|
+
|
|
8
163
|
## [0.1.0] - 2026-08-27
|
|
9
164
|
|
|
10
165
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Signal K Dead Reckoning
|
|
2
2
|
|
|
3
|
-
An offline-first dead reckoning and sensor fusion engine for Signal K that maintains a continuously computed "shadow boat" position from water-track sensors (speed through water, compass heading, and learned leeway and current corrections), so you always have a navigational fallback when GPS becomes unreliable — whether from jamming, spoofing, or plain receiver failure. While GPS is trusted, the engine learns vessel-specific calibration corrections against ground truth and watches for GPS anomalies; when it isn't, the same learned model keeps the dead-reckoned position, its uncertainty polygon, and a water-track log going. Fixes from celestial sights, compass bearings, and vertical angles are entered through a unified pipeline and can snap dead reckoning back on track, with optional write-through to `signalk-logbook`.
|
|
3
|
+
An offline-first dead reckoning and sensor fusion engine for Signal K that maintains a continuously computed "shadow boat" position from water-track sensors (speed through water, compass heading, and learned leeway and current corrections), so you always have a navigational fallback when GPS becomes unreliable — whether from jamming, spoofing, or plain receiver failure. While GPS is trusted, the engine learns vessel-specific calibration corrections against ground truth and watches for GPS anomalies; when it isn't, the same learned model keeps the dead-reckoned position, its uncertainty polygon, and a water-track log going. Fixes from celestial sights, compass bearings, and vertical angles are entered through a unified pipeline and can snap dead reckoning back on track, with optional write-through to `signalk-logbook`.
|
|
4
4
|
|
|
5
5
|
**Note:** This is just a toy. Make your own navigation calculations and decisions.
|
package/SPEC.md
CHANGED
|
@@ -38,10 +38,10 @@ The three motivations are causally linked: (1) generates the fix data and crew e
|
|
|
38
38
|
|---|---|---|
|
|
39
39
|
| `navigation.deadReckoning.position` | `{ latitude, longitude, altitude }` | **Always-on** 1Hz inertial "shadow boat" position — computed continuously regardless of mode. |
|
|
40
40
|
| `navigation.deadReckoning.active` | boolean | Whether DR is currently the *authoritative* source feeding `navigation.position` (i.e. OVERRIDE engaged), as distinct from merely running in the background. |
|
|
41
|
-
| `navigation.deadReckoning.method` | string: `inertial-polar` \| `inertial-paddlewheel` \| `fallback-zero` | Active
|
|
41
|
+
| `navigation.deadReckoning.method` | string: `inertial-polar` \| `inertial-paddlewheel` \| `fallback-zero` | Active speed-source mode, re-published every tick: `inertial-paddlewheel` while the paddlewheel serves (raw STW present, not fouled); `inertial-polar` when it doesn't and the polar fallback can engage (§6.1); `fallback-zero` when no usable speed source exists (DR holds position). Selection is fault-based, never merit-based — a working measurement always outranks a model. |
|
|
42
42
|
| `navigation.deadReckoning.log` | number (nm) | Cumulative **water-track** distance, integrated from STW. Independent of GPS. |
|
|
43
43
|
| `navigation.deadReckoning.trip.log` | number (nm) | Same, reset at trip boundaries (see §9.2). |
|
|
44
|
-
| `navigation.speedThroughWater` | number | Calibrated STW output (matrix-corrected). |
|
|
44
|
+
| `navigation.speedThroughWater` | number | Calibrated STW output (matrix-corrected). Silent while the polar fallback is active — a model estimate must not masquerade as a measurement. |
|
|
45
45
|
| `navigation.headingTrue` | number | Calibrated true heading (corrected for dynamic deviation). |
|
|
46
46
|
| `environment.current` | `{ setTrue, drift, meta: { source, expiresAt } }` | Current vector broadcast, per §6.2 hierarchy. |
|
|
47
47
|
| `notifications.navigation.gpsSpoofed` | alarm state | High-severity: sudden position discontinuity inconsistent with DR/physics, or at-anchor/moored displacement beyond plausible bound. See §7. |
|
|
@@ -55,6 +55,7 @@ The three motivations are causally linked: (1) generates the fix data and crew e
|
|
|
55
55
|
| `navigation.position` | GPS baseline for training mode and anomaly detection. |
|
|
56
56
|
| `navigation.speedThroughWater`, `navigation.headingMagnetic`, `navigation.attitude` | Raw sensor inputs (heel/pitch). |
|
|
57
57
|
| `environment.wind.angleApparent`, `environment.wind.speedApparent` | Wind inputs for leeway/upwash modeling. |
|
|
58
|
+
| `performance.polarSpeed` | Polar-derived boat speed (m/s) from `signalk-polar-performance-plugin` — requirement for the §6.1 `inertial-polar` fallback: that plugin installed and configured with its polar-speed output enabled. The raw delta is a step-function polar lookup driven by gusty wind and is **running-averaged** before integration; sustained nulls (wind out-of-table) age the average out to staleness rather than decaying it toward zero. |
|
|
58
59
|
| `navigation.sails` | Active sail configuration, from `signalk-logbook`. |
|
|
59
60
|
| `environment.seaState` | Sea state tier, from logbook watch entries. |
|
|
60
61
|
| `navigation.state` | `anchored` \| `moored` \| `sailing` \| `motoring` \| ... — trip boundaries, and the anchored/moored anomaly-detection gates. |
|
|
@@ -273,7 +274,14 @@ Worker Thread (DR Physics)
|
|
|
273
274
|
|
|
274
275
|
**Inference Mode** — active when `isGpsReliable = false` OR OVERRIDE is manually engaged:
|
|
275
276
|
- Freezes matrix learning. Reads raw sensors, looks up matching bins, applies corrections, integrates the resolved current vector, publishes `navigation.deadReckoning.position` as authoritative (`navigation.deadReckoning.active = true`).
|
|
276
|
-
|
|
277
|
+
|
|
278
|
+
**Speed-source fallback** — mode-independent (the shadow boat integrates regardless of GPS trust, §5, so a sensor lost in NORMAL mode is handled the same way as one lost in Inference/OVERRIDE). A fouled or silent paddlewheel degrades the speed source by preference:
|
|
279
|
+
|
|
280
|
+
1. **Polar-derived speed** — the running-averaged `performance.polarSpeed` (§3.2). Gated to underway + sailing: wind on a moored mast must not sail the shadow boat off the dock, and a polar is meaningless under power. While on this source: no matrix corrections (leeway/speed-loss bins were trained on real paddlewheel STW — feeding them a model estimate is circular), uncertainty grows at the fallback rate, and matrix training / maneuver detection are suspended (never train on synthetic input — same principle as the §6.3/§6.4 write gates). The divergence advisory (§7.3) keeps watching: model drift must be detected even though it never re-selects the method.
|
|
281
|
+
2. **GPS-SOG-derived speed** — if no polar source is available and GPS is at least partially available.
|
|
282
|
+
3. **Hold last-known-good STW** — with explicitly faster-growing uncertainty, if neither.
|
|
283
|
+
|
|
284
|
+
The paddlewheel-failure fallback is a distinct branch from the "GPS unreliable" case, since the two can occur independently or together.
|
|
277
285
|
|
|
278
286
|
### 6.2 Current Hierarchy of Truth
|
|
279
287
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meri-imperiumi/signalk-dead-reckoning",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Offline-first dead reckoning and sensor fusion engine for Signal K",
|
|
5
5
|
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
],
|
|
34
34
|
"recommends": [
|
|
35
35
|
"@meri-imperiumi/signalk-autostate",
|
|
36
|
-
"@meri-imperiumi/signalk-logbook"
|
|
36
|
+
"@meri-imperiumi/signalk-logbook",
|
|
37
|
+
"signalk-polar-performance-plugin"
|
|
37
38
|
],
|
|
38
39
|
"screenshots": [
|
|
39
40
|
"./doc/dr-map.png",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic symmetric hysteresis for a boolean condition (sustained raise,
|
|
3
|
+
* sustained clear), extracted from the divergence advisory monitor's
|
|
4
|
+
* debounce shape so other watchkeeper alerts (§6.3 paddlewheel fouling,
|
|
5
|
+
* idle-but-making-way) can't flap when a sensor hovers at a threshold.
|
|
6
|
+
*
|
|
7
|
+
* The condition must persist for `sustainS` before the debounced flag
|
|
8
|
+
* goes true, and stay false for `clearS` before it drops — a single
|
|
9
|
+
* jittery tick neither raises nor clears.
|
|
10
|
+
*
|
|
11
|
+
* Pure logic over an explicit state object — unit-testable without
|
|
12
|
+
* Signal K plumbing.
|
|
13
|
+
*
|
|
14
|
+
* @file hysteresis.js
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default windows (seconds) for sensor-health style flags. Slower than
|
|
19
|
+
* the divergence advisory's 30 s — fouling and idle transitions are
|
|
20
|
+
* mechanical conditions that don't change meaningfully in a second, but
|
|
21
|
+
* fast enough that a real fault surfaces within a watch check.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_SUSTAIN_S = 10;
|
|
24
|
+
const DEFAULT_CLEAR_S = 10;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a fresh debounce state.
|
|
28
|
+
*
|
|
29
|
+
* @returns {{active: boolean, exceedS: number, insideS: number}}
|
|
30
|
+
*/
|
|
31
|
+
function createFlagState() {
|
|
32
|
+
return {
|
|
33
|
+
/** debounced flag currently raised */
|
|
34
|
+
active: false,
|
|
35
|
+
/** sustained seconds of the raw condition accumulated */
|
|
36
|
+
exceedS: 0,
|
|
37
|
+
/** sustained seconds of the raw condition being absent */
|
|
38
|
+
insideS: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Advances the debounce one tick.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} state - from createFlagState(), mutated
|
|
46
|
+
* @param {boolean} raw - the undebounced per-tick condition
|
|
47
|
+
* @param {number} dtS - tick interval in seconds
|
|
48
|
+
* @param {object} [opts]
|
|
49
|
+
* @param {number} [opts.sustainS=DEFAULT_SUSTAIN_S]
|
|
50
|
+
* @param {number} [opts.clearS=DEFAULT_CLEAR_S]
|
|
51
|
+
* @returns {{active: boolean, transition: "raise"|"clear"|null}}
|
|
52
|
+
*/
|
|
53
|
+
function flagTick(state, raw, dtS, opts = {}) {
|
|
54
|
+
const sustainS = opts.sustainS ?? DEFAULT_SUSTAIN_S;
|
|
55
|
+
const clearS = opts.clearS ?? DEFAULT_CLEAR_S;
|
|
56
|
+
let transition = null;
|
|
57
|
+
|
|
58
|
+
if (raw) {
|
|
59
|
+
state.insideS = 0;
|
|
60
|
+
state.exceedS += dtS;
|
|
61
|
+
if (!state.active && state.exceedS >= sustainS) {
|
|
62
|
+
state.active = true;
|
|
63
|
+
transition = "raise";
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
state.exceedS = 0;
|
|
67
|
+
state.insideS += dtS;
|
|
68
|
+
if (state.active && state.insideS >= clearS) {
|
|
69
|
+
state.active = false;
|
|
70
|
+
transition = "clear";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { active: state.active, transition };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
DEFAULT_SUSTAIN_S,
|
|
79
|
+
DEFAULT_CLEAR_S,
|
|
80
|
+
createFlagState,
|
|
81
|
+
flagTick,
|
|
82
|
+
};
|