# ADR-0003: ScriptableObject Asset Loading Strategy

**Status:** Accepted  
**Date:** 2026-06-23  
**Deciders:** Mahmoud Aglan  
**Engine:** Unity 6  

---

## Context

IStalk uses ScriptableObjects as the data backbone for all game entities: loot items (`ItemData_SO`), shrine items (`ShrineItemData_SO`), traps (`TrapData_SO`), and room definitions (`RoomNode_SO`). The `ItemDatabase` singleton must discover and register all SOs at runtime to provide `GetLootItem(id)`, `GetShrineItem(id)`, etc.

Two standard approaches exist in Unity:
1. **`Resources.LoadAll<T>(path)`** — loads all assets of type T from a `Resources/` folder at runtime.
2. **Addressables** — Unity's asset management system with async loading, memory management, remote hosting, and per-bundle granularity.

---

## Decision

**Use `Resources.LoadAll<T>()` for MVP. Revisit Addressables when procedural map generation (SYS-10, Alpha tier) requires dynamic level streaming.**

Folder structure:
```
Assets/Resources/
    Data/Loot/          ← ItemData_SO assets
    Data/Shrine/        ← ShrineItemData_SO assets
    Data/Traps/         ← TrapData_SO assets
    Data/Rooms/         ← RoomNode_SO assets
```

Loading pattern:
```csharp
public class ItemDatabase : MonoBehaviour
{
    public static ItemDatabase Instance { get; private set; }

    private Dictionary<string, ItemData_SO> _lootItems;
    private Dictionary<string, ShrineItemData_SO> _shrineItems;
    private Dictionary<string, TrapData_SO> _traps;
    private Dictionary<string, RoomNode_SO> _rooms;

    private void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);

        _lootItems = Resources.LoadAll<ItemData_SO>("Data/Loot")
            .ToDictionary(so => so.ItemId);
        _shrineItems = Resources.LoadAll<ShrineItemData_SO>("Data/Shrine")
            .ToDictionary(so => so.ItemId);
        _traps = Resources.LoadAll<TrapData_SO>("Data/Traps")
            .ToDictionary(so => so.TrapId);
        _rooms = Resources.LoadAll<RoomNode_SO>("Data/Rooms")
            .ToDictionary(so => so.RoomId);
    }
}
```

---

## Rationale

| Criterion | Resources.LoadAll (chosen) | Addressables |
|-----------|---------------------------|--------------|
| Setup complexity | Zero — just put files in a folder | Requires Addressables package, group config, build pipeline changes |
| Loading speed (MVP scale: ~50-100 SOs total) | Instant (< 5ms synchronous) | Async overhead unnecessary at this scale |
| Hot-reload in Editor | Automatic — change SO, enter Play Mode, done | Requires address rebuild or Play Mode script |
| Memory control | All SOs loaded permanently — fine for ~100 small data objects | Per-bundle load/unload — overkill for pure data SOs |
| Procedural map streaming | Not suitable (loads everything upfront) | Designed for this |
| Remote asset hosting | Not possible | Supported |

**Why not Addressables now:**
- MVP uses procgen maps assembled from a pool of room prefabs — all prefabs are known at compile time and loaded upfront.
- Total SO count is < 100. Memory footprint is negligible (each SO is ~200 bytes of data).
- Addressables adds build pipeline complexity that slows iteration during prototyping.
- The `ItemDatabase` API is identical regardless of backing store — migration to Addressables later requires changing only the `Awake()` loading code, not any consumer.

**When to migrate:**
- If room prefab count exceeds ~50 and loading them all at startup causes noticeable delay.
- If the game ships with 500+ items, Addressables per-category bundles would reduce startup time.
- If remote asset delivery is needed (DLC, live content updates).

---

## Consequences

**Positive:**
- Zero setup cost — start creating SOs immediately.
- Synchronous loading means `ItemDatabase` is guaranteed ready before any `NetworkBehaviour.OnStartServer()` accesses it (no race conditions).
- Editor workflow is frictionless — create SO, put in folder, it appears in-game.

**Negative:**
- `Resources/` folder contents are always included in the build, even unused SOs. Acceptable at MVP scale.
- No memory granularity — all SOs loaded on app start. Acceptable for pure data objects.
- Must refactor loading code (not consumers) when Addressables is adopted.

---

## GDD Requirements Addressed

- TR-DAT-003: `Resources.LoadAll<T>()` for SO discovery at runtime

---

## References

- `design/gdd/sys-01-item-database.md` — ItemDatabase singleton specification
- `design/gdd/sys-33-fishnet-replication.md` — String ID networking contract (consumers of ItemDatabase)
