HyperbasisHyperbasisDocs
Home

Troubleshooting

Common issues and solutions when working with Hyperbasis.

Relocalization Issues

Anchors Don't Reappear After Restart

Symptoms: You saved anchors, but when you relaunch the app, nothing shows up in the AR view.

Possible causes:

  1. World map wasn't saved properly

    • Check that tracking state was .normal before saving
    • Ensure arSession.currentWorldMap succeeded
  2. Relocalization hasn't completed

    • AR needs time to recognize the space
    • Move the device around to help it match features
  3. Environment changed significantly

    • Furniture moved, lighting changed drastically
    • ARKit can't match the stored features

Solutions:

// 1. Verify world map was saved
let spaces = try await storage.loadAllSpaces()
print("Saved spaces: \(spaces.count)")

// 2. Check relocalization status
func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
    switch camera.trackingState {
    case .normal:
        print("Relocalized! Now load anchors")
        Task { await loadAnchors() }
    case .limited(let reason):
        print("Limited tracking: \(reason)")
    case .notAvailable:
        print("Tracking not available")
    }
}

// 3. Help user understand what's happening
if camera.trackingState == .limited(.relocalizing) {
    showMessage("Move your phone around to help recognize the space")
}

Anchors Appear in Wrong Positions

Symptoms: Anchors load but they're offset from where they were placed.

Possible causes:

  1. Partial relocalization - ARKit matched some features but not perfectly
  2. World map captured too early - Not enough features were scanned
  3. Different starting position - User started AR session in a different spot

Solutions:

// Wait for stable tracking before loading anchors
func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
    guard camera.trackingState == .normal else { return }

    // Add small delay to ensure stable relocalization
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        Task { await self.loadAnchors() }
    }
}

Prevention:

  • Save world map only when tracking is .normal
  • Encourage users to scan more of the environment
  • Save world map after placing several anchors (more features)

"Relocalization Failed" or Never Completes

Symptoms: AR session stays in .limited(.relocalizing) state indefinitely.

Possible causes:

  1. Environment changed too much - Room rearranged, different time of day
  2. Wrong room - User is in a different physical space
  3. Corrupted world map data

Solutions:

// Implement a timeout and fallback
class RelocalizationManager {
    private var relocalizationTimer: Timer?

    func startRelocalization(with worldMap: ARWorldMap) {
        let config = ARWorldTrackingConfiguration()
        config.initialWorldMap = worldMap
        arSession.run(config, options: [.resetTracking])

        // Timeout after 30 seconds
        relocalizationTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: false) { [weak self] _ in
            self?.handleRelocalizationTimeout()
        }
    }

    func handleRelocalizationTimeout() {
        // Offer to start fresh
        showAlert(
            title: "Can't Recognize Space",
            message: "The room may have changed. Start fresh?",
            actions: [
                ("Try Again", { self.retryRelocalization() }),
                ("Start New Space", { self.createNewSpace() })
            ]
        )
    }
}

Storage Issues

"File Not Found" Errors

Symptoms: loadSpace(id:) or loadAnchor(id:) returns nil or throws an error.

Possible causes:

  1. ID mismatch - Using wrong UUID
  2. Storage cleared - clearLocalStorage() was called
  3. App reinstalled - Local storage is deleted with the app

Solutions:

// Always handle the nil case
if let space = try await storage.loadSpace(id: savedId) {
    // Use space
} else {
    // Space not found - create new one
    print("Space \(savedId) not found, creating new space")
}

// Check what's actually in storage
let allSpaces = try await storage.loadAllSpaces()
print("Available spaces: \(allSpaces.map { $0.id })")

Storage Size Growing Too Large

Symptoms: App using significant disk space over time.

Solutions:

// Check storage size
let size = try storage.localStorageSize()
print("Storage: \(ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: .file))")

// Purge old deleted anchors
let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: Date())!
try await storage.purgeDeletedAnchors(before: thirtyDaysAgo)

