dInfinity

Tables

Design: the table picker is option 1u of the clickable design (design/); 9j adds an installed set’s table and 8a pins one to a saved-roll group.

The table is the dice tray: the box the dice tumble in. Two things about it are fixed and one is exchangeable.

Geometry

Simulation units are millimetres.

Property Value
Long side 240 mm, always (a real dice tray, regardless of phone size)
Short side 240 mm × screen aspect ratio, clamped to 0.40–0.75 (Pixel 10a: 20:9 → ~108 mm)
Wall height 60 mm as drawn; the collision box is closed to the ceiling at 200 mm
Inner corner radius 12 mm (dice do not wedge into sharp corners)
Floor friction / restitution from the table look, clamped (see below)

The two heights are not a contradiction. Sixty millimetres is the rim the renderer draws — the wall you can see. The collision walls run the full two hundred up to the ceiling, because “dice cannot leave the table no matter how hard the phone is shaken” is not true of a box with a lid two hundred millimetres up and sides only sixty: a die thrown hard leaves through the gap and never comes back. The rounded corners are only as tall as the rim, which is as far up as a die can wedge into anything.

Orientation follows the phone: portrait phone → portrait table. Rotating the device mid-roll does not rotate the table; the current roll finishes first.

The physical size of the phone’s screen is deliberately not used to scale the table. A 6.3” and a 6.9” phone get the same table; only the aspect ratio differs. This keeps rolls comparable and keeps the capacity numbers stable across devices.

Capacity rule

Before any physics body is created, the roll is checked against the table:

floorArea     = longSide × shortSide
footprint(d)  = π × r(d)²           r = bounding-sphere radius of die d at scale 1
required      = Σ footprint(d) over all dice in the roll
                (including the dice a first explosion could add)

scale = min(1, sqrt(0.30 × floorArea / required))

if scale < 0.40   → roll refused
if diceCount > 100 → roll refused (engine hard cap, independent of scale)
else               → all dice are spawned at `scale`

r(d) comes from the shape, not from a guess. A die’s size_mm is its nominal size, quoted the way a dice maker quotes one: the edge length for a polyhedron, the diameter for the coin. The bounding-sphere radius is then a fixed ratio of it, one per catalogue shape:

Shape radius ÷ nominal size   Shape radius ÷ nominal size
coin √(¼ + 1/64) ≈ 0.5154   dodecahedron (√3/4)(1+√5) ≈ 1.4013
tetrahedron √6/4 ≈ 0.6124   enneagonal-trapezohedron ≈ 0.7184
cube √3/2 ≈ 0.8660   icosahedron √(10+2√5)/4 ≈ 0.9511
octahedron √2/2 ≈ 0.7071   pentagonal-trapezohedron ≈ 0.7477

So a “16 mm d6” is a cube with 16 mm edges and a bounding radius of 16·√3/2 ≈ 13.9 mm, which is where every number in the worked table below comes from. For the coin, nominal size is its diameter — a cylinder has no edge to measure — and for the two trapezohedra it is the long apex edge.

A trapezohedron has no closed form worth writing down. It is fixed by its own two conditions and they leave no freedom at all: its kite faces have to be flat, which forces the apex to sit 2/(1 − cos(π/n)) − 1 times the ring height up, and every corner has to be on one sphere, which is what a fair die is — an insphere touching every face, a circumsphere through every corner. The ratios above are what falls out.

That is: dice may collectively cover at most 30 % of the floor with their bounding circles, and they may shrink to 40 % of their nominal size to get there. Both numbers are tunable constants and both are covered by the golden determinism tests.

Worked example on a Pixel 10a table (240 × 108 mm ≈ 259 cm²):

Roll Required at scale 1 Result
1d20 7.3 cm² scale 1.0
8d6 (16 mm d6) 48 cm² scale 1.0
20d6 121 cm² scale 0.80
60d6 362 cm² scale 0.46
80d6 483 cm² scale 0.40 — the limit
100d6 604 cm² refused: “100 dice don’t fit on the table; up to 80 do”
500d6 refused

The refusal message always says the largest count that would fit, and offers to open the outcome graph instead, which has no such limit.

Why refuse rather than batch or grow the table: a physics engine with hundreds of convex bodies packed into a small box tunnels, jitters and explodes. The result would not be a roll of the dice, it would be a bug. The outcome graph already answers “what does 500d6 look like”; the table answers “what did these dice do”, and that only means something when they had room to do it.

Table looks

A table look is a [[table]] entry in a package’s diceset.toml (docs/dice-sets.md). A package can contain only tables — a “table pack”.

[[table]]
id = "green-felt"                 # slug, unique within the package
name = "Green felt"
floor_texture = "tables/felt.png" # optional, relative, inside the package
floor_tiling = [3, 6]             # texture repeats across short/long side, default [1, 1]
wall_texture = "tables/oak.png"   # optional
wall_tiling = [8, 1]
floor_color = "#1f5e3a"           # tint; the whole colour when no texture
wall_color = "#5a3a1e"
roughness = 0.9                   # 0..1
metallic = 0.0                    # 0..1
friction = 0.6                    # floor + walls, clamped to 0.2..1.0
restitution = 0.2                 # clamped to 0.0..0.6
sound = "felt"                    # felt | wood | glass | stone | plastic  (built-in impact sound sets)
light = "warm"                    # neutral | warm | cool | dim  (built-in lighting presets)

Rules:

Built-in tables

The bundled package ships felt-green, felt-black, oak, dark-glass and plain (a neutral grey that is easy on the eyes and on the battery — it is what power-saving mode’s result screen echoes).

Your own photo

“Use a photo as table” in settings takes any image from the system picker, downsizes it to 2048 px on the long side, and writes a [[table]] entry into the user’s personal package (mine). It is then a normal table and can be exported with the rest of mine.

Selecting a table