fix: make circular and spiral presets truthful
This commit is contained in:
@@ -572,6 +572,53 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을
|
||||
() => documentObject.documentElement.dataset.arrayValid === "true",
|
||||
"ellipse ring validation",
|
||||
);
|
||||
assert(
|
||||
documentObject.querySelector("#preset-hint").textContent.includes("실제 내접원"),
|
||||
"ring hint describes a true circle instead of the boundary perimeter",
|
||||
);
|
||||
for (let index = 0; index < microphoneCount(documentObject); index += 1) {
|
||||
microphoneButtons(documentObject)[index].click();
|
||||
const [x, y] = selectedCoordinates(documentObject);
|
||||
assertNear(Math.hypot(x, y), 0.215, 1e-8, "visible ring radius " + index);
|
||||
}
|
||||
|
||||
dispatchValue(
|
||||
windowObject,
|
||||
documentObject.querySelector("#preset-type"),
|
||||
"archimedean",
|
||||
);
|
||||
assert(
|
||||
!documentObject.querySelector("#preset-turns").closest(".field").hidden,
|
||||
"Archimedean preset exposes its turn count",
|
||||
);
|
||||
assert(
|
||||
documentObject.querySelector("#preset-hint").textContent.includes("r∝θ"),
|
||||
"Archimedean hint identifies the curved spiral law",
|
||||
);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#preset-count"), 9);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#preset-turns"), 1.5);
|
||||
documentObject.querySelector("#generate-preset-button").click();
|
||||
await waitFor(() => microphoneCount(documentObject) === 9, "Archimedean generation");
|
||||
microphoneButtons(documentObject)[0].click();
|
||||
assertEqual(
|
||||
selectedCoordinates(documentObject).join(","),
|
||||
"0,0,0",
|
||||
"Archimedean spiral visibly starts at the center",
|
||||
);
|
||||
microphoneButtons(documentObject).at(-1).click();
|
||||
const archimedeanEnd = selectedCoordinates(documentObject);
|
||||
assertNear(archimedeanEnd[0], -0.215, 1e-8, "1.5-turn spiral ends at -x");
|
||||
assertNear(archimedeanEnd[1], 0, 1e-8, "1.5-turn spiral endpoint y");
|
||||
|
||||
dispatchValue(windowObject, documentObject.querySelector("#preset-type"), "spiral");
|
||||
assert(
|
||||
documentObject.querySelector("#preset-turns").closest(".field").hidden,
|
||||
"Fermat preset does not claim an Archimedean turn count",
|
||||
);
|
||||
assert(
|
||||
documentObject.querySelector("#preset-hint").textContent.includes("해바라기형"),
|
||||
"Fermat hint explains why its points do not look like one connected curve",
|
||||
);
|
||||
|
||||
dispatchValue(windowObject, documentObject.querySelector("#preset-type"), "random");
|
||||
dispatchValue(windowObject, documentObject.querySelector("#preset-count"), 12);
|
||||
@@ -585,7 +632,7 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을
|
||||
assertEqual(repeatedRandom, firstRandom, "same seed repeats the visible layout");
|
||||
|
||||
documentObject.querySelector("#undo-button").click();
|
||||
assertEqual(microphoneCount(documentObject), 8, "preset replacement is undoable");
|
||||
assertEqual(microphoneCount(documentObject), 9, "preset replacement is undoable");
|
||||
documentObject.querySelector("#redo-button").click();
|
||||
assertEqual(microphoneCount(documentObject), 12, "preset replacement is redoable");
|
||||
|
||||
|
||||
@@ -67,6 +67,116 @@ test("격자·선형·링·Fermat preset이 개수와 생성 경계를 지킨다
|
||||
}
|
||||
});
|
||||
|
||||
test("원형과 Fermat preset은 비정사각 경계에서도 실제 내접원 좌표를 만든다", () => {
|
||||
const aperture = { width: 0.5, height: 0.3 };
|
||||
const edgeInsetM = 0.02;
|
||||
const expectedRadius = 0.13;
|
||||
const count = 12;
|
||||
|
||||
for (const type of ["ring", "spiral"]) {
|
||||
const rectangle = generatePresetMicrophones({
|
||||
type,
|
||||
aperture,
|
||||
shape: "rectangle",
|
||||
edgeInsetM,
|
||||
count,
|
||||
});
|
||||
const ellipse = generatePresetMicrophones({
|
||||
type,
|
||||
aperture,
|
||||
shape: "ellipse",
|
||||
edgeInsetM,
|
||||
count,
|
||||
});
|
||||
assertEqual(
|
||||
JSON.stringify(rectangle),
|
||||
JSON.stringify(ellipse),
|
||||
type + " coordinates describe the preset instead of tracing the boundary shape",
|
||||
);
|
||||
assert(
|
||||
rectangle.every((microphone) =>
|
||||
positionInsideAperture(microphone.position, aperture, "rectangle") &&
|
||||
positionInsideAperture(microphone.position, aperture, "ellipse")),
|
||||
type + " inscribed-circle coordinates fit either generation boundary",
|
||||
);
|
||||
}
|
||||
|
||||
const ring = generatePresetMicrophones({
|
||||
type: "ring",
|
||||
aperture,
|
||||
shape: "rectangle",
|
||||
edgeInsetM,
|
||||
count,
|
||||
});
|
||||
for (const microphone of ring) {
|
||||
assertNear(
|
||||
Math.hypot(microphone.position[0], microphone.position[1]),
|
||||
expectedRadius,
|
||||
1e-8,
|
||||
"ring radius",
|
||||
);
|
||||
}
|
||||
|
||||
const spiral = generatePresetMicrophones({
|
||||
type: "spiral",
|
||||
aperture,
|
||||
shape: "rectangle",
|
||||
edgeInsetM,
|
||||
count,
|
||||
});
|
||||
spiral.forEach((microphone, index) => {
|
||||
assertNear(
|
||||
Math.hypot(microphone.position[0], microphone.position[1]),
|
||||
expectedRadius * Math.sqrt((index + 0.5) / count),
|
||||
1e-8,
|
||||
"equal-area Fermat radius " + index,
|
||||
);
|
||||
});
|
||||
assertEqual(
|
||||
generatePresetMicrophones({ type: "spiral", aperture, count: 1 })[0].position.join(","),
|
||||
"0,0,0",
|
||||
"single-microphone spiral is centered",
|
||||
);
|
||||
});
|
||||
|
||||
test("Archimedean preset은 중심부터 내접원까지 선형 반경의 실제 나선을 만든다", () => {
|
||||
const aperture = { width: 0.5, height: 0.3 };
|
||||
const edgeInsetM = 0.02;
|
||||
const maximumRadius = 0.13;
|
||||
const turns = 2.5;
|
||||
const count = 11;
|
||||
const microphones = generatePresetMicrophones({
|
||||
type: "archimedean",
|
||||
aperture,
|
||||
shape: "rectangle",
|
||||
edgeInsetM,
|
||||
turns,
|
||||
count,
|
||||
});
|
||||
assertEqual(microphones.length, count, "Archimedean count");
|
||||
microphones.forEach((microphone, index) => {
|
||||
assertNear(
|
||||
Math.hypot(microphone.position[0], microphone.position[1]),
|
||||
maximumRadius * index / (count - 1),
|
||||
1e-8,
|
||||
"Archimedean linear radius " + index,
|
||||
);
|
||||
});
|
||||
assertEqual(microphones[0].position.join(","), "0,0,0", "spiral starts at center");
|
||||
assertNear(microphones.at(-1).position[0], -maximumRadius, 1e-8, "2.5 turns end at -x");
|
||||
assertNear(microphones.at(-1).position[1], 0, 1e-8, "2.5 turns end on x axis");
|
||||
assertThrows(
|
||||
() => generatePresetMicrophones({
|
||||
type: "archimedean",
|
||||
aperture,
|
||||
turns: 0,
|
||||
count,
|
||||
}),
|
||||
"0.25",
|
||||
"Archimedean turn lower bound",
|
||||
);
|
||||
});
|
||||
|
||||
test("타원 격자는 요청한 rows x columns 수를 유지하며 경계 안에 배치된다", () => {
|
||||
const aperture = { width: 0.45, height: 0.3 };
|
||||
const microphones = generatePresetMicrophones({
|
||||
|
||||
Reference in New Issue
Block a user