Skip to content

Fix Input System dependency so the SDK compiles out of the box on Unity 6+#1996

Open
brnkhy wants to merge 54 commits into
mainfrom
fix/input-system-package-dep
Open

Fix Input System dependency so the SDK compiles out of the box on Unity 6+#1996
brnkhy wants to merge 54 commits into
mainfrom
fix/input-system-package-dep

Conversation

@brnkhy

@brnkhy brnkhy commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary:
On newer Unity projects (Unity 6 and up), this package failed to compile because of how we handled the optional Input System package. This PR makes the dependency explicit and reliable, so the SDK now builds correctly regardless of which input setup a project uses.

What changed:

  • com.unity.inputsystem is now a proper package dependency, so Unity always installs it alongside the SDK.
  • Input handling now follows the project's own Player Settings (Active Input Handling) instead of guessing based on whether a package happens to be installed.
  • Updated docs/README to match the new behavior.

Impact on existing projects:
No action needed. Projects using the old (legacy) input setup will keep working exactly as before — this only fixes projects that were previously broken by the compile error.

Bumped to v3.1.1 with changelog notes for anyone upgrading.

@flasher297

brnkhy and others added 30 commits February 25, 2026 15:53
fix an issue where tilejsonresponse didn't work with medium level code stripping setting
* fix file cache path format

fix image file names to use underscore to fix bugs and issues in small x/y coordinates (in example; 223 to 22_3)
add runtime cache manager behaviour to be able to change database name (prefix) to start/test with a clean cache

* fix a bug in base module editor assembly sttings

remove unnecessary if check in runtime cache behaviour
* add runtime image style change support

add support to change style in runtime
add public method to change imagery tilesetid
add short doc and snippet about how to use it

* remove unused methods

fix empty method to yield break instead of returning null

* merge similar fetch methods in image source

fix typo in documentation
change inactiveCapacity of memory cache to readonly

* fix reload tiles method to handle delay execution
* custom image api module and terrain fixes

add LoadTileData to Terrain module
add mapbox map extensions class with TryGetElevation, TryGetMapTile methods
fix map visualizer to check inside composite modules when searching
fix a bug where terrain module target tile id method wasn't clamped
add custom image api module
change unity map service to have public getters for data fetcher and cache manager

* fix tile parent loops in mapbox extensions

fix namespaces for custom api module

* cleanup

remove commented out code
fix tooltip
add nullchecks
* add clear caches method to map service and map script

add clear caches method to map service and map script

# Conflicts:
#	Runtime/Mapbox/BaseModule/Map/MapboxMapExtensions.cs
#	Runtime/Mapbox/BaseModule/Map/MapboxMapExtensions.cs.meta

* fix world map demo scene for terrain changes

fix the world map demo scene settings to work better with latest terrain module changes

* add directions api tests generated by claude

* add null check and fix an iteration bug

* fix a typo in map unity service
* add clear caches method to map service and map script

add clear caches method to map service and map script

# Conflicts:
#	Editor/MapboxConfigurationWindow.cs
#	Runtime/Mapbox/BaseModule/Map/MapboxMapExtensions.cs

* fix a typo in map unity service

* fix mapmatching, add docs for apis

fix mapmatching api
add documents and examples for mapmatching, directions and geocoding apis

* mapmatching enum fix
add mapbox common location module support
handle device permissions better
change map initialization process for better startup procedure
* fix tests
* simplify mapbox config load procedure
* fix token config in tests
add mapbox components system, an alternative to vector module for higher performance mesh generation
commit 67433d3b235df142868031413098eb76cc0c6938
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Tue Apr 7 21:32:23 2026 +0300

    add new input system support

commit 9e8cb96f0aa43dc1f94e5ad84512807e3c478f13
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Tue Apr 7 20:42:27 2026 +0300

    camera improvements

    add touch support to camera
    refine camera scripts for extensibility

commit 13408dede59d9cb220e0b0f526882f6cafeeeb07
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Tue Apr 7 16:57:30 2026 +0300

    add touch controls for pan and zoom

commit c4a5df9503844fefe7496aab0105bad1699743ac
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Tue Apr 7 13:39:30 2026 +0300

    cleanup moving and slippy camera scripts
commit 76af9ca5c34aa82af7a515651583dbcbc216f914
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Mon Apr 6 11:58:58 2026 +0300

    improve unity tile provider script and mapinfo

    add terrain information to mapInfo
    fix tile visibility issues while improving quadtree system
    add min/max map heights to mapInfor terrain section

commit c52f8069f5d663c6ca863f666c1cf7ea65767241
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Mar 13 23:05:12 2026 +0300

    fix the unity tile provider quad tree
commit 51deba6
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Wed May 6 17:43:47 2026 +0300

    fix shader normal calculation

    change normal calculation step from 1 to 2 for smoother shading

commit 7fc6a81
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Mon May 4 22:12:21 2026 +0300

    fix assembly defs and add burst

commit 68fcced
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 20:07:47 2026 +0300

    fix custom terrain layer module to use its settings

commit 483e8a8
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 19:19:27 2026 +0300

    Squashed commit of the following:

    commit bf509a1358578bbf064a85ee308f566c4a51c792
    Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
    Date:   Thu Apr 23 15:14:22 2026 +0300

        custom terrain improvements

        fix a bug where non-invert Y didn't work
        add mapbox service checkbox for token addition

commit 2e7bd3e
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 16:52:05 2026 +0300

    burst-compile terrain-rgb decode and collider mesh build

    add com.unity.burst 1.8.12 as an SDK dependency
    move SyncExtractElevationArray / AsyncExtractElevationArray decode into [BurstCompile] IJobs run via .Run() on the main thread
    accumulate min/max elevation inline in the decode job to skip the C# second pass
    move ElevatedTerrainStrategy's collider vertex fill into a Burst IJob with persistent NativeArray buffers
    cache the elevation NativeArray mirror per data tile so the 16 render tiles sharing it pay the copy only once
    generate collider triangles once per sampleCount change (they don't depend on elevation) instead of per tile build
    upload collider geometry via Mesh.SetVertices(NativeArray) + Mesh.SetIndices(NativeArray) for zero-copy
    dispose the persistent NativeArrays in the strategy's OnDestroy cascade

commit 1e1ab7e
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 14:59:09 2026 +0300

    bilinear elevation sampling to fix cpu terrain stepping

    QueryHeightData, TerrainData.ReadElevation, and the inlined collider build loop all did nearest-neighbor via (int)xx truncation. When the mesh vertex grid is denser than the data texture's sub-region (routine: a render tile's 129-vertex mesh samples a ~64x64 sub-region of the parent data tile), multiple verts land in the same texel and produce visible stepping. Converted all three sites to 4-tap bilinear.

commit 45e0ed5
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 12:31:35 2026 +0300

    terrain perf pass: shared mesh, MPB, pooled elevation

    share a single flat Mesh across render tiles in shader mode (one Mesh per pool instead of N)
    use MeshRenderer.localBounds instead of MeshFilter.mesh.bounds to stop Unity's silent Mesh cloning
    switch per-tile Material instancing to sharedMaterial + MaterialPropertyBlock so the SRP Batcher can merge tile draws
    pool TerrainData.ElevationValues float[] across cache evictions via new ElevationArrayPool
    skip collider rebuild when (data, tileId) is unchanged across temp->final transitions
    replace Linq in GetDataId(IEnumerable) with a pooled HashSet
    lift width/scale-offset math out of the collider sampling inner loop
    add TerrainStrategy.OnDestroy cascaded from TerrainLayerModule for shared-mesh disposal

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

