Skip to content

Codebase audit 2026-07-06 — tracking issue #25

Description

@GFier

Tracking issue for the full adversarial codebase audit of 2026-07-06. Full report with system map, per-finding scenarios, design tensions, and open questions: docs/audits/codebase-audit-2026-07-06.md (in-repo).

97 findings — 7 Critical · 23 High · 38 Medium · 29 Low. All Critical/High findings are filed as the 20 issues below (clustered by fix unit). The remaining Medium/Low findings are tracked in the checklist here rather than as individual issues.

Filed issues

Engine (packages/engine)

React (packages/react)

Docs

Infra / repo

Suggested order

  1. Safety net first: [C7] infra: zero CI — the only workflows live in website/.github/, which GitHub Actions never reads #20 (CI incl. dist-drift check) + [C59] infra: zero tests for the physics engine and React bindings #21 (seed regression tests — the audit's confirmed bugs are ready-made test cases).
  2. Decide the fork: the audit's open question 1 — rewrite the resolver as a real impulse solver vs. rename/redocument the behavior — gates [C4][C21][C22][C23] engine: collision resolution is not elastic-collision math #7 and half of [C9][C10][C11][C12][C13][C20][C57][C82] docs: the root README documents an API that doesn't exist #18/[C44][C89] docs: engine docs make false physics claims; docs/ is orphaned and stale #19. (All 20 open questions are in §6 of the report.)
  3. Engine correctness: [C2] engine: circle trapped inside rectangle — inverted contact normal #5, [C3][C38] engine: dense-bucket broadphase silently drops cross-cell collision pairs #6, [C24][C25][C27][C74][C75] engine: no input validation — NaN cascades, main-thread hang, energy explosion #8, [C8][C36][C37] engine: spatial hash — big bodies miss collisions, gridSize is documented backwards, NaN degeneration #9, [C26] engine: re-running initialCondition with null elements leaves ghost colliders #10 (independent of the fork; each with its regression test).
  4. React lifecycle: [C1] react: documented install cannot run — hamo is imported unconditionally but declared an optional peer #11 (one-liner), [C17] react: 'use client' directives are stripped from the published bundle #14 (one-liner), then [C5][C42] react/engine: mounting or unmounting a box corrupts the simulation — registration is never reconciled #12/[C14][C45][C47] react: every resize or registration change re-randomizes the whole world; init runs on unmeasured rects #13/[C15][C16][C54] react: config and props handling — CanvasBox physics props aren't live, substeps silently dropped, DOM/canvas defaults diverge #16/[C19] react: canvas context null-asserts elastica and rebuilds its value every render #17 — consider the body-handle API from [C5][C42] react/engine: mounting or unmounting a box corrupts the simulation — registration is never reconciled #12's design tension, which collapses most of them.
  5. Docs last ([C9][C10][C11][C12][C13][C20][C57][C82] docs: the root README documents an API that doesn't exist #18, [C44][C89] docs: engine docs make false physics claims; docs/ is orphaned and stale #19), after the API decisions, regenerated from shipped types.
  6. Infra hygiene ([C6][C28][C63] infra: website consumes the committed dist via a tsconfig alias + hand-written type shim — three engine copies coexist #22, [C29][C68] infra: license contradictions (ISC vs MIT vs no LICENSE file) and a four-way naming split #23, [C30][C60][C61][C66] infra: broken dev script, unguarded publish, three dead git-hook systems, alien lint configs #24) can proceed in parallel any time.

Remaining Medium findings (not individually filed)

  • C31 [engine] update() mutates fixedDeltaTime as a substep channel, no try/finally — one callback exception permanently corrupts the timestep (elastica.ts:320-383)
  • C32 [engine] Static reset assigns cached position array by reference — in-place mutation corrupts the static cache (elastica.ts:335-338)
  • C33 [engine] Rigid borders use unrotated half-extents — rotated OBB corners clip through walls (borders.ts:39-65)
  • C34 [engine] Borders perfectly elastic regardless of restitutions[i], no angular response (borders.ts:39-65)
  • C35 [engine] collisions: false freezes all rotation — integrateAngularMotion gated on collisions (elastica.ts:353-364)
  • C39 [engine] collisionsList overwritten per substep — consumers only see the last substep (elastica.ts:356, 367)
  • C40 [engine] cornersPool.release never called — pool drains after 32 acquisitions; SAT recomputes corners 4× (pool.ts:61-94; obb.ts:41, 81)
  • C41 [engine] Engine reads element.dataset.state in the core — DOM contract in the physics layer; canvas must bypass via array pokes (elastica.ts:140)
  • C43 [engine] No tunneling protection (no swept tests/CCD), undocumented (collision/*)
  • C46 [react] Engine constructed in render, discarded, rebuilt in effect; ×3 under StrictMode (dom-elastica.tsx:137, 151-157)
  • C48 [react] Presets place static elements in document coords in a container-space sim; canvas statics pile at center (presets.ts:66-73, 100-111)
  • C49 [react] --er/--eds CSS vars only written when non-default — stick at stale values (dom/renderer.ts:50-62)
  • C50 [react] Removing the last CanvasBox leaves frozen pixels — early return before clearRect (canvas-elastica.tsx:160)
  • C51 [react] Canvas path allocates ~5N objects/frame while sold as the 200+-elements performance mode (canvas-elastica.tsx:141-162)
  • C52 [react] No pause/play on CanvasElastica; no prefers-reduced-motion; no offscreen pause in either mode
  • C53 [react] Canvas pre-init block is dead code (el.element === null never true) (canvas-elastica.tsx:165-182)
  • C55 [react] InitialConditionParams/UpdateParams duplicated in presets.ts and types.ts — silent drift (presets.ts:5-45)
  • C56 [react] Two hash-grid debuggers, different geometry — DOM overlay is viewport-fixed, wrong unless full-screen (utils.tsx:31-58)
  • C58 [react] Vestigial visibility machinery: write-only timeRef; useJavascriptEnable misnamed, gates nothing, publicly exported (dom-elastica.tsx:92)
  • C62 [website] website/ READMEs are the unmodified Satus template — commands and routes that don't exist here
  • C64 [website] Debug tooling ships to production; FPS overlay defaults ON; global Cmd+O palette (lib/features/index.tsx:59-61)
  • C65 [website] Two contradictory WebGL flags; the "central" one is dead code (lib/config/features.ts:7 vs lib/features/index.tsx:14)
  • C67 [website] Examples 2 and 5 commented out of the nav with no explanation — rotting silently (app/page.tsx)

Remaining Low findings (not individually filed)

  • C69 [engine] hasBounced(i) increments and returns a count — name promises a boolean read (elastica.ts:218-222)
  • C70 [engine] hash/buckets getters return live internals and vanish under object spread (elastica.ts:41-47)
  • C71 [engine] Pair key (A<<16)|B silently collides above 65,535 bodies; undocumented at call sites (aabb.ts:239)
  • C72 [engine] Element larger than container triggers both opposite walls per substep — jitter + bounce-counter spam (borders.ts:39-65)
  • C73 [engine] ElementData.rect.left/top typed but never read; zero-arg default callback yields all bodies at origin (types.ts:19-23)
  • C76 [engine] Published entry is terser-minified ESM; readable UMD exists but isn't the entry (rollup.config.js:17-30)
  • C77 [engine] vectorPool dead code beside an "only used ones exported" comment (pool.ts:136)
  • C78 [engine] 3×3 neighborhood logic duplicated in two modules (aabb.ts:68-91; spatial-hash.ts:57-82)
  • C79 [engine] Stale comments/names: "cached state object" (isn't), CollisionRecord.loop/inHash (structure long gone)
  • C80 [react] initalConditionsPresets typo is the only exported spelling (presets.ts:129)
  • C81 [react] rightFlow doesn't flow right; dvdScreenSaverOBBdvdScreenSaver; DragAndGravity casing (presets.ts:179-232)
  • C83 [react] Injected stylesheet silently overrides user transforms on boxes; no escape hatch (dom/renderer.ts:9-19)
  • C84 [react] Package default export is DomElastica while the engine's default is the Elastica class (index.tsx:62)
  • C85 [react] Permanent will-change: transform on every physics element (dom/renderer.ts:16)
  • C86 [react] dpr read once, no matchMedia listener — stale after monitor moves (canvas-elastica.tsx:67)
  • C87 [react] The !rect init guard can never trip — hamo's {} is truthy (dom-elastica.tsx:179)
  • C88 [react] HashGrid/isEmptyArray/useJavascriptEnable exported publicly, documented nowhere (index.tsx:46)
  • C90 [repo] latest = 1.0.0-dev, no CHANGELOG/tags/releases; ESM-only entry untested against CJS consumers
  • C91 [website] Hash-grid debug labels permanently overwrite element text (example-1.tsx:106-108)
  • C92 [website] Example6 default gravity outside its own slider range; sizes re-randomize on re-render (example-6.tsx:14, 77-81)
  • C93 [website] Example4 hand-rolled neighbor check has row-wrap false positives + off-by-one (example-4.tsx:281-298)
  • C94 [website] Error page is the only place mounting WebGL (app/error.tsx:19)
  • C95 [website] Metadata base falls back to https://localhost:3000; sitemap/OG URLs wrong without env var (app/layout.tsx:18)
  • C96 [website] Inline next/script without required id (app/layout.tsx:82) — PLAUSIBLE
  • C97 [repo] Tool caches committed (.tldr/cache/* tracked despite gitignore); stale website/.cursor/rules/*

Maintainer decisions needed

§6 of the audit report lists 20 open questions the code alone can't resolve. The ones that gate filed issues: Q1 resolver rewrite vs rename (#7), Q3 intended useOBB default (#18), Q4 are dynamic element lists in scope (#12/#13), Q9 real React support range (#15), Q10 hamo as dependency vs peer (#11), Q11 per-element physics props in DOM mode — planned API or doc fiction (#18), Q14/Q15 naming + license (#23), Q19 website standalone or not (#22).

Metadata

Metadata

Assignees

No one assigned

    Labels

    auditFinding from a codebase auditepicTracking issue

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions