DisclosureOS documentation · v2 Beta
Active sonar profile
Validate echo samples, preserve missing reception, and inspect delay and modeled distance.
An active sonar recording pairs a transmitted ping with samples received after it. The active sonar profile connects those samples to a capture, defines their time and delay axes, and identifies which samples were retained.
This guide uses a fictional echo envelope, a grid of relative amplitudes rather than an acoustic waveform. You will reproduce its files, validate them, read a retained and a missing sample, and diagnose three failures.
Profile status
Published in npm 2.0.0-beta.4. The profile evaluators and readers are available through @disclosureos/schema/experimental/v2. The retained source revision below is only needed to regenerate this example.
Start with Equipment and captures for the shared equipment model. This optional profile describes a complete echo-envelope capture. It composes an observation, acquisition context and source selection; a scalar measurement binding or claim history is not required.
1. Open the published-package example
Requirements: Node.js 22, npm and a ZIP extractor. Create a local project and install the published API:
mkdir disclosureos-sonar-beta4
cd disclosureos-sonar-beta4
npm init -y
npm install --save-exact @disclosureos/schema@2.0.0-beta.4
curl --fail --location https://os.disclosure.org/examples/meridian.zip --output meridian.zip
unzip meridian.zip
curl --fail --location https://os.disclosure.org/examples/v2/sonar/check-sonar.mjs --output check-sonar.mjs
node check-sonar.mjs meridian/sonar-active-v1The download contains the full fictional example case. This guide uses only meridian/sonar-active-v1. Keep the terminal in this npm project for the checks below. The displayed profile and selected-value results are explained in the following sections.
What the profile describes
This profile is monostatic: transmission and reception are assumed to occur at the same position. It supports a regular ping schedule, a two-way delay axis, normalized envelope amplitudes and a paired receive mask. A constant assumed sound speed provides an optional display of modeled path length.
Four connected documents
| Document | What it provides |
|---|---|
observation.json | Sources and the two retained products, with an observation interval. |
acquisition.json | Equipment, pinned configuration, the echo channel, capture interval and both raw artifacts. |
source-selection.json | Array shape, axis definitions, byte order, units, byte lengths and digests. |
sonar-profile.json | References to those documents, product roles, ping timing, receive-mask meaning and the distance assumption. |
The profile selects acquisition:sonar-capture and channel echo-envelope. Its acquisitionProductRef and receiveMaskAcquisitionProductRef select two distinct products in the acquisition context. Its products.amplitudes and products.receiveMask select the observation products and their source layouts. The evaluator checks these relationships, even where the local names are identical.
Two source arrays
| File | Layout | Meaning |
|---|---|---|
echo-envelope.f32 | 65,536 bytes; little-endian float32 | Dimensionless amplitudes from 0 to 1, with zero placeholders where reception was not retained. |
receive-mask.u8 | 16,384 bytes; uint8 | One availability value per amplitude: 1 retained, 0 not retained. |
Both arrays use row-major [ping, delay bin] order. methods.json states the equations, gaps and limits; model.mjs generates them. The example's index.json helps locate files. It is a convenience index, not another standard contract.
2. Validate files and read samples
The reproduction above checks this particular fixture against its generator. The downloaded check-sonar.mjs uses the installed Beta 4 package. Run it from the npm project created above.
From your npm project, run:
node check-sonar.mjs \
meridian/sonar-active-v1The script imports evaluateSonarProfile and readSonarSample from @disclosureos/schema/experimental/v2. It loads the four saved documents and both local files, then calls:
const result = await evaluateSonarProfile(
observation,
context,
selection,
profile,
{ assets },
);assets maps each layout's productRef to complete file bytes: product:amplitudes and product:receiveMask here. Filenames and sample indices are not asset keys. The runner uses the example index to locate local files; the evaluator checks their lengths and digests. It does not fetch artifact URLs.
The first result is:
{
"success": true,
"checks": {
"structure": "passed",
"sources": "passed",
"semantics": "passed"
},
"scientific": "not_checked",
"physicalCalibration": "not_checked",
"propagationAccuracy": "not_checked",
"issues": []
}| Check | Meaning |
|---|---|
structure | All four inputs satisfy their prerequisite contracts. |
sources | Source selections and retained bytes pass their checks, including lengths and digests. |
semantics | Capture and channel bindings, time bounds, axes, amplitude values and receive-mask rules satisfy this profile. |
A passing report does not establish physical calibration, propagation accuracy, depth or object identity. Unlike the fixture's not_performed calibration declaration, the evaluator reports not_checked: it does not verify calibration.
Only after success, the script reads two samples from the validated, decoded products:
const retained = readSonarSample(profile, result.data.products, 12, 64);
const missing = readSonarSample(profile, result.data.products, 28, 64);All indices are zero-based. The output is:
{
"retained": {
"ping": 12,
"bin": 64,
"delaySeconds": 0.064,
"modeledDistanceM": 48,
"timeOffsetSeconds": 12,
"receiveOffsetSeconds": 12.064,
"amplitudeByteOffset": 12544,
"maskByteOffset": 3136,
"available": true,
"amplitude": 0.2900271415710449
},
"missing": {
"ping": 28,
"bin": 64,
"delaySeconds": 0.064,
"modeledDistanceM": 48,
"timeOffsetSeconds": 28,
"receiveOffsetSeconds": 28.064,
"amplitudeByteOffset": 28928,
"maskByteOffset": 7232,
"available": false,
"amplitude": null
}
}The offsets identify the four-byte amplitude and one-byte mask in separate files. For [12, 64], the row-major index is 12 × 256 + 64 = 3136; the amplitude offset is 3136 × 4 = 12544.
The dependency-free readers check declarations, layouts and values. They do not check digests or acquisition lineage. Validate with evaluateSonarProfile before presenting their output as checked source data.
Availability belongs to the source
Read the mask before displaying an amplitude. The missing sample above still has a declared delay and a modeled distance, but its amplitude is null. Those coordinates locate a storage slot; they do not establish that an echo was received there.
The file stores zero at unavailable positions. Treating those zeros as measurements would invent a low-amplitude return. Use readSonarSample to preserve the distinction, then show a gap or an explicit unavailable state. Do not draw a continuous line through missing samples.
In this fixture, pings 28–30 are entirely missing. Pings 44–49 also omit bins 120–151. That accounts for 768 whole-ping positions and 192 partial-ping positions. The gaps are authored omissions, with no inferred physical cause. The receive mask is not a detection decision, coverage map or acoustic-shadow model.
Delay and modeled distance
Keep these quantities distinct:
| Quantity | Retained sample [12, 64] |
|---|---|
| Ping origin | 12 seconds after 2026-07-28T22:10:00Z. |
| Two-way delay | 0.064 seconds after that ping. |
| Receive time | 12.064 seconds after the profile origin, or 22:10:12.064Z. |
| Modeled one-way path length | 48 metres, using the assumed 1,500 m/s sound speed. |
The declared conversion is:
modeled distance = two-way delay × assumed sound speed / 2
48 m = 0.064 s × 1500 m/s / 2The final bin has a delay of 0.255 seconds and a modeled distance of 191.25 metres. Bin spacing is 0.001 seconds, or 0.75 metres under this assumption. Spacing is not physical range resolution or accuracy. The example supplies no pulse bandwidth, measured sound-speed profile or uncertainty model.
Switching the viewer between delay and distance changes the axis interpretation, not the source bytes. Without direction and geometry, the modeled path length is not depth, bearing or a straight-line position. The regular UTC schedule is authored and has no physical clock traceability.
3. Diagnose a failed check
Each flag changes only in-memory inputs. Use one flag at a time. Deliberate failures exit with status 1 and stop before reading the selected samples. An unsupported flag or extra argument exits with usage status 2.
Withhold the receive mask
node check-sonar.mjs \
meridian/sonar-active-v1 \
--missing-maskThe profile reports sources: failed and semantics: not_checked, with this issue:
{
"code": "SONAR.SOURCE_BYTES",
"pointer": "/products",
"message": "All retained source bytes are required for verification."
}The additional source report leaves external: not_checked and has no source issue. Missing bytes are not contradictory bytes. An empty issue list does not mean the check completed, and the envelope alone is insufficient to distinguish retained zeros from placeholders.
Change one mask byte
node check-sonar.mjs \
meridian/sonar-active-v1 \
--changed-maskThe profile again stops at source checking, but now the bytes contradict the retained digest:
{
"code": "SONAR.SOURCE.EXTERNAL.DIGEST",
"pointer": "product:receiveMask",
"message": "Artifact digest differs."
}The additional source report has external: failed and code EXTERNAL.DIGEST. The pointer names the product whose bytes changed. Do not replace the declared digest merely to make an unexpected file pass.
Let the receive window reach the next ping
node check-sonar.mjs \
meridian/sonar-active-v1 \
--overlapping-pingsThis changes the declared ping interval from 1 second to 0.255 seconds, exactly the delay of the final bin. Structure passes, semantics fails and sources remain not_checked:
{
"code": "SONAR.DECLARATION",
"pointer": "/sampling",
"message": "Sonar receive window reaches the next ping."
}This profile requires the receive window to end strictly before the next ping. The pointer selects the sampling declaration in sonar-profile.json. The evaluator stops at that contradiction before inspecting the array axes or bytes.
Rerun the normal command without a flag to recover. None of these exercises changes a generated file.
Reproduce and inspect
Use the active sonar viewer to compare the echo map, selected ping and delay history. Its source inspector shows separate amplitude and mask offsets. The example case includes the same sonar-active-v1 packet in its download.
For exact field and API definitions at this revision:
- Sonar profile schema: required declarations and product references.
- Profile evaluator: bindings, source verification and semantic checks.
- Source reader: sample addressing, receive availability and modeled distance.
The shared equipment field reference covers acquisition and measurement bindings. It is not a field reference for this newer sonar profile.
Limits and next step
This is an authored envelope, not a physical propagation simulation. Two converging components and a brief additional echo demonstrate inspection; they are not recovered objects. The profile does not supply carrier waveforms, Doppler, beamforming, detection, tracking, bathymetry or classification. raw identifies the retained input product, not unprocessed electrical or acoustic measurements.
Passive sonar is also a separate profile: it has no transmitted ping or automatic delay-to-distance conversion. For the general active/passive distinction, see NOAA's introduction to sonar.