commit 595bca3
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 10:16:57 2026 +0300

    fix the issue with cpu terrain normals

commit 6dc0bf9
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 00:47:44 2026 +0300

    add async terrain collider baking

commit a14660c
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 00:31:52 2026 +0300

    change fallback max elevation from setting to static

commit 88d389d
Author: Baran Kahyaoglu <baran.kahyaoglu@mapbox.com>
Date:   Fri Apr 24 00:25:53 2026 +0300

    terrain improvements

    add per-tile MeshCollider generation with render-matched grid, plus optional child GameObject for a dedicated collider layer
    add ExtractCpuElevationData opt-out to skip terrain-RGB decode in shader-only apps
    add FallbackMaxElevationMeters so shader-displaced verts stay inside the cull volume
    add SimplificationFactor preset dropdown + live help box (old slider had many dead values)
    add Unity Layer dropdown for layerId via [GameObjectLayer]
    fix skirt outer offset overlapping neighbor tiles at coarse SimplificationFactor
    fix TerrainData.ElevationValuesUpdated silently wiping subscribers (convert to multicast event)
    fix DisableTerrain leaving stale TerrainData + subscription
    fix MeshCollider auto-assignment overwriting the render mesh
    fix Mesh leak by destroying render + collider Meshes in UnityMapTile.OnDestroy
    optimize terrain-RGB extraction: int loop vars, drop dead math, zero-copy NativeArray
    tune MeshCollider.cookingOptions and pool collider arrays for faster tile churn
- MapboxExamples.asmdef: add Unity.InputSystem reference and the
  com.unity.inputsystem versionDefine that defines MAPBOX_NEW_INPUT_SYSTEM
  (without this, MapInput's #if branches don't compile against the new
  input package). Also wires up Unity.Recorder for the benchmark pipeline.
- MapInput: normalize new-input scroll delta (scroll.y / 1200) so it
  matches the legacy Input.GetAxis("Mouse ScrollWheel") magnitude;
  zoom speed was ~10x off between input paths.
- MapInput: track TouchCountDecreasedThisFrame so cameras can reset
  _dragOrigin when a 2-finger pinch drops to 1 finger (the surviving
  finger may be at a different screen position than the lifted one,
  causing a single-frame pan jump).
- SlippyMapCamera, Moving3dCamera: consume TouchCountDecreasedThisFrame
  alongside GetPointerDown to re-seed drag origin.
- SlippyMapCamera: gate the CenterLatitudeLongitude update and clamp on
  _output.HasChanged, and use System.Math.Clamp on doubles instead of
  Mathf.Clamp on a float-cast Latitude (avoids precision drift at z18+
  and skips per-frame work when nothing moved).
- MapCameraBehaviour: unsubscribe MapBehaviour.Initialized in OnDestroy
  to avoid dead-behaviour references across scene reload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- UnityTileProvider.ShouldSplit: zero the vertical component of the
  per-corner offset instead of overriding it to +cameraHeight. The
  native SDK override is correct in its tile-coordinate system but
  flips the sign of the vertical dot-product contribution in Unity's
  world units, which triggers the projDist<=0 always-split early-exit
  for downward-pitched cameras. Using a ground-plane projection is
  geometrically consistent with the existing bottom-corner-only sampling.
- UnityTileProvider.FrustumBuffer: rewrite tooltip/docs so it's clear
  the value is in Unity world units that depend on mapInformation.Scale.
- MapboxMapVisualizer: defer child pooling in InternalUpdateCoroutine
  by one frame via a _pendingPool queue, so the parent has finished
  rendering before the children deactivate (closes the iOS flicker on
  zoom-out described in project_ios_tile_flicker).
- MapboxMapVisualizer: reuse unityMapTile.Children across pool/reuse
  cycles in Load() instead of allocating a new List per cache miss.
- MapboxMapVisualizer.PoolTile: recompute Terrain.Min/MaxElevation from
  the remaining ActiveTiles when the pooled tile was holding either
  boundary, so the bounds no longer monotonically inflate over a session.
- MapboxMapVisualizer.RemoveUnnecessaryTiles: add a comment explaining
  why Fillers are no longer exempt (intentional behavioral change in
  46664b8 — fixes orphaned filler z-fighting).
