Skip to content

feat: add snow globe and magic 8-ball examples - #36

Merged
blazejkustra merged 5 commits into
mainfrom
feat/magic-8-ball
Sep 13, 2026
Merged

blazejkustra merged 5 commits into
mainfrom
feat/magic-8-ball

Conversation

@blazejkustra

Copy link
Copy Markdown
Owner

Problem

The magic 8-ball example looked cheap: a flat black disc with a sticker-like 8, a pasted-on bezel, and a plain triangle that popped into view. The snow globe example rides along on this branch.

Approach

One procedural studio environment lights the sphere, bezel and glass, so their highlights agree. The die is rebuilt as an equilateral face with cut corners plus the three faces hinged back off its edges, world-locked to gravity, and floated up by buoyancy against drag rather than a spring.

Tests

  • Verified on the iPhone 17 Pro simulator: resting look, and the full sink → submerged → rise → settle cycle via screen recording plus a state log.
  • Tilt checked through the __ballTilt debug hook; the die's top edge stays level and the answer text turns with it.
  • Every answer rendered and confirmed to fit the die at identical size (line breaks are now explicit, no runtime font shrinking).
  • WGSL validated with naga; yarn lint and yarn typecheck pass.
  • NOT verified: real-hardware accelerometer gains (the simulator has no sensors, so only the debug hooks were exercised), Android, and GPU frame time.

Screenshots

Shake the phone to stir the snow, tilt to change where it falls and
settles. The scene inside is globe-fixed while the snow is gravity-fixed,
so the trees turn with the phone and the flakes do not.

Flakes are three cell-grid layers with a per-cell swirl and a 3x3 gather
rather than a particle loop, so the count stays modest on the
fragment-only pipeline. Settled drift saturates at an angle of repose
instead of levelling like a liquid.

__snowShake/__snowTilt/__snowState are exposed in dev builds so the
simulator, which has no accelerometer, can drive the physics.

Claude-Session: https://claude.ai/code/session_019UeqdTFg5KZcGCAmWMuNip
Flake visibility was inverted: the smoothstep edges ran high-to-low, so
low-density cells drew flakes and dense ones hid them. At rest the globe
was a permanent snowfall and a shake made it sparser.

The repose limit was the complement of itself, levelling the drift like a
liquid at small tilt instead of holding a slope. A globe-fixed pile reads
as the gravity-frame slope, so the limited version is the tanh term
itself, not tilt minus it.

Also reworded the docstrings that described the drift as self-levelling,
which contradicted the repose design.

Claude-Session: https://claude.ai/code/session_019UeqdTFg5KZcGCAmWMuNip
Shake the phone and the ink churns, the answer die sinks out of sight,
and buoyancy carries a new one up to the window as the churn decays.

A four-state machine drives it: churn strips the die off the glass, the
next answer is picked while it is submerged and unseen, and the rise only
starts once churn falls below a threshold. The die travels along real
world-up, so tilt decides where it surfaces from.

__ballShake/__ballTilt/__ballState are exposed in dev builds so the
simulator, which has no accelerometer, can drive the physics.

Claude-Session: https://claude.ai/code/session_019UeqdTFg5KZcGCAmWMuNip
The ball read as a flat black disc with a sticker on it. Everything glossy is
now lit by one procedural studio environment — a rectangular softbox, a fill
panel, and two wall washes behind the ball, which are the only thing that can
shape the ring of sphere around the bezel. The sphere, the moulded rim and the
domed glass all reflect that same room, so their highlights agree. The 8 is a
decal on the sphere rather than a squashed screen-space ellipse.

The die is rebuilt from the real object: an equilateral face with its three
corners cut flat, and the three neighbouring faces hinged back off its edges as
trapezoids, so it reads as a solid twenty-sider rather than a triangle floating
in ink. Its orientation belongs to the world, so its top edge stays level as the
phone tilts, and it arrives from a shake turned and rotates into place.

Buoyancy against linear drag replaces the rise spring. A spring is slowest where
it starts and fastest where it arrives, so it crossed the window in a third of a
second however soft it was made; a constant push gives a steady climb that reads
over a second and a half.

Answers are short and carry their own line breaks. adjustsFontSizeToFit does not
wrap a phrase that is too wide for a line, it shrinks it past minimumFontScale,
so NO CHANCE was rendering two thirds the size of LIKELY.

Claude-Session: https://claude.ai/code/session_014cpWXFQgSmDpKXDaj2zk5j
# Conflicts:
#	example/src/App.tsx
#	example/src/screens/HomeScreen.tsx
#	example/src/types.ts
@blazejkustra
blazejkustra merged commit 9793c8b into main Sep 13, 2026
2 checks passed
@blazejkustra
blazejkustra deleted the feat/magic-8-ball branch September 13, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant