# DSP-Geometry-Engine — canonical rules for LLM agents

> MCP server turning proje7-engine vertex dumps into 1D signals, FFT/roughness math, and compact JSON.
> Mission: root-cause the forearm corrugation on the imported `cc0_male_rigged3` character.
> These rules are ground truth verified against engine source. When in doubt, they win over intuition.

## Rules

1. The dump tool is `layered_field_dump_cli` — there is no `provmap_cli`. Engine executables live
   under `build/<preset>/` inside the engine checkout (never `build/bin`). Preferred binary:
   `build/windows-msvc-static-md-release/layered_field_dump_cli.exe` (no DXR/GPU dependency).
   CLI surface: `--sex male|female --out <ply> [--pose <clipId>] [--list-sources] [--bust-cm ...]`.
   Patched builds add `--character <baked.json>`, `--time <sec>`, `--palette-out <json>`, `--weights`;
   the bridge feature-detects these via `--help` — never assume they exist on a given exe.
   Exit codes: unknown argument or missing `--out` -> 2; runtime failure -> 1.

2. PLY contract (ASCII 1.0 only; binary is rejected). Vertex properties, in order:
   `float x y z nx ny nz`, `uchar red green blue`, `int sourceIndex`, `float restx resty restz`;
   patched `--weights` appends `int j0 j1 j2 j3` + `float w0 w1 w2 w3`. Face rows are literally
   `3 i j k`. Engine dumps are post-import-pipeline and in meters; the raw baked character JSON is
   pre-pipeline and in centimeters — never mix the two spaces without applying the import scale.

3. Quirk that must never be "fixed": the vertex property NAMED `sourceIndex` carries the dominant
   skin-joint index (argmax of the vertex's 4 weights), not a field-source id. If a future writer
   emits a property literally named `dominantJoint`, that one wins.

4. One posed dump contains BOTH bind (`restx/resty/restz`) and posed (`x/y/z`) positions on the same
   vertex row — bind-vs-posed comparison needs exactly one engine invocation. Rest iso-surface dumps
   come from a different pipeline with a different vertex count: only compare vertex counts
   posed-vs-posed, never posed-vs-rest-mode.

5. The bone table exists ONLY on stderr: `bone-map: 0=pelvis 1=spine ...`. Also on stderr:
   `...-posed-ok clip=<clipId> sampleTime=<sec> verts=<n>` and
   `deformers applied (collisionPush=<float> bakedCollisionPush=<float>)` — those floats are the max
   deformer push MAGNITUDES in meters (e.g. 0.0012), not counts. Parsed metadata is persisted next to
   each dump as `<dump>.meta.json`; patched `--character` runs additionally log
   `import-ok verts=... bones=... clips=... packClips=...`.

6. The asset is a baked character JSON from the operator's local library, e.g.
   `<character-library>/cc0_male_rigged3.baked.json` — a path supplied per call (`--character`), never
   bundled with this repo. Its own `clips` array is empty; engine clips are retargeted onto the import
   at load time. Useful clip ids: `clip-cin-stand-attention` (near-rest) and `clip-cin-walktalk`
   (flexion). Forearm bones are `armLowerL` / `armLowerR` in the 55-bone engine-canonical skeleton.

7. Units: all spectra are reported in cycles/meter (`dominant_freq_cpm`), always accompanied by
   `dominant_wavelength_m` (= 1/f) and `ridge_count_est`, the estimated ridge count over the analyzed
   segment length. Never report cycles-per-bin or cycles-per-sample.

8. Corrugation RCA VERDICT (measured 2026-07-11, first rigged3 vertex dumps): the forearm ripple is
   118-123 cy/m (~8.4 mm = the mesh edge-loop spacing, ~20 dB prominence) and exists IDENTICALLY in
   the raw baked JSON's bind positions (engine rest matches raw JSON to 0.005 mm; posed ≈ rest;
   deformer pushes <= 0.7 mm). Engine capsule welds, flexion deformers, and retargeted weights are
   EXONERATED — the defect enters upstream, in the Blender retarget/bake step that produced the
   character, not in anything the engine does at load or pose time. Pelvis (19.4 dB @ 79 cy/m) and
   spine show similar peaks. The dual-telemetry method (pure-LBS vs engine dump) remains the tool for
   future imports.

