The phone ran hot, and the app got killed
In ICIDO's architecture notes, the section describing how navigation overlays are drawn carries a heading with a parenthesis in it: performance rules, "learned the hard way — the app ran hot and got killed".
Every rule under that heading was written after something failed on a real device. Read together they are a fair description of what it costs to draw anything on top of a live camera.
Navigation is not a quiet screen. The camera feed is running, the LiDAR depth map is being sampled so overlays can hide behind real furniture, ARKit is recovering the device's position against a saved map, and on top of all of that the app draws a lattice on the floor, wireframe boxes around furniture, a route ribbon, waypoint pins and a corner mini-map. Sensing and drawing share one thermal budget.
One publish, not five
The first rule is about how often the app says anything to the screen at all.
Rather than five separately published arrays updating at 60 Hz, every projected overlay goes out as a single frame at roughly 30 Hz — the note calls it "one coalesced publish". The device pose is throttled to the same 30 Hz, and to 15 Hz while scanning.
Two things are being limited there, not one: how fast updates go out, and how many separate updates there are. The rule names both.
Soft edges, without a blur
The overlay uses no blur filters. The reason is stated plainly: a full-screen canvas blur "re-rasterizes huge offscreen buffers every frame" on top of the AR camera.
So the soft, glowing look in navigation is not a blur. It is layered strokes at decreasing opacity, faking the falloff a filter would have produced. The visual style was not chosen and then implemented — it was implemented inside a rule that already existed.
Fewer points to project
Everything drawn on the camera has to be projected from world coordinates into screen coordinates, per point, per frame. The cheapest way to make that faster is to have fewer points.
The floor's boundary outline is simplified with Douglas–Peucker at about 6 cm tolerance, and loops smaller than 0.35 m² are dropped entirely, so per-frame projection handles "tens of points, not thousands". The tolerance is not cosmetic: without it, the staircase edges left by a grid never merge into straight runs.
Distance does the rest of the culling. Furniture boxes further than about 7 m from the user are dropped each frame. The floor lattice draws within 4.5 m and fades with distance. The glowing dots at the lattice intersections only appear within about 3.2 m.
Merging is bounded too. When fragmented clusters are being unioned into furniture boxes, fragments under 5 cm are discarded and the candidate list is capped at 300 before merging starts, with each pass folding fragments into the accumulated boxes rather than restarting — which is what keeps a large scan from turning the merge into a cubic problem.
The scan screen had the same problem, differently
While scanning, the dominant cost is somewhere else: the map canvas paths redraw on the main thread at the pose rate, and their size is what makes that expensive.
Three limits follow. The live scan preview uses a coarse 24 cm grid where saved maps use 12 cm. The pose is throttled to 10 Hz rather than 30. Preview rebuilds run about every 3 seconds. The note records what happened without them: "a fine-grained preview grid froze the scan UI on device".
The preview also avoids re-reading work it has already done. Mesh extraction is cached per anchor, and only anchors that changed since the last pass are read back out of ARKit's buffers, so the cost tracks what moved rather than how big the room is.
A related decision points the same way. A "detailed" 8 cm grid option existed briefly and produced messy, fragmented maps — the small-island cleanup threshold is counted in cells, so at 8 cm it cleaned less than half the physical area it was meant to, and the number of filled rectangles doubled. Saved maps are fixed at 12 cm.
Moving the heavy work off the thread that draws
Opening a place unarchives a multi-megabyte world map, and that unarchive happens off the main thread; building the map geometry from the mesh is off-main as well, with the screen staying in its loading state until both are ready. On save, the archiving and serialization run off the ARKit delegate thread.
One smaller fix in the same family is a good illustration of how specific this gets. The flowing chevrons on a route advance with a phase derived from the clock. That phase is computed as a Double and only then narrowed to Float, because a Float holding seconds since the epoch now has a resolution of roughly 64 seconds — enough to stop the animation dead.
Why any of this is a product question
Removing live new-object detection let navigation drop scene reconstruction entirely, which the notes identify as the session's single biggest battery and thermal cost. Scene depth stayed, because occlusion needs it.
That trade is the whole argument in miniature. A phone that gets hot enough to be killed by the system is not a rendering defect to be filed and scheduled. It is the app failing at the exact moment someone is standing in an unfamiliar building holding it up, which is the only moment it exists for.
ICIDO is in TestFlight and coming to the App Store. Scanning requires the LiDAR sensor — iPhone 12 Pro and later Pro models — and scanned maps, tagged spots and settings are stored locally on the iPhone.