fix: expose physical aliasing and delayed arrivals
This commit is contained in:
@@ -133,6 +133,20 @@ test("Dataset Builder 전체 저장·재연결 흐름이 동일한 scene과 prev
|
||||
frame.style.width = "1400px";
|
||||
await new Promise((resolve) => windowObject.requestAnimationFrame(() => windowObject.requestAnimationFrame(resolve)));
|
||||
|
||||
dispatchValue(windowObject, documentObject.querySelector("#tone-frequency"), 5000, "input");
|
||||
assert(
|
||||
documentObject.querySelector("#source-list").textContent.includes("5000 Hz tone") &&
|
||||
!documentObject.querySelector("#source-list").textContent.includes("ADC 3000 Hz alias"),
|
||||
"tone card starts as baseband at 48 kHz sample rate",
|
||||
);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#sample-rate"), 8000, "change");
|
||||
assert(
|
||||
documentObject.querySelector("#source-list").textContent.includes("5000 Hz tone → ADC 3000 Hz alias"),
|
||||
"sample-rate change immediately rerenders the tone alias card",
|
||||
);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#sample-rate"), 48000, "change");
|
||||
dispatchValue(windowObject, documentObject.querySelector("#tone-frequency"), 1000, "input");
|
||||
|
||||
const previewSamples = new Float32Array(2000);
|
||||
for (let index = 0; index < previewSamples.length; index += 1) {
|
||||
previewSamples[index] = Math.sin((2 * Math.PI * 440 * index) / 8000) * 0.12;
|
||||
|
||||
@@ -171,6 +171,7 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
|
||||
"Result JSON download",
|
||||
);
|
||||
const resultDocument = await fetch(resultDownload.url).then((response) => response.json());
|
||||
assertEqual(resultDocument.schemaVersion, "1.1.0", "exported sampling-aware Result version");
|
||||
assert(
|
||||
!documentObject.documentElement.dataset.unsavedWork.includes("Result"),
|
||||
"Result export clears Result dirty state",
|
||||
@@ -399,11 +400,24 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
|
||||
scene.name = "Two-tone band preservation";
|
||||
const secondSource = structuredClone(scene.sources[0]);
|
||||
secondSource.id = "source-2";
|
||||
secondSource.name = "1.2 kHz tone";
|
||||
secondSource.name = "48 kHz DC-edge tone";
|
||||
secondSource.position = [-0.12, 0.09, 2];
|
||||
secondSource.signal.frequency = 1200;
|
||||
secondSource.signal.frequency = 48000;
|
||||
secondSource.signal.phaseDeg = 45;
|
||||
scene.sources.push(secondSource);
|
||||
const nyquistEdgeSource = structuredClone(scene.sources[0]);
|
||||
nyquistEdgeSource.id = "source-3";
|
||||
nyquistEdgeSource.name = "72 kHz Nyquist-edge tone";
|
||||
nyquistEdgeSource.position = [0.18, -0.12, 2];
|
||||
nyquistEdgeSource.signal.frequency = 72000;
|
||||
nyquistEdgeSource.signal.phaseDeg = 90;
|
||||
scene.sources[0].name = "47 kHz aliased tone";
|
||||
scene.sources[0].signal.frequency = 47000;
|
||||
scene.sources.push(secondSource, nyquistEdgeSource);
|
||||
scene.ambient.push({
|
||||
id: "ambient-1",
|
||||
gainDb: -30,
|
||||
signal: { type: "tone", frequency: 46800, phaseDeg: 20 },
|
||||
});
|
||||
|
||||
const openArrayDefinition = await loadJson(
|
||||
"/assets/array-geometries/rectangular-16x12.array.json",
|
||||
@@ -486,38 +500,62 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
|
||||
);
|
||||
downloads.length = 0;
|
||||
|
||||
const contextInvalidExperiment = structuredClone(experiment);
|
||||
contextInvalidExperiment.id = "nyquist-invalid-after-rebind";
|
||||
contextInvalidExperiment.frequencyBands[0].maxHz = openScene.sampleRate / 2;
|
||||
const aliasedExperiment = structuredClone(experiment);
|
||||
aliasedExperiment.id = "above-nyquist-after-rebind";
|
||||
aliasedExperiment.sceneId = openScene.id;
|
||||
aliasedExperiment.arrayId = openArrayDefinition.id;
|
||||
aliasedExperiment.frequencyBands[0].minHz = openScene.sampleRate / 2 + 800;
|
||||
aliasedExperiment.frequencyBands[0].maxHz = openScene.sampleRate / 2 + 1_000;
|
||||
aliasedExperiment.frequencyBands[1].minHz = openScene.sampleRate / 2 - 1_000;
|
||||
aliasedExperiment.frequencyBands[1].maxHz = openScene.sampleRate / 2 - 800;
|
||||
assertEqual(
|
||||
(await validateExperiment(contextInvalidExperiment)).length,
|
||||
(await validateExperiment(aliasedExperiment, {
|
||||
scene: openScene,
|
||||
arrayDefinition: openArrayDefinition,
|
||||
})).length,
|
||||
0,
|
||||
"Nyquist fixture is valid without a Scene context",
|
||||
"above-Nyquist observation band remains valid with a Scene context",
|
||||
);
|
||||
const contextInvalidFile = new windowObject.File(
|
||||
[JSON.stringify(contextInvalidExperiment)],
|
||||
"nyquist-invalid-after-rebind.experiment.json",
|
||||
const aliasedFile = new windowObject.File(
|
||||
[JSON.stringify(aliasedExperiment)],
|
||||
"above-nyquist-after-rebind.experiment.json",
|
||||
{ type: "application/json" },
|
||||
);
|
||||
dispatchFiles(
|
||||
windowObject,
|
||||
documentObject.querySelector("#experiment-input"),
|
||||
[contextInvalidFile],
|
||||
[aliasedFile],
|
||||
);
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.inputBusy === "false" &&
|
||||
documentObject.documentElement.dataset.cameraValid === "true",
|
||||
"relationship-invalid Experiment rejection",
|
||||
documentObject.documentElement.dataset.cameraValid === "true" &&
|
||||
documentObject.querySelector("#experiment-id").value === aliasedExperiment.id,
|
||||
"above-Nyquist Experiment import",
|
||||
);
|
||||
assertEqual(
|
||||
documentObject.querySelector("#experiment-id").value,
|
||||
experiment.id,
|
||||
"context relationship validation rejects the imported Experiment before assignment",
|
||||
aliasedExperiment.id,
|
||||
"above-Nyquist Experiment is assigned instead of rejected",
|
||||
);
|
||||
assertEqual(
|
||||
JSON.stringify(readBandEditors()),
|
||||
JSON.stringify(expectedBands),
|
||||
"failed relationship validation leaves the current bands unchanged",
|
||||
JSON.stringify(aliasedExperiment.frequencyBands),
|
||||
"above-Nyquist requested bands remain explicit in the editor",
|
||||
);
|
||||
const samplingWarning = documentObject.querySelector("#sampling-warning");
|
||||
assert(!samplingWarning.hidden, "above-Nyquist steering warning remains visible");
|
||||
assert(
|
||||
samplingWarning.textContent.includes("ADC FFT bin") &&
|
||||
samplingWarning.textContent.includes("원래 analog carrier를 사용하지 않습니다") &&
|
||||
samplingWarning.textContent.includes("같은 ADC bin 집합") &&
|
||||
samplingWarning.textContent.includes("order 1/inverted"),
|
||||
"warning states that localization does not use the original carrier",
|
||||
);
|
||||
dispatchFiles(windowObject, documentObject.querySelector("#experiment-input"), [experimentFile]);
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.inputBusy === "false" &&
|
||||
documentObject.querySelector("#experiment-id").value === experiment.id,
|
||||
"restore original multi-band Experiment",
|
||||
);
|
||||
|
||||
let replacementConfirmCount = 0;
|
||||
@@ -546,7 +584,7 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
|
||||
dispatchFiles(windowObject, documentObject.querySelector("#scene-input"), [sceneFile]);
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.inputBusy === "false" &&
|
||||
documentObject.querySelectorAll("#source-summary .source-line").length === 2 &&
|
||||
documentObject.querySelectorAll("#source-summary .source-line").length === 3 &&
|
||||
documentObject.querySelectorAll("#band-list .band-editor").length === 2 &&
|
||||
documentObject.documentElement.dataset.cameraValid === "true",
|
||||
"source-added Scene reimport",
|
||||
@@ -560,6 +598,26 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
|
||||
documentObject.querySelector("#run-status").textContent.includes("분석 대역 2개"),
|
||||
"Camera status keeps the preserved band count visible",
|
||||
);
|
||||
assert(
|
||||
documentObject.querySelector("#source-summary").textContent.includes(
|
||||
"48000 Hz tone → ADC 0 Hz alias (signed +0 Hz) · DC phase-degenerate edge",
|
||||
),
|
||||
"Camera summary keeps the aliased exact-DC phase-degenerate edge visible",
|
||||
);
|
||||
assert(
|
||||
documentObject.querySelector("#source-summary").textContent.includes(
|
||||
"72000 Hz tone → ADC 24000 Hz alias (signed +24000 Hz) · Nyquist phase-degenerate edge",
|
||||
),
|
||||
"Camera summary keeps the aliased exact-Nyquist phase-degenerate edge visible",
|
||||
);
|
||||
const sceneAliasWarning = documentObject.querySelector("#sampling-warning");
|
||||
assert(!sceneAliasWarning.hidden, "aliased Scene tone keeps the sampling warning visible");
|
||||
assert(
|
||||
sceneAliasWarning.textContent.includes("선택 ADC bin으로 접힌 Scene tone") &&
|
||||
sceneAliasWarning.textContent.includes("tone-1k ← source source-1 47000→ADC 1000 Hz") &&
|
||||
sceneAliasWarning.textContent.includes("tone-1.2k ← ambient ambient-1 46800→ADC 1200 Hz"),
|
||||
"baseband bands disclose contributing source and ambient aliases",
|
||||
);
|
||||
|
||||
const arrayFile = new windowObject.File(
|
||||
[JSON.stringify(arrayDefinition)],
|
||||
@@ -583,6 +641,32 @@ test("Acoustic Camera가 Experiment를 현재 입력에 연결하고 Scene·Arra
|
||||
"ready status keeps the connected Experiment band count visible",
|
||||
);
|
||||
|
||||
dispatchValue(windowObject, documentObject.querySelector("#scan-columns"), 8);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#scan-rows"), 8);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#backend"), "cpu");
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.cameraValid === "true" &&
|
||||
!documentObject.querySelector("#run-button").disabled,
|
||||
"aliased Scene tone snapshot setup",
|
||||
);
|
||||
documentObject.querySelector("#run-button").click();
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.runState === "done",
|
||||
"aliased Scene tone snapshot",
|
||||
30000,
|
||||
);
|
||||
const metricText = documentObject.querySelector("#metric-grid").textContent;
|
||||
assert(
|
||||
metricText.includes("tone-1k · ADC steering") &&
|
||||
metricText.includes("source source-1 47000→ADC 1000 Hz"),
|
||||
"baseband source-alias steering metric is rendered",
|
||||
);
|
||||
assert(
|
||||
metricText.includes("tone-1.2k · ADC steering") &&
|
||||
metricText.includes("ambient ambient-1 46800→ADC 1200 Hz"),
|
||||
"baseband ambient-alias steering metric is rendered",
|
||||
);
|
||||
|
||||
documentObject.querySelector("#export-experiment-button").click();
|
||||
const experimentDownload = await waitFor(
|
||||
() => downloads.find((download) => download.fileName.endsWith(".experiment.json")),
|
||||
|
||||
Reference in New Issue
Block a user