iris is the framework alone; the app is one crate in app-rust/
Iris: "the organization of the rust rewrite is a mess right now... there shouldn't be anything related to the app inside of iris. Iris is supposed to be the UI framework alone." And, on the crate count: "I'm confused why the app only code needs more than one crate though." Nine cargo workspaces become three, and the port's project code -- which sat in five places, four of them inside the framework -- becomes one crate, `ai-app`, in `app-rust/`: client-core -> app-rust/src/client iris/transcript-ui -> app-rust/src/ui iris/transcript-fixture -> app-rust/src/ui/fixture.rs + tests/ + touch/ iris/desktop-app -> app-rust/src/desktop + src/bin_desktop.rs iris/android-app -> app-rust/src/android + android-project/ android-shell -> app-rust/src/shell iris/ keeps core, macro, the iris crate, tabs-ui and rig-input, and now mentions no session, transcript, setup or server anywhere. Only two of the old splits had a reason that survived reading. event-model stays a crate at the repo root because server/ depends on it too, so a crate is what makes the backend and the app agree by construction. The two Android .so names looked like a hard constraint -- a package produces one library artifact -- until P2 turned out to already plan merging those two Android apps into one; both faces now come out of libai_app.so, picked apart by features so `--no-default-features --features shell` keeps wgpu, parley and iris out of the Compose app's APK. docs/RUST.md's "One app crate" has the rest, including what each remaining feature is for. DECISIONS.md and SUBAGENTS.md move into docs/ with everything else. Verified: ./run-tests.sh and `cd iris && cargo test` green, clippy and fmt clean in all five workspaces, `cargo ndk -t x86_64` links libai_app.so, build-apk.sh produces an APK that installs and launches on this checkout's emulator (Gl ... virgl, as expected), and the phone-sized headless screenshot renders the transcript unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b54aaf3c4
commit
a9312e9431
113 files changed
+23221
-2992
No files matched your search
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""AOSP's fling spline, transcribed independently of the Rust port.
|
||||
|
||||
This exists so the numbers in `sense.rs`'s `the_spline_matches_aosps_own_table`
|
||||
and `a_flick_decelerates_the_way_aosp_says_it_does` are not the Rust code
|
||||
grading its own homework. Every test iris's fling had before 2026-09-07
|
||||
compared the curve with itself -- monotonic, signed, integrates to the closed
|
||||
form -- and all of them passed while `distance_fraction(t)` was returning
|
||||
exactly `t` (see `android_fling_spline`'s doc comment). Numbers checked into a
|
||||
test have to come from somewhere else, and this is the somewhere else.
|
||||
|
||||
Transcribed by hand from, and only from:
|
||||
|
||||
* frameworks/base `core/java/android/widget/OverScroller.java`,
|
||||
`SplineOverScroller`'s static initialiser, `getSplineDeceleration`,
|
||||
`getSplineFlingDistance`, `getSplineFlingDuration` and `update`.
|
||||
* androidx.compose.animation:animation:1.12.0 `SplineBasedDecay.kt`
|
||||
(`computeSplineInfo`, `AndroidFlingSpline.flingPosition`) and
|
||||
`FlingCalculator.kt` (`computeDeceleration`, `flingDistance`,
|
||||
`flingDuration`, `FlingInfo.position`/`velocity`). The two agree line for
|
||||
line, which is why iris ports one curve rather than two.
|
||||
|
||||
Run it with no arguments; it prints the table entries and the (velocity,
|
||||
density, t) points the Rust tests assert on.
|
||||
"""
|
||||
|
||||
NB_SAMPLES = 100
|
||||
INFLEXION = 0.35
|
||||
START_TENSION = 0.5
|
||||
END_TENSION = 1.0
|
||||
P1 = START_TENSION * INFLEXION
|
||||
P2 = 1.0 - END_TENSION * (1.0 - INFLEXION)
|
||||
|
||||
# ViewConfiguration.getScrollFriction(), and SplineOverScroller's own
|
||||
# "look and feel tuning" constant -- a different number in a different place
|
||||
# of the same formula, which is the pair iris got the wrong way round once.
|
||||
SCROLL_FRICTION = 0.015
|
||||
TUNING = 0.84
|
||||
GRAVITY_EARTH = 9.80665
|
||||
INCHES_PER_METER = 39.37
|
||||
|
||||
import math
|
||||
|
||||
DECELERATION_RATE = math.log(0.78) / math.log(0.9)
|
||||
|
||||
|
||||
def spline_positions():
|
||||
"""SPLINE_POSITION: distance fraction at each of 101 even time steps."""
|
||||
position = [0.0] * (NB_SAMPLES + 1)
|
||||
x_min = 0.0
|
||||
for i in range(NB_SAMPLES):
|
||||
alpha = i / NB_SAMPLES
|
||||
x_max = 1.0
|
||||
while True:
|
||||
x = x_min + (x_max - x_min) / 2.0
|
||||
coef = 3.0 * x * (1.0 - x)
|
||||
# Solved on the P1/P2 curve...
|
||||
tx = coef * ((1.0 - x) * P1 + x * P2) + x * x * x
|
||||
if abs(tx - alpha) < 1e-5:
|
||||
break
|
||||
if tx > alpha:
|
||||
x_max = x
|
||||
else:
|
||||
x_min = x
|
||||
# ...and sampled on the tension curve.
|
||||
position[i] = coef * ((1.0 - x) * START_TENSION + x * END_TENSION) + x * x * x
|
||||
position[NB_SAMPLES] = 1.0
|
||||
return position
|
||||
|
||||
|
||||
POSITION = spline_positions()
|
||||
|
||||
|
||||
def fling_sample(t):
|
||||
"""(distance fraction, velocity fraction) at time fraction `t`."""
|
||||
t = min(max(t, 0.0), 1.0)
|
||||
index = int(t * NB_SAMPLES)
|
||||
if index >= NB_SAMPLES:
|
||||
return 1.0, 0.0
|
||||
t_inf = index / NB_SAMPLES
|
||||
t_sup = (index + 1) / NB_SAMPLES
|
||||
velocity_coef = (POSITION[index + 1] - POSITION[index]) / (t_sup - t_inf)
|
||||
return POSITION[index] + (t - t_inf) * velocity_coef, velocity_coef
|
||||
|
||||
|
||||
def physical_coefficient(density):
|
||||
return GRAVITY_EARTH * INCHES_PER_METER * density * 160.0 * TUNING
|
||||
|
||||
|
||||
def deceleration(velocity, density):
|
||||
return math.log(
|
||||
INFLEXION * abs(velocity) / (SCROLL_FRICTION * physical_coefficient(density))
|
||||
)
|
||||
|
||||
|
||||
def fling_distance(velocity, density):
|
||||
l = deceleration(velocity, density)
|
||||
return (
|
||||
SCROLL_FRICTION
|
||||
* physical_coefficient(density)
|
||||
* math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)
|
||||
)
|
||||
|
||||
|
||||
def fling_duration_s(velocity, density):
|
||||
l = deceleration(velocity, density)
|
||||
return math.exp(l / (DECELERATION_RATE - 1.0))
|
||||
|
||||
|
||||
def position_at(velocity, density, t_seconds):
|
||||
d = fling_duration_s(velocity, density)
|
||||
return fling_distance(velocity, density) * fling_sample(t_seconds / d)[0]
|
||||
|
||||
|
||||
def velocity_at(velocity, density, t_seconds):
|
||||
d = fling_duration_s(velocity, density)
|
||||
return fling_sample(t_seconds / d)[1] * fling_distance(velocity, density) / d
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("SPLINE_POSITION at a few indices (index: value)")
|
||||
for i in (0, 1, 10, 25, 50, 75, 99, 100):
|
||||
print(f" {i:3}: {POSITION[i]:.6f}")
|
||||
print()
|
||||
print("distance/velocity fraction at time fractions")
|
||||
for t in (0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0):
|
||||
d, v = fling_sample(t)
|
||||
print(f" t={t:<5} distance={d:.6f} velocity={v:.6f}")
|
||||
print()
|
||||
# 2.55 is Iris's Pixel 9 Pro XL (docs/bench/iris-phone-v2-2026-09-06.md);
|
||||
# 2.75 is this checkout's emulator.
|
||||
for density in (2.55, 2.75):
|
||||
# 15250 is `transcript-fixture/touch/flick-120hz.touch`'s own
|
||||
# release velocity (velocity_reference.py), so `phone_screen.rs`
|
||||
# can bound the fling it produces from *here* rather than from the
|
||||
# `FlingCalculator` under test (docs/REVIEW-2026-09-07.md's T1).
|
||||
for velocity in (5000.0, 11064.0, 15250.0):
|
||||
dur = fling_duration_s(velocity, density)
|
||||
print(
|
||||
f"density={density} v={velocity}: "
|
||||
f"distance={fling_distance(velocity, density):.3f}px "
|
||||
f"duration={dur:.4f}s"
|
||||
)
|
||||
# Deliberately not round fractions. The velocity coefficient is
|
||||
# piecewise *constant* across each of the 100 samples, so it
|
||||
# steps at t = k/100 and a test asserting on 0.75 is asserting
|
||||
# on which side of a discontinuity the last float landed --
|
||||
# which is genuinely different between Python and Rust and says
|
||||
# nothing about the curve.
|
||||
for frac in (0.125, 0.335, 0.505, 0.755):
|
||||
t = frac * dur
|
||||
print(
|
||||
f" t={frac:>4} of duration ({t:.4f}s): "
|
||||
f"pos={position_at(velocity, density, t):.3f}px "
|
||||
f"vel={velocity_at(velocity, density, t):.3f}px/s"
|
||||
)
|
||||
Reference in new issue
Block a user