// Delete unused spaces
for space in try await storage.loadAllSpaces() {
    let anchors = try await storage.loadAnchors(spaceId: space.id)
    if anchors.isEmpty && space.updatedAt < thirtyDaysAgo {
        try await storage.deleteSpace(id: space.id)
    }
}

Cloud Sync Issues

Sync Fails Silently

Symptoms: Data isn't appearing on other devices, no errors thrown.

Possible causes:

  1. Cloud not configured - Using .localOnly backend
  2. Network issues - Device is offline
  3. Supabase credentials wrong - Check URL and anon key

Solutions:

// Check if cloud is enabled
print("Cloud enabled: \(storage.isCloudEnabled)")
print("Pending operations: \(storage.pendingOperationCount)")

// Force a sync and handle errors explicitly
do {
    try await storage.sync()
    print("Sync completed")
} catch HBStorageError.cloudNotConfigured {
    print("Cloud not configured - using local only")
} catch HBStorageError.networkError(let underlying) {
    print("Network error: \(underlying)")
} catch {
    print("Sync failed: \(error)")
}

Pending Operations Stuck

Symptoms: pendingOperationCount keeps growing, sync not completing.

Solutions:

// Check pending count
if storage.pendingOperationCount > 10 {
    print("Warning: \(storage.pendingOperationCount) operations pending")

    // Try syncing when network is available
    let monitor = NWPathMonitor()
    monitor.pathUpdateHandler = { path in
        if path.status == .satisfied {
            Task { try? await storage.sync() }
        }
    }
    monitor.start(queue: .global())
}

Performance Issues

Slow World Map Loading

Symptoms: arWorldMap() takes several seconds to return.

Cause: Deserialization of large world maps is CPU-intensive.

Solutions:

// Load on background thread, apply on main
Task.detached(priority: .userInitiated) {
    let worldMap = try space.arWorldMap()

    await MainActor.run {
        let config = ARWorldTrackingConfiguration()
        config.initialWorldMap = worldMap
        arSession.run(config)
    }
}

// Show loading indicator
isLoading = true
defer { isLoading = false }

Memory Warnings When Saving

Symptoms: App receives memory warnings during storage.save(space).

Cause: Large world maps (30-50MB) require significant memory during serialization.

Solutions:

// Check world map size before saving
let sizeInMB = Double(space.worldMapSize) / 1_000_000
if sizeInMB > 40 {
    print("Warning: Large world map (\(space.worldMapSizeFormatted))")
}

// Use compression (enabled by default)
let config = HBStorageConfig(compression: .balanced)  // ~40% size reduction
let storage = HBStorage(config: config)

Common Error Messages

ErrorMeaningSolution
HBSpaceError.serializationFailedARWorldMap couldn't be archivedEnsure ARWorldMap is valid, try recapturing
HBSpaceError.deserializationFailedStored data is corruptedDelete the space and recapture
HBAnchorError.invalidTransformTransform array isn't 16 floatsCheck transform creation code
HBStorageError.notFoundRequested item doesn't existVerify ID, check if storage was cleared
HBStorageError.cloudNotConfiguredTrying to sync without cloud setupConfigure Supabase backend or use local only

Debug Utilities

Inspect Local Storage

// Print all stored data
func debugStorage() async throws {
    let spaces = try await storage.loadAllSpaces()
    print("=== Spaces (\(spaces.count)) ===")
    for space in spaces {
        print("  \(space.id): \(space.name ?? "unnamed") (\(space.worldMapSizeFormatted))")

        let anchors = try await storage.loadAnchors(spaceId: space.id, includeDeleted: true)
        print("    Anchors: \(anchors.filter { !$0.isDeleted }.count) active, \(anchors.filter { $0.isDeleted }.count) deleted")
    }

    print("\nTotal size: \(ByteCountFormatter.string(fromByteCount: Int64(try storage.localStorageSize()), countStyle: .file))")
}

Reset Everything

// Nuclear option - clear all data and start fresh
func resetAll() throws {
    try storage.clearLocalStorage()
    print("All local data cleared")
}
Warning

clearLocalStorage() permanently deletes all spaces and anchors. This cannot be undone.

Next Steps