← All posts

Product update4 min read

When places sit side by side instead of stacked

A garage, a parking lot and a house are peers, not floors. How ICIDO groups them as one site by reusing machinery it already had.

The assumption inside a building

ICIDO can group scanned places, and until a change earlier this month there was exactly one way to do it: a building, which is a named, ordered list of floors, where each floor holds one or more places and each place keeps its own map and its own coordinate frame. A building is grouping plus switching, never a merged model, because a session relocalizes against exactly one map at a time.

The ordering in that list is not decoration. It is what the floor-change prompts are built on. A barometer watches relative altitude, and a sustained change of at least 2 m from the current floor's baseline means you changed floors. Barometric altitude is used rather than vision because it survives an elevator, where the cabin is featureless, ARKit loses tracking, and you do not rise relative to the car. That banner re-runs the place probe rather than guessing, since an elevator ride is an unknown number of floors — but the no-permission fallback, vertical drift on the stairs, does use the order directly and offers the floor at the neighbouring index.

A garage, a parking lot and a house break the assumption. They are peers at one site. Two of them are outdoors. Nothing is above or below anything else, so every affordance derived from the order is not merely unhelpful — it is wrong. The commit that added properties states it in one line: a building assumes its members are stacked, and that ordering is load-bearing (5f65fa8).

The shape was already shipped

The obvious move is a new container — a Property type sitting beside Building with its own membership list. That was rejected. It would have created a second membership authority and needed edits to every mutation path in the store that tracks which group a place belongs to.

What shipped is smaller. Building gained a kind, building or site, and a site is a building with exactly one floor holding every member. That shape is the point, because "several peer places under one label, each its own map, walk between them and the app follows you" was already built. It is what a multi-place floor does. A floor has always been able to hold more than one place: Floor 1 = Parking + Pool.

So the lateral behaviour comes free. The function that answers "which other places share this floor" returns a property's other members with zero code change (5f65fa8). Walking out of one member past the edge of its map hands off to the neighbour by the mechanism that already handles two places on one floor of a building: the bounding box of the mapped floor is tracked, and a pose sitting more than 1.5 m beyond it for more than three seconds triggers a probe of just the siblings, switching without a confirm tap when one of them relocalizes. That lateral switch is marked experimental in Settings, and is on by default.

The prompts that do not apply are switched off rather than left to misfire. The barometer and the floor-change prompts are now gated on whether the group is vertically ordered, so a property never claims you changed floors and never starts the altimeter. The comment left in the viewer gives the second reason: running it would cost battery to raise prompts that can never be right. The adjacency arithmetic is structurally dead in a one-floor group anyway — there is no index either side of the only index there is.

The line that could have deleted every group

One detail in this change was more dangerous than the feature. The new kind field is Optional, and the commit marks it as something that must stay that way.

Building has no hand-written decoder, and Swift's synthesized Codable ignores property defaults. Writing the field as a non-optional with a default therefore throws keyNotFound against every buildings.json written before the field existed. The loader swallows that error and leaves the list of buildings empty, and the next save overwrites the file. Unlike the place index, which is salvaged record by record and moved aside as a dated damaged copy before any later write can clobber it, the buildings file has no salvage path and no damaged-file backup. The result would have been the silent, unrecoverable loss of every group a tester had made.

It was checked by running the decode rather than by reasoning about it: a defaulted non-optional throws, an Optional decodes as nil, and the encoder omits nil, so a record written now is byte-identical to one written before the field existed. A missing value means building, which is what every older record is.

The test plan that went out with the build puts that first. The step marked most important is not creating a property — it is opening the app with buildings you already had and confirming every one of them is still there, with the right floors.

Where this is

None of this changes what leaves the device, which is nothing. ICIDO has no accounts, no sign-in, no analytics and no network connections. Scans, tagged spots, groups and settings stay on the iPhone.

The work described here is in TestFlight builds. ICIDO is coming to the App Store, and scanning requires the LiDAR sensor — iPhone 12 Pro and later Pro models.

Related reading