test: Vitest characterization harness + first jsfeat parity test (Phase 0) - #49
Merged
Conversation
- Add vitest + original jsfeat (golden oracle) as devDependencies - Add vitest.config.ts (node env) and test / test:watch scripts - First parity test: math.get_gaussian_kernel matches original jsfeat within float tolerance across 4 cases (imports the real impl from src/, so it stays green through the planned de-dup refactor) Part of Phase 0 (#39) — the test gate that makes the monolith de-duplication provably behavior-preserving. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vitest 4 pulls in rolldown, which ships per-platform native binaries. The committed package-lock.json is generated on Windows, so on the Linux CI runner npm skipped the linux-x64 binding and the Test step crashed with 'Cannot find module @rolldown/binding-linux-x64-gnu'. Use 'npm install --no-package-lock' in CI so dependencies resolve for the runner's platform. Stopgap until the Vite migration (#42) reworks tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI installs with --no-package-lock so vitest 4's rolldown native binary resolves on Linux, but that also unpins prettier — a newer prettier reformatted 4 existing src files and failed format-check. Pin prettier to exactly 3.5.1 so the lock-free install keeps the formatter that matches the committed code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the fragile 'npm install --no-package-lock' (which unpinned every dependency and cascaded into prettier, then std-env ESM, breakage) with 'npm ci'. Regenerate package-lock.json in a node:20 Linux container so it correctly resolves the platform-specific native binaries that vitest 4 pulls in via rolldown. Verified end-to-end: 'npm ci && vitest run' in node:20 -> 4 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
npm 10.8.2 (bundled with Node 20) intermittently fails to install vitest 4's platform-specific rolldown native binary on the Linux CI runner (npm bug #4828), crashing 'npm ci' in the Test step. Node 24 ships npm 11, which resolves optional dependencies correctly. Verified in node:24 containers: 'npm ci' + vitest (4 passed) and 'npm run build-ts' both succeed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This branch bumps .nvmrc to Node 24 (npm 11) and adds Vitest characterization tests, so update the canonical AGENTS.md and the inlined Copilot instructions accordingly (Node version + 'npm test'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s oracle Expands the Phase 0 characterization harness (#39) and starts filling the function-level parity audit (#45): - tests/parity/matmath.test.ts: all 12 matmath functions vs the oracle - tests/parity/transform.test.ts: all 3 transform functions vs the oracle - Replace the npm jsfeat dev-dependency with a vendored oracle (tests/vendor/, from inspirit/jsfeat@4c7b336): npm jsfeat@0.0.8 and even the distributed build/jsfeat.js NEVER included the transform module, so oracle.cjs evaluates src/jsfeat_transform.js against the bundle. Parity findings captured (for the audit doc / #45): - jsfeat's transform module is absent from every distributed jsfeat build; only math.perspective_4point_transform made it into the bundle. - Signature divergence: jsfeat.transform.* take raw arrays, jsfeatNext's transform methods take matrix_t. npm test: 20 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows checkouts were getting CRLF working trees, making local prettier --check disagree with the Linux CI format-check (prettier 3 defaults endOfLine: lf). Normalize everything to LF; renormalizes two example JS files that were committed with CRLF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45: - math: qsort (full + sub-range), median, and the deprecated matrix_t-based perspective_4point_transform (parity with the jsfeat bundle's math version; jsfeatNext adds a deprecation console.warn - documented divergence) - linalg: lu_solve, cholesky_solve, svd_decompose (U_T|V_T), svd_solve, svd_invert, eigenVV - all match the oracle on identical inputs npm test: 30 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45. 14 imgproc functions vs the oracle on identical synthetic images: grayscale (RGBA + BGRA codes), resample (u8 fast path), box_blur_gray (scaled + NOSCALE), gaussian_blur, pyrdown, scharr/sobel derivatives, compute_integral_image (sum+sqsum+tilted), equalize_histogram, canny, warp_perspective, warp_affine - all bit-for-bit matches. Parity finding: the ORIGINAL jsfeat hough_transform references min_theta / max_theta without declaring them and throws ReferenceError under the bundle's strict-mode IIFE - the distributed function is unusable. jsfeatNext declares them (a fix, documented divergence); the test pins both behaviors. npm test: 44 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45 on a deterministic corner-rich synthetic image: - fast_corners: identical corners (x, y, score) at threshold 20 - yape06: identical keypoints at default thresholds - yape: identical keypoints (API divergence documented: original is a static namespace, jsfeatNext requires instantiation) - orb.describe: identical 32-byte descriptors for identical corners+angles - optical_flow_lk.track: identical status + tracked positions across a (3,2) shift, tracking FAST corners through 2-level pyramids npm test: 49 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45. RANSAC and LMEDS made deterministic by stubbing Math.random with an identical seeded sequence for both sides: - ransac + homography2d: same success flag, same 3x3 model (5 decimals), same inlier mask; synthetic outliers rejected on both sides - lmeds + homography2d: same model + mask - ransac + affine2d: BUG FOUND - jsfeatNext throws TypeError because the motion_model base class lost the default check_subset() that original jsfeat provides; the oracle succeeds on identical data. Pinned as a documented divergence; fix tracked in #51. npm test: 52 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the planned Phase 0 coverage (#39 / #45): - matrix_t: construction shape/buffer, resize grow+shrink, copy_to - get_data_type / get_channel / get_data_type_size across all popular formats (API divergence documented: top-level functions in jsfeat, instance methods in jsfeatNext) - imgproc.resample f32 path npm test: 57 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 7, 2026
kalwalt
added a commit
that referenced
this pull request
Jul 7, 2026
jsfeatNext's affine2d kernel was missing the error() and check_subset() methods that original jsfeat defines on it, so motion_estimator.ransac / lmeds with an affine2d kernel threw 'kernel.check_subset/error is not a function'. Port both from jsfeat's affine2d (error = affine reprojection residual; check_subset = true). The parity test added in #49 is flipped from pinning the crash to a full parity check: affine2d RANSAC now matches the oracle's 3x3 model and inlier mask on identical seeded data, and rejects the synthetic outliers. npm test: 57 passed; tsc --noEmit: clean. Closes #51 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 7, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Phase 0 of the parity/refactor roadmap (#46): a complete characterization-test suite that pins jsfeatNext behavior against the original jsfeat as a golden oracle. This is the gate that makes the de-dup refactor (#47) and the package upgrades provably behavior-preserving.
Closes #39. Executes the function-level parity audit for #45 (findings posted on that issue as they were found).
The suite — 57 tests, 9 files
mathmatmathtransformlinalgimgprocfast_corners,yape06,yapeorb.describeoptical_flow_lk.trackmotion_estimatorBugs & divergences found (the suite pins each)
affine2dkernel throwsTypeError(basemotion_modellost the defaultcheck_subset()); original jsfeat works.hough_transformthrowsReferenceError(undeclaredmin_theta) in every distributed jsfeat build.transformmodule was never shipped in any jsfeat build/npm release — oracle restores it from src (vendored, commit 4c7b336).yape), raw-array vsmatrix_tsignatures intransform, top-level vs instance data-type helpers.Infrastructure
npm test/test:watch.tests/vendor/(npmjsfeat@0.0.8is stale — missingtransform)..nvmrc) — npm 11 fixes the optional-native-binary install bug that broke CI ([BUG] Platform-specific optional dependencies not being included inpackage-lock.jsonwhen reinstalling withnode_modulespresent npm/cli#4828)..gitattributesforcing LF (Windows checkouts previously disagreed with CI's format-check).AGENTS.md, Copilot) updated: Node 24, test suite noted.Verification
npm test→ 57 passed. CI (format + tests) and Build both green onubuntu-24.04.🤖 Generated with Claude Code