- TerrainInfo: trim unused IsEnabled and Exaggeration fields; drop the
  matching write in TerrainLayerModuleScript. MinElevation/MaxElevation
  remain (consumed by the tile provider bounds-height calculation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ElevatedTerrainStrategy.GetOrCreateCollider: hoist cookingOptions
  assignment out of the component-creation branch so every call refreshes
  it. If the collider's cookingOptions diverges from BakeColliderJob's,
  PhysX discards the async-cooked cache and re-cooks synchronously on
  sharedMesh assignment — silently defeating the async-bake path.
- ElevatedTerrainStrategy.CompleteBakeAndAssign: null-guard the new mesh
  after handle.Complete(). A parallel bake completion landing first can
  Destroy() this mesh as its "previous" before we get to assign it.
- ElevatedTerrainStrategy: change _elevationNativeMirror from float[] to
  TerrainData. ElevationArrayPool recycles float[] buffers, so the same
  reference can hold different data across dispose+re-rent — the prior
  ReferenceEquals(source, _elevationNativeMirror) check could return a
  false cache hit. TerrainData instances aren't recycled the same way.
- ElevatedTerrainStrategy.Initialize: destroy any pre-existing
  _sharedFlatMesh before allocating a new one. Without this, a second
  Initialize call orphans the previous mesh (re-init can happen without
  a preceding OnDestroy on settings change / editor reload).
- ElevationArrayPool: cap per-size stack at 32 entries. Past the cap,
  returns drop to GC. Bounds worst-case RAM after a CacheSize spike.
- TerrainSettingsInspectorHelper: cache the SceneHasFeaturesNeedingCpu-
  Elevation result, invalidate on EditorApplication.hierarchyChanged.
  Was running FindObjectsByType<MonoBehaviour> on every Inspector OnGUI.
- SimplificationFactorDrawer: cache preset factor + label arrays per
  (Min, Max, VertexBase). LINQ Where/Select/ToArray was allocating
  fresh arrays every repaint while the Inspector was open.
- TerrainLayerModule.GetDataId(IEnumerable): add <remarks> documenting
  that the returned enumerable is reusable scratch — callers must
  finish iterating before the next call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The inline notes on the sharedMaterial + MaterialPropertyBlock pattern in
UnityMapTile and TileCreator claimed it lets URP's SRP Batcher merge tile
draws. That is wrong on URP 2022.3: any Renderer.SetPropertyBlock call
disqualifies the renderer from the SRP Batcher. The pattern's real win is
avoiding a Material clone per renderer (reading Renderer.material clones,
sharedMaterial+MPB does not). It is also not GPU-instanced today — the
terrain materials have m_EnableInstancingVariants:0 and the shadergraphs
have no UNITY_INSTANCING_BUFFER block. Comments rewritten to describe what
the pattern actually delivers; see project_terrain_instancing memory for
what a real batching rework would require.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ElevatedTerrainStrategy.RegisterTile (CPU branch): never Clear() the
  shared flat mesh. When a runtime config change (sampleCount or
  _useTileSkirts toggle) makes RequiredVertexCount diverge from the
  shared mesh, a shader→CPU tile transition would otherwise wipe
  _sharedFlatMesh — taking out the geometry of every other shader-mode
  tile pointing at it. Allocate a fresh per-tile mesh in that case.
- ElevatedTerrainStrategy: opportunistic sweep of dead Unity Object
  keys in _lastColliderBuild (Dictionary<MeshCollider, ...>) on insert
  past a soft cap, plus an explicit Clear() in OnDestroy. Unity's
  overloaded == treats destroyed objects as null but Dictionary lookups
  use C# reference equality, so dead proxies would otherwise linger.
- MapboxMapVisualizer.CreateTile: replace the inline Min/MaxElevation
  accumulation with a RecomputeTerrainBounds() call. The prior path
  used `terrainData.MinElevation < terrain.MinElevation` against an
  initial 0, so for tiles entirely at or above sea level the comparison
  never fired and MinElevation stayed pinned at 0 forever.
- CHANGELOG: document the IMapInformation.Terrain breaking change for
  external implementers under v3.0.7 Breaking changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ElevatedTerrainStrategy / UnityMapTile: hoist mesh-name magic strings
  to public constants (TerrainColliderMeshName, SharedFlatMeshName) and
  reference them from both files. Eliminates the silent rename hazard
  in UnityMapTile.OnDestroy's leak cleanup.
- UnityTileTerrainContainer.SetTerrainData: drop the inconsistent
  `terrainData?.SetDisposeCallback(null)` and add an explicit early-
  return with Debug.LogError if a null TerrainData ever gets through.
  The rest of the method dereferences unconditionally; the prior `?.`
  was misleading.
- ElevationArrayPool: add a UNITY_ASSERTIONS-only double-return
  detector. O(N) scan on Return only in editor/dev; silently aliasing
  two callers to the same buffer is hard to debug after the fact.
- MapCameraBehaviour.Awake: when no MapBehaviourCore exists in the
  scene, log an explicit error and disable the component instead of
  NRE'ing on MapBehaviour.Initialized.
- UnityTileProvider: clamp _maxZoom to ≤30 internally. Guards against
  Inspector misconfig of MaximumZoomLevel that would overflow the
  sign bit in `1 << (_maxZoom - zoom)`.
- MapboxMapVisualizer.DelveInto: convert recursion to an explicit
  stack-frame iteration. Eliminates the new bool[4]/UnwrappedTileId[4]
  allocations per call (~85 per Load cache miss recursively before).
  Same semantics as the prior recursion — at each level missing
  quadrants recurse deeper, and any satisfied level fills its unfound
  siblings with temp filler tiles.
- Documentation~/CameraSystem.md: add an Input System Support section
  explaining the versionDefines-driven MAPBOX_NEW_INPUT_SYSTEM toggle.
  Input System is intentionally a soft dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- UnityMapTile: add Generation int. Bumped by PoolTile so deferred-pool
  consumers can detect tiles that were synchronously pool-recycled (and
  potentially re-issued via GetTile) between queue and flush.
- MapboxMapVisualizer: replace _pendingPool List<UnityMapTile> with
  List<PendingPoolEntry> storing (tile, generation-snapshot). QueuePool
  snapshots gen; FlushPendingPool skips entries whose tile.Generation
  moved. Closes a critical re-borrow race where Load() between coroutine
  ticks could synchronously pool a deferred tile and re-issue it via
  GetTile, then the next flush re-pooled the now-reassigned reference.
- MapboxMapVisualizer.Load: route _toRemove pools through QueuePool as
  well, so the same-frame parent-show + child-pool iOS-flicker fix
  applies to the zoom-out Load path too.
- ElevatedTerrainStrategy: track deferred collider-rebuild closures in
  _pendingRebuilds. OnDestroy unsubscribes them, preventing the (tile,
  data) self-reference cycle that previously held both indefinitely
  when TerrainData was disposed before ElevationValuesUpdated fired.
- ElevatedTerrainStrategy: fix CPU-elevation render-mesh leak. The
  CreateElevatedMesh path used tile.MeshFilter.mesh, which implicitly
  clones the assigned sharedMesh and orphans the original. Now uses
  .sharedMesh after RegisterTile pre-ensures a uniquely-owned per-tile
  mesh named "TerrainCpuMesh". The shader→CPU same-vertex-count
  transition is also handled.
- MapboxTileData: replace single-callback SetDisposeCallback with
  AddDisposeCallback / RemoveDisposeCallback (multicast). Terrain data
  is shared across up to 16 render tiles (data zoom = render zoom − 2);
  the prior assign-overwrite meant only the most-recent consumer got
  the dispose notification, leaving 15 silently rendering with disposed
  data. UnityTileTerrainContainer and UnityTileImageContainer migrated
  to the add/remove pattern.
- TerrainLayerModule.GetTileCoverCoroutines / LoadTiles: materialize
  GetDataId output into a fresh List before returning the lazy chain.
  The shared _dataIdScratch could be cleared by a subsequent GetDataId
  call before the lazy chain was consumed.
- CustomImagery backward-compat:
  * CustomSource.CreateTile: restore empty-UrlFormat fallback to plain
    RasterTile (was throwing string.Format(null,…) otherwise).
  * CustomTMSTile: add 4-arg constructor overload matching the pre-3.0.7
    signature (defaults: invertY=true, isMapboxService=false). External
    subclasses keep compiling.
- SlippyMapCamera.Initialize: null-guard the FindObjectOfType<MapBehaviourCore>()
  fallback. LogError + early return on null instead of NRE'ing on .transform.
- CHANGELOG v3.0.7 Breaking changes: document CustomTMSTile constructor
  overloads and the MapboxTileData callback API change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MapInput: pinch/tilt gesture decision now made once per frame in
  UpdateInputState (exposed via CurrentTwoFingerGesture enum +
  pinch/tilt delta fields). Closes the race where both detectors
  independently updated _previousPinchDistance and could fire same
  frame, contradicting CameraSystem.md.
- Moving3dCamera.Zoom: guard against div-by-zero. preDistance ≤ ε or
  camera→cursor distance ≤ ε (top-down ortho, first-frame, or curve
  evaluating to 0) previously propagated NaN into _targetPosition.
  Fall back to a straight target snap.
- UnityTileTerrainContainer.SetTerrainData: detach prior subscriptions
  unconditionally, then early-return on null. The previous order leaked
  the prior subscription when callers passed null to clear the slot.
- TerrainInfo: conservative defaults (MinElevation=0, MaxElevation=5000)
  in field initializers. RecomputeTerrainBounds also resets to these
  when ActiveTiles is empty so the first-frame AABB isn't flat and
  steep terrain doesn't get culled on initial paint.
- MapboxMapVisualizer: hoist hard-coded recursion-cap literal (22) to
  public const MaxMercatorZoom; DelveInto reads it.
- UnityTileProvider: warn at construction if MinimumZoomLevel >
  MaximumZoomLevel (was silently masked by the absolute zoom cap).
- Sync/AsyncExtractElevationArray.ExtractHeightData(Texture2D, Action<float[]>):
  allocate fresh float[] instead of renting from ElevationArrayPool.
  External callers had no return contract and would drain the pool.
- SimplificationFactorDrawer: wrap in BeginProperty/EndProperty (prefab-
  override + multi-edit safety) and remove the silent first-paint snap.
  Legacy non-preset values now display a warning HelpBox; the property
  only writes when the user picks a preset (deliberate, undoable).
- TerrainColliderOptionsDrawer.GetPropertyHeight: drop one extra
  line+spacing — the foldout body has 4 line-rows + 1 var-row.
- package.json: 3.0.7 → 3.1.0. Minor bump per semver: source-breaking
  changes (TerrainData.ElevationValuesUpdated event conversion,
  IMapInformation.Terrain addition, CustomTMSTile ctor extension)
  require ≥minor.
- CHANGELOG: rename section to v3.1.0; document Burst dep, event
  conversion, CustomTMSTile/CustomSource behavioral changes; expand
  Fixes list to reflect the v3.0.7→3.1.0 review-fix work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Blockers
- TerrainSource.ExtractElevationValues: switch to the TerrainData overload
  of ExtractHeightData so Min/MaxElevation are decoded inline. The previous
  Action<float[]> + 1-arg SetElevationValues path left Min=Max=0 on every
  tile loaded through LoadTileCoroutine — defeating the RecomputeTerrainBounds
  fix entirely. Wait for completion via a one-shot ElevationValuesUpdated
  subscription.
- MapboxMapVisualizer.CreateTile: shader-mode tiles complete on texture-ready
  before CPU decode arrives; subscribe a one-shot watcher on TerrainData so
  late-arriving Min/Max widen TerrainInfo when the async decode lands. Watches
  are de-duped per TerrainData (shared across 16 render tiles) and detached
  in OnDestroy.
- MapboxMapVisualizer.CreateTileInstant failure path: bump tile.Generation
  before PutTile. Mirrors PoolTile so any stale PendingPoolEntry from a
  previous queue is invalidated on the next flush — closes a re-borrow
  bypass that bypassed all the protection added elsewhere.
- UnityTileTerrainContainer.OnDestroy: also RemoveDisposeCallback, not just
  the ElevationValuesUpdated handler. Otherwise a later eviction of shared
  TerrainData fires the multicast into a destroyed tile.
- MapboxExamples.asmdef: drop hard refs to Unity.Recorder and Unity.Recorder.Editor.
  These were a hard build dep for any project that didn't have the Recorder
  package, contradicting CameraSystem.md's "optional package" framing. The
  benchmark's recorder-using code is removed alongside (see Changes below).

Visualizer / tile provider
- Deferred RecomputeTerrainBounds via _terrainBoundsDirty flag. PoolTile and
  CreateTile mark dirty; InternalUpdateCoroutine drains once per tick.
  Eliminates O(N²) when recursive PoolTile pools a subtree.
- Tightened the contributor check so flat tiles (Min==Max==0) don't trigger
  unnecessary recomputes.
- OnDestroy now flushes _pendingPool so deferred tiles get Recycle() at
  teardown (pooled elevation arrays return, shared meshes restore). Also
  detaches any pending async-decode watchers.
- Filler-protection pass also bumps each rescued child's Generation so any
  in-flight PendingPoolEntry doesn't silently re-pool it on the same frame.
- UnityTileProvider.boundsHeight now uses [MinElevation, MaxElevation] in
  world units. Sub-sea-level tiles (Death Valley, Dead Sea) used to fall
  outside the AABB and frustum-cull. TileNode.Set takes the bottom too.
- DistToSplitScale: floor `dz` at ε. dz=0 (camera exactly at ground) made
  r=d/0 → Inf/NaN and the tile pinned to its coarsest LOD forever.

Strategy / async
- AsyncExtractElevationArray: guard the GPU-readback callback against
  TerrainData.IsDisposed both before and after the decode. Without this,
  late callbacks assigned into evicted data and leaked the pool-rented
  buffer.
- TerrainData.IsDisposed flag (flipped by Dispose) for the above guard.
- RegisterCollider deferred path de-dups by (data, tile) against
  _pendingRebuilds. Temp→final transitions previously added two closures
  and ran two async bakes per tile.
- ColliderChildName renamed from "TerrainCollider" → "TerrainColliderChild"
  so it doesn't collide with TerrainColliderMeshName (same string used for
  the dedicated-layer GameObject name and the per-tile collider mesh name).