9. The corrugation was originally observed in depth RENDERS. Check CPU vertex dumps
   (position-space) first; if position-space is quiet, check the normal channel (nx/ny/nz, already in
   the PLY) before drawing any conclusion; escalate to render-side causes (imaging lane,
   `compare_depth_renders`) only if position-space is clean. Never assert that the defect exists in
   vertex dumps until a forearm spectral peak has actually been confirmed there.

10. Stale-binary policy: every bridge response carries an `engine_stale` flag (exe mtime vs the
    newest engine source under `src/core` + `tools/layered-field-dump` + CMake files). It is a
    WARNING only — the server never auto-builds (17-minute engine builds would blow MCP timeouts).
    Rebuild explicitly with `scripts/build-engine.ps1`; when the rtx preset is chosen, ALL rtx tools
    relink together (stale-sibling policy).

11. Eleven tool packs (see the README table for the full 49-tool inventory), in `TOOLSETS` order:
    `geometry` (`extract_mesh_telemetry`, `analyze_corrugation`, `compare_geometry_signals`,
    `localize_defect`, `lbs_differential`, `score_bake`, `analyze_mesh_topology`), `imaging`, `stats`,
    `engmath`, `systems`, `ml`, `netqueue`, `os`, `rendering`, `video` (rule 20), `perceptual`
    (rule 21). Exactly ONE of the 49 tools (`extract_mesh_telemetry`) shells out to the C++ engine;
    the other 48 are pure Python over files on disk. When/where to reach for each in an engine
    session is `docs/ENGINE-PLAYBOOK.md`. Every tool returns a compact pydantic-JSON summary —
    arrays never cross the MCP boundary, they live on disk under `data/` (plots/, series/, images/,
    features/, models/, brdf/). Packs are toggled per client with the `DSP_TOOLSETS` env var
    (comma-separated pack names; unset = all packs). General data goes in as .csv/.tsv/.json/.npz/.npy
    paths (column-addressed); `.ply` paths address engine dumps as `column="<joint>[:posed|rest]"`.

12. stats pack (ELE320): `describe`, `fit_distribution` (MLE + AIC + KS/chi2), `hypothesis_test`
    (auto/t/welch/mannwhitney/ks2/f_var/levene), `regression_fit` (OLS + CIs + residual diagnostics),
    `compare_dump_ripples` (bootstrap CI + unpaired Mann-Whitney on sector band energies — the
    SIGNIFICANCE verdict; for descriptive deltas use `compare_geometry_signals`). On `.ply` inputs,
    n = axial profile samples, not vertices.

13. imaging pack (ELE490 additions): `enhance_image` (histogram_eq/adaptive/gamma/log),
    `filter_image` (gaussian/median/unsharp/sobel/butterworth — Gonzalez-Woods H=1/(1+(D/D0)^2n)),
    `segment_image` (otsu/adaptive/kmeans + morphology + component stats — the defect-mask tool),
    `restore_image` (wiener). All write PNGs under data/images/ and return paths + stats only.

14. engmath pack (MAT235+236, sympy): `solve_ode`, `laplace_transform`, `linear_algebra`,
    `residues_and_integrals`, `fourier_series`. Expression strings are parsed with a restricted
    sympy namespace (whitelisted functions, literal-size gates) — hostile or huge expressions get a
    fast ToolError, never an eval. Symbolic results return latex + plain text.

15. systems pack (ELE301 + control): `lti_response`, `pole_zero`, `bode` (+margins), `sampling_check`
    (aliasing verdict; needs the reference sampled well above the proposed rate), `convolve_signals`,
    `group_delay` (LTI tau_g=-dphi/dw OR cross-spectrum lag between two vertex-correspondent signals),
    `state_space_analysis` (controllability/observability rank — "can inputs reach every state").
    Formats without an intrinsic dt (csv/json) require an explicit dt= argument.

