memlnaut-nisps/nisps/CMakeLists.txt
monkey-w1n5t0n 9490e20a7a feat(ml)!: P3 core — geometric dislike in nisps/, jolt/OU + geo ABI
Geometric dislike (rl-feedback-design §2.1/§4; upstream InterfaceRL @
0a541cc ported verbatim, constants included):

- nisps/ml/replay.hpp: ReplayView over storage-owned buffers — deepen-or-
  store negatives (dedup 0.05, clamp -16), k-NN positive centroid with
  deterministic index tie-break + fixed accumulation order, proportional
  decay (0.0025*max(|r|,1)) + eviction, order-preserving compaction.
- nisps/ml/geo_push.hpp: push-away target (pushStep clamp(|avgNeg|,.25,1)
  *0.5, taper /(1+len), useRandom on len<=1e-4 via nisps::Rng — the single
  deliberate divergence from libc rand()), negLRRatio 0.5-0.4*negFraction.
- mlp.hpp: train_targets(input, computed-target, lr, out_mask) — trains
  toward computed targets (negative lr = cold-start train-away); solo/
  focus gating zeroes masked derivs.
- feedback.hpp: AvoidStyle {Geometric (new default), Diffuse (legacy
  move_weights, kept for A/B)}; dislike_geometric() collapses upstream's
  press+optimise into one synchronous call; on_up in geometric Avoid
  feeds the positive centroid; dislike-multiplier bookkeeping. Storage
  gains replay buffers (Fixed: ReplayCap=32 firmware default ≈ +8KB SRAM;
  Dynamic arena: cap 64).
- bindings: nisps_ml_feedback_{dislike_geometric,store_positive,
  positive_count,negative_count,set_avoid_style} + P3.2 jolt/OU ABI:
  nisps_ml_jolt_{press,step,release,active,lr_scale,tick_lr_ramp},
  nisps_ml_explore_{intensity,get_intensity,apply} (OUNoise<4096>
  over-provisioned; same code the firmware ModeBase runs).
- parity v4: Stage 6 scripted geometric session (2 likes → 2 dislikes,
  f32-exact heard vectors via Math.fround) — 961 floats PASS at 2.4e-7.
- tests: test_mlp_geo_dislike.cpp (replay dedup/deepen/clamp, centroid
  tie-break, push direction/taper/mask/clamp, cold-start inertness +
  train-away, determinism, Diffuse legacy); legacy Avoid test pinned to
  Diffuse per the ADR's deliberate-break note.

Firmware: PAFSynth .text/.data unchanged (geometric path not referenced
by current glue). NOTE: discovered pre-existing bug 10c3e55c — the
explore/place wiring is linker-GC'd out of the PAFSynth ELF (predates
this refactor; evidence in the ergo task).
2026-07-14 04:16:21 +02:00

