This guide is for creative technologists building movement-to-sound, movement-to-image, or embodied robot prototypes. It provides a small Python 3.11+ logging pattern that records whether every control value was observed, inferred, generated, or actively requested, so a rehearsal failure can be traced instead of explained by guesswork.

Why an evidence ledger matters

A camera does not measure force. A pressure sensor does not directly identify intention. A generative model may fill a missing frame so smoothly that the substitution becomes invisible. Recent systems such as DirtyMoCap, TouchSight, and AVT-Fabric solve different sensing problems, but all make transformations between raw evidence and useful state.

An evidence ledger stores those transformations beside the values that drive the artwork. It is not a complete observability platform. It is a reviewable record for a single-machine prototype.

Use four provenance classes

  • observed: read directly from a named sensor;
  • inferred: estimated from observed inputs by a model or rule;
  • generated: synthesised to fill, extend, or transform evidence;
  • requested: obtained because the system actively chose another measurement.

Keep confidence separate from provenance. A generated value may receive a high model confidence and still not be observed.

Minimal JSONL logger

Save the following as evidence_ledger.py. It uses only the Python standard library.

from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import json
from pathlib import Path
from typing import Literal

Origin = Literal["observed", "inferred", "generated", "requested"]

@dataclass(frozen=True)
class EvidenceEvent:
    stream: str
    value: float
    unit: str
    origin: Origin
    source: str
    confidence: float | None = None
    parent_ids: tuple[str, ...] = ()
    event_id: str = ""
    recorded_at: str = ""

def record(path: Path, event: EvidenceEvent) -> None:
    now = datetime.now(timezone.utc).isoformat()
    row = asdict(event) | {
        "event_id": event.event_id or f"{event.stream}:{now}",
        "recorded_at": event.recorded_at or now,
    }
    if not 0 <= (row["confidence"] if row["confidence"] is not None else 1) <= 1:
        raise ValueError("confidence must be between 0 and 1")
    with path.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(row, ensure_ascii=False) + "\n")

When a camera-derived pose drives a sound parameter, record both the camera observation and the inferred pose. Link the inference to its parent event. If a missing pose is interpolated, mark it generated; do not overwrite the missing observation.

from pathlib import Path

ledger = Path("rehearsal-2026-09-28.jsonl")

record(ledger, EvidenceEvent(
    stream="left_wrist_speed",
    value=0.82,
    unit="m/s",
    origin="inferred",
    source="pose-model-v3.2",
    confidence=0.74,
    parent_ids=("camera-02:frame-18422",),
))

Log decisions as well as signals

If the system requests a second sensor reading, log the reason: low confidence, disagreement, or a safety threshold. If it continues without new evidence, log that decision too. The resulting sequence should answer: what did the system know, what did it assume, and why did it act now?

At the end of a rehearsal, calculate three simple counts: generated events per minute, low-confidence inferred events that reached an output, and requested observations that changed the decision. These are diagnostic measures, not quality scores.

Test failure deliberately

Cover the camera for five seconds, delay the IMU stream, and disconnect one sensor. Confirm that the ledger shows missing observations and any generated substitute. The public output should degrade safely rather than silently becoming more synthetic.

Pair this provenance audit with Somatic-AI Lab’s latency-honest prototype guide and consumer sensor selection guide. Latency tells you when a value arrived; provenance tells you what kind of value arrived.

Limits and safety

The ledger may contain biometric or behavioural data. Use participant consent, minimise identifiers, define retention before recording, and do not upload rehearsal logs by default. A provenance label also does not validate the model: inferred says how a value was produced, not whether it is correct.

References