# Asset cache (asset.cache) ## What it is for Keeps the game’s files on the player’s device so a second visit downloads nothing that did not change. In a page it works down a chain of stores the game allows; entries are keyed by path and SHA-256, so a new version of a file is another key and the old one is removed. It is one of three kits that work together: see the [asset loading guide](guide:asset-preload) for the loading plan and the passports. In a page the stores are tried in this order: IndexedDB; else Cache Storage with a service worker scoped to the game, which serves the kept files to every request of the game; else the browser HTTP cache, driven by the headers of your servers; else nothing (each file is still downloaded once by `asset.preload`). Outside a page, the native player reads its files from disk and the kit keeps its decisions on an in-memory store. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "asset.cache" } gripforge_gamekit_configure { project_id: "gkp_…", id: "asset.cache", config: { "quotaMB": 256, "eviction": "lru" } } ``` ## First example A game refuses to keep an intro video (a hook document) and a mod sets `minBytes` to 3 and another refuses music. Writing four files keeps only `assets/hero.glb`: the checksum is compared without regard to case, so reading `ABC2` finds it. ```json { "id": "intro_fresh", "hook": "asset.cache.store", "match": { "path": "assets/intro.webm" }, "cancel": true, "reason": "fresh at every visit" } ``` ## Settings and defaults | Setting | Default | Meaning | |---|---|---| | `enabled` | `true` | Off: nothing is kept; every visit downloads the files (once each) | | `quotaMB` | 512 | Megabytes the store may hold; past it the entries read longest ago leave first (0 keeps nothing) | | `version` | 1 | Generation of the store: changing it empties the store at the next visit | | `store` | `"auto"` | `auto` walks the chain of fallbacks in a page, memory elsewhere; a store name (`indexeddb`, `cachestorage`, `http`, `memory`) forces it; `off` keeps nothing | | `fallbacks` | `["indexeddb","cachestorage","http"]` | The stores a page may use, in order; none left means no cache (each file still downloaded once) | | `eviction` | `"lru"` | Which entries leave at the quota: `lru` (read longest ago), `fifo` (written first), `none` (a full store keeps nothing more) | | `serviceWorker` | `true` | A service worker scoped to the game folder serves the kept files to every request of the game, by checksum only | | `minBytes` | 0 | Files smaller than this many bytes are not kept | A game or a mod may change `enabled`, `quotaMB`, `fallbacks`, `eviction` and `minBytes`; `version`, `store` and `serviceWorker` belong to how the game is delivered. ## Services - `stats()`: the mode in use (`indexeddb`, `cachestorage`, `http`, `memory`, `off`), `entries`, `bytes`, `quota`, `hits`, `misses`, `writes`, `evicted`, and the `reason` and `tried` stores when one was refused. - `entries()`: the kept files as `{ key, path, sha, bytes, used }`. - `write(path, sha, bytes)`: keeps a file (an empty checksum or non-byte data is refused; the hook below may refuse it too); answers whether it was kept. - `read(path, sha)`: the bytes of a kept file, or null when absent or when the checksum differs (a read counts as a use for the eviction order). - `invalidate(path)` removes every entry of a path; `clear()` empties the store. Variables: `asset.cache.mode` (the store in use), `asset.cache.bytes` and `asset.cache.entries`, for a debug panel or a loading screen. ## Events | Name | Detail | |---|---| | `asset.cache.opened` | The cache is open: payload `{ mode, tried, quota, eviction }`, the store in use and the ones refused before it. | | `asset.cache.hit` | A file was read from the store, so nothing was downloaded; payload `{ path, sha, bytes, mode }`. | | `asset.cache.miss` | A file is not in the store or cannot be read, so it is downloaded; payload `{ path, sha, reason, mode }` with `reason` one of `absent`, `damaged`, `no_checksum`, `http`, `off`, `refused`. | | `asset.cache.stored` | A downloaded and checked file was kept; payload `{ path, sha, bytes, mode }`. | | `asset.cache.evicted` | Entries left to make room; payload `{ keys, bytes, reason }` (reason `quota`). | | `asset.cache.invalidated` | The entries of a file were removed, because a new checksum replaced them or `invalidate(path)` was called; payload `{ path, keys }`. | | `asset.cache.unavailable` | The store cannot be used; payload `{ reason, error }` with `reason` `refused` (private browsing or blocked storage), `quota` (the browser’s quota is reached) or `disabled` (the settings). The game goes on without a cache. | ## Hooks and modding A game or a mod taps a hook with a document of its `hooks` list, without touching the kit: - `asset.cache.store`: before a downloaded, checked file is kept; payload `{ path, sha, bytes }`; cancel to refuse it (a file wanted fresh at every visit, a file too large for this game). - `asset.cache.evict`: before an entry leaves to make room, in the order of the eviction policy; payload `{ key, path, bytes, used, policy }`; cancel to keep it, and the next entry is asked (a file the game keeps whatever happens). ## When it does not do what you expect - Nothing is ever a hit: the checksum is missing (`no_checksum`), the store is `off` or `disabled`, or the browser blocks storage (`unavailable` with `refused`, as in private browsing); check `stats()`. - Everything is downloaded again after a release: the `version` setting changed, which empties the store at the next visit. ## Limits - It stores files by checksum: a file without a SHA-256 is never kept. - The native player keeps nothing on disk through this kit; it only applies the same decisions (keys, quota, order of eviction, the store hook) on a store in memory. ## Versions 1.0.0 (2026-10-11): first release. Requires `game.engine`; used by `asset.preload`.