183 lines
7.9 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(nisps_core CXX)
# ---------------------------------------------------------------------------
# Toolchain / standard
# ---------------------------------------------------------------------------
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Default to a Release build when invoked without -DCMAKE_BUILD_TYPE so host
# tests get optimized math; flip with `-DCMAKE_BUILD_TYPE=Debug` for stepping.
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
# ---------------------------------------------------------------------------
# Core interface library — header-only. Other parts of the build (ml/, dsp/,
# engines/, modes/) will link against this once they exist.
# ---------------------------------------------------------------------------
add_library(nisps_core INTERFACE)
target_include_directories(nisps_core
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_compile_features(nisps_core INTERFACE cxx_std_20)
# ---------------------------------------------------------------------------
# Emscripten / WASM target detection
# ---------------------------------------------------------------------------
# When building for WASM via emcmake, ${EMSCRIPTEN} is set automatically. We
# don't actually emit a WASM binary from this CMakeLists yet — the WASM build
# script lives in stream 7 (playground/build) and assembles its own
# Emscripten link command. Here we just gate the host-only test executable so
# `emcmake cmake -S nisps -B build-wasm` configures cleanly.
if(EMSCRIPTEN)
message(STATUS "nisps_core: configuring for Emscripten/WASM target")
endif()
# ---------------------------------------------------------------------------
# Test scaffold — only built on the host. We use a hand-rolled assertion-
# based harness (see tests/cpp/test_helpers.hpp) rather than Catch2/doctest;
# the rationale is in test_helpers.hpp.
# ---------------------------------------------------------------------------
if(NOT EMSCRIPTEN)
set(NISPS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../tests/cpp)
add_executable(nisps_core_tests
${NISPS_TEST_DIR}/test_main.cpp
${NISPS_TEST_DIR}/test_fixed_buffer.cpp
${NISPS_TEST_DIR}/test_ring_buffer.cpp
${NISPS_TEST_DIR}/test_rng.cpp
${NISPS_TEST_DIR}/test_math.cpp
${NISPS_TEST_DIR}/test_mlp_init.cpp
${NISPS_TEST_DIR}/test_mlp_inference.cpp
${NISPS_TEST_DIR}/test_mlp_training.cpp
${NISPS_TEST_DIR}/test_mlp_loss.cpp
${NISPS_TEST_DIR}/test_mlp_rl.cpp
${NISPS_TEST_DIR}/test_mlp_storage_parity.cpp
${NISPS_TEST_DIR}/test_mlp_jolt.cpp
${NISPS_TEST_DIR}/test_mlp_ou_noise.cpp
${NISPS_TEST_DIR}/test_mlp_feedback.cpp
${NISPS_TEST_DIR}/test_mlp_geo_dislike.cpp
${NISPS_TEST_DIR}/test_mlp_serialize.cpp
)
target_link_libraries(nisps_core_tests PRIVATE nisps_core)
# Chris's rule: the core compiles cleanly under -Wall -Wextra -Werror.
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(nisps_core_tests PRIVATE
-Wall -Wextra -Werror -Wpedantic
)
elseif(MSVC)
target_compile_options(nisps_core_tests PRIVATE /W4 /WX)
endif()
enable_testing()
add_test(NAME nisps_core_tests COMMAND nisps_core_tests)
# ---------------------------------------------------------------------
# DSP + engines tests (stream 3). Built as a separate executable so
# failures here don't take core/ML tests with them.
# ---------------------------------------------------------------------
add_executable(nisps_dsp_engine_tests
${NISPS_TEST_DIR}/test_main.cpp
${NISPS_TEST_DIR}/test_dsp_biquad.cpp
${NISPS_TEST_DIR}/test_dsp_delay.cpp
${NISPS_TEST_DIR}/test_dsp_reverb.cpp
${NISPS_TEST_DIR}/test_dsp_pitch_shift.cpp
${NISPS_TEST_DIR}/test_engine_no_op.cpp
${NISPS_TEST_DIR}/test_engine_paf_synth.cpp
${NISPS_TEST_DIR}/test_engine_channel_strip.cpp
${NISPS_TEST_DIR}/test_engine_xiasri.cpp
${NISPS_TEST_DIR}/test_engine_verb_fx.cpp
${NISPS_TEST_DIR}/test_engine_memlcelium.cpp
${NISPS_TEST_DIR}/test_engine_breakor.cpp
${NISPS_TEST_DIR}/test_engine_elysiamorf.cpp
${NISPS_TEST_DIR}/test_engine_analysis.cpp
)
target_link_libraries(nisps_dsp_engine_tests PRIVATE nisps_core)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(nisps_dsp_engine_tests PRIVATE
-Wall -Wextra -Werror -Wpedantic
)
elseif(MSVC)
target_compile_options(nisps_dsp_engine_tests PRIVATE /W4 /WX)
endif()
add_test(NAME nisps_dsp_engine_tests COMMAND nisps_dsp_engine_tests)
# ---------------------------------------------------------------------
# Mode tests (stream 4). Cover concept satisfaction, voice space
# dispatch, control-event pumping, and end-to-end inference→audio.
# ---------------------------------------------------------------------
add_executable(nisps_modes_tests
${NISPS_TEST_DIR}/test_main.cpp
${NISPS_TEST_DIR}/test_mode_concepts.cpp
${NISPS_TEST_DIR}/test_mode_paf_synth.cpp
${NISPS_TEST_DIR}/test_mode_voice_space.cpp
${NISPS_TEST_DIR}/test_mode_breakor_events.cpp
${NISPS_TEST_DIR}/test_mode_learning.cpp
)
target_link_libraries(nisps_modes_tests PRIVATE nisps_core)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(nisps_modes_tests PRIVATE
-Wall -Wextra -Werror -Wpedantic
)
elseif(MSVC)
target_compile_options(nisps_modes_tests PRIVATE /W4 /WX)
endif()
add_test(NAME nisps_modes_tests COMMAND nisps_modes_tests)
# ---------------------------------------------------------------------
# Stream 11 verification suite (golden vectors + engine impulse).
# Lives in tests/cpp/, registered separately so a regression here can
# be diagnosed without rebuilding the world.
# ---------------------------------------------------------------------
add_executable(nisps_golden_tests
${NISPS_TEST_DIR}/test_main.cpp
${NISPS_TEST_DIR}/ml_golden_vectors.cpp
${NISPS_TEST_DIR}/engine_impulse.cpp
)
target_link_libraries(nisps_golden_tests PRIVATE nisps_core)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(nisps_golden_tests PRIVATE
-Wall -Wextra -Werror -Wpedantic
)
elseif(MSVC)
target_compile_options(nisps_golden_tests PRIVATE /W4 /WX)
endif()
add_test(NAME nisps_golden_tests COMMAND nisps_golden_tests)
# Run the impulse test from the repo root so the relative baseline path
# in `engine_impulse.cpp` resolves to tests/cpp/engine_impulse_baseline.bin.
set_tests_properties(nisps_golden_tests PROPERTIES
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..
)
# Standalone parity-check runner. NOT registered with ctest — it's
# invoked from scripts/parity-check.sh which orchestrates native+WASM
# together.
add_executable(nisps_parity_check
${NISPS_TEST_DIR}/parity_check.cpp
)
target_link_libraries(nisps_parity_check PRIVATE nisps_core)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(nisps_parity_check PRIVATE
-Wall -Wextra -Werror -Wpedantic
# Disable FP multiply-add contraction so native matches the WASM
# build, which has no FMA instruction. Without this, native clang/gcc
# fuses MACs in the training backprop and the (chaotic) loop amplifies
# the rounding difference past the 1e-5 parity tolerance — pronounced
# since the input layer widened to 32 for mix-and-match inputs.
-ffp-contract=off
)
elseif(MSVC)
target_compile_options(nisps_parity_check PRIVATE /W4 /WX /fp:precise)
endif()
endif()