The prototype in design/ and the documents in docs/ are two
halves of one specification. This is the list of places where the two halves do
not yet say the same thing, kept so that the next pass over either one starts
from what is actually true.
It was written on 2026-09-17 as a hand-over to the designer, eleven questions long. The pass came back the same day and answered all eleven. What is left here is what that pass did not cover, plus the questions it asked back.
A decision lives in docs/ once it is taken, so none of these are specified
here any more — this table is only a map from the question to its home.
| Question | Answer | Written down in |
|---|---|---|
| Does the roll screen keep its bands, or become one picture? | One picture. Every control is an opaque floating plate | docs/physics-and-rendering.md, “What is drawn over the table” |
| Where does the running readout go? | Its own plate across the bottom, with the count, the still-possible range and a progress rule | same |
| Should last-pass dice be marked? | Yes — accent outline and a pass 2 label |
same |
| What does the accent do over felt? | Nothing. Accent only ever appears on a plate | same |
Typography for the + on an open chain |
--color-accent-700, like every accent-coloured run of text at body size |
same |
| Six accent swatches wrap 5 + 1 | Four across, 44 dp: six presets and a custom swatch come out 4 + 3 | docs/architecture.md, “Settings” |
| A filled accent tag needs both ends of a ramp | Both ends are mixed from the accent the player picked, against text and ground rather than black and white. Built: ui/common’s TagKind.Accent, and it is what says “Update available” on a dice-set row |
docs/architecture.md, “Settings” |
| Is there a 3D preview in the designer? | There is now: a Solid tab | docs/face-designer.md, “The solid, not just the face” |
| The designer’s tool row should be icons, undo and redo in the app bar, the strip 52 dp thumbnails | All three, from the prototype’s own sprite; the thumbnail keeps its label, which is the pairing this suggested | docs/face-designer.md, “The tools are pictures” |
| Where does the total go, and how many times? | Once, in the result sheet | docs/physics-and-rendering.md, “What is drawn over the table” |
The pass also decided a good deal nobody had asked about — per-set weight,
translucency and size; a table-view setting; staggered drops; opposite-face
numbering; dragged saved rolls; the two-stage back gesture; and the removal of
the sound switch. All of it is in docs/ beside the rest, and docs/TODO.md
has the work each one makes.
They are in docs/TODO.md under “Open questions”: uppercase on a session-name
kicker, whether Statistics splits its three axes, whether History groups by day
or by saved roll, whether a set row carries make-default, whether the d18 keeps
its true shape, and whether an oversized stamp on a narrow face shrinks to fit.
"0 %", "< 0.1 %" and "%.1f %%" are
three renderings of the same idea, and the space before the % is a
convention that differs by language.A die the picker adds falls onto the table. Tapping a d6 used to put one
there; it is now let go about 60 mm above the middle of the tray and comes down
under real physics, tumbling to a stop among the dice already down — which it
may knock (docs/physics-and-rendering.md, “The dice waiting to be thrown”).
Several added at once leave that one spot a tenth of a second apart, so the
eye can follow them.
The prototype’s tray is a still picture of a board, so it shows dice standing
and cannot show them arriving. The two disagree in one respect, and the
prototype is the one that is out of date: its board is laid out in even
spacing, and the real board is wherever the dice came to rest, which is the
point of the change. What is missing from design/ is the motion and the
scatter, and the only decisions in it — where, how often, how high, how hard,
how much spin —
need a hand rather than a drawing; they are in docs/TODO.md under 5.6.
1u is a
two-column grid of cards. Deliberate — a row fits a 44 × 64 dp thumbnail and
a name at a touch target worth pressing — but a real divergence, and a card
gives a picture about five times the area.skull-d6) are typable as 3{skull-d6}kh1, and the row now offers
them after the standard dice, captioned with their id and writing the braces
(docs/architecture.md, decision 75). The prototype’s row still shows only
the ten standard dice; a caption as long as skull-d6 under a 26 dp
silhouette is the part worth drawing.#ic-copy and the turn keeps its words, because Turn 3/4 is a count. The
other two are drawn in the sprite’s own idiom — a 24 × 24 box,
stroke-width: 2, round caps and joins — as a clipboard with its clip and
as an axis with a shape either side of it. They are the two glyphs in the
app that the prototype cannot confirm, and they are in
feature/designer’s DesignerIcons marked as such. Adopt them, replace
them, or say the controls do not belong..btn-primary and because
ModernistButton takes a word by design. #ic-download is the sprite’s
“out of the app” mark and would fit a save; whether a footer action should
carry one is the design’s call.The app has moved ahead of the prototype here, on a device session’s word
rather than a drawing’s, and the prototype has not been re-imported since. The
prototype is generated — edited in the Claude Design project and re-imported
whole (“Editing”, design/README.md) — so only small, local corrections have
been made to design/dInfinityPhone.dc.html by hand (a Fudge face’s printed
label, and the throw buttons below), and a re-import has to keep them. A
rearrangement of the whole roll screen is not a small correction, so this one
is written down rather than drawn.
What the app does now, and what the prototype still draws
(docs/physics-and-rendering.md, “What is drawn over the table”):
| The app | dInfinityPhone.dc.html |
|
|---|---|---|
| The dice | a pull-down at the top of the table: a Dice head with the count and a chevron, opening onto the picker row |
the picker strip, always out, in the slot at order:{{ pickerOrder }} |
| The set chooser | folded inside that pull-down | a dropdown off the strip’s Set button (setMenuOpen) — the same idea, one level further in |
| The formula | a tab on the right edge under the menu button, with the field sliding in horizontally behind it; the formula itself is not on the table | a plate in the top left corner, plateLeft / plateTop, with the formula printed on it |
| The saved rolls | a pull-up on the bottom edge, parked by default | option 1c says “saved rolls behind a pull-up” in one line; dInfinityPhone.dc.html still draws the strip in the column |
| The empty-tray hint | none | a hint block in the bottom-left corner |
| The expected range | on the ready plate, on the two waiting plates, and in the result sheet’s grip | not drawn at all |
| The tray’s own shadow | none: the wall and the rim cast nothing, the dice cast | the fake bezel, trayBorder: 6px solid {{ tbl.wall }} |
See the odds, Save as roll |
at the foot of the result sheet | already at the foot of the result sheet, as Graph and Save — the app has caught up here, and only the wording differs |
| After a throw | the formula’s tab folds into the shut Dice pull-down until it is opened (decision 83) |
nothing changes along the top |
| A double tap on the felt | every pull-down, pull-up and tab slides off the table, and the next double tap brings them back (decision 83) | no gesture on the table |
Seven questions for the next pass over the prototype:
Graph and Save against “See the odds” and “Save as roll”. The app’s
wording is the wording it uses everywhere else for those two acts; the
prototype’s is shorter. One of them should give.Formula and a chevron pointing inwards, on a plate, mirroring Dice at
the other end of the corner, and turns both red when the formula does not
read. The prototype has no shut state to compare it with: a tab flush with
the edge, an icon alone, or something narrower would all be defensible, and
the choice is a drawing rather than a rule.1c asks for the saved rolls behind a pull-up and 1e–1g for the
result as one, but nothing draws the two of them together. How tall the
saved rolls’ grip should be, and whether SAVED ROLLS belongs on it, is
the part a drawing would settle.Type a formula, or open Dice at the
top. is gone at the session’s request, and the formula it pointed at is no
longer on the felt either. On a fresh install the first-launch screen still
says what to do; on a tray somebody has just cleared, nothing does. That may
be right — it is the state where the felt is all there is — but it is a
deliberate silence rather than an oversight, and the next pass should
confirm it.Dice head, and a double tap clears every
control off the felt (decision 83). Nothing in the prototype says a
control can be cleared by a gesture, and nothing on a cleared table says
how to get the controls back; whether it should — a faint hint the first
time, say — is a drawing to make.The saved-roll editor’s colour tag. The prototype 1r has always drawn
thirteen swatches — the twelve tags and a custom one that opens the browser’s
own colour picker — with the hex printed beside them. The app had a hex
field there instead, which is what a phone found awkward on v0.1.1. It is
the prototype’s shape now: a thirteenth swatch opening ui/common’s
ColourPicker, and the hex as a readout rather than an input. What is left of
the difference is where the hex sits — the prototype keeps it inline at the
end of the swatch row, the app puts it on the line below, because thirteen
48 dp targets already wrap on a phone.
The Roll button is gone from the app: shaking the phone is the only way to
throw (docs/physics-and-rendering.md, “Starting a roll”). The prototype was
already close — it has no Roll button either, and a tap on the tray stands in
for the shake a browser cannot make — so most of the change was deleting
things.
Hand-edited in dInfinityPhone.dc.html, and in step — one of the two
hand edits since the 2026-09-17 sync that a re-import has to keep. The
earned plate has lost Throw {{ n }} more and Stop the chain and now says
to shake again; the stuck plate has lost
Throw those {{ n }} again and keeps Cancel the roll; and the result sheet
has lost Again (compact) and Roll again (poster). Its rollAgain handler
is left in the script, unreferenced, rather than unpicked by hand.
Not done, and wanted from the design:
+ for an
open chain, and avg 10.5. It is the counting plate’s own Range reused,
so the figures before the throw and the figures during it are one
calculation — but the plate is the app’s invention and the prototype has
no such block. What should it look like, and should the hint and the range
share one plate or sit in two?--color-bg on --color-accent, with, for picked dice,
“Tap a ringed die again to put it back” on the ground under it — raised when
a chain earns a throw, when dice could not be read, when a roll gives up and
when dice are picked, and gone with the shake (docs/architecture.md,
decision 84). Drawn into the prototype by hand (rollState unread and
picked); does it want a design of its own, and is the top of the tray the
place for it?THROW AGAIN plate says how many, how many of
the throw were read, and that a shake throws them (decision 70,
docs/physics-and-rendering.md, “Avoiding stacked and cocked dice”). The
app draws it on the earned plate’s layout — an accent-700 kicker, a line of
copy, the STILL TO COME range — and the prototype now draws it by hand as
rollState unread. Should the dice that are
waiting be marked on the tray, the way the design marks last-pass dice?docs/architecture.md, decision 66) — under a total it picks a die up for
the next shake — so the prototype’s stand-in reads as a specification it is
not.
Worth a word on the board saying it is a browser’s substitute for a shake,
and the welcome’s “Shake the phone or tap the tray to roll” wants the second
half taken off.docs/architecture.md, decisions 68 and 76;
docs/physics-and-rendering.md, “Picking a die up and throwing it again”).
The app marks a picked die with a ring over the felt in the accent, on a
broader ring of the plates’ ground so the accent never meets the felt
(decision 84, at the owner’s request — it was the ground and the ink, and
did not stand out), and says “Shake to throw the 2 picked dice” over the
tray. The prototype draws both by hand as rollState picked; nothing on
the sheet says which dice are picked: does the sheet want to show the pick
too?9a) is up, the app registers no shake at all, so nothing can be
thrown under it and no result sheet can land behind its buttons
(docs/architecture.md, decision 74). Its copy still says that a shake
rolls; that is the lesson for the tray it leads to, not an invitation to
shake the welcome itself. The prototype reads no motion at all — a tray tap
stands in for the shake, and the welcome covers the tray — so nothing on the
board has to change.1y still lists one).
A shake is the only way to start a roll, so a switch that turned it off
would leave the app with no way to roll at all (decision 66). The row wants
deleting from the prototype’s Settings.None of these was changed by either side, save the first two. They are structure, and structure is the design’s.
The designer’s Material step has an Opacity slider the prototype does not
draw: between the Colour row and the Edges slider, glass to solid in steps of
five per cent, with “75 % opaque: 25 % of the light goes through” under it
(docs/face-designer.md, “Material, colour and edges”; decision 102). The
owner asked for it. It wants drawing in dzSteps in the Edges slider’s idiom,
and whether the Material menu’s names belong as ticks on it is the design’s
call.
The installed sets (1s) show four sample sets, and none of them glows.
The app now comes with five: a Halloween set of glow-in-the-dark resin, pale
by day and green in the dark, with a pumpkin die whose jack-o’-lantern faces
sit opposite blank ones (docs/dice-sets.md, “Sets that come installed”;
decision 111 for the glow). The list wants its fifth row. The prototype fakes
its physics and its light, so drawing a die that glows is optional; whether a
set row or a set’s dice grid should say “glows” in words, or show it, is the
design’s call.
Statistics (screen="stats") is one screen in the prototype: a set
segmented control, a die segmented control, a grid of four figures, a vertical
face histogram with a dashed “expected if fair” line, a streak sentence, a table
of every die, and two buttons out. The app is a list of dice you open one at a
time, with the filters as text buttons and the histogram one row per face. The
histogram has a stated reason — a d100 is a hundred bars on a 360 dp screen —
but the rest is a different screen. The design’s own question 2 is about this
screen, so it is being looked at from both ends.
Saved-roll statistics (rollstats) likewise: the prototype has four
figures, the totals chart, a verdict and a ranked Rolled / Mean / Expected / Δ
table; the app has a roll list and a stack of sentences. Is the sentence form
deliberate?
History groups by session where the prototype groups by day — Tue 12 Mar ·
14 rolls against a heading whenever the session changes, with no count. A day
is a fact about when; a session is a thing somebody named. The design’s own
question 3 asks the same thing.
Sessions is missing the prototype’s whole lower half — the inline “New session” field, the note that deleting moves rolls to Unfiled, and the entire “Import and export” block.
Dice set details has a two-tier header (a 20 dp bar, then the set’s name at 28 dp/800), provenance as a two-column table with rules, dice as a 4-up grid of rendered pictures, and a footer row of primary/secondary/ghost actions. The app merges the header, stacks the provenance, lists the dice and scatters the actions up the page. 28 dp is not on the type scale — the steps either side are 25 and 32. Which did you mean?
A neutral tag has no dark ground. .dz-dark in
dInfinityPhone.dc.html reflects each
ramp about its middle step — --color-accent-100 becomes the light
-900, -200 becomes -800, -700 becomes -300, -800 becomes -200,
and the neutrals do the same, -200↔-800, -300↔-700, -400↔-600,
with -500 its own fixed point. A step is a depth rather than a pigment,
which is a genuinely elegant rule, and the app follows it.
It is applied to eight steps, and tag-neutral is made of the two it
misses. .tag-neutral is --color-neutral-100 filled and -800
lettered, and neither is in the override list — so on a dark page it comes
out as a near-white chip with dark grey text, an inverted badge among tags
that follow the ground. tag-accent is the question that no longer arises:
the app mixes both of its ends from the accent the player picked, against
the ground’s own page and ink, so it follows the page by construction rather
than by being on the override list (core/model/AccentRamp). The app
extends the reflection to the two missing steps, because the rule is
unambiguous where the list is silent — but it is an inference, and it is the
one thing here that would change a drawn screen if you meant the other. Its
contrast is measured in a test rather than eyeballed: 9.3:1 on paper and
11.4:1 on the dark ground.
.dialog-backdrop in styles.css centres its card and
.dialog-actions puts the buttons at flex-end; every sheet in
dInfinityPhone.dc.html overrides all three — align-items: end,
width: 100%, justify-content: flex-start. The app follows the phone,
because the thumb is at the bottom of a phone. Worth making the export say
the same thing, so the next person reading the stylesheet alone does not
build the centred one again. There is one shared Sheet in the app, so it
is a one-line change when you decide it.One tag says something other than what the prototype writes. The
dice-set rows are badged default and, for a set somebody has turned off,
switched off where the prototype writes disabled.
The reason is not a preference. On Android, “disabled” is the word a screen reader says about a control that cannot be operated, and these rows very much can be — tap to open, long-press to act. A listener hearing “Brass, 1.0.0, 5 dice, disabled” would take it as a dead row. It is the one place where the app says something the prototype does not, and it is flagged here so that nobody “fixes” it back.
.btn-secondary, and fills the destructive confirm with
.btn-primary while giving the ghost to “Keep it” — so the destructive
action is the loudest button on the sheet. The app follows both, and the
“this is the one that takes something away” signal has gone with it. A
system with one red and no other colour has only weight and wording left to
say “careful”.ui/common’s shared ColourPicker — the one
picker Settings, the face designer and the saved-roll editor all open — at
Material’s own 44 dp, with a circular thumb and a rounded track, in a system
with no round anything.text-transform: uppercase with wide tracking. The tracking is applied; the
case is not, because Compose has no text transform, so applying it means
uppercasing the string itself and changing what a screen reader says. There
is one shared kicker and it does not, so the app is consistent — and one
reason it does not is that a kicker is sometimes a name somebody typed,
which is the design’s own first open question seen from this side.gap: 6px in the face strip is not on the 4/8/12 scale. Deliberate?min-width: 0 text column and a flex: none segmented control, which in a
browser means the control keeps its width and the page overflows sideways.
A phone has nowhere to overflow to, so the app puts the control under
the text once less than 120 dp is left for the sentence
(docs/architecture.md, “Settings”). It is the one place on that screen
where the app answers a question the drawing does not ask, and it is worth
drawing so that nobody invents a third answer.| Screen | Module |
|---|---|
| Roll, result sheet, picker, tray | feature/roll |
| Outcome graph | feature/graph |
| Saved rolls, editor | feature/saved |
| Dice sets, set detail | feature/sets |
| Table picker | feature/tables |
| Face designer | feature/designer |
| Statistics, history, sessions | feature/stats |
| Settings, menu, notation reference | feature/settings |
| Shared widgets, and the design tokens | ui/common |
| Palette, type and shapes as Material’s roles | app/src/main/kotlin/de/drehtuer/dinfinity/theme |
A test in ui/common reads styles.css and proves the tokens still agree with
it, so the two sides of the specification cannot drift apart silently.