Most consumer software assumes a server in the middle: you sign in, telemetry goes up, machine learning runs in a data center, and state flows back through REST endpoints. For social feeds and collaborative documents that architecture is the right call. For parking dead reckoning, 48 kHz audio synthesis, and calibrated noise measurement it fits badly, and those are the three problems Wheria, Estua, and Sonarish work on every day. The phone in your pocket is not a thin client displaying someone else's computation; for our sensor apps, it is the computer.
On-device-first means the entire pipeline executes locally. Sensors get sampled at hardware rates, estimates fused in real time, the interface rendered, history persisted inside the app sandbox. Phyzix follows the same rule: lab sessions and CSV exports stay on the device until you explicitly share them. Stashio is the deliberate exception, with optional cloud sync for anyone who wants bookmarks on a second phone while capture, indexing, and search keep working in airplane mode. Nobody here treats the rule as ideology. Three things force it: the physics of latency, the economics of privacy for a 3-person studio, and the places our users actually stand.
The deadline arithmetic
Wheria redraws your walked path after every detected step. At a normal pace footfalls land 0.5–0.7 s apart, and beneath them the IMU streams accelerometer and gyroscope samples at 50–200 Hz. The fusion filter has to ingest each measurement, propagate uncertainty, and redraw the polyline within a few milliseconds. A round trip from Saigon to a Singapore data center costs about 80 ms on a good day; the physical distance is roughly 1,100 km, fiber covers that in about 11 ms both ways, and the remaining 70 ms is queueing and radio scheduling. Compass heading arrives at 50 Hz, which leaves 20 ms between updates. Wi-Fi round-trip time alone consumes that entire allowance before any server-side Kalman filter gets a turn.
Estua lives under a harder deadline. The render callback fires every 512 samples at 48 kHz, so each buffer must be filled within 512 / 48000 s ≈ 10.7 ms, every callback, for hours. Miss one and the output carries a discontinuity the ear reports as a click. At 2 AM, with a listener finally asleep, a single click undoes the whole product. No practical path exists that POSTs PCM to a remote server and receives synthesized noise back inside 10.7 ms, which is why the full synthesis graph runs inside the callback under the no-malloc, no-locks discipline we wrote up in life on the audio thread.
Sonarish carries the same constraint on the capture side. Its FFT windows span 2048–4096 samples at 48 kHz, which is 43–85 ms of sound per transform, and a real-time spectrogram needs that transform running on the same device that owns the microphone HAL. Pushing raw audio upstream would insert a network hop into every column of pixels. The A-weighting filter that turns raw spectra into dB(A) is a fixed cascade and runs locally too.
What a garage does to your radio
The latency argument still assumes a link exists. Underground it usually does not. In June we logged connectivity across 40 garage descents on 6 phones (3 iPhone, 3 Android), pinging a Singapore endpoint every 2 s. Median LTE round trip at street level was 42 ms. At B1 it stretched to 310 ms with 38% packet loss. At B2 no usable link existed on 31 of the 40 descents. B2 is also where the cars are.
An early Wheria prototype learned this the slow way. It streamed raw IMU frames over a WebSocket to a laptop so we could tune the step filter in Python, and at a desk the setup ran beautifully. One ramp below street level, the stream stalled every few seconds and the plotted path froze mid-turn. That afternoon the no-network rule went into the engineering notes.
The pattern repeats far from garages. Sonarish users run noise surveys inside shielded factory halls where the cellular radio gives up before the machines do. Estua has to keep synthesizing through a 13-hour flight in airplane mode. Treating connectivity as optional keeps the apps working in the exact places people open them: underground, inside metal structures, at 3 AM when the household Wi-Fi is off. ktuyen's release checklist therefore includes a full offline garage walk before every release; if indoor navigation required a network handshake, it would fail the moment you descend the ramp.
The sensors never notice any of this. Atmospheric pressure falls by about 12 Pa per metre of altitude near sea level, so one 3.2 m garage floor is worth roughly 38 Pa against a phone barometer noise floor of 0.3–1 Pa RMS, and the floor hint follows from Δh = ΔP/(ρg) with no server in the loop. The full derivation lives in barometer floor math.
The phone is fast enough
The classic argument for cloud offload was compute, and that argument quietly expired years ago. From our July bench notes: a 4096-point real FFT through Accelerate averages about 70 µs on an iPhone 12, and the same transform through KissFFT on a mid-range Snapdragon lands near 350 µs (each figure averaged over 10,000 transforms in release builds). Both sit 2–3 orders of magnitude below the 43–85 ms of audio the window represents. One Wheria EKF update over a handful of states is microseconds of matrix arithmetic. Steps arrive at walking cadence, under 3 Hz. Estua's per-sample work (a pink filter, a few biquads, some slow LFOs) occupies a low single-digit percentage of one core at 48 kHz.
Power is the honest cost, and it stays modest. With the screen at half brightness and the IMU at 100 Hz, a find-my-car walk costs about 1% of battery over 4 minutes on the same 6 bench phones. The radio runs the other direction: a phone hunting for signal in a basement draws more power than one that has stopped trying, so staying offline underground actually saves battery.
Privacy as a byproduct of the wiring
The privacy story is mostly an absence. We never built the server-side infrastructure that could violate it, so there is no policy to audit and nothing to police. Wheria keeps parking coordinates, walked-path polylines, floor hints, and optional photos entirely inside the app sandbox, with no account and no upload queue behind the UI. The record that says floor B2, pillar E9 exists in one database file on one phone. Sonarish stores noise sessions, machine baselines, and exported spectrogram images locally by default. Estua remembers exactly two things about you: a scene preference and a random seed. The audio stream itself is never recorded unless you export it yourself.
No third-party analytics SDKs ship in these applications, and crash reports flow through Apple's and Google's standard channels only. Model training on our servers does not happen, for the airtight reason that no servers collect accelerometer traces in the first place. Data that was never centralized cannot leak, and cannot be quietly repurposed when a company changes hands.
The economics of not running servers
There are three of us. A server-side sensor pipeline would mean an on-call rotation, database backups, API versioning, and a bill that arrives every month whether or not anyone parks a car. On-device compute is hardware the user already bought, so our marginal infrastructure cost per Wheria user rounds to zero. The Wheria server bill has never had an outage.
Longevity is the quieter half of the argument. A sensor app whose processing lives on a startup's backend tends to die with the startup. An app whose entire pipeline is local keeps working years after release, even through a quarter where we ship nothing. atuan saves his backend effort for the one product where it earns its keep.
The Stashio exception
Stashio breaks the rule on purpose. A save-for-later tool earns trust when a link captured on the phone shows up on the tablet, so an optional account exists and atuan runs a small sync service behind it. The local-first spine stays intact: capture, indexing, and search all work in airplane mode, and the search index is built on the device before anything syncs. My own backlog holds about 2,400 links collected over four years; after three months of daily use, roughly 94% of retrieval attempts succeed in under 10 s, offline included. When sync is on, data travels as encrypted blobs. The sensor apps and Stashio sit at opposite ends of one decision, which makes the Stashio post a useful mirror of this one.
What we give up, in writing
On-device-first has costs and we keep a written list of them. Cross-device sync for Wheria parking pins (carrying a garage walk from an iPhone to a Pixel without a manual export) remains unbuilt, because the encrypted sync layer has never reached the top of the queue. A community heatmap of parking availability would need everyone's positions on a server. Models that improve from pooled user data would need the pool. Competitors ship these features and they are genuinely useful; they also dismantle the latency and privacy properties that make a sensor app worth trusting.
The standing trade is slower feature velocity in exchange for millisecond feedback, offline reliability, and data that stays on the phone until you choose to export it. For a 4-minute walk back to your car, that trade is correct. To see what the local computation actually does underground, continue with indoor navigation when GPS dies; for the render-deadline discipline Estua shares with Sonarish, read life on the audio thread.
Phần lớn app consumer mặc định có server đứng giữa: đăng nhập, đẩy telemetry lên, chạy machine learning ở data center, đồng bộ trạng thái qua REST. Feed mạng xã hội và tài liệu cộng tác hợp với kiến trúc đó. Dead reckoning tìm xe, tổng hợp audio 48 kHz và đo ồn đã hiệu chuẩn thì không, mà đấy lại là ba bài toán Wheria, Estua và Sonarish xử lý mỗi ngày. Điện thoại trong túi bạn không phải thin client hiển thị kết quả tính ở đâu đó; với app cảm biến của studio, nó chính là cái máy tính.
On-device-first nghĩa là toàn bộ pipeline chạy tại chỗ. Lấy sample cảm biến ở tốc độ phần cứng, fusion ước lượng real-time, render giao diện, lưu lịch sử trong sandbox của app. Phyzix theo đúng luật này: session lab và file CSV export nằm yên trên máy đến khi bạn chủ động chia sẻ. Stashio là ngoại lệ có chủ đích, có sync cloud tùy chọn cho ai muốn bookmark hiện trên máy thứ hai, còn capture, index và tìm kiếm vẫn chạy ở chế độ máy bay. Không ai trong team coi luật này là lý tưởng để thờ. Ba thứ ép ra nó: vật lý của độ trễ, kinh tế privacy của một studio 3 người và những chỗ user thực sự đứng khi mở app.
Phép tính deadline
Wheria vẽ lại đường đi sau mỗi bước chân phát hiện được. Đi bộ bình thường, bước cách nhau 0,5–0,7 s. IMU bên dưới đẩy sample gia tốc và con quay ở 50–200 Hz. Bộ lọc fusion phải nuốt đo mới, lan truyền độ bất định rồi vẽ lại polyline trong vài millisecond. Round-trip từ Sài Gòn sang data center Singapore mất chừng 80 ms hôm đẹp trời; khoảng cách vật lý cỡ 1.100 km, ánh sáng trong sợi quang đi khứ hồi hết khoảng 11 ms, 70 ms còn lại là hàng đợi và lập lịch sóng. Heading la bàn về ở 50 Hz, tức chỉ có 20 ms giữa hai lần cập nhật. Riêng round-trip Wi-Fi đã ăn sạch định mức đó trước khi Kalman phía server kịp chạy.
Estua chịu deadline gắt hơn. Callback render nổ mỗi 512 sample ở 48 kHz, nên mỗi buffer phải xong trong 512 / 48000 s ≈ 10,7 ms, callback nào cũng vậy, kéo dài hàng giờ. Trễ một lần là output đứt quãng, tai nghe ra tiếng click. Hai giờ sáng, người nghe vừa thiếp đi, một tiếng click phá hỏng cả sản phẩm. Không có đường nào POST PCM lên server rồi nhận noise tổng hợp về kịp trong 10,7 ms. Vì thế cả đồ thị synthesis nằm trong callback, không malloc, không lock, đúng kỷ luật đã viết trong sống trên audio thread.
Sonarish bị ràng phía thu. Cửa sổ FFT dài 2048–4096 sample ở 48 kHz, tức 43–85 ms âm thanh mỗi lần transform. Spectrogram real-time cần transform chạy ngay trên thiết bị giữ HAL của micro. Đẩy audio thô lên mạng là chèn thêm một hop vào từng cột pixel. Bộ lọc A-weighting đổi phổ thô sang dB(A) cũng chỉ là chuỗi filter cố định, chạy local luôn.
Hầm xe làm gì với sóng điện thoại
Lý lẽ độ trễ còn giả định là có mạng. Dưới hầm thường không có. Tháng 6 team log kết nối trong 40 lượt xuống hầm trên 6 máy (3 iPhone, 3 Android), ping một endpoint Singapore mỗi 2 s. Round-trip LTE trung vị ở mặt đường: 42 ms. Xuống B1 giãn thành 310 ms, mất gói 38%. Xuống B2 thì 31 trên 40 lượt không còn link dùng được. Mà B2 mới là chỗ để xe.
Prototype Wheria đời đầu học bài này theo cách chậm. Nó stream frame IMU thô qua WebSocket sang laptop để mình tune bộ lọc bước bằng Python. Ngồi bàn làm việc thì chạy đẹp. Xuống dưới mặt đường một con ramp là stream nghẽn vài giây một lần, đường đi trên màn hình đứng hình giữa khúc quẹo. Chiều hôm đó luật không-mạng được ghi vào sổ kỹ thuật.
Chuyện lặp lại ở nơi khác. User Sonarish khảo sát ồn trong xưởng che chắn kín, radio di động bỏ cuộc trước cả máy móc. Estua phải synthesize liên tục suốt chuyến bay 13 tiếng ở chế độ máy bay. Coi mạng là thứ tùy chọn thì app chạy được đúng những chỗ người ta cần: dưới hầm, trong khối kim loại, lúc 3 giờ sáng khi Wi-Fi nhà đã tắt. Checklist release của ktuyen vì vậy có màn đi hầm hoàn toàn offline trước mỗi bản phát hành; indoor navigation mà cần bắt tay server thì fail ngay lúc xe vừa đổ dốc.
Cảm biến thì chẳng quan tâm mấy chuyện này. Áp suất khí quyển giảm chừng 12 Pa mỗi mét độ cao gần mực nước biển, nên một tầng hầm 3,2 m tương đương khoảng 38 Pa, trong khi nhiễu áp kế điện thoại chỉ 0,3–1 Pa RMS. Gợi ý tầng đi ra từ Δh = ΔP/(ρg), không dính server. Phần chứng minh đầy đủ nằm trong toán tầng áp kế.
Điện thoại đủ nhanh từ lâu
Lý do kinh điển để đẩy việc lên cloud là thiếu sức tính. Lý do đó hết hạn từ lâu mà ít ai chịu cập nhật. Sổ bench tháng 7 của team: FFT thực 4096 điểm qua Accelerate trung bình cỡ 70 µs trên iPhone 12; cùng transform đó qua KissFFT trên Snapdragon tầm trung rơi quanh 350 µs (đều lấy trung bình 10.000 lần, build release). Cả hai con số thấp hơn 43–85 ms âm thanh của cửa sổ tới 2–3 bậc độ lớn. Một lần cập nhật EKF của Wheria với vài state chỉ tốn vài microsecond nhân ma trận. Bước chân về theo cadence đi bộ, dưới 3 Hz. Phần synthesis mỗi sample của Estua (filter hồng, vài biquad, mấy LFO chậm) chiếm vài phần trăm một core ở 48 kHz.
Chi phí thật là pin. Vẫn ở mức dễ chịu. Màn hình nửa sáng, IMU ở 100 Hz, một lượt tìm xe tốn cỡ 1% pin trong 4 phút trên đúng 6 máy bench đó. Radio thì ngược chiều: điện thoại dò sóng trong tầng hầm ngốn điện hơn điện thoại đã thôi dò, nên offline dưới hầm còn tiết kiệm pin hơn.
Privacy là hệ quả của cách đấu dây
Chuyện privacy chủ yếu là một khoảng trống. Team chưa từng dựng hạ tầng server có khả năng vi phạm nó, nên không có chính sách nào phải canh. Wheria giữ tọa độ đỗ, polyline đường đi, gợi ý tầng và ảnh tùy chọn trọn trong sandbox của app, không tài khoản, không hàng đợi upload sau giao diện. Bản ghi "tầng B2, cột E9" nằm trong một file database trên một cái điện thoại. Sonarish lưu session ồn, baseline máy móc và ảnh spectrogram export tại máy theo mặc định. Estua nhớ đúng hai thứ về bạn: scene ưa thích và một seed ngẫu nhiên. Luồng audio không bao giờ được ghi lại trừ khi bạn tự export.
Ba app này không nhúng SDK analytics bên thứ ba. Crash report đi qua kênh chuẩn của Apple và Google, hết. Không có chuyện train model trên server của studio, lý do kín kẽ nhất: chẳng có server nào thu trace gia tốc để mà train. Dữ liệu chưa từng gom về một chỗ thì không thể rò rỉ, cũng không thể bị đem dùng vào việc khác khi công ty đổi chủ.
Kinh tế của việc không nuôi server
Studio có ba người. Pipeline cảm biến phía server đồng nghĩa trực on-call, backup database, quản lý version API và một hóa đơn đến đều mỗi tháng, có ai đỗ xe hay không cũng đến. Tính trên máy user là dùng phần cứng người ta đã mua sẵn, nên chi phí hạ tầng biên cho mỗi user Wheria làm tròn về 0. Hóa đơn server của Wheria chưa bao giờ sập.
Nửa còn lại của lập luận là tuổi thọ. App cảm biến mà phần xử lý sống trên backend của một startup thường chết cùng startup. App có pipeline nằm trọn trên máy thì vẫn chạy nhiều năm sau khi phát hành, kể cả khi team nghỉ một quý không ship gì. atuan để dành sức backend cho đúng sản phẩm đáng bỏ sức.
Ngoại lệ tên Stashio
Stashio cố tình phá luật. Công cụ save-for-later chỉ đáng tin khi link lưu trên điện thoại hiện được trên tablet, nên có tài khoản tùy chọn và atuan vận hành một dịch vụ sync nhỏ phía sau. Phần xương local-first vẫn nguyên: capture, index và tìm kiếm đều chạy ở chế độ máy bay, index tìm kiếm build ngay trên máy trước khi sync bất cứ thứ gì. Kho của mình chứa khoảng 2.400 link gom trong bốn năm; sau ba tháng dùng hằng ngày, chừng 94% lần tìm ra kết quả dưới 10 s, tính cả lúc offline. Bật sync thì dữ liệu đi dưới dạng blob mã hóa. App cảm biến và Stashio đứng hai đầu của cùng một quyết định, nên bài về Stashio là tấm gương soi ngược của bài này.
Những thứ đánh đổi, ghi rõ ra giấy
On-device-first có giá. Team giữ hẳn một danh sách. Sync pin đỗ Wheria giữa các máy (mang một lượt đi hầm từ iPhone sang Pixel mà không phải export tay) vẫn chưa xây, vì lớp sync mã hóa chưa bao giờ leo lên đầu hàng đợi. Heatmap cộng đồng về chỗ trống cần vị trí của mọi người nằm trên server. Model học từ dữ liệu gộp cần chính cái dữ liệu gộp đó. Đây là những tính năng hữu ích thật và đối thủ có ship; đồng thời chúng tháo rời đúng những tính chất độ trễ và privacy khiến app cảm biến đáng tin.
Phép đổi cố định là: ship tính năng chậm hơn, đổi lấy phản hồi millisecond, chạy ổn khi offline và dữ liệu nằm yên trên máy đến khi bạn tự export. Với quãng đi bộ 4 phút về chỗ xe, phép đổi đó đúng. Muốn xem phần tính toán local làm gì dưới hầm, đọc tiếp dẫn đường trong nhà khi GPS chết; về kỷ luật deadline render mà Estua chia sẻ với Sonarish, đọc sống trên audio thread.