16. ml pack (ELE489, scikit-learn): `feature_engineer_dump` (per-vertex features -> data/features/),
    `cluster`, `reduce_dims` (pca/lda), `classify_eval` (k-fold CV; persists model + sidecar to
    data/models/), `predict` (ONLY loads models this server wrote — path-confined to data/models/).
    Everything is seeded; rerunning with the same seed reproduces results.

17. netqueue pack (ELE412): `queueing_calc` (mm1/mmm/mg1 via Pollaczek-Khinchine; scv=0 -> M/D/1),
    `little_law`, `erlang_blocking` (Erlang-B/C). Unstable inputs (rho >= 1) return
    "unstable queue: rho=<v> >= 1" with the remedy in the hint.

18. os pack (Tanenbaum): `schedule_sim` (fcfs/sjf/srtf/rr/priority + Gantt), `page_replacement_sim`
    (fifo/lru/opt/clock; OPT is the offline lower bound), `bankers_check` (safe sequence via
    pass-continuation scan). Deterministic textbook simulators — same input, same output.

19. rendering pack (PBR / ray-tracer energy): `verify_brdf_energy` — white-furnace hemisphere
    integral of a BRDF (lambertian or GGX Cook-Torrance, via NDF importance sampling so it stays
    correct at low roughness). rho(wo) > 1 = energy LEAK (fireflies / exposure blowout); rho << 1 at
    high roughness = single-scatter LOSS (darkening). A DESIGN calculator — point it at the engine's
    ACTUAL shader model (roughness/f0/NDF) to match shipped HLSL; the engine's own C++ furnace tests
    remain ground truth. Also in geometry: `analyze_mesh_topology` (connected components / watertightness
    / non-manifold edges / valence / Euler / geodesic — whole-mesh QA, complementing per-joint
    `analyze_corrugation`).

20. video pack (AI-video comparison gate). Two TEMPORAL tools take frames = a directory or path list of
    images: `evaluate_spatiotemporal_frequencies` (3-D FFT over the T axis — high temporal-frequency
    energy = flicker/boil; gen-vs-ref delta) and `verify_motion_consistency` (bidirectional Lucas-Kanade
    forward-backward residual ||f + b(p+f)|| — a deterministic hallucination map where motion disobeys
    geometry; pure-scipy flow, not calibrated OpenCV). Three PER-FRAME GEOMETRIC/PHOTOMETRIC gates
    validate one generated frame against the engine's ground-truth AOV passes: `verify_camera_projection`
    (Shi-Tomasi corners tracked ref->gen by the repo's own LK, then homography + fundamental-matrix
    RANSAC fits — homography drives the verdict camera-consistent/camera-drift/geometry-inconsistent;
    the requested symmetric epipolar distance is reported but flagged `epipolar_degenerate` on a
    same-view/zero-parallax pair where F is unidentifiable), `analyze_photometric_consistency` (does the
    generated shading obey the engine NORMAL pass — Lambertian S~ambient+N.L least-squares fit, R^2 is
    the verdict; optional ALBEDO pass adds the albedo->shading gradient leak; a linear proxy, no
    shadow/clamp modeling) and `evaluate_occlusion_boundaries` (variance-of-Laplacian sharpness of the
    generated frame inside the DEPTH pass's Sobel-edge band vs interior; with a reference render the
    gen/ref boundary-sharpness ratio is the reliable read for depth-layer bleeding). The SPATIAL-detail
    half of the gate is `compare_wavelet_signatures` (imaging pack): per-scale wavelet band-energy parity
    — macro geometry vs micro texture, so "Scale-4 macro 99% / Scale-1 micro -40%" localizes generator
    smoothing. The comparison gate spans packs: `score_bake` (silhouette/shape) +
    `compare_wavelet_signatures` (spatial detail) + the video pack (temporal + camera/lighting/occlusion).
    See docs/ENGINE-PLAYBOOK.md.

