feat: add physically explicit narrowband MUSIC

This commit is contained in:
2026-08-29 12:40:48 +09:00
parent 044be3cf92
commit 11fee76280
40 changed files with 6473 additions and 275 deletions
+9 -2
View File
@@ -613,6 +613,9 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을
() => cameraDocument.documentElement.dataset.appReady === "true",
"Camera handoff initialization",
);
const defaultCameraExperiment = await cameraWindow
.fetch("/assets/sample-scenes/single-tone.experiment.json")
.then((response) => response.json());
const handoffFile = new cameraWindow.File(
[JSON.stringify(editedExport)],
"builder-handoff.array.json",
@@ -642,8 +645,12 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을
);
assertEqual(
cameraExperiment.arrayId,
editedExport.id,
"Camera rebinds Experiment to the Builder Array ID",
defaultCameraExperiment.arrayId,
"Camera preserves the Experiment Array provenance label",
);
assert(
cameraExperiment.arrayId !== editedExport.id,
"opening a Builder Array does not rewrite Experiment provenance",
);
} finally {
cameraWindow.URL.createObjectURL = originalCameraCreate;
+85 -16
View File
@@ -84,7 +84,7 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
assert(!documentObject.querySelector("#array-details").open, "array developer detail starts collapsed");
assert(!documentObject.querySelector("#camera-help").open, "limitations help starts collapsed");
assertEqual(documentObject.querySelector("#algorithm").value, "das", "default DAS algorithm");
assertEqual(documentObject.querySelectorAll("#algorithm option").length, 3, "three runnable algorithms");
assertEqual(documentObject.querySelectorAll("#algorithm option").length, 4, "four runnable algorithms");
assert(!documentObject.querySelector("#postprocess"), "disabled postprocess advertisement is absent");
assertEqual(documentObject.querySelector("#acoustic-profile").value, "free-field", "default free-field profile");
assertEqual(
@@ -325,6 +325,63 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
);
assertEqual(documentObject.documentElement.dataset.unsavedWork, "", "all Camera work is saved");
documentObject.querySelector("#load-music-example-button").click();
await waitFor(
() =>
documentObject.querySelector("#algorithm").value === "music" &&
documentObject.querySelector("#microphone-count").textContent === "12 ch" &&
documentObject.documentElement.dataset.cameraValid === "true" &&
!documentObject.querySelector("#run-button").disabled,
"quick MUSIC example load",
);
assert(
documentObject.querySelector("#workload-summary").textContent.includes(
"1 analog hypotheses / 1 ADC bins",
) &&
documentObject.querySelector("#workload-summary").textContent.includes(
"cap",
),
"MUSIC workload exposes hypotheses and hard cap",
);
documentObject.querySelector("#run-button").click();
await waitFor(
() =>
documentObject.documentElement.dataset.runState === "done" &&
documentObject.documentElement.dataset.runAlgorithm === "music",
"quick MUSIC example run",
30000,
);
assertEqual(
documentObject.documentElement.dataset.resultBandCount,
"1",
"quick MUSIC example returns one map",
);
const downloadCountBeforeMusic =
downloads.length;
documentObject.querySelector("#export-result-button").click();
const musicDownload = await waitFor(
() =>
downloads.length > downloadCountBeforeMusic
? downloads.at(-1)
: null,
"MUSIC Result JSON download",
);
const musicResult = await fetch(musicDownload.url).then(
(response) => response.json(),
);
assertEqual(musicResult.algorithm, "music", "MUSIC example export");
assertEqual(
musicResult.musicModel.hypothesisSource,
"experiment-band-not-scene-truth",
"MUSIC export never claims Scene-truth carrier selection",
);
assertEqual(
musicResult.bands[0].frequencyObservation
.configuredAnalogFrequencyHz,
1125,
"configured analog carrier is exported",
);
frame.style.width = "390px";
await nextPaint(windowObject);
assert(
@@ -448,7 +505,7 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
() => documentObject.documentElement.dataset.inputBusy === "false" &&
documentObject.documentElement.dataset.cameraValid === "true" &&
!documentObject.querySelector("#run-button").disabled,
"Experiment import and automatic reference binding",
"Experiment import with independent provenance labels",
);
assertEqual(
documentObject.querySelector("#experiment-id").value,
@@ -465,7 +522,7 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
!documentObject.querySelector("#validation-message").textContent.includes("sceneId") &&
!documentObject.querySelector("#validation-message").textContent.includes("arrayId") &&
!documentObject.querySelector("#run-status").textContent.includes("연결 대기"),
"automatic binding does not leave a reference mismatch warning",
"provenance labels do not create a reference mismatch warning",
);
assert(
documentObject.querySelector("#run-status").textContent.includes("분석 대역 2개") &&
@@ -474,29 +531,33 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
);
documentObject.querySelector("#export-experiment-button").click();
const reboundDownload = await waitFor(
const preservedDownload = await waitFor(
() => downloads.find((download) => download.fileName.endsWith(".experiment.json")),
"automatically rebound Experiment export",
"provenance-preserving Experiment export",
);
const reboundExperiment = await fetch(reboundDownload.url).then((response) => response.json());
assertEqual(reboundExperiment.sceneId, openScene.id, "import binds the open Scene ID");
const preservedExperiment = await fetch(preservedDownload.url).then((response) => response.json());
assertEqual(
reboundExperiment.arrayId,
openArrayDefinition.id,
"import binds the open Array ID",
preservedExperiment.sceneId,
experiment.sceneId,
"import preserves the Experiment Scene provenance label",
);
assertEqual(
JSON.stringify(reboundExperiment.frequencyBands),
preservedExperiment.arrayId,
experiment.arrayId,
"import preserves the Experiment Array provenance label",
);
assertEqual(
JSON.stringify(preservedExperiment.frequencyBands),
JSON.stringify(expectedBands),
"automatic reference binding preserves every imported band",
"provenance preservation keeps every imported band",
);
assertEqual(
(await validateExperiment(reboundExperiment, {
(await validateExperiment(preservedExperiment, {
scene: openScene,
arrayDefinition: openArrayDefinition,
})).length,
0,
"rebound Experiment passes relationship validation",
"provenance labels may differ from the open Scene and Array",
);
downloads.length = 0;
@@ -678,8 +739,16 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
JSON.stringify(expectedBands),
"Experiment export preserves every band after Scene reimport",
);
assertEqual(exportedExperiment.sceneId, scene.id, "Experiment rebinds only the Scene ID");
assertEqual(exportedExperiment.arrayId, arrayDefinition.id, "Experiment rebinds the Array ID");
assertEqual(
exportedExperiment.sceneId,
experiment.sceneId,
"Experiment keeps its Scene provenance label after Scene replacement",
);
assertEqual(
exportedExperiment.arrayId,
experiment.arrayId,
"Experiment keeps its Array provenance label after Array replacement",
);
} finally {
windowObject.confirm = originalConfirm;
windowObject.HTMLAnchorElement.prototype.click = originalAnchorClick;