Q3 point tuples — OPNS point joins (grades 2–7) now analyze to a
PointSet via the quadric complement: _analyze_q3 dualizes IPNS → OPNS so
every IPNS grade routes through a single dispatch path,
intersect_three_quadrics locates Q3 = 0 along the quartic Q1 ∩ Q2
(exact plane-pair member, or a Newton-refined cone member), and
pointset_from_blade filters the candidates to the points lying on all
complement quadrics; a grade-7 join yields its eight base points
(Cayley–Bacharach).
point_tuples_demo.py — visualizes 1–7 point tuples in distinct colors,
each translated into its own quadrant of a 5×5×5 cube.
Developer docs — quadric-module.md architecture page and
conic-quadric-space.md GA math page, plus a dev/theory note on the
point-tuple dual/net correspondence. The type-hint work adds
typing-and-annotations.md (the annotation policy: annotate everything,
avoid Any, prefer proving over casting, and how the ty + ruff ANN gates
are wired) and a Handler types section in
viz-controls-and-interactions.md documenting every handler alias
(ControlHandler, EnumOptionsHandler, InteractionHandler,
ActHandler/ActEventHandler/ActClickHandler), the ControlEvent →
InteractionEvent → ClickEvent/DragEvent/ScrollEvent event hierarchy,
and how each family registers in the shared (id, event) registry.
pytanga.viz.Handler removed — the name ambiguously resolved to the
1-arg interaction handler while the on_* control fields are typed with the
2-arg shape; use the new ControlHandler ((value, event)) or
InteractionHandler ((event)) alias instead.
Interaction API typed instead of Any — on_interaction(...) now
declares event_type: InteractionEventType / handler: InteractionHandler
and set_interaction(...) declares config: InteractionConfig on
Visualizer, VizSceneHandle, VizObjectRef, InteractionHost and
Scene. The runtime contract is unchanged (event_type.value already
required an enum member), but code passing loose values no longer
type-checks.
Quartic intersection sampled too sparsely — intersect_quadrics shadowed
its sampling count with the cone's negative-inertia count, so the cone was
sampled with n = 1; the cone is now sampled at the requested density.
Point-set labels stuck at the origin — PointSet now anchors its label at
the centroid of its points instead of the world origin.
Point tuples not renderable — PointSet was missing from the geometry
Entity union, so the viewer failed to resolve it; added.
ReflectionPlane label frame raised AttributeError — the label frame
read entity.normal, which a ReflectionPlane does not have (it exposes the
wrapped plane), so labelling a plane-reflection operator failed; it now
reads entity.plane.normal.
Dev notes moved under dev/theory — the quadric plane-pair and rotor
derivations moved from dev/notes to dev/theory, and the release-notes file
was renamed to the 2.4.0-rcX series.
One ControlEvent class — pytanga.viz._interaction declared a second
class of that name for its events; the interaction events (now sharing the
new InteractionEvent base, which carries object_id, event_type and
camera) derive from the public pytanga.viz.ControlEvent, so the package
has a single event hierarchy.
Explicit handler aliases — ControlHandler and InteractionHandler
replace the ambiguous Handler; the shared (id, event) registry types both
families and exposes get_interaction() alongside get().
Type-hint coverage + ty enforcement — py/pytanga and py/examples
are fully type-annotated (644 untyped definitions) and both gates are now
permanent: ruff carries the ANN coverage rules from pyproject.toml
(ANN401 relaxed for the dynamically-typed binding/registry surfaces) and
ty runs as a gating pre-commit hook and CI job. Triage of the 584
baseline ty diagnostics fixed real bugs (see above) and corrected several
annotations: Geometry.__call__, Visualizer.add and MVTensor.__getitem__
are now overloaded, view/control
enums accept their string values (direction="horizontal",
position="bottom-right", product="op"), Scene.add/update_entity
accept the full viz entity union, and sequence-typed inputs
(to_tensor, product_matrix, MVTensor.masks, PointPath.default_colors)
take Sequence[...] so list[MV] is accepted despite invariance.
Deliberate suppressions, all verified false positives:
blade_mask/_dispatch.py (unsound-return-statement — the binding is
resolved by name via getattr) and the three globals().update()
blade-name lines in ga/basis/basis_usage.py
(unresolved-reference). The py/tests suite is deferred from the
coverage gate and keeps its star-import lint debt; the standing policy for
all new code lives in docs/dev/architecture/typing-and-annotations.md.
One-time ruff format sync — pre-commit run --all-files surfaced 51
already-drifted files (mostly under py/tests), so the repository now matches
its own ruff-format hook; the change is whitespace/line-splitting only.