- BakeColliderJob: add a comment explaining it is intentionally not
  [BurstCompile] (single P/Invoke into PhysX).

Editor
- TerrainSettingsInspectorHelper: stop writing child.boolValue = true during
  OnGUI. Runtime already evaluates NeedsCpuElevation (an OR over the forcing
  flags) so the persisted value is irrelevant. Writing during paint silently
  dirtied every inspected asset and bypassed prefab overrides.
- SimplificationFactorDrawer: set EditorGUI.showMixedValue from
  property.hasMultipleDifferentValues for proper multi-object editing
  indicator. Remove the now-dead NearestPreset helper.

Cleanup
- TerrainData: cache sqrt(ElevationValues.Length) in _cachedWidth so
  QueryHeightData / ReadElevation don't recompute it per call.
- ElevationArrayPool: clear the static dict on play-mode SubsystemRegistration.
  In editor, static state otherwise survives play-mode stop, retaining last
  session's buffers across runs.
- CustomTerrainSource.CreateTile: mirror the empty-UrlFormat fallback that
  was added in CustomSource so an unset URL doesn't throw from string.Format.
- Moving3dCamera / SlippyMapCamera pan branches: gate HasChanged on
  non-zero movement. Stationary touches no longer trigger a redraw round-trip.
- CHANGELOG: note the CustomTerrainLayerModuleScript DataSettings behavior
  change and the TileProviderBenchmark Recorder-removal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical
- MapboxExamples.asmdef: drop the hard "Unity.InputSystem" reference.
  versionDefines set the define conditionally but the reference itself
  was unconditional, so projects without com.unity.inputsystem failed
  to compile the examples assembly. Unity.InputSystem's asmdef is
  autoReferenced so the using-directive inside #if MAPBOX_NEW_INPUT_SYSTEM
  resolves when the package is present and disappears cleanly when absent.
- TerrainSource.ExtractElevationValues: also subscribe to the dispose
  multicast. The async GPU readback no-ops when TerrainData.IsDisposed
  flips and never raises ElevationValuesUpdated; without the dispose
  hook the coroutine yield-forever-pinned the dead data. Triggers on
  rapid pan during initial LoadTileCoroutine when eviction beats readback.
- UnityTileImageContainer.OnDestroy: was empty. RemoveDisposeCallback
  on shutdown to mirror the terrain-container fix; otherwise a destroyed
  tile's closure stays attached to shared RasterData and a later eviction
  fires _onDispose into a dead tile → OnDataDisposed → OnTileBroken →
  PoolTile on already-pooled state.
- MapboxMapVisualizer._pendingElevationWatches: closures now self-detach
  on dispose too (AddDisposeCallback alongside ElevationValuesUpdated).
  TerrainData.Dispose() doesn't raise the values-updated event, so
  without this watch entries leaked until visualizer.OnDestroy.
- ElevatedTerrainStrategy._pendingRebuilds: same shape, same fix —
  subscribe to dispose so the closure detaches on eviction. List was
  growing O(failed-decodes) under bursty zoom.