21. perceptual pack (classical FR-VQA — the perceptual/semantic layer that bridges MATHEMATICAL parity
    and HUMAN parity; pure-scipy stand-ins for LPIPS/DINOv2, NO torch/weights so the server stays
    pure-wheel + deterministic + cloud/cron-safe). `evaluate_perceptual_similarity` reports CW-SSIM
    (Complex-Wavelet SSIM over a complex Gabor bank — SHIFT-TOLERANT: a few-pixel texture shift, a huge
    FFT/wavelet error, still scores ~1) alongside MS-SSIM and pixel SSIM; `shift_tolerant_gap` =
    cw_ssim - ssim is large exactly when the difference is a benign shift. Use it as the TIE-BREAKER:
    when compare_depth_renders / compare_wavelet_signatures flag a big error but cw_ssim is high, the
    generator preserved the perceptual intent. `verify_identity_coherence` takes a frame sequence + an
    engine object mask (ID/segmentation pass) and tracks the masked object's colour+texture signature's
    cosine drift across frames — the exact frame an object MORPHS (leather jacket -> plastic raincoat),
    which optical flow (where pixels move, not what they are) cannot see; verdict coherent /
    identity-drift / morph, with first_break_frame localizing the change. Both are CLASSICAL and honest
    about it: cw_ssim is not neural-SOTA, and the identity tracker flags gross colour/texture/shape
    morphing (not fine semantics) over a FIXED mask region. Full arrays live under data/perceptual/.
    Full comparison-gate preset: DSP_TOOLSETS=video,imaging,geometry,perceptual.

22. This repo registers TWO independent MCP processes: `dsp-geometry-engine` runs `dsp-server`
    (49 analysis tools; stdio or streamable HTTP, which must be authenticated when exposed), while
    `blender-body-mesh` runs
    `bodymesh-server` (6 body-generation tools; local stdio only). The latter is not running under
    the former and is not a DSP toolset pack. It uses installed Blender 4.2 + MPFB 2.0.x through
    background subprocesses and stores immutable JSON jobs under `data/bodymesh/`. Completed
    candidates also pass through an exact
    55-bone arms-down Blender retarget and `character_bake_cli`. For completed candidates,
    `render_identity_set` resolves the MCP-owned `character.blend` and writes the separately versioned
    `identity/identity-v1` manifest with eight face closeups and three body views; it never mutates the
    frozen `CandidateResult`. The client LLM alone orchestrates
    the two MCP connections by passing returned artifact paths into later tool calls; neither server
    discovers, launches, or calls the other. `bodymesh-server` may reuse pure DSP library modules
    such as `dsp_server.engine.ply`; that does not start `dsp-server` or merge tool registries. Never
    start either stdio server manually inside a registered session.

23. Body-photo fitting is an iterative PARAMETRIC approximation, not one-shot reconstruction.
    Neither MPFB nor MB-LAB infers a body from pixels. Prefer calibrated front + side references,
    known height, and masks produced by `segment_image`; create a candidate, compare its orthographic
    masks with DSP imaging/perceptual tools, then revise bounded MPFB macro/target values. A single
    clothed/perspective image is underdetermined and must never be presented as an exact reconstruction.

24. The body-mesh process boundary accepts only allowlisted local image paths, copies them into the
    job, never accepts arbitrary Python/operators/Blender args/output paths, invokes direct
    `blender.exe` (never `blender-launcher.exe`) with a timeout + single-worker lock, strips secret
    environment variables, and validates every returned artifact path. Keep it local-only; remote
    exposure needs a dedicated identity, private staging, strong auth, network policy, and quotas.

25. MPFB is the ONLY supported body backend. MB-LAB 1.8.1 may be reported by environment discovery
    but is never invoked: it is archived and its own data/model terms default generated meshes to
    AGPL-3. The Blender-side workers are GPL-3.0-or-later and isolated from the `bodymesh-server`
    host process by JSON.
    `body_dsp.ply` is neutral Blender telemetry (meters; posed=rest; pseudo-joint `body`), suitable for
    topology/descriptive DSP only. The separate `body_engine.glb` + `body_engine.baked.json` carry the
    exact sex-specific engine skeleton, arms-down bind, normalized four-slot weights, UV tangents, and
    validated engine clips; inverse binds, clip bone references, downward arm directions, and all 23
    left/right skin-locality pairs are producer-gated. `mpfb_result.json` is intermediate;
    candidate `result.json` is the authoritative atomic success/failure. Use the baked JSON for
    `--character` posing and LBS/deformer telemetry.
