import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Predicates

The three predicate methods on `OctetSdk.loc`. All three are `async` (Swift) / `suspend` (Kotlin) and **do not throw**. Runtime problems surface as `INDETERMINATE` verdicts, not exceptions.

## `isWithin`

The primary predicate. Reads as: *"my currently provable location is inside `region`."*

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func isWithin(
    region: OctetRegion,
    atTime: Date = Date()
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun isWithin(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
): OctetVerdict
```

  </TabItem>
</Tabs>

### Example

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
let v = await sdk.loc.isWithin(
    region: .country(isoCode: "US"),
    atTime: Date()
)
if v.result == .yes { ship(v.proof!) }
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
val v = sdk.loc.isWithin(
    region = OctetRegion.country("US"),
    atTime = Instant.now()
)
if (v.result == OctetVerdict.Result.YES) ship(v.proof!!)
```

  </TabItem>
</Tabs>

---

## `isOutside`

Reads as: *"my currently provable location is outside `region`."*

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func isOutside(
    region: OctetRegion,
    atTime: Date = Date()
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun isOutside(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
): OctetVerdict
```

  </TabItem>
</Tabs>

:::caution

**`isOutside` is not `!isWithin`.** An `INDETERMINATE` is not `outside`. The separate call lets the caller assert the negative claim and get a *proof of the negative*. A verifier can check that proof just as easily as a `YES`. Negating an `INDETERMINATE` would erase the difference between "I'm not inside" and "I can't tell".

:::

---

## `contains`

Reads from the device's perspective: *"my location contains this point within `tol` meters."*

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func contains(
    center: LatLon,
    tol: Meters,
    atTime: Date = Date()
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun contains(
    center: LatLon,
    tol: Meters,
    atTime: Instant = Instant.now(),
): OctetVerdict
```

  </TabItem>
</Tabs>

Mathematically equivalent to `isWithin(region: .disc(center: center, radiusMeters: tol), atTime:)`. Kept as a distinct verb because the device-centric reading is the natural one for proximity use cases ("am I near the delivery dropoff", "did I arrive at the geofence").

### Rolling your own

`isWithin` is the load-bearing primitive. `contains` is a thin one-liner over it. The same trick rolls custom predicates:

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
extension OctetLoc {
    func isNear(_ center: LatLon, _ tol: Meters) async -> OctetVerdict {
        await isWithin(region: .disc(center: center, radiusMeters: tol))
    }
    func isInsideAny(_ regions: [OctetRegion]) async -> OctetVerdict? {
        for r in regions {
            let v = await isWithin(region: r)
            if v.result == .yes { return v }
        }
        return nil
    }
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun OctetLoc.isNear(center: LatLon, tol: Meters): OctetVerdict =
    isWithin(OctetRegion.disc(center, tol))

suspend fun OctetLoc.isInsideAny(regions: List<OctetRegion>): OctetVerdict? {
    for (r in regions) {
        val v = isWithin(r)
        if (v.result == OctetVerdict.Result.YES) return v
    }
    return null
}
```

  </TabItem>
</Tabs>

## Time semantics

All three predicates take an optional `atTime`. See [Time Semantics](/docs/concepts/time-semantics/) for the live / historical / future regimes and the per-resolution validity windows. Default is "now". Future times beyond ±2 s come back `INDETERMINATE / FUTURE_TIME`.

## See also

- [`OctetRegion`](/docs/api-reference/octet-region/). The shapes you can pass.
- [`OctetVerdict`](/docs/api-reference/octet-verdict/). What you get back.
- [Verdicts](/docs/concepts/verdicts/). The trichotomy and the `ReasonCode` taxonomy.