Should-fix
- Moving3dCamera.Zoom: add finite-check on preDistance, CameraDistance,
  and the lerp output. mapInformation.Scale=0 early in init makes
  CalculateCameraDistance return Inf which passed the MinDistance check
  and produced Inf - Inf = NaN at the assignment. Falls back to a
  target snap if anything non-finite slips through.
- UnityTileProvider.DistToSplitScale: early return 1.0 when dz is at
  the floor (camera at ground). The prior dz=ε clamp made r huge,
  Pow saturated to Inf, scale returned ~0, distToSplit collapsed to 0,
  the tile pinned to MinimumZoomLevel. Street-level cameras hit this.
- MapboxMapVisualizer.RecomputeTerrainBounds empty-fallback: also
  trigger dirty when this is the last ActiveTiles entry, so all-flat
  sessions reset Min/Max to TerrainInfo defaults instead of leaving
  them at 0 forever (the contributor-only triggers required non-zero
  values, missing the all-zero case).
- UnityTileProvider.boundsBottom: drop the Mathf.Min(0f, …) clamp.
  For elevated-only terrain (Himalayan/Andean tiles with Min ≥ +500m)
  the clamp doubled the AABB volume by forcing the bottom to 0; using
  the observed min directly tightens culling and still handles
  sub-sea-level cases (Min is negative there, bottom goes negative).
- UnityTileProvider.ShouldSplit projDist<=0 branch: cap the always-split
  recursion at zoom < 18. FrustumBuffer expansion admits behind-camera
  tiles which would otherwise recurse to _maxZoom=22 — pathologically
  expensive on tilted views with many corner-behind-camera tiles.
- MapboxMapVisualizer filler-protection rescue: only bump Generation
  for children whose LoadingState is still Filler. The blanket bump
  would have invalidated legitimate pending-pool entries queued for
  other reasons.
- MapInput pinch/tilt tiebreaker: normalize both magnitudes to
  Screen.height (DPI-independent) and let pinch win ties (pinchFrac >=
  tiltFrac). The strict-> on both sides previously produced "None"
  frames when magnitudes registered exactly equal — common on near-pure
  vertical drags with two parallel fingers.
- MapInput pinch state across 2→3→2 finger transitions: track the
  active touch ID pair. EnhancedTouch.activeTouches reorders on add/
  remove, so without ID tracking the next 3→2 frame compared distance
  against a different physical pair → single-frame zoom spike.
- MapInput.Teardown virtual hook (+ MapCameraBehaviour.OnDestroy wiring,
  + Moving3dCamera and SlippyMapCamera overrides): unsubscribe
  IMapInformation events on teardown. MapInformation can outlive the
  camera (DontDestroyOnLoad / scene reload); without detach, the
  closures rooted the destroyed camera + its Camera Transform.
- TerrainData: QueryHeightData(Vector2) and (float,float) overloads
  now guard for null ElevationValues, mirroring the 3-arg overload.
  Shader-only mode (ExtractCpuElevationData=false) hit the NRE.
- TerrainData.Dispose idempotency: bail early on IsDisposed.
- SyncExtractElevationArray.ExtractHeightData(Texture2D, TerrainData):
  mirror the async dispose guard — even though sync, cache-race paths
  can call into a disposed instance. Skip + return the rent on dispose.
- SimplificationFactorDrawer.GetPropertyHeight: use the same
  legacy-vs-preset branch as OnGUI so the HelpBox height matches the
  rendered message (the longer legacy-value warning was clipping into
  the next inspector row).
- TileProviderBenchmark: gate Input.GetKeyDown via ENABLE_LEGACY_INPUT_MANAGER
  and add a parallel new-Input-System path. UnityEngine.Input.* throws
  under "Input System Package (New)"-only.
