beqforge 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {beqforge-0.3.2 → beqforge-0.3.3}/PKG-INFO +1 -1
- {beqforge-0.3.2 → beqforge-0.3.3}/TODO.md +1 -1
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/__init__.py +1 -1
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/impact.py +114 -27
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/impact.toml +23 -4
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/bass-ceiling.md +6 -1
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/redesign-scope.md +51 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_impact.py +93 -1
- {beqforge-0.3.2 → beqforge-0.3.3}/.dockerignore +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.gitattributes +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/dependabot.yml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/workflows/build-designer-image.yml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/workflows/build-device-check.yml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/workflows/build-executable.yml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.github/workflows/tests.yml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.gitignore +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/.gitignore +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/beqforge.iml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/editor.xml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/misc.xml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/modules.xml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/.idea/vcs.xml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/AGENTS.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/CLAUDE.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/LICENCE.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/README.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/AGENTS.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/biquad.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/provenance.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/publication.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beq_common/types.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/AGENTS.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/__init__.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/accept.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/adjustments.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/beqd.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/biquad.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/charts.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/cli.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/dc_offset.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/design.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/designer.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/diagnose.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/e10.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/explain.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/extraction.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/filters.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/fit_kernel.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/harness.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/identify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/material.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/pipeline.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/record.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/reference.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/rolloff.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge/verify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge-device-check.spec +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge.spec +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/report.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/AGENTS.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/__init__.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/cli.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/core.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/beqoptimiser/parameters.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/device-check.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/docs/optimiser.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/evidence_validation.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/negative_corpus.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/packaging/designer/Dockerfile +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/packaging/designer/beqforge-designer.cmd +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/packaging/designer/smoke.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/E10-validation-protocol.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/contract-1.5-adjustments.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/default-record-and-log-locations.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/design-version.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/done-design-and-pipeline.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/e10-titles-v1.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/fit-kernel.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/fit-weighting.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/library-review-improvements.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/library-v5-review.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/parameter-space-optimisation.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/playback-contract.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/research-design-decisions.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/texture-yardstick-sub-band.md +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/plans/texture-yardstick-sub-band.patch +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/pyproject.toml +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/__init__.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/conftest.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/fixtures/design_golden.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_cli.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_accept.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_adjustments.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_channel_evidence.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_charts.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_corpus.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_dc_offset.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_design.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_designer.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_designer_server.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_diagnose.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_evidence.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_explain.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_extraction.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_filters.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_golden.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_guard.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_harness.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_headroom.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_hum.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_identify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_parallel.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_pipeline.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_playback.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_publication.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_record.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_reference.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_references.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_ripple_band.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_strategy_config.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_target_fit.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_timings.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_design_verify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_designer_image.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_designer_server_startup.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_e10.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_extract_layout.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_fit_kernel.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_optimiser.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_optimiser_parameters.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/__init__.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/design_beq.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/designer_server.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/e10_cases.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/e10_run.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/parameter_benchmark.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/probe.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/extract.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/impact.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/ledger_template.html +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/negative_corpus.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/render_ledger.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/replay.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/smoke_test_exe.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/smoke_test_install.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/summarise.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/tools/validate_evidence.py +0 -0
- {beqforge-0.3.2 → beqforge-0.3.3}/uv.lock +0 -0
|
@@ -56,7 +56,7 @@ ID alone (AGENTS.md says where each lives), and the stale contract versions are
|
|
|
56
56
|
| 31 | Review the version 5 library redesign | open: handoff received 23 October 2026; group 1 released in 0.2.10 (design version 6) | Outcome and evidence: [the v5 review](plans/library-v5-review.md). beqdesigner's v5 rerun (job `ef4265fa`, 1,083 titles, about 1,000 designed and 69 declined; 18 titles v4 designed now decline, about half wrongly) is triaged into four groups in `/media/beq/triage/handoff-v5.md` (queue ids, v5 records and earlier runs per title). 1 (built): O2 refused a plateau starting just under 10 Hz on the knee of a real rolloff (A Quiet Place: Day One, Elysium, Pan); it now keeps such a plateau when it reaches 20 Hz and the shortfall beneath exceeds the passband's ripple, and all three design. Probe, protocol and corpus unchanged. 2: a whole candidate rejected for missing one check by a fraction: cliff by under 3 dB/oct (Heartburn, Back to the Future, Guardians of the Galaxy, The Commitments), tilt (Guardians, The Exorcist, Alien Resurrection, Philadelphia), level (Ray), F9 residual just below the content edge (Drive, Amy). The library has no negatives, so a tolerance it suggests still goes through the protocol and E10, never the catalogue. 3: every candidate corrected only down to above the content edge (Return to Seoul, Spirited Away, The Jungle Book, Bugonia). 4: known, rows 5/6 (steep filter; `judge_from_content_edge` is a server flag, not on the wire), F5 (Eastern Promises, Fame, Home Alone) and row 26. Earlier scope of this row: 0.2.9 reports design version 5, so beqdesigner's out-of-date filter flags every title designed before it. Review the redesign's declines and changed designs from their run records, as the first review was (row 29): confirm the fixes' titles moved as measured, rerun the open cases (row 29's tilt titles, the near-texture watch cases Black Bag, Bugonia and Incredible Hulk), and collect real-title evidence for rows 26 and 29's research. It can show wrong declines but not false acceptances (the library has no known negatives), so a retune it suggests still goes through E10. Do row 28 first if both machines will design. Records are kept by default from 0.2.10 (row 27, done). |
|
|
57
57
|
| 32 | Fit optimum selection | part built (design version 7, not released); strategy choice still unstable | Found by row 28: reproducible is not stable. Changing the fit's input by at most 0.005 dB changed 24 winners on 54 titles and variants; Black Bag LR4 35's recovery fell from a 13.1 dB to a 3.4 dB peak. **Built (25 October 2026):** the fitter returns every fit within its 0.5 dB residual bar of its answer, and the pipeline keeps the first the judge passes, fewest sections first; an alternative must lift by more than the goal tolerance to replace the answer. Perturbing the fit input by 0.004 dB now flips 14 winners where HEAD flipped 19, 33 verdicts against 36, and no title swaps between designed and declined (HEAD 1). Black Bag LR4 35 goes to a 0.4 dB shortfall and holds; 13 of row 35's 14 new declines design again and none of its 17 gains is lost. Protocol unchanged, corpus gate passes. **Left:** most remaining flips are which strategy wins (flatten against a counterfactual on the steep variants, up to 38 dB apart below the content edge). The choice among strategies is untouched, and that is the next research step. See [the outcome](plans/library-review-improvements.md#row-32-choosing-among-near-equal-fits-25-october-2026-design_version-7) and [the finding](plans/library-review-improvements.md#built-a-reproducible-fit-input-22-october-2026-design_version-6). |
|
|
58
58
|
| 33 | Noise lift beyond the evidence (found on a pumping floor) | open: the evidence bound was measured and not adopted; next, validate the floor detection | E10's first runs lifted `nonstationary_floor` 27-33 dB, but that recipe swung about 37 dB inside every event: event-locked content, matching real stopband leakage on contrast and coherence, so not a failure under the content principle ([v5](plans/E10-validation-protocol.md#the-pumping-floor-v5-28-october-2026) reports it as `event_locked_floor` and counts realistic 2-6 dB `tracking_floor` and `breathing_floor`). On seeds 1-12 under [v6](plans/E10-validation-protocol.md#the-skirt-and-the-cases-it-leaves-v6-28-october-2026) (noise lift counted beyond half an octave under the truth band), 4 of 72 realistic-floor rows lift noise. The two large ones are the judge passing the candidate that fits worst: on `breathing_floor/2` four three-section fits within 1.1-2.1 dB of target fail the cliff check, and a single shelf 6.4 dB off target and 5.5 dB over the evidence ceiling passes and is selected; on `breathing_floor/5` (gentle) a single section 9.0 dB over the ceiling at an unsupported bin is selected over a passing three-section fit 1.9 dB over. Nothing refuses a design for exceeding its evidence: the over-lift bar is measured from the goal, and `evidence_excess` is reported only. **Bounding lift by the evidence, not adopted (29 October 2026):** the fitter bound at the licence plus 1.5 dB (A), a judge check on any excess over 1.5 dB (B), and that check limited to excesses at least half an octave wide (C) were each built on branch `experiment-evidence-bound`, uncommitted. C cleared E10's counted noise lifts, but applied to the v8 library's stored `evidence_excess` it refuses the accepted design for 96 of 1,033 titles, and 14 of those would decline. Inspection showed that most of these are a smooth fit against a ragged licence on content that tracks the programme (Any Given Sunday, To Die For). The large lifts it catches below a diagnosed floor (Wait Until Dark, White Heat) sit inside a licence that allows 20-25 dB there, and C catches them only by an edge excess. **Lift below the diagnosed floor:** 247 of the 304 designed titles with measured bins under `noise_floor_hz` lift more than 3 dB there, and 151 more than 10 dB. Almost all are flatten, holding its lift flat, among them Moana (37 dB under 8.4 Hz) and White Heat (29 dB under 40 Hz). These are not counts of noise lift. `noise_floor_hz` is the mix tracking boundary, the first band whose envelope fails to track the passband, and failing that is not proof of noise. The contrast licence often disagrees with it (Wait Until Dark: 20 dB licensed below it). **On E10's truth** ([evidence](plans/E10-validation-protocol.md#is-the-tracking-boundary-a-noise-detector-29-october-2026)), the boundary sits 0.24-0.42 octave (median) below the true noise boundary on every floor family, up to 1.12 octave, and never above it: it calls the noise just above it content. It also misses half of `finite_stopband`'s exposed cases, and calls LFE-only content noise at 15-27 Hz on 3 of 12 `partial_channels`. **On the library**, an independent test calibrated on E10 (does the band's level scale with programme loudness, and is it steady) finds content under the boundary on 151 of 300 floored titles, among them Moana, Creed III and Juror #2, the largest lifts below it. Only 10 look like steady noise. The other 139, mostly older, neither scale nor stay steady, which these statistics cannot resolve. So the boundary is not fit to gate a rule, and its content-as-noise error also holds restorations flat that the content supports. Measured within 240 s stretches, only 27 of those 139 become steady noise; 85 stay bursty within minutes without scaling with the plateau, which is either content independent of the plateau or transient noise (splices, handling), and E10 has no transient noise to calibrate against. **Next:** listen to Wait Until Dark, Marnie and GoodFellas (bursty, not scaling) against Mary Poppins and The Exorcist (steady noise). If bursty and non-scaling proves to be noise, add a transient-noise family to E10. Then improve the floor detection itself, before deciding how a restoration should behave below a floor shown to be noise: hold flat, cap at the local target as the counterfactuals do, or return to 0 dB. [Evidence](plans/E10-validation-protocol.md#bounding-lift-by-the-evidence-three-experiments-none-adopted-29-october-2026). Related: rows 32, 36, E4/E9. |
|
|
59
|
-
| 34 | Redesign scope | built and released in 0.2.12 (contract 1.6's `POST /impact`); beqdesigner's step 2 live check passed, step 4 verification next | The pipeline is a catalogue of 37 named features (`beqforge/impact.py`), each owning its code and reading from a run record how a design relied on it. Each answer-changing design version declares in `beqforge/impact.toml` which features it changed and how much that moved; `tools/impact.py check` refuses changed code no feature owns and undeclared answer-reaching changes (regression step 5). `POST /impact` scores up to 500 records (15 s over the share), and records from before 0.2.11 are identified by the release that wrote them, so the current library can be scored. Version 7's fit-choice degree was measured on the library on one machine: 18 of 34 post-fit declines moved, 7 of 15 designs with a failing candidate, 2 of 15 with all passing, 0 of 8 analysis declines. Against the library: 34 likely, 1,015 possible, 236 unaffected, 70 unknown (pre-design-version builds). It doesn't meet the plan's bar for this change (likely holds 18 of the sample's 27 movers): a change that re-rolls the fit can't be narrowed by outcome class. The ranking still holds: the top 30% hold about two thirds of the expected movers. Records now carry each verdict's margins and each fit `_choose_fit` had, so a threshold change is scored exactly (`will_move` or `unaffected`), and `unknown` (`no_figure`) only for older records or a losing candidate that would start passing; this changes no answer (rejudge probe 11 of 11 at `--tol 0`, refit probe 0 of 54 moved). Responses carry each change's `basis` and `unknown_reason` (agreed with beqdesigner). `impact.MOVED`'s cascade line is now 20/f dB, capped at 2 dB at 10 Hz and below, not a flat 1 dB. Version 7's designed classes were recounted on beqdesigner's 105-title sample: 16 of 35 with a failing candidate, 22 of 60 all passing. **Left:** beqdesigner's step 2 live check and step 4 verification sample (105 titles), and the check's from-the-results direction. See [the outcome](plans/redesign-scope.md#outcome). |
|
|
59
|
+
| 34 | Redesign scope | built and released in 0.2.12 (contract 1.6's `POST /impact`); beqdesigner's step 2 live check passed, step 4 verification next | The pipeline is a catalogue of 37 named features (`beqforge/impact.py`), each owning its code and reading from a run record how a design relied on it. Each answer-changing design version declares in `beqforge/impact.toml` which features it changed and how much that moved; `tools/impact.py check` refuses changed code no feature owns and undeclared answer-reaching changes (regression step 5). `POST /impact` scores up to 500 records (15 s over the share), and records from before 0.2.11 are identified by the release that wrote them, so the current library can be scored. Version 7's fit-choice degree was measured on the library on one machine: 18 of 34 post-fit declines moved, 7 of 15 designs with a failing candidate, 2 of 15 with all passing, 0 of 8 analysis declines. Against the library: 34 likely, 1,015 possible, 236 unaffected, 70 unknown (pre-design-version builds). It doesn't meet the plan's bar for this change (likely holds 18 of the sample's 27 movers): a change that re-rolls the fit can't be narrowed by outcome class. The ranking still holds: the top 30% hold about two thirds of the expected movers. Records now carry each verdict's margins and each fit `_choose_fit` had, so a threshold change is scored exactly (`will_move` or `unaffected`), and `unknown` (`no_figure`) only for older records or a losing candidate that would start passing; this changes no answer (rejudge probe 11 of 11 at `--tol 0`, refit probe 0 of 54 moved). Responses carry each change's `basis` and `unknown_reason` (agreed with beqdesigner). `impact.MOVED`'s cascade line is now 20/f dB, capped at 2 dB at 10 Hz and below, not a flat 1 dB. Version 7's designed classes were recounted on beqdesigner's 105-title sample: 16 of 35 with a failing candidate, 22 of 60 all passing. **Version 9's scores (1 November 2026):** the ceiling's basis came from a sample chosen for lifting above 60 Hz, and its two touches were combined as independent although they were one measurement, so every one of the v8 library's 1,033 designs scored `likely`. Each touch can now be confined above a frequency (`above_hz`, `basis_below`). A record is scored by whether its band, target or cascade reaches above 60 Hz, and the largest share among a change's touches stands for the change. That gives 365 `likely` and 684 `possible` (0.29, row 32's re-roll); expected movers fall from 999 to 532. No answer changes (probe 11 of 11 at `--tol 0`). See [the outcome](plans/redesign-scope.md#scoring-a-change-by-what-it-reaches-1-november-2026). **Left:** beqdesigner's step 2 live check and step 4 verification sample (105 titles), and the check's from-the-results direction. See [the outcome](plans/redesign-scope.md#outcome). |
|
|
60
60
|
| 35 | Review the version 6 library redesign | open: handoff received 24 October 2026; the 500 and record overwriting fixed in 0.2.11; the 14 new declines traced to row 28, 13 of them designing again on row 32 (version 7, unreleased) | beqdesigner's v6 rerun (job `e6a70f7e`, 1,287 titles): 1,016 designed and 270 declined. A Quiet Place: Day One, Elysium and Pan now design (row 31's O2 fix). 17 titles declined on v5 now design, and 14 designed on v5 now decline. Handoff: `/media/beq/triage/handoff-v6.md`; ledger `/media/beq/triage/ledger.jsonl`, stage `E`. **Fixed:** A Quiet Place Part II got a 500 every time, because `TargetFit.within_tolerance` came back as 1.0000000000000002. Zero-weight bins that miss the tolerance change how numpy pairs up a sum, so the in-tolerance sum could round above the full one. It is now met / (met + missed), which can't exceed 1; this code predates row 28. The figure is reporting only, so no design version: the title's filters are unchanged (low shelf 18.25 Hz +24.6 dB, peak 28.38 Hz −3.5 dB), and its response is now valid. Probe 11 of 11 unchanged at `--tol 0`; suite passes. **The 14 new declines (analysed 24 October 2026):** all 14 were rerun on one machine, at v0.2.9, at `f6dca94` (row 28 alone) and at HEAD, from requests identical to the library's (same digest). All 14 design on 0.2.9 and decline at `f6dca94`; HEAD agrees with `f6dca94`. So row 28 alone is the cause, and processor variance accounts for none of them. In every case the target is identical (0.000 dB apart) and the fit lands on a different cascade: row 28's 0.01 dB quantisation of the fit's input re-rolls the fitter's choice among near-equal optima (row 32). Except for TINA, the new fits are within 1 dB of the old by the fitter's own measure, worst-case error (Kung Fu Panda 4 0.4-0.9 dB worse, You've Got Mail 0.9 dB better), yet a judge check that depends on the error's shape flips: cliff (Platoon, The Manchurian Candidate, You've Got Mail, How to Marry a Millionaire), tilt (On Golden Pond, Christine, Twilight's counterfactual), ripple (Arrival, Kung Fu Panda 4), turnover (Magnolia, Zoolander), extent (Twilight's flatten) or slot (Rocky, Souleymane's Story). In TINA's case the fit itself is worse: 0.2.9's three-section fits drifted 2.1-3.0 dB on publication rounding, against a 3.0 limit, with their lowest sections about 4 quantisation steps from the device's DC limit. The new fits most likely drifted just past it, and the exposure rule fell back to one shelf fitting 4-6 dB off. No threshold should move on this evidence: the declines are as arbitrary as the 0.2.9 acceptances were, and the fix is row 32's, choosing among near-equal fits by what the judge checks rather than by which optimum the arithmetic lands on. **Slot check:** Rocky's and Souleymane's Story's new fits carry sections under 1 dB that the pruner keeps, because dropping them costs 1.02 and 1.12 dB of worst-case error against its 1 dB allowance. Their 0.2.9 two-section fits reached the same error without them. `designer-interface.md` §4 allows a rejection only for a reason internal to the evidence, which a redundant section is not, so the stated reason is wrong whatever the outcome. The outcome itself is doubtful too: both designs are 0.2-2.6 dB sections at 71-353 Hz, mostly above an 80 Hz crossover, not low-end restoration. The library's flip lists are not ground truth for row 34 by themselves (see [the proposal](plans/redesign-scope.md#validation-before-relying-on-it)): its v5 records were overwritten by v6's, because a record was named by its request's audio digest. **Fixed (0.2.11):** a record is now named by digests of the audio and of the rest of the request (coverage, bass management, adjustments, settings) and by the design version, and holds `design_version` and `design_settings` itself. A response's `run_record` (1.6, agreed in principle with beqdesigner) names it relative to the shared root. No answer changes. |
|
|
61
61
|
| 36 | Is a design worth publishing at all? | open | The judge checks that a design does what its target asks, never whether the target is worth asking for. Only the target stage refuses a correction within the goal tolerance (1.5 dB), so anything that reaches the judge by another route can pass as a trivial design: row 32's search over near-equal fits accepted a single 0.98 dB shelf on the band-exclusion test until `pipeline._lifts_beyond_goal` gated alternatives. Rocky's and Souleymane's Story's v6 designs (row 35: 0.2-2.6 dB sections at 71-353 Hz, mostly above the crossover) are the real-title form of it. Decide whether the judge should refuse a design whose low-end lift stays within the goal tolerance, and with what reason under `designer-interface.md` §4. A decision change: protocol, corpus and the library's small designs. |
|
|
62
62
|
| 37 | A frequency-dependent fit tolerance | variant C works; held back behind row 36, not in 0.3.0 | The fit scores the worst absolute dB error the same at every frequency, so near-equal fits are interchangeable to it wherever their error sits. Version 8's re-roll (row 23) moved 28 Years Later by 1.13 dB at 33 Hz and Obsession by 1.09 dB at 29 Hz. `impact.MOVED` now uses 20/f dB, capped at 2 dB at 10 Hz. Weighting the fit's bars as well as its search failed: the fitter either spent sections on fractions of a dB above 50 Hz (A), or the near-equal window, 2 dB wide at 10 Hz, let single shelves up to 3.6 dB off win on section count (B). **C**, weighting only the search (f/40 Hz, 0.25 to 1), works: over the 31 winners, median worst error at 30-40 Hz falls from 1.11 to 0.63 dB and at 5-10 Hz rises from 1.05 to 1.65. No real winner changes, protocol and corpus gate are unchanged, and corpus recovery goes from 1.37 to 0.85 dB RMS. It is held back because the band-exclusion test now designs a 1 dB nothing where version 8 declined, which is row 36's hole. **Next:** C together with a row 36 rule (refuse the fitter's answer, not only its alternatives, when its lift stays within the goal tolerance), validated as one decision change. Code on branch `experiment-fit-weighting-search`. See [the evidence](plans/fit-weighting.md). |
|
|
@@ -689,9 +689,16 @@ class Touch:
|
|
|
689
689
|
old: float | None = None
|
|
690
690
|
new: float | None = None
|
|
691
691
|
note: str = ""
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
692
|
+
above_hz: float | None = None
|
|
693
|
+
"""For a behaviour or an all whose effect is confined above a frequency: a record that
|
|
694
|
+
reaches above it (`reaches_above`) is scored by `basis`, any other by `basis_below`."""
|
|
695
|
+
above_lift_db: float = 0.0
|
|
696
|
+
"""How far a recorded cascade may lift above `above_hz` without reaching it."""
|
|
697
|
+
basis_below: dict[str, tuple[int, int]] | None = None
|
|
698
|
+
"""As `basis`, measured on the records that reach nothing above `above_hz`."""
|
|
699
|
+
|
|
700
|
+
def share(self, kind: str, below: bool = False) -> float:
|
|
701
|
+
moved, measured = (self.basis_below if below else self.basis)[kind]
|
|
695
702
|
return moved / measured if measured else 0.0
|
|
696
703
|
|
|
697
704
|
|
|
@@ -705,13 +712,20 @@ class Change:
|
|
|
705
712
|
|
|
706
713
|
def basis(self) -> list[dict]:
|
|
707
714
|
"""The measured counts behind its scores, for `changes` (§7.1), one entry per outcome
|
|
708
|
-
class in `OUTCOMES` order: the largest share declared for it,
|
|
709
|
-
|
|
715
|
+
class in `OUTCOMES` order: the largest share declared for it, the most that can score a
|
|
716
|
+
record of it, and of equal shares the larger measurement."""
|
|
710
717
|
found: dict[str, tuple[int, int]] = {}
|
|
711
718
|
for touch in self.touches:
|
|
712
|
-
|
|
719
|
+
declared = [
|
|
720
|
+
*(touch.basis or {}).items(),
|
|
721
|
+
*(touch.basis_below or {}).items(),
|
|
722
|
+
]
|
|
723
|
+
for kind, (moved, measured) in declared:
|
|
713
724
|
held = found.get(kind)
|
|
714
|
-
if held is None or
|
|
725
|
+
if held is None or (
|
|
726
|
+
(moved / measured if measured else 0.0, measured)
|
|
727
|
+
> (held[0] / held[1] if held[1] else 0.0, held[1])
|
|
728
|
+
):
|
|
715
729
|
found[kind] = (moved, measured)
|
|
716
730
|
return [
|
|
717
731
|
{"class": kind, "measured": found[kind][1], "moved": found[kind][0]}
|
|
@@ -764,19 +778,30 @@ def parse(text: str) -> ImpactData:
|
|
|
764
778
|
if t["feature"] not in _BY_ID:
|
|
765
779
|
raise ValueError(f"change {c['id']}: unknown feature {t['feature']!r}")
|
|
766
780
|
basis = t.get("basis")
|
|
781
|
+
below = t.get("basis_below")
|
|
782
|
+
if ("above_hz" in t) != (below is not None):
|
|
783
|
+
raise ValueError(
|
|
784
|
+
f"change {c['id']}: {t['feature']} needs above_hz and basis_below together"
|
|
785
|
+
)
|
|
786
|
+
if below is not None and kind not in ("behaviour", "all"):
|
|
787
|
+
raise ValueError(
|
|
788
|
+
f"change {c['id']}: only a behaviour or an all is scored above a frequency"
|
|
789
|
+
)
|
|
767
790
|
if kind in ("behaviour", "all"):
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
791
|
+
for declared in (basis, below) if below is not None else (basis,):
|
|
792
|
+
if not declared or set(declared) != set(OUTCOMES):
|
|
793
|
+
raise ValueError(
|
|
794
|
+
f"change {c['id']}: {t['feature']} needs a basis of "
|
|
795
|
+
f"[moved, measured] for each of {OUTCOMES}"
|
|
796
|
+
)
|
|
797
|
+
if any(
|
|
798
|
+
len(v) != 2 or not 0 <= int(v[0]) <= int(v[1])
|
|
799
|
+
for v in declared.values()
|
|
800
|
+
):
|
|
801
|
+
raise ValueError(
|
|
802
|
+
f"change {c['id']}: a basis is [moved, measured], "
|
|
803
|
+
"moved <= measured"
|
|
804
|
+
)
|
|
780
805
|
if kind == "threshold":
|
|
781
806
|
if "old" not in t or "new" not in t:
|
|
782
807
|
raise ValueError(f"change {c['id']}: a threshold needs old and new")
|
|
@@ -795,6 +820,11 @@ def parse(text: str) -> ImpactData:
|
|
|
795
820
|
t.get("old"),
|
|
796
821
|
t.get("new"),
|
|
797
822
|
t.get("note", ""),
|
|
823
|
+
float(t["above_hz"]) if "above_hz" in t else None,
|
|
824
|
+
float(t.get("above_lift_db", 0.0)),
|
|
825
|
+
{k: (int(v[0]), int(v[1])) for k, v in below.items()}
|
|
826
|
+
if below
|
|
827
|
+
else None,
|
|
798
828
|
)
|
|
799
829
|
)
|
|
800
830
|
changes.append(
|
|
@@ -951,7 +981,7 @@ MOVED_TOLERANCE_DB = np.minimum(2.0, 20.0 / MOVED_GRID)
|
|
|
951
981
|
boost is inaudible, the same dB near the top of the band is a change of balance."""
|
|
952
982
|
|
|
953
983
|
|
|
954
|
-
def _response_db(filters: list) -> np.ndarray:
|
|
984
|
+
def _response_db(filters: list, grid: np.ndarray = MOVED_GRID) -> np.ndarray:
|
|
955
985
|
from beqforge import BiquadSpec
|
|
956
986
|
from beqforge.filters import biquad_sos, magnitude_db
|
|
957
987
|
from beqforge.pipeline import PUBLISH_FS
|
|
@@ -962,7 +992,6 @@ def _response_db(filters: list) -> np.ndarray:
|
|
|
962
992
|
else BiquadSpec(*f)
|
|
963
993
|
for f in filters
|
|
964
994
|
]
|
|
965
|
-
grid = MOVED_GRID
|
|
966
995
|
if not specs:
|
|
967
996
|
return np.zeros_like(grid)
|
|
968
997
|
return magnitude_db(biquad_sos(specs, PUBLISH_FS), grid, PUBLISH_FS)
|
|
@@ -988,6 +1017,44 @@ def moved(before: dict, after: dict) -> tuple[bool, str]:
|
|
|
988
1017
|
# -- scoring
|
|
989
1018
|
|
|
990
1019
|
|
|
1020
|
+
def reaches_above(record: dict, hz: float, lift_db: float) -> tuple[bool | None, str]:
|
|
1021
|
+
"""Whether a record's design reached above `hz`, and how: a candidate's judged band ending
|
|
1022
|
+
above it, its priced target asking for any lift above it, or its cascade lifting more than
|
|
1023
|
+
`lift_db` above it (to the top of the design grid). None when a fitted candidate holds
|
|
1024
|
+
neither its band nor its target, so the record can't say."""
|
|
1025
|
+
from beqforge import DESIGN_GRID
|
|
1026
|
+
|
|
1027
|
+
above = DESIGN_GRID > hz
|
|
1028
|
+
for c in record.get("candidates") or []:
|
|
1029
|
+
label = c.get("label", "?")
|
|
1030
|
+
top = ((c.get("correction") or {}).get("band_hz") or (None, None))[1]
|
|
1031
|
+
target = c.get("target_db")
|
|
1032
|
+
if top is None and target is None:
|
|
1033
|
+
return (
|
|
1034
|
+
None,
|
|
1035
|
+
f"{label}'s record holds neither its judged band nor its target",
|
|
1036
|
+
)
|
|
1037
|
+
if top is not None and top > hz:
|
|
1038
|
+
return True, f"{label}'s judged band ends at {top:.0f} Hz"
|
|
1039
|
+
if target is not None and len(target) == len(DESIGN_GRID):
|
|
1040
|
+
asked = np.asarray(target, dtype=float)[above]
|
|
1041
|
+
if asked.max() > 0.0:
|
|
1042
|
+
at = DESIGN_GRID[above][int(np.argmax(asked))]
|
|
1043
|
+
return (
|
|
1044
|
+
True,
|
|
1045
|
+
f"{label}'s target asks {asked.max():.1f} dB at {at:.0f} Hz",
|
|
1046
|
+
)
|
|
1047
|
+
if c.get("filters"):
|
|
1048
|
+
lift = _response_db(c["filters"], DESIGN_GRID[above])
|
|
1049
|
+
if lift.max() > lift_db:
|
|
1050
|
+
at = DESIGN_GRID[above][int(np.argmax(lift))]
|
|
1051
|
+
return (
|
|
1052
|
+
True,
|
|
1053
|
+
f"{label}'s cascade lifts {lift.max():.1f} dB at {at:.0f} Hz",
|
|
1054
|
+
)
|
|
1055
|
+
return False, f"no candidate reaches above {hz:g} Hz"
|
|
1056
|
+
|
|
1057
|
+
|
|
991
1058
|
@dataclass(frozen=True, slots=True)
|
|
992
1059
|
class Reason:
|
|
993
1060
|
change: str | None
|
|
@@ -1099,8 +1166,10 @@ def assess(record: dict, current: tuple[int, str], data: ImpactData) -> Assessme
|
|
|
1099
1166
|
settings digest) is to differ from the one it holds (§7.1, "Impact").
|
|
1100
1167
|
|
|
1101
1168
|
The score is an estimated probability of moving: each touch's measured share for the
|
|
1102
|
-
record's outcome class,
|
|
1103
|
-
|
|
1169
|
+
record's outcome class (or, for one confined above a frequency, of the records that reach
|
|
1170
|
+
above it or don't), 1.0 for a threshold that flips a deciding verdict; the largest share
|
|
1171
|
+
among a change's touches stands for the change, and changes combine as independent,
|
|
1172
|
+
1 - prod(1 - p)."""
|
|
1104
1173
|
identity = record_identity(record, data)
|
|
1105
1174
|
if isinstance(identity, tuple):
|
|
1106
1175
|
return _unknown(*identity)
|
|
@@ -1137,6 +1206,9 @@ def assess(record: dict, current: tuple[int, str], data: ImpactData) -> Assessme
|
|
|
1137
1206
|
stays = 1.0
|
|
1138
1207
|
certain = reaches_all = False
|
|
1139
1208
|
for change in data.after(version, now):
|
|
1209
|
+
# A basis is measured over the change as a whole, by its regression check, so the
|
|
1210
|
+
# touches of one change are not independent chances: the largest stands for it.
|
|
1211
|
+
largest = 0.0
|
|
1140
1212
|
for touch in change.touches:
|
|
1141
1213
|
if touch.kind == "unchanged":
|
|
1142
1214
|
continue
|
|
@@ -1158,15 +1230,30 @@ def assess(record: dict, current: tuple[int, str], data: ImpactData) -> Assessme
|
|
|
1158
1230
|
share, certain = 1.0, True
|
|
1159
1231
|
detail = f"{why} ({touch.old} -> {touch.new})"
|
|
1160
1232
|
else:
|
|
1161
|
-
|
|
1162
|
-
|
|
1233
|
+
below, where = False, ""
|
|
1234
|
+
if touch.above_hz is not None:
|
|
1235
|
+
reached, why = reaches_above(
|
|
1236
|
+
record, touch.above_hz, touch.above_lift_db
|
|
1237
|
+
)
|
|
1238
|
+
if reached is None:
|
|
1239
|
+
return _unknown(
|
|
1240
|
+
"no_figure", why, version, change.id, touch.feature
|
|
1241
|
+
)
|
|
1242
|
+
below, where = not reached, f"; {why}"
|
|
1243
|
+
share = touch.share(relied.kind, below)
|
|
1244
|
+
moved_, measured = (touch.basis_below if below else touch.basis)[
|
|
1245
|
+
relied.kind
|
|
1246
|
+
]
|
|
1163
1247
|
if touch.kind == "all":
|
|
1164
1248
|
reaches_all = True
|
|
1165
1249
|
elif share <= 0.0:
|
|
1166
1250
|
continue
|
|
1167
|
-
detail =
|
|
1168
|
-
|
|
1251
|
+
detail = (
|
|
1252
|
+
f"{relied.detail}{where}; {moved_} of {measured} such designs moved"
|
|
1253
|
+
)
|
|
1254
|
+
largest = max(largest, share)
|
|
1169
1255
|
reasons.append(Reason(change.id, touch.feature, detail))
|
|
1256
|
+
stays *= 1.0 - largest
|
|
1170
1257
|
score = 1.0 - stays
|
|
1171
1258
|
if certain:
|
|
1172
1259
|
tier: Tier = "will_move"
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
# is unaffected, and one that predates margins is unknown;
|
|
19
19
|
# - "all": reaches every design that relies on the feature (at least `likely`), with a basis
|
|
20
20
|
# as for a behaviour;
|
|
21
|
+
# - either of those confined above a frequency adds `above_hz`, `above_lift_db` and a
|
|
22
|
+
# `basis_below`: a record whose candidates reach above it (`impact.reaches_above`) is
|
|
23
|
+
# scored by `basis`, any other by `basis_below`;
|
|
21
24
|
# - "unchanged": the code changed but the answers it computes did not (say how that was shown).
|
|
25
|
+
#
|
|
26
|
+
# A basis is measured over its change as a whole, so the largest share among a change's touches
|
|
27
|
+
# scores a record; changes combine as independent chances of moving.
|
|
22
28
|
|
|
23
29
|
# The oldest design version this file can score a record from: every change after it is below.
|
|
24
30
|
since = 6
|
|
@@ -130,15 +136,28 @@ kind = "behaviour"
|
|
|
130
136
|
# not separable): v8 library titles designed at version 8 and 9 on one machine, from the
|
|
131
137
|
# library's audio with default settings. The designed classes are 60 titles lifting 3 dB above
|
|
132
138
|
# 60 Hz at version 8 (random, plus those named in review) and 20 random others; the declined
|
|
133
|
-
# classes a random sample of each.
|
|
134
|
-
#
|
|
135
|
-
|
|
139
|
+
# classes a random sample of each. That sample is chosen for what the ceiling reaches, so it is
|
|
140
|
+
# split by it: `basis` counts the records `impact.reaches_above` says reach above 60 Hz (a
|
|
141
|
+
# judged band ending above it, any target lift above it, or a cascade lifting more than the
|
|
142
|
+
# level tolerance above it), `basis_below` the rest. Of the v8 library's 1,033 designs, 365
|
|
143
|
+
# reach above it. Below, the moves are fit re-rolls (row 32): the fitter's new bound above
|
|
144
|
+
# 60 Hz moves which near-equal fit it lands on, including the 3 declined_after_fit titles that
|
|
145
|
+
# now design, whose bands end at 36-43 Hz. Re-rolls don't depend on which side of the ceiling a
|
|
146
|
+
# record falls, so declined_after_fit is pooled across both (0 of 3 above, 3 of 9 below), and
|
|
147
|
+
# below it the two designed classes are pooled (0 of 3 and 4 of 11).
|
|
148
|
+
above_hz = 60.0
|
|
149
|
+
above_lift_db = 3.0
|
|
150
|
+
basis = { no_fit = [0, 0], declined_after_fit = [3, 12], designed_with_failures = [11, 12], designed_all_passed = [50, 54] }
|
|
151
|
+
basis_below = { no_fit = [0, 10], declined_after_fit = [3, 12], designed_with_failures = [4, 14], designed_all_passed = [4, 14] }
|
|
136
152
|
|
|
137
153
|
[[change.touches]]
|
|
138
154
|
feature = "judge.overlift"
|
|
139
155
|
kind = "behaviour"
|
|
140
156
|
# The same measurement as targets.shared.
|
|
141
|
-
|
|
157
|
+
above_hz = 60.0
|
|
158
|
+
above_lift_db = 3.0
|
|
159
|
+
basis = { no_fit = [0, 0], declined_after_fit = [3, 12], designed_with_failures = [11, 12], designed_all_passed = [50, 54] }
|
|
160
|
+
basis_below = { no_fit = [0, 10], declined_after_fit = [3, 12], designed_with_failures = [4, 14], designed_all_passed = [4, 14] }
|
|
142
161
|
|
|
143
162
|
[[change.touches]]
|
|
144
163
|
feature = "judge.assess"
|
|
@@ -136,7 +136,12 @@ Adopted as design version 9.
|
|
|
136
136
|
| designed_with_failures | 11 / 15 |
|
|
137
137
|
| designed_all_passed | 54 / 65 |
|
|
138
138
|
|
|
139
|
-
`judge.assess` is declared unchanged: only its wording changed.
|
|
139
|
+
`judge.assess` is declared unchanged: only its wording changed. Because the sample was chosen
|
|
140
|
+
for what the ceiling reaches, these counts scored the whole library `likely`. Since 1 November
|
|
141
|
+
2026 they are split by whether a record reaches above 60 Hz: 61 of 66 above, 4 of 14 designs
|
|
142
|
+
and 3 of 9 declines below. On the v8 library that is 365 `likely` and 684 `possible`, where it
|
|
143
|
+
was 1,033 `likely`
|
|
144
|
+
([redesign scope](redesign-scope.md#scoring-a-change-by-what-it-reaches-1-november-2026)).
|
|
140
145
|
- **Golden answers:** regenerated.
|
|
141
146
|
- **Tests:** the two that expected the old over-lift wording now also accept the ceiling's.
|
|
142
147
|
New tests cover the bar above the ceiling, the target taper and the judged top.
|
|
@@ -249,3 +249,54 @@ Both users agreed the following, and beqforge built it before 0.2.12:
|
|
|
249
249
|
The list is `/media/beq/triage/impact-step4-sample.json`. The person picks the cut-off for the
|
|
250
250
|
rest of the library from the measured rates, and the counts are folded back into
|
|
251
251
|
`impact.toml`, which changes scores but no design.
|
|
252
|
+
|
|
253
|
+
### Scoring a change by what it reaches, 1 November 2026
|
|
254
|
+
|
|
255
|
+
Version 9 (the bass ceiling, TODO 39) scored all 1,033 designed records in the v8 library
|
|
256
|
+
`likely`, with 999 expected to move. Two separate faults produced that figure, and neither was
|
|
257
|
+
in the ceiling itself.
|
|
258
|
+
|
|
259
|
+
* **The basis came from a chosen sample.** The paired sample was 60 titles picked because they
|
|
260
|
+
lift 3 dB above 60 Hz, plus 20 controls. Its 54 of 65 was then applied to every design in the
|
|
261
|
+
class. In the library, 12.4% of designs lift that far.
|
|
262
|
+
* **One measurement was counted twice.** `targets.shared` and `judge.overlift` were measured
|
|
263
|
+
together and declared the same basis, but `assess` combined them as independent: 0.83 became
|
|
264
|
+
0.97. A basis measures its change as a whole, as `Change.basis` already assumed. Now the
|
|
265
|
+
largest share among a change's touches stands for the change, and only changes combine as
|
|
266
|
+
independent.
|
|
267
|
+
|
|
268
|
+
**Built.** A `behaviour` or `all` touch can now be confined above a frequency (`above_hz`,
|
|
269
|
+
`above_lift_db`, `basis_below`). `impact.reaches_above` reads each candidate in the record. A
|
|
270
|
+
record reaches above the frequency when any candidate's judged band ends above it, its priced
|
|
271
|
+
target asks for any lift there, or its cascade lifts more than `above_lift_db` there, up to the
|
|
272
|
+
top of the design grid. That record is scored by `basis`, and any other by `basis_below`. A
|
|
273
|
+
fitted candidate that holds neither its band nor its target is `unknown` (`no_figure`). These
|
|
274
|
+
are the ceiling's three mechanisms: the target taper, the judged band's capped top, and the
|
|
275
|
+
over-lift bar above the ceiling, which the fitter is given too. The ceiling's own sample
|
|
276
|
+
splits cleanly on them:
|
|
277
|
+
|
|
278
|
+
| Outcome class | Reaches above 60 Hz | Doesn't |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| no_fit | 0 / 0 | 0 / 10 |
|
|
281
|
+
| declined_after_fit | 0 / 3 | 3 / 9 |
|
|
282
|
+
| designed_with_failures | 11 / 12 | 0 / 3 |
|
|
283
|
+
| designed_all_passed | 50 / 54 | 4 / 11 |
|
|
284
|
+
|
|
285
|
+
The moves below the ceiling are fit re-rolls (row 32): Predator, Run Lola Run, Scarface and
|
|
286
|
+
Terminator 2 among the controls, and the 3 declines that now design. Re-rolls don't depend on
|
|
287
|
+
which side a record falls, so the declared basis pools them. `declined_after_fit` is 3 of 12
|
|
288
|
+
on both sides. Below the ceiling, the two designed classes share 4 of 14. With 3 cases,
|
|
289
|
+
`designed_with_failures` alone would have read 0 and called those records unaffected.
|
|
290
|
+
|
|
291
|
+
**On the v8 library** (1,069 records at version 8, scored against version 9):
|
|
292
|
+
|
|
293
|
+
| | likely | possible | unaffected | expected movers |
|
|
294
|
+
| --- | --- | --- | --- | --- |
|
|
295
|
+
| before | 1,033 | 16 | 20 | 999 |
|
|
296
|
+
| after | 365 | 684 | 20 | 532 |
|
|
297
|
+
|
|
298
|
+
The 365 `likely` records are the designs that reach above 60 Hz, at 0.92-0.93. The rest score
|
|
299
|
+
0.29, or 0.25 for a decline. Most of what remains is row 32's re-roll, which only stabilising
|
|
300
|
+
the fit choice will narrow. This changes scores but no design: the probe is unchanged at
|
|
301
|
+
`--tol 0` (11 of 11). The response's schema is unchanged. Each `changes` item's `basis` is
|
|
302
|
+
still the largest share declared per class, and of equal shares the larger measurement.
|
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
|
-
from beqforge import impact
|
|
11
|
+
from beqforge import DESIGN_GRID, impact
|
|
12
12
|
from beqforge.designer import (
|
|
13
13
|
DESIGN_VERSION,
|
|
14
14
|
DesignRequest,
|
|
@@ -124,6 +124,29 @@ def test_the_shipped_impact_data_loads() -> None:
|
|
|
124
124
|
"records no margin",
|
|
125
125
|
),
|
|
126
126
|
('feature = "judge.cliff"\nkind = "threshold"\nold = 2.0', "old and new"),
|
|
127
|
+
(
|
|
128
|
+
(
|
|
129
|
+
'feature = "fit.choice"\nkind = "behaviour"\nabove_hz = 60.0\nbasis = { '
|
|
130
|
+
"no_fit = [0, 1], declined_after_fit = [0, 1], designed_with_failures = "
|
|
131
|
+
"[0, 1], designed_all_passed = [0, 1] }"
|
|
132
|
+
),
|
|
133
|
+
"above_hz and basis_below together",
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
(
|
|
137
|
+
'feature = "judge.cliff"\nkind = "threshold"\nold = 2.0\nnew = 2.5\n'
|
|
138
|
+
"above_hz = 60.0\nbasis_below = { no_fit = [0, 1] }"
|
|
139
|
+
),
|
|
140
|
+
"only a behaviour or an all",
|
|
141
|
+
),
|
|
142
|
+
(
|
|
143
|
+
(
|
|
144
|
+
'feature = "fit.choice"\nkind = "behaviour"\nabove_hz = 60.0\nbasis = { '
|
|
145
|
+
"no_fit = [0, 1], declined_after_fit = [0, 1], designed_with_failures = "
|
|
146
|
+
"[0, 1], designed_all_passed = [0, 1] }\nbasis_below = { no_fit = [0, 1] }"
|
|
147
|
+
),
|
|
148
|
+
"needs a basis",
|
|
149
|
+
),
|
|
127
150
|
],
|
|
128
151
|
)
|
|
129
152
|
def test_a_bad_declaration_is_refused(touch: str, match: str) -> None:
|
|
@@ -254,6 +277,75 @@ def test_changes_combine_as_independent_chances_of_moving() -> None:
|
|
|
254
277
|
assert [r.change for r in found.reasons] == ["six", "seven"]
|
|
255
278
|
|
|
256
279
|
|
|
280
|
+
def test_one_changes_touches_are_one_measurement_not_independent_chances() -> None:
|
|
281
|
+
"""Two features measured together by one regression check move together: the larger
|
|
282
|
+
share stands for the change, and each still gives its reason."""
|
|
283
|
+
data = _data(
|
|
284
|
+
_behaviour("targets.shared", designed_all_passed=(1, 2)),
|
|
285
|
+
_behaviour("judge.overlift", designed_all_passed=(1, 4)),
|
|
286
|
+
)
|
|
287
|
+
found = impact.assess(
|
|
288
|
+
_record([_candidate("flatten", True)], "flatten"), (7, SETTINGS), data
|
|
289
|
+
)
|
|
290
|
+
assert (found.tier, found.score) == ("likely", 0.5)
|
|
291
|
+
assert [r.feature for r in found.reasons] == ["targets.shared", "judge.overlift"]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _reaching(
|
|
295
|
+
band_top: float | None = 40.0,
|
|
296
|
+
target: list[float] | None = None,
|
|
297
|
+
filters: tuple[dict, ...] = (),
|
|
298
|
+
) -> dict:
|
|
299
|
+
candidate = _candidate("flatten", True) | {"filters": list(filters)}
|
|
300
|
+
if band_top is not None:
|
|
301
|
+
candidate["correction"] = {"band_hz": [5.0, band_top]}
|
|
302
|
+
if target is not None:
|
|
303
|
+
candidate["target_db"] = target
|
|
304
|
+
return _record([candidate], "flatten")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _asking(hz: float, db: float) -> list[float]:
|
|
308
|
+
return np.where(DESIGN_GRID <= hz, db, 0.0).tolist()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def test_what_reaches_above_a_frequency_is_read_from_the_record() -> None:
|
|
312
|
+
def reach(record: dict) -> bool | None:
|
|
313
|
+
return impact.reaches_above(record, 60.0, 3.0)[0]
|
|
314
|
+
|
|
315
|
+
assert reach(_reaching(band_top=190.0)) is True
|
|
316
|
+
assert reach(_reaching(target=_asking(80.0, 0.5))) is True
|
|
317
|
+
assert reach(_reaching(target=_asking(55.0, 12.0))) is False
|
|
318
|
+
# a shelf's skirt lifts 10 dB at 30 Hz but under 3 dB above 60 Hz; at 60 Hz it lifts more
|
|
319
|
+
assert reach(_reaching(filters=(SHELF,))) is False
|
|
320
|
+
assert reach(_reaching(filters=(SHELF | {"freq_hz": 60.0},))) is True
|
|
321
|
+
assert reach(_reaching(band_top=None)) is None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _confined(**counts: tuple[int, int]) -> impact.Touch:
|
|
325
|
+
return dataclasses.replace(
|
|
326
|
+
_behaviour("targets.shared", designed_all_passed=(9, 10)),
|
|
327
|
+
above_hz=60.0,
|
|
328
|
+
above_lift_db=3.0,
|
|
329
|
+
basis_below={k: counts.get(k, (0, 1)) for k in impact.OUTCOMES},
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_a_change_confined_above_a_frequency_scores_by_which_side_a_record_falls() -> (
|
|
334
|
+
None
|
|
335
|
+
):
|
|
336
|
+
"""A sample chosen for what a change reaches would otherwise score the whole library by
|
|
337
|
+
the designs it was chosen for."""
|
|
338
|
+
data = _data(_confined(designed_all_passed=(1, 4)))
|
|
339
|
+
above = impact.assess(_reaching(band_top=190.0), (7, SETTINGS), data)
|
|
340
|
+
assert (above.tier, above.score) == ("likely", 0.9)
|
|
341
|
+
assert "judged band ends at 190 Hz; 9 of 10" in above.reasons[0].detail
|
|
342
|
+
below = impact.assess(_reaching(), (7, SETTINGS), data)
|
|
343
|
+
assert (below.tier, below.score) == ("possible", 0.25)
|
|
344
|
+
assert "no candidate reaches above 60 Hz; 1 of 4" in below.reasons[0].detail
|
|
345
|
+
unread = impact.assess(_reaching(band_top=None), (7, SETTINGS), data)
|
|
346
|
+
assert (unread.tier, unread.unknown_reason) == ("unknown", "no_figure")
|
|
347
|
+
|
|
348
|
+
|
|
257
349
|
def test_a_design_declined_before_the_fit_does_not_rely_on_it() -> None:
|
|
258
350
|
data = _data(_behaviour("fit.choice", **dict.fromkeys(impact.OUTCOMES, (1, 1))))
|
|
259
351
|
found = impact.assess(_record(), (7, SETTINGS), data)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|