dInfinity

TODO

The implementation plan. Steps run in order; within a step the boxes are roughly ordered too. Finished items are deleted, not ticked — git history and docs/STATUS.md are the record. Keep the file a plan, not a diary.

Everything here builds the specification in README.md, docs/ and the prototype in design/. Where a step says device, it cannot run on CI (see .claude/CLAUDE.md). Steps 1 and 3, 4.7, 4.9 and 5.1 are done.

Step 2 — CI

Done apart from boxes that wait on other projects; none blocks anything.

Step 4 — Screens

Every screen is built and connected; what is below is what is left. The design option ids (1a, 9c, …) are the labels on the canvas in design/. A change to a screen is state, UI, tests (unit, Robolectric, accessibility) and the matching document, in one PR.

4.1 Roll screen — feature/roll

Design 1a–1j, 2a, 3a–3c, 4a, 4b, 6d, 6f, 9a, 9c, 1z. Spec: docs/dice-notation.md, docs/tables.md, docs/physics-and-rendering.md (“What is drawn over the table”).

Implementation notes recorded only here:

Judgement on the phone:

Done when every example in docs/dice-notation.md can be typed, rolled and read here, and a seed gives the same result with the renderer on and off.

4.2 Outcome graph — feature/graph

4.3 Saved rolls — feature/saved

4.4 Dice sets — feature/sets

4.5 Table picker — feature/tables

4.6 Face designer — feature/designer

4.10 Settings and menu — feature/settings

Step 5 — Physics and rendering on a real phone

None of it can run on CI. A roll has to look like dice landing, not like an animation of a random number. Runs again after every physics change, with tools/harness.sh (docs/build-setup.md, “The physics harness”).

Latest harness run — Pixel 10a, 1,000 rolls of 20d20, 2026-10-03, after the livelier tumble (60–120 rad/s of spin, die restitution 0.55; the before column is main at #336):

target bar before measured
dice at rest on another die / corrections after rest 0 / 0 0 / 0 0 / 0
dice corrected 0.5 % 0.000 % 0.000 %
dice re-thrown 0.05 % 2.65 % 1.18 %
median / p99 settle 2 s / 4 s 0.83 s / 1.53 s 0.96 s / 1.76 s
rolls that gave up / forced settles 0 / 0 0 / 0 0 / 0
deepest die-into-die overlap 0.2 mm 5.29 mm 7.76 mm (p99 roll 4.67 → 5.68 mm)
turns after landing ≥ 1.00 1.55 2.70
p99 step time 8.33 ms 0.22 ms 0.38 ms (0.20 ms on a cooler phone, same physics)

Re-run at the stack’s tip on 2026-10-04 before trying collision sub-steps (5.4): the same within noise — re-throws 1.19 %, settle 0.96 s / 1.96 s, overlap 7.76 mm, 2.72 turns, p99 step 0.23 ms; over 5,000 rolls each on seeds 2 and 3, overlap 6.94 mm and 7.90 mm and re-throws 1.23 % and 1.21 %.

60d20 over 1,000 rolls: turns 1.16 → 2.06, median settle 1.28 s → 1.52 s, re-throws 5.11 % → 2.64 %, none gave up, slowest roll 10.5 s (4.7 s before). 100d6 still gives up within its first 200 rolls, before and after.

5.2 Fairness and determinism

Fairness is done on the Pixel 10a (the d18 held to the worst-face bound), and ModesAgreeTest holds power-saving and drawn modes to the same faces.

5.3 Capacity and corner cases

The 1–100 d6 sweep under counting (Pixel 10a, 2026-10-04, at #349; 200 rolls each up to 20 dice, 60 above): no roll gave up at any count, no die read while standing on another, p99 step at most 1.23 ms (60 dice) against 8.33.

dice 1 2 5 10 20 40 60 80 100
re-thrown 0 % 0.25 % 0.10 % 0.20 % 1.10 % 1.42 % 1.83 % 1.38 % 1.22 %
settle median / p99 (s) 0.68 / 0.92 0.75 / 0.96 0.81 / 0.98 0.83 / 1.54 0.86 / 1.70 0.88 / 1.64 1.45 / 1.73 1.40 / 1.68 1.43 / 1.64
deepest overlap (mm) 0 3.6 5.6 4.4 6.4 5.3 6.0 6.4 4.8
turns after landing 2.73 2.90 2.96 2.90 2.54 2.02 2.03 1.94 1.82

5.4 Collisions

5.5 Stacking and cocking — no invisible hand

Nothing corrects a die and nothing throws one but the player: a roll counts the dice that can be read, and the ones that cannot wait where they lie for the shake that throws them again (docs/physics-and-rendering.md, “Avoiding stacked and cocked dice”; decision 70). Figures in the table above, measured while the roll still threw them again itself; the harness’s scripted hand keeps them comparable.

5.6 Feel — the user’s call, not a metric

5.7 Performance on the Pixel 10a

MemoryTest holds the native heap across 500 rolls (1,440 bytes left behind).

Frame targets met on the Pixel 10a (tools/harness.sh --rendered, decision 80): 60.3 fps at 20d20 with p99 work 8.3 ms and GPU 12.6 ms; 57.3 fps at the 100-dice limit (docs/physics-and-rendering.md, “Performance, and how it is measured”). Re-run after any change to the renderer or the physics step.

Done when every target is met on the Pixel 10a and the user agrees the dice look right. Wrong numbers become the new numbers in the docs.

5.8 The hand tosses the dice

Decision 105, adopted by the owner on 2026-10-20.

5.9 A tone curve, and post-processing of our own

Light past white is bent by a pass of our own since decision 108: identity below 0.8, PBR Neutral’s shoulder above, about 1 ms of GPU a frame on the Pixel 10a.

Step 6 — v1 release

After v1

Coverage

Branch coverage is ~89.3 % against a floor of 80. Most of the rise from 75.8 % is JaCoCo 0.8.16 filtering the Compose compiler’s generated branches (docs/build-setup.md, “Coverage”); the last two points are about two hundred tests of real behaviour. What is still missed is mostly screens’ decisions and layout (DesignerScreen, RollScreen, DInfinityApp, SavedScreen, StatsScreen), guards that cannot be reached, and filesystem failures. What helps: extract decisions from draw lambdas, and test shared components on their own. Figures go in every PR description.

Found by the test pass of 2026-10-05, each left as it was so no test locks a wrong answer in:

Found by the test pass of 2026-11-06, again left as they were. “Run” means a probe showed it; “read” means it was only read in the code:

Open questions

Rendering and physics

Product

Tables and photos

Sets and collections

The design’s own

None blocks code (design decision log of 2026-09-17).

Toolchain