- ElevatedTerrainStrategy: refine the comment on collider-vs-render
  grid alignment to spell out the skirt-mode exception (collider
  intentionally omits the outside-tile skirt verts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MapInput.GetPinchZoomDelta / GetTwoFingerTiltDelta: use Mathf.Max(Screen.height, 1)
  to match the guard already present in UpdateInputState. Raw Screen.height can be 0
  (minimized window) which produced Inf — caught downstream by Moving3dCamera.Zoom's
  finite-check but the divisor pattern should be consistent across all three sites.
- SimplificationFactorDrawer.CalcHelpBoxHeight: reuse a static GUIContent and assign
  .text per call. GetPropertyHeight runs multiple times per repaint while the
  Inspector is open; the old `new GUIContent(message)` per call was steady GC churn
  even in idle Editor sessions.
- MapCameraBehaviour.OnMapInitialized: if a previous MapInformation was bound,
  call Core.Teardown on it before re-binding. MapBehaviour.Initialized firing
  twice (re-init, swapped map asset) previously leaked the prior subscriptions
  until OnDestroy. Defensive — not currently exercised by the example scenes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The wildcard `using Mapbox.BaseModule.Data.DataFetchers;` exposed
DataFetchers.TerrainData alongside UnityEngine.TerrainData, producing
CS0104 in the _pendingElevationWatches / WatchForAsyncElevationDecode
references. Switch to a type alias matching the pattern already used
in UnityTileTerrainContainer.cs and ElevatedTerrainStrategy.cs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The deferred-pool mechanism added in 272acdc was based on an
unverified iOS flicker hypothesis. Once the queue existed, a re-borrow
race surfaced (Load synchronously pools a queued tile + GetTile re-issues
it, then flush re-pools the now-reassigned reference). That was patched
with a Generation counter on UnityMapTile, snapshot/check on flush,
filler-rescue Generation bumps, and CreateTileInstant failure-path
bumps — ~150 lines of state-machine code in MapboxMapVisualizer
maintaining a queue/flush invariant that only existed to support a
behaviour we never reproduced.

The chain put visualizer state on the tile struct (Generation), grew
the central tile pipeline file, and was duplicating work that
synchronous PoolTile already did correctly. Reverting it. PoolTile is
synchronous again in both Load and InternalUpdateCoroutine.

Removed:
- UnityMapTile.Generation field
- PendingPoolEntry struct, _pendingPool list, QueuePool, FlushPendingPool
- Generation++ in PoolTile, CreateTileInstant failure path, filler rescue
- _pendingPool flush in OnDestroy

Kept (all independent of the iOS-flicker chain):
- RecomputeTerrainBounds (terrain bounds reset bug — real)
- _terrainBoundsDirty flag (batches CreateTile+PoolTile recomputes)
- WatchForAsyncElevationDecode (shader-mode async-decode plumbing)
- _pendingElevationWatches with dispose-multicast self-detach
- _pendingRebuilds in ElevatedTerrainStrategy (closure-leak fix)
- DelveInto iterative + Children list reuse
- All other unrelated review fixes (camera, tile provider, terrain
  strategy, terrain data lifecycle, custom imagery, editor drawers).

Memory updated: project_ios_tile_flicker.md now documents the
revert and lists the less-invasive fix paths (Phase A/B PoolTile split,
visualizer-side flag map, or upload-completion-aware deferral) to
consider if the flicker is ever actually reproduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The benchmark was an internal tool that never should have been in the
package. Untrack it from git, add to .gitignore so future `git add .`
doesn't re-include it, and keep the file local for ad-hoc use.

CHANGELOG line about the recorder removal in the benchmark is also
dropped — the whole tool is gone from the package now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
branches from MapInput

Twelve #if MAPBOX_NEW_INPUT_SYSTEM blocks in MapInput.cs collapsed
to zero. All input-backend specifics now live in a single new file
(PointerInput.cs) behind one #if/#else, with one IPointerInput
implementation compiled in based on whether com.unity.inputsystem
is installed.

PointerInput.cs:
- IPointerInput interface: TouchCount + per-slot Position/DeltaY/Id/
  Phase/PointerId, MousePosition + LMB/RMB pressed/held, MouseScrollY
  (already normalized to ~0.1/notch on both backends), EnableTouchSupport.
- Two sealed implementations: one for the new Input System (uses
  EnhancedTouch + Mouse.current), one for the legacy Input class.
- PointerTouchPhase enum decouples MapInput from each backend's
  TouchPhase type.

MapInput.cs:
- Drops the #if-gated using-block at the top.
- Drops the EnhancedTouchSupport.Enable() call in Initialize (now
  IPointerInput.EnableTouchSupport()).
- GetTouchCount, IsPointerOverUI, GetPointerPosition, GetPointerDown,
  GetPointerHeld, GetSecondaryDown, GetSecondaryHeld, GetPinchZoomDelta,
  GetZoomCenter, and the two-finger read block in UpdateInputState
  now all read through _input. No #if branches anywhere.

No behaviour change: each backend's PointerInput maps onto the same
primitives MapInput previously read directly via #if. MouseScrollY
folds the /1200f new-input normalization into the abstraction so the
caller no longer needs to know about it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
brnkhy and others added 24 commits May 13, 2026 18:24
Mouse-vs-touch input logic is now in two separate classes, selected at
compile time by build target. There's no runtime detection — each
platform gets a single-purpose input path.

InputHandlers.cs (new):
- IInputHandler interface covering the full input vocabulary (pointer
  down/held, secondary, pinch zoom, two-finger tilt, zoom center,
  is-over-UI, touch count, decreased-this-frame).
- MouseInputHandler: LMB / RMB / scroll wheel. Touch-only concepts
  (touch count, pinch, tilt, touch-count-decreased) are no-ops.
- TouchInputHandler: single-finger pan, two-finger pinch / tilt mutex,
  touch-pair ID tracking for 2→3→2 transitions. Mouse-only concepts
  (secondary button, scroll) are no-ops.

MapInput.cs:
- 357 → 157 lines. Drops the TwoFingerGesture enum, pinch/tilt state,
  touch-pair tracking, UpdateInputState body, and the per-helper
  implementations. All replaced by thin delegates to _handler.
- One #if selects the handler:
    (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR  → TouchInputHandler
    else                                            → MouseInputHandler
- No behaviour change for either platform: each handler implements the
  same helpers the cameras call, with the modes' respective logic.

Camera files untouched — same helper vocabulary as before.

PointerInput.cs (backend abstraction) survives unchanged and is shared
by both handlers.

Memory: project_camera_gesture_recognizer.md documents Option B
(recognizer layer) as deferred future work — to revisit when adding
game controller / VR input or when touch-rotate becomes a requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MapboxMapVisualizer was carrying ~100 lines of terrain-specific
bookkeeping (Min/MaxElevation contributor checks, async-decode
watchers, dirty flag, recompute walk). It made the visualizer
implicitly terrain-aware and bloated a file that should just be
orchestrating tile lifecycle.

Extracted into a TerrainBoundsTracker owned by TerrainLayerModule:
- Subscribes to the visualizer's existing TileLoaded / TileUnloading
  events (no new public surface needed there).
- Runs its own per-frame drain coroutine.
- Handles the async-decode one-shot watcher with the same
  dispose-multicast self-detach pattern.

Wired via a new opt-in marker interface ITileLifecycleListener in
BaseModule. MapboxMapVisualizer.Initialize iterates LayerModules
and calls AttachToVisualizer on each implementer before their own
Initialize runs. Modules that don't need the visualizer reference
simply don't implement the interface.

MapboxMapVisualizer changes:
- Drop `using TerrainData` alias (no longer referenced).
- Expose `MapInformation` as a public property (was protected field
  only; needed so listeners can read TerrainInfo).
- Drop _terrainBoundsDirty + drain in InternalUpdateCoroutine.
- Drop _pendingElevationWatches + WatchForAsyncElevationDecode +
  RecomputeTerrainBounds + their OnDestroy detachment.
- Drop the in-CreateTile / in-PoolTile contributor & async hooks.
- Net: ~100 lines lighter, fully terrain-agnostic except for the
  existing existsTerrainModule check that picks FlatTerrainStrategy
  fallback (orchestration, not bookkeeping).

TerrainLayerModule:
- Implements ITileLifecycleListener.
- AttachToVisualizer constructs the tracker.
- OnDestroy disposes it.

No behaviour change: same dirty-flag drain timing, same contributor
heuristics, same default-fallback when ActiveTiles is empty. Just
located where it belongs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Disambiguates from the layer-visualizer concept elsewhere in the
project. The interface only ever sees MapboxMapVisualizer, but the
shorter name was ambiguous.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cleanups to the tile-lifecycle observer plumbing:

1. ITileLifecycleListener → ITileLifecycleObserver. The pattern is
   classic Observer (Gang of Four) and the new name reads that way.
   File renamed via git mv so the .meta GUID is preserved (Unity
   references intact).

2. New ITileLifecycleSource interface. Carries IMapInformation, the
   TileLoaded/TileUnloading events, and ActiveTiles as
   IReadOnlyDictionary. MapboxMapVisualizer implements it (uses
   explicit interface impl for ActiveTiles so internal code keeps
   the mutable Dictionary it needs for Add/Remove).

   TerrainBoundsTracker now depends on this narrow interface instead
   of the concrete MapboxMapVisualizer. The tracker only ever needed
   tile events + map state, not orchestration internals — making
   that explicit in the type makes the dependency obvious and the
   tracker testable in isolation.

   ITileLifecycleObserver.AttachToMapVisualizer also took the
   interface as its parameter type. Typical caller is still
   MapboxMapVisualizer (which the method name reflects), but the
   contract accepts any source.

No behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Items from the review pass on the terrain layer.

TerrainBoundsTracker:
- Replace Mathf.Approximately with exact == for the contributor check.
  Values being compared were assigned directly from each other in the
  previous Recompute; Approximately's magnitude-scaled tolerance would
  also accept near-matches from sibling tiles, triggering redundant
  recomputes.
- Drop the `terrainData != null` requirement from `wasLastActive`.
  An all-flat session where every tile had no terrain data wouldn't
  trip the contributor checks, and the wasLastActive gate was hiding
  the empty-fallback when the last shader-only tile pooled.
- Warn loud if Runnable.Instance is null at construction. Should
  never happen in normal play, but the silent path leaves the drain
  coroutine unstarted and bounds stuck at defaults.

UnityTileTerrainContainer:
- Drop the "no texture?" Debug.Log — fires routinely on shader-mode
  startup before the texture arrives.
- Replace "TerrainData is null, missing a isRecycled check?" Debug.Log
  with a silent bail + comment. Multicast dispose callback can
  legitimately fire on a container whose data was already cleared.

ElevatedTerrainStrategy.CreateElevatedMesh:
- Use cached _sideVertexCount instead of Mathf.Sqrt(mesh.vertexCount).
  Same value, one fewer sqrt per CPU-elevated-tile build, and the
  source of truth is now consistent with how _sideVertexCount is used
  everywhere else in the file.

TerrainLayerModule.AttachToMapVisualizer:
- Idempotent: bail if a tracker is already running. A second call
  would orphan the first (coroutine + subscriptions still live).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rewrite CHANGELOG.md v3.1.0 section with detailed breaking-change,
  feature, performance, fix, and known-limitations breakdowns. Drops
  the reverted iOS-flicker / generation-counter lines and adds the
  IPointerInput / IInputHandler / ITileLifecycleObserver-Source /
  TerrainBoundsTracker / AddDisposeCallback / ElevationArrayPool /
  TerrainData.IsDisposed / TileProviderBenchmark-removal entries
  that were missing.

- MapInformation.Initialize now resets Terrain.MinElevation /
  MaxElevation to TerrainInfo defaults so a previous session's bounds
  don't bleed into a fresh map.

- MapInput.ClampZoom max hoisted from a hardcoded 22f to
  public const float MaxClampZoom so the cap is discoverable from the
  camera surface. Mirrors MapboxMapVisualizer.MaxMercatorZoom (still
  int 22) — kept as a const because C# default-parameter values must
  be compile-time constants of the exact parameter type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Editor folder under VectorModule/ComponentSystem/ had three editor
scripts (BuildingLayerVisualizerObjectEditor, ExtrusionOptionsDrawer,
MapboxComponentsModuleScriptEditor) but no asmdef. They were folded
into the runtime MapboxComponentSystem assembly, pulling UnityEditor
references into player builds and breaking compilation.

New MapboxComponentSystem.Editor.asmdef is Editor-only and references
the runtime asm + MapboxBaseModule (for Mapbox.BaseModule.Data.Interfaces).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ration

(1) MapboxMapBehaviour.CreateMapVisualizer silently produced a magenta
tile material in player builds when no TileCreatorBehaviour was
assigned. The fallback uses Shader.Find on the default terrain shader,
which doesn't resolve in builds unless the shader is referenced by a
shipped Material or listed in Always Included Shaders. Now throws a
clear InvalidOperationException pointing the user at either fix.

(2) UnityTileProviderSettings.SubdivisionBias deserialized as 0 in
scenes saved before the field existed, since Unity assigns default(T)
for missing fields and skips C# field initializers in that case. The
0 value collapses distToSplit and forces only the MinimumZoomLevel
tile to render. New C# default is 0.6f and ISerializationCallbackReceiver
upgrades legacy <=0 values on deserialize.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the Shader.Find runtime construction with a serialized
Material reference. The previous fallback in CreateMapVisualizer used
Shader.Find on the default terrain shader, which doesn't survive
build-time stripping unless the shader is referenced from a shipped
Material asset or listed in Always Included Shaders — so demos that
didn't wire a TileCreatorBehaviour shipped with magenta tiles.

New [SerializeField] protected Material _defaultTileMaterial field
holds the asset reference directly. Demo scenes (LocationExample,
ApiTest) ship with it wired to ElevatedTerrainMaterial.mat so the
shader survives stripping via the asset reference.

If both _tileCreatorBehaviour and _defaultTileMaterial are null,
throws InvalidOperationException with instructions for either fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The custom editor draws each field explicitly, so the new field added
in 428189f wasn't visible. Wired it up in OnEnable and rendered
right after Tile Creator Behaviour in the Override Modules section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every tile insert was failing on Android IL2CPP builds with Managed
Stripping Level set to Medium or High, producing a constant stream of
"Error inserting … (extended=ConstraintForeignKey)" log lines.

Root cause: sqlite-net writes row values back to model class properties
via PropertyInfo.SetValue (i.e., reflection). IL2CPP's Medium+
stripping removes auto-property accessor methods (set_id, set_name,
etc.) on the cache model classes because static analysis doesn't see
them used (sqlite-net's reflection isn't visible to the analyzer).
PropertyInfo.SetValue then becomes a silent no-op, every row maps to
id=0, and downstream `tiles` inserts fail FK on `tile_set`.

Fixes:

- Add [UnityEngine.Scripting.Preserve] to every persisted property on
  the four SQLite model types (tiles, tilesets, offlineMaps,
  tile2offline) — [Preserve] on the class alone doesn't preserve
  members in Unity's linker.

- Add the same types to Plugins/link.xml as a defensive secondary
  preservation path.

- Append SQLite's extended error code to the catch-site log so future
  cache-side failures (UNIQUE / FK / NotNull / etc.) are diagnosable
  from the first line.

Map rendering was unaffected (memory cache covered) but the SQLite
disk cache wasn't actually persisting on Android with non-Minimal
stripping. Verified working on Android with Medium stripping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Overview doc lives at Documentation~/Overview.md and is intended as
the landing page for the SDK: version, supported Unity editor
versions, supported build platforms (iOS and Android only — Standalone,
WebGL, console, VisionOS, etc. are explicitly out of scope), required
and optional dependencies, Android Managed Stripping notes, module
table, samples table, and an index of the other docs in
Documentation~/.

The v3.1.0 CHANGELOG had a "Known limitations" section listing items
that were actually unfinished work, not real shipping limitations
(touch-rotate, async PhysX bake validation, shader-graph visual
verification, GPU instancing). Removed it from CHANGELOG.md and the
cross-reference from Overview.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…E merge

Five small additions identified by the state-of-the-SDK review as
low-risk / high-leverage for v3.1.0 itself rather than deferring to
v3.2:

(1) MapRoot.transform fix. MapboxMapVisualizer was positioning tiles
via transform.position (world space), silently resetting any parent
transform on the MapRoot / BaseTileRoot hierarchy on every tile
cover update. Switched to localPosition. Behavior is identical on
every existing scene (MapRoot at world origin), but AR/MR setups
that anchor MapRoot under an ARAnchor or custom rig now compose
correctly under translation + rotation. Non-unit MapRoot.localScale
is still unsupported — guidance is to drive scale through
MapInformation.Scale.

(2) Migration guide. New Documentation~/Migration-3.0-to-3.1.md
walks through every source-level break, every behavioral change,
and a step-by-step verification checklist for upgrading from v3.0.

(3) "Place a GameObject at lat/lon" recipe added to
GettingStartedWithMapboxMapObject.md — the most common indie use
case, previously unaddressed in the docs.

(4) First test coverage for v3.1.0 surface. Editor-mode unit tests
under Tests/Editor/BaseModule/ for ElevationArrayPool (rent / return
/ pool cap / null safety), TerrainInfo (defaults), and
MapInformation (Initialize resets Terrain bounds, sets lat/lon,
idempotent). More coming in v3.2.

(5) README rewrite + Overview.md merge. README now serves as the
single landing page: quick-facts header, supported Unity / build
targets, dependencies, install, token, demo scenes, location
workflows, iOS / Android build sections (with the v3.1.0 stripping
notes), modules table, full docs index. Overview.md removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MapRoot localPosition fix in 844164a made terrain tiles compose
with any parent transformation, but vector content (buildings, areas,
roads) stayed in world space and broke the visual when the map was
rotated or anchored under an AR rig (see Image #2 in the discussion —
terrain rotates, buildings stay flat at world Y=0).

Three call sites in the vector module were writing world-space
positions on the layer-root GameObjects:

(1) VectorLayerVisualizer constructor: `transform.position += offset`
    applied the layer's settings offset in world space. Converted to
    `localPosition += offset` so the offset stays in MapRoot-local
    space.

(2) VectorLayerVisualizer.UpdateForView: read the layer root's
    world-space `transform.position.x/z` and subtracted from the
    map-local `position` to compute entity local position. Mixed
    coordinate spaces — wrong whenever the layer root wasn't at world
    origin. Now reads `localPosition.x/z` so both operands are in the
    same space.

(3) AreaComponentVisualizer + RoadComponentVisualizer constructors:
    wrote `transform.position = new Vector3(..., GameObjectOffset, ...)`
    to set the layer's y-offset. Converted to `localPosition`.

Behavior is identical when MapRoot is at world origin with identity
rotation/scale. Under AR/MR parent transforms, vector content now
follows the same rotation/translation as the tiles it overlays.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the camera drifts far enough from the map's current center, the
shifter updates MapInformation's lat/lon and compensates by shifting
RuntimeGenerationRoot the same amount, keeping previously-generated
vector content visually in place. The compensation was written as
`RuntimeGenerationRoot.position -= viewCenter` — a world-space delta.

With a rotated parent (MapRoot under an AR anchor or custom rig),
that world-space assignment forces Unity to back-compute a
localPosition that compensates for the parent's rotation. The
practical effect: RuntimeGenerationRoot's local space gets
"counter-rotated" relative to MapRoot so its world position lands at
the requested value — and all vector content under it stays pinned
to world coordinates instead of rotating with the rest of the map.

Reported symptom: terrain tiles rotate with MapRoot, buildings /
roads / areas don't.

Switched to `localPosition -=` so the shift stays in MapRoot-local
space. Behavior is identical when MapRoot is at identity (the only
case shipped scenes exercise). Under parent rotation, vector
content now follows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause: Transform.SetParent(parent) defaults to
worldPositionStays: true, which makes Unity preserve world transform
by writing inverse-parent-rotation into the child's localRotation.
For runtime-created roots (BaseTileRoot, RuntimeGenerationRoot,
per-layer _layerRootObject, pooled entity GameObjects), when their
new parent is a rotated MapRoot, Unity sets their localRotation to
inverse(MapRoot.rotation). Vector content under those roots then
visually counter-rotates against MapRoot — staying world-aligned
while terrain tiles rotate correctly.

The previous .position fixes (commits 686ad04 and dd68da6)
addressed translation but not this orientation issue.

All eight SetParent call sites now pass worldPositionStays: false:
- UnityContext: BaseTileRoot, RuntimeGenerationRoot
- VectorLayerVisualizer: _layerRootObject + entity GameObject
- MapboxComponentVisualizer.VectorEntityGenerator
- BuildingComponentVisualizer, AreaComponentVisualizer,
  RoadComponentVisualizer: entity SetParent calls

Plus explicit localRotation = Quaternion.identity on the auto-created
roots and visualizer layer roots, as defensive belt-and-suspenders in
case the scene-authored root has a non-default rotation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier claim that v3.1.0 "supports any Managed Stripping Level
including Medium and High" overstated what was actually tested. Only
Medium was briefly verified during the Android stripping fix. High
was never tested, and we shouldn't imply it's a supported target.

Updated CHANGELOG, README, and migration guide to a tiered statement:

- Low / Minimal / Disabled — tested target, recommended
- Medium — briefly tested, appears to work
- High — untested, likely works but no guarantees
ConversionTests: five new editor-mode unit tests covering pure math —
- WebMercator round-trip across 8 diverse coordinates including poles
  and antimeridian
- LatLng -> TileId round-trip across three cities and five zoom levels
- Tile01 center invariant
- TileSize halving across zoom levels
- TileSize scaling linearly with map scale

ElevationArrayPoolTests: Pool_CapsAtMaxPerSizeDepth was counting the
same buffer multiple times because the test rented and immediately
returned in a loop. Switched to draining the pool with a HashSet of
references to count unique retained buffers correctly.

DataCompressionTests: Assert.Less -> Assert.LessOrEqual on the
EditMode-in-iOS/Android branch. The pre-existing assertion assumed
the response arrives gzipped; in practice UnityWebRequest / the CDN
often returns it already decompressed, making the sizes equal. The
loosened assertion accepts both cases without losing the smoke check
that decompression doesn't break the data.
CameraSystem.md: new "AR / MR setups" section after "Writing a Custom
Camera". Covers what works (translate + rotate MapRoot, parent under
an ARAnchor, runtime created roots compose correctly), what doesn't
(non-unit MapRoot.localScale breaks quadtree LOD; use
MapInformation.Scale instead, with DynamicScalingMapInformation for
animated scale transitions), and the recommended setup (replace
built-in camera behaviours, write a thin MapCameraBehaviour<T> that
pushes AR camera pose into MapboxMap.ChangeView, point
UnityTileProviderBehaviour at the AR camera, animate Scale not
transform). Explicit "early access" framing — architecture supports
it but hasn't been validated against a full ARKit / ARCore sample.

README: dropped the misleading "3D landmark features" phrase from
the header description (landmarks isn't in v3.1.0 — it lives on a
separate branch). Fixed the "All other platforms" row that was
missing its Notes column. Regrouped the documentation index from a
flat 15-row table into six task-oriented sections: Upgrading,
Getting started, Map composition, Camera / input / AR / MR, Vector
content, APIs.
The override of OnMapInitialized in SlippyMapCameraBehaviour wasn't
running the IsInitialized / prior-Core teardown block that
MapCameraBehaviour.OnMapInitialized performs. If MapBehaviour.Initialized
fired twice (scene reload, swapped map asset), the prior SlippyMapCamera
core's IMapInformation.SetView subscription would leak until OnDestroy.

Can't simply call base.OnMapInitialized(map) — base invokes the 2-arg
Core.Initialize and we'd re-init with the 3-arg control-plane overload
right after, double-initializing the core. Inlined the defensive
teardown directly with a comment explaining why.

Moving3dCameraBehaviour doesn't override OnMapInitialized so it
inherits the defensive behavior naturally.
Declare com.unity.inputsystem (>= 1.7.0) in package.json and restore the
Unity.InputSystem GUID reference in MapboxExamples.asmdef. Together these
let the Examples assembly compile under every Active Input Handling
combination without scene edits or per-project setup.

PointerInput.cs now gates its #if on Unity's built-in ENABLE_INPUT_SYSTEM
(driven by Player → Active Input Handling), not MAPBOX_NEW_INPUT_SYSTEM
(driven by package presence). This is the critical lever for preserving
existing legacy-input projects: when those projects update the SDK, UPM
auto-installs the input system package but Unity does not change Player
Settings, so ENABLE_INPUT_SYSTEM stays unset and the legacy #else branch
keeps compiling. Runtime behavior is unchanged for those users until
they explicitly opt in to the new input system via the Active Input
Handling setting.

Drops the now-unused MAPBOX_NEW_INPUT_SYSTEM versionDefines block.
Add v3.1.1 changelog entry covering why the package dependency was
reclassified from soft to hard, what changed in PointerInput.cs's #if
gate, and how to recover if upgrading an existing project hits a snag
(stale Library cache, manifest pinning, "New only" UGUI exception,
manual physical removal). The 3.1.0 entry is left intact as the
historical record.
@brnkhy brnkhy self-assigned this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant