# Shader authoring

**Akari** (`src/akari/`) owns the shader product path end-to-end: shader package sources
under `shaders/akari/packages/*.akari`, shared modules under
`shaders/akari/modules/*.akari`, transpile → MSL/HLSL (bindings resolved at emit),
platform compile (`xcrun metal` / DXC / DXC `-spirv`). Metal metalc uses **`-std=metal4.0`** and
**`-mmacosx-version-min=26.0`**. Specs: `build.akari.metal.json` /
`build.akari.d3d12.json` / `build.akari.vulkan.json` (type `KhronosVulkan`; same
package list as the other two). Vulkan compiles through the vendored DXC in
SPIR-V mode, so it cooks on any host — no Vulkan SDK.

**Language reference (all syntax and features):** [Akari language](/docs/reference/akari-language.md). Grammar: [`src/akari/syntax.ebnf`](https://github.com/Flying-Rat/hikari/blob/develop/src/akari/syntax.ebnf).  
Editor highlighting: [`tools/vscode-extensions/akari`](https://github.com/Flying-Rat/hikari/blob/develop/tools/vscode-extensions/akari) (VS Code / Cursor); [`tools/nvim-plugins/akari`](https://github.com/Flying-Rat/hikari/blob/develop/tools/nvim-plugins/akari) (Neovim / Vim).  
Product binary: flat `bin/akari/akari` (not platform-arch). Shinra invokes via `--akari-sc`. ABI packing: [`docs/reference/akari-abi.md`](/docs/reference/akari-abi.md).

There is no dual-native `metal/` / `d3d12/` / `*_includes/` authoring tree.
Ray tracing uses first-class intrinsics (`rt_visibility`, `rt_trace`, bindless loads).

> [!NOTE]
> Vulkan shader cooking is available through Akari and DXC even though the Linux/Vulkan runtime is not yet a product peer. Compiling SPIR-V does not imply full runtime support.

One authored package feeds each target's compiler:

```mermaid
flowchart TD
    source["Akari packages + shared modules"] --> akari["Akari — validate and emit resolved bindings"]
    spec["Target build specification"] --> akari
    akari --> msl["MSL"]
    akari --> hlsl["HLSL"]
    msl --> metal["Metal compiler — Metal 4 / macOS 26"]
    hlsl --> dxc["DXC — D3D12 target"]
    hlsl --> spirv["DXC -spirv — Vulkan target"]
    metal --> metalout["Metal shader artifacts"]
    dxc --> dxout["D3D12 shader artifacts"]
    spirv --> vkout["SPIR-V shader artifacts"]
```

## Pass roles and packages

One package ≈ one pass role. Materials pick a package; they do not invent keyword sets.
Graph pass order and gates: [Rendering — Implemented pipeline](/docs/systems/rendering.md#implemented-pipeline).

| Role | Package | PipelineKind | Layout |
|------|---------|--------------|--------|
| Opaque deferred geometry | `_engine/gbuffer` (or game package) | `gbuffer_geometry` | `standard` |
| Soft-ref / missing material | `_engine/error_material` | `gbuffer_geometry` | `standard` |
| Transparent forward | `_engine/forward_lit` | `forward_transparent` / `forward_transparent_hdr` | `forward_lit` |
| Shadow depth | `_engine/shadow_depth` | `shadow_depth` | `standard` |
| Shadow depth, alpha-cut casters | `_engine/shadow_depth_masked` | `shadow_depth` (same state; the material carries the PS) | `standard` |
| Deferred lighting | `_engine/deferred_light` | `lighting` / `hdr_lighting` | `fullscreen_shadowed` |
| Hybrid-shadow lighting | `_engine/hybrid_light` | `lighting` / `hdr_lighting` | `fullscreen_hybrid_shadowed` |
| RT-shadow lighting | `_engine/raytraced_light` | `lighting` / `hdr_lighting` | `fullscreen_raytraced` |
| Screen-space AO | `_engine/screen_space_ambient_occlusion_compute` | `ambient_occlusion` | `fullscreen_reflections` |
| Ray-traced AO | `_engine/ray_traced_ambient_occlusion` | `ambient_occlusion` | `fullscreen_raytraced` |
| Screen-space reflections | `_engine/screen_space_reflections` | `hdr_lighting` | `fullscreen_reflections` |
| Ray-traced reflections | `_engine/ray_traced_reflections` | `hdr_lighting` | `fullscreen_raytraced` |
| TAA resolve | `_engine/temporal` | `hdr_lighting` | `fullscreen_reflections` |
| Tonemap (ACES) | `_engine/tonemap` | `tonemap` (`TONEMAP=aces`) | `fullscreen_texture_table` |
| Tonemap (Reinhard) | `_engine/tonemap_reinhard` | `tonemap` (`TONEMAP=reinhard`, same source) | `fullscreen_texture_table` |
| Editor viewport blit | `_engine/composite` | `tonemap` | `fullscreen_texture_table` |
| Debug G-buffer grid | `_engine/debug_visualizers` | `debug_visualizers` | `debug_visualizers` |
| UI | `_engine/ui` | `ui` | `ui` / `ui_atlas` |
| Camera meshlet cull (compute) | `_engine/meshlet_cull` | *(compute PSO, not mesh `PipelineKind`)* | `standard` |
| RT opacity micromap bake (compute) | `_engine/opacity_micromap_bake` | *(lazy renderer-owned compute PSO)* | `standard` |

Other engine packages (compute / post, not material roles) live beside these in `src/hikari/shaders/akari/packages/`: bloom, exposure, depth of field, motion blur, auto-focus, GI (screen-space, ray-traced, temporal/spatial/bounce), particles, volumetrics, atmosphere and clouds, light binning, skinning writeback, shadow meshlet cull, deferred decals.

Game-authored packages (sample project: `hikari-games/example/assets/shaders/`) stage under `data/shaders/`, not `_engine/shaders/`. Materials may set `"binding_layout": "mesh_vertex_texture"` and optional `"async_compute": { "package", "entry", "resolution", "threads", "format", "lag_frames" }` to drive content compute (producer key = package/entry/resolution/threads/format; `lag_frames` 0|1 only; prefer independent compute queue, fall back to graphics; `format` omit/`r32f`, `rg16f`, or `rgba16f`). Encode writes the selected ring textures onto `GpuMaterialRecord.vertexTextureIndex` / `previousVertexTextureIndex` in the bindless heap, so deforming vertex shaders can emit deformation-correct motion vectors. Producer kernels may sample their own prior ring result through `content_compute.history`; gate it with `frame.historyValid` and use simulation-clock `frame.deltaTime` for temporal evolution. Producers may also consume up to 64 game-published opaque records through `content_compute.inputs` / `frame.inputCount`; update them in one batch with `hi.render().setContentComputeInputs`. The engine tracks opaque vs transparent consumers so async-compute acquisition occurs at the first real sampling pass. Sample: packages `water` + `water_sim`.

**Temporal geometry contract:** opaque `GBufferOutput.depth` is `float2(currentViewDepth, previousViewDepth)`; compute the previous depth from the same previous pose used for motion and write a nonpositive value when there is no correspondence. Forward/OIT entry points (including fallbacks) declare a third output `float4(previousViewDepth, valid, 0, coverage)`; `hikari_oit_encode` weights it like colour. Particles without previous poses emit validity zero.

**Composability:** geometry packages (including content like water) write the deferred G-buffer only. They must not hardcode RT, deferred vs RT lighting, or other materials. Lighting / AO / reflection packages read the G-buffer (and optional TLAS) only. Content compute packages write a storage texture consumed through those bindless material heightfield indices. Full contracts: [Rendering — Rendering contracts](/docs/systems/rendering.md#rendering-contracts-composability).

Shared forward BRDF/IBL helpers accept an optional trailing `dielectric_f0`
(`float3(0.04)` by default) and `specular_occlusion` (`-1` = use the material
AO). Engine `forward_lit` / `forward_lit_rt` derive F0 from IOR
(`hikari_dielectric_f0`) and pass that same value to direct lighting, IBL, and
the transmission mix. A transmitting interface is a dielectric: metalness is
scaled by `(1 - transmission)` so it cannot steal F0, and IBL specular is not
multiplied by baked AO (the Fresnel lobe is the environment, not a cavity).
`thickness == 0` is thin-walled: the raster path still walks the refracted ray
by one pixel of world length at the surface depth (Snell in screen space, no
Beer-Lambert). Volumes keep using authored thickness.

Formats, blend, and depth write live on `PipelineKind`, not shader `#ifdef`.
Optional map presence uses a runtime **map mask** in the material row (`HikariGpuMaterial.material.w`, mirror of `GpuMaterialRecord`)
(dynamic branches), not compile-time permutations.

Compile-time choices are `const` parameters with named instances (e.g. the
tonemap operator). Never emit a Cartesian product of material features. Lighting-family
scene textures (G-buffer, shadow atlas, skybox, captures, …) are **bindless**:
index them through `@bind(lighting.resource_indices)` (`HikariLightingResources`),
not fixed `HK_TEXTURE(lighting,…)` slots. The language still allows resource
arrays (`TexCube[N]` / `Tex2D[N]`) — see the
[language reference](/docs/reference/akari-language.md#resources-and-bindings).

## Source provenance and distribution notices

Record adapted shader code and its applicable permission in
`src/hikari/shaders/PROVENANCE.md`; retain complete upstream notices in
`src/hikari/shaders/THIRD_PARTY_NOTICES.txt`. A technique citation alone does not
establish permission to copy its reference implementation. Record equation-based
implementations separately from source adaptations, and preserve inherited notices.

Kaji includes the shader notices in game and editor products as
`licenses/HIKARI-SHADER-NOTICES.txt` (`Contents/Resources/licenses/` inside a macOS
app bundle). The bundled editor SDK also carries the notices and provenance
registry beside its shader source modules. Notice changes invalidate the relevant
staging steps. Keep the notices when distributing shader sources separately.
Packaging fails if a registered product notice is missing from the staging tree;
rebuild `package-host` to restore it.

## PSO share

Mesh PSOs are keyed by `(package, vertex entry, fragment entry, PipelineKind)`
and shared across materials. Residency still refcounts handles.

Native shader programs are independently cached by package, entry points,
binding layout, shader model, and AssetStore content version. Material instances
retain the immutable program while keeping per-material compute/culling state.
The cache drops its strong reference on shader-source rebind or hot reload; old
programs survive only while deferred GPU materials still reference them. Unused
programs age out after 120 frames, so streamed chunks do not grow an immortal
shader cache.

## Akari file headers

One root per file, then optional headers, then items (`struct` / `fn` / …):

```akari
#shader composite
#layout fullscreen_texture_table
#import preamble, fullscreen_triangle

@vertex
fn composite_vertex(@vertex_id vid: uint) -> V { … }
```

| Directive | Role |
|-----------|------|
| `#shader name` / `#module name` | File kind + name |
| `#version N` | Syntax version (default `2`) |
| `#layout name` | Binding layout contract (`#shader` only) |
| `#import a, b` | Pure modules from `akariImportPaths` |
| `#require a, b` | Package capability declarations (optional; also inferred from use) |

Stages are attributes on functions (`@vertex` / `@fragment` /
`@compute(threads=(x,y,z))`), not text pragmas. D3D12 emit splits one entry per
file because DXC produces one stage artifact per invocation.

Current layouts: `standard`, `fullscreen_texture_table`, `fullscreen_shadowed`,
`fullscreen_hybrid_shadowed`, `fullscreen_reflections`, `forward_lit`, `forward_lit_rt`, `fullscreen_raytraced`,
`debug_visualizers`, `ui` / `ui_atlas`, `mesh_vertex_texture`.

## Language surface (authoring)

Full syntax, tables, and worked examples: **[Akari language](/docs/reference/akari-language.md)**. Grammar: [`src/akari/syntax.ebnf`](https://github.com/Flying-Rat/hikari/blob/develop/src/akari/syntax.ebnf).  
GPU packing: [`docs/reference/akari-abi.md`](/docs/reference/akari-abi.md).

Array textures sample with a packed coord (e.g. `Tex2DArray` uses `float3(uv, layer)`);
Metal emit splits layer from the coord. Prefer `@bind` over raw `@slot` except
non-manifest compute experiments.

## Binding manifest

Authoring uses `@bind(group.name)` / `@target(group.name)` / `@sampler(…)`.
`@bind` and `@target` resolve slots from `bindings.kaji.json`. Manifest keys keep
the historical `HK_*` spelling:

```text
HK_BUFFER(domain,name)
HK_TEXTURE(domain,name)
HK_TARGET(domain,name)
```

`@sampler(filter, address)` (and the comparison form) is **not** a binding key:
it names a state of the engine's static sampler set (`static_samplers`, below).

Unknown `@bind` / `@target` names are errors. Values come from the shared
`bindingManifest`; they are not hardcoded in `.akari` source.

### Domains

Slot numbers live only in `bindings.kaji.json`; author against the names. The
domains you will meet:

| Domain | Names | Who reads it |
|---|---|---|
| `frame` / `draw` / `geometry` | `uniforms`, `object`, `vertices` | geometry packages (mesh geometry is meshlet-pulled from `meshlet.vis_tris` / `meshlet.works` plus the bindless geometry table; shadow depth uses the same pull) |
| `resources` | `texture_table`, `geometry_samplers` | material maps via `GpuMaterialRecord` indices; content-compute heightfields land on the same heap (`vertexTextureIndex` / `previousVertexTextureIndex`) |
| `lighting` | `frame`, `records`, `clusters`, `shadows`, `probes`, `resource_indices` | deferred / forward lighting, AO, GI, reflections. Scene textures are bindless through `resource_indices` (`HikariLightingResources`) |
| `post` | `hdr`, `lut`, `bloom_add`, `exposure` | tonemap / composite family |
| `ui` | `vertices`, `atlas` | UI packages |
| `debug` | `albedo` … `hdr` (eight float slots the active visualizer assigns) plus `material` (`Tex2D<uint>`, its own slot) | debug visualizers |

### Samplers

`@sampler(...)` states come from one engine-wide **static sampler set**, declared
once in `bindings.kaji.json` under `static_samplers`:

| Name | State | D3D12 | Vulkan (set 2) |
|---|---|---|---|
| `point_clamp` | `@sampler(point, clamp)` | `s0` | `0` |
| `linear_clamp` | `@sampler(linear, clamp)` | `s1` | `1` |
| `linear_repeat` | `@sampler(linear, repeat)` | `s2` | `2` |
| `comparison_greater_equal` | `@sampler(comparison, greater_equal, linear, clamp)` (reverse-Z shadow compare) | `s3` | `3` |

A sampler's register follows from its state alone — not from declaration order
or from which samplers an entry happens to use — and every D3D12 root signature
with space-0 samplers carries the whole set (`binding_layout.static_samplers`,
which a test keeps equal to the manifest). Metal bakes the state as a
`constexpr sampler`. A state outside the set is a compile error on every
backend; adding one is a manifest row plus a `binding_layout` row. Two samplers
with the same state in one entry share the register (HLSL aliases the second).
The material geometry sampler catalog is separate (space 1, `geometry_sample`).

### Render targets

G-buffer outputs use:

- `HK_TARGET(gbuffer,albedo)`
- `HK_TARGET(gbuffer,normal)`
- `HK_TARGET(gbuffer,arm)`
- `HK_TARGET(gbuffer,depth)`
- `HK_TARGET(gbuffer,material)` (`uint`, not `float`)
- `HK_TARGET(gbuffer,motion)`
- `HK_TARGET(gbuffer,anisotropy)`
- `HK_TARGET(gbuffer,clearcoat)`

They expand to Metal color indices `0` through `7` and HLSL semantics
`SV_TARGET0` through `SV_TARGET7`.

```metal
float4 albedo [[color(HK_TARGET(gbuffer,albedo))]];
```

```hlsl
float4 albedo : HK_TARGET(gbuffer,albedo);
```

Ordinary single-target HLSL fragment outputs continue to use native
`SV_TARGET`.

## Authored foliage normals

`material.flags.preserve_authored_normals` opts foliage out of the default two-sided policy (which mirrors the normal on back faces): G-buffer, forward, material-graph, and RT secondary hits keep the authored side of the tangent frame for both faces. Normal maps and clearcoat still perturb it. It is a normal policy only: alpha coverage, shadows, translucency, and the BRDF are unchanged, and `double_sided` is still set separately. Model sidecars set `preserve_authored_normals: true` in an exact-name `model.material_overrides` entry so recooks keep the choice; no name heuristic exists. No shader permutations or GPU record bytes are added (one flag bit in the material word and RT instance descriptor).

## Shader specifications

The JSON specification version must be 2:

```json
{
  "version": 2,
  "type": "AppleMetal",
  "bindingManifest": "bindings.kaji.json",
  "akariImportPaths": ["akari/modules/"],
  "compilation": [
    {
      "name": "gbuffer",
      "workingDirectory": "akari/packages/",
      "optimizationLevel": 0,
      "includeDebugSymbols": true,
      "sources": ["gbuffer.akari"]
    }
  ]
}
```

Per-entry `optimizationLevel` / `includeDebugSymbols` are author defaults.
Product builds override them with Kaji `--shaders:config=debug|release`
(defaults to product `--config`, unless explicitly overridden). That maps to:

| `--shaders:config` | optimization | debug symbols |
|---|---|---|
| `debug` | `0` | on |
| `release` | `2` | off |

Akari passes the optimization flag explicitly to every native compiler,
including `-O0` for debug; compiler defaults do not choose the profile.
DXC uses explicit HLSL 2021 mode to preserve scalar short-circuit expressions.

`akari compile` accepts `--config=debug|release`. Shinra forwards the same
profile via `--shaders-config=` (hyphen; Clap cannot use `:` in long option
names).

Spec entries take only the keys above (unknown keys are an error). One pass
that the host loads as several packages — the tonemap operators — is one
module plus one tiny package per choice: `modules/tonemap_pass.akari` holds the
template entry with `const operator: TonemapOperator`, and `packages/tonemap.akari`,
`tonemap_reinhard.akari`, `tonemap_agx.akari` and `tonemap_filmic.akari` each
instantiate it as `tonemap_fragment`.

`bindingManifest` and `akariImportPaths` may be omitted when `--engine-sdk` can
resolve `bindings.kaji.json` and engine `akari/modules/` from the SDK tree.

The engine and game specifications both reference
`src/hikari/shaders/bindings.kaji.json`. Each manifest entry maps one complete
semantic expression to its Metal and D3D12 token, and optionally a Vulkan one:

```json
{
  "version": 1,
  "bindings": {
    "HK_BUFFER(frame,uniforms)": {
      "metal": "1",
      "d3d12": "b0",
      "spirv": "0:1"
    }
  }
}
```

Keep one shared manifest for all packages so game-authored and built-in shaders
cannot drift.

### Vulkan binding model

Vulkan has no per-class register namespaces, so Akari fixes one descriptor-set
layout (`akari_emit_common::vulkan`) that every Vulkan pipeline layout mirrors:

| Set | Contents | Binding |
|-----|----------|---------|
| 0 | `Uniform` / `Buf` / `RwBuf` / `AtomicBuf` / `AccelStruct` | the Metal buffer index |
| 1 | sampled and storage textures | the Metal texture index |
| 2 | samplers; `@sampler(...)` states bind at their `static_samplers` `spirv` binding | the Metal sampler index |
| 3 | renderer-global: `0` bindless `ByteAddressBuffer[]`, `1` `Texture2D[]`, `2` `TextureCube[]`, `3` `Texture3D[]`, `4` `Texture2D<float>[]` (depth), `5` geometry sampler catalog `SamplerState[18]` | fixed |

An entry without a `spirv` value therefore needs no edit: its Metal index is the
binding inside the class's set. `"spirv": "set:binding"` overrides that for one
entry. A D3D12 register in the `spirv` column is rejected. Render targets use the
Metal attachment index (`SV_TargetN` → `Location N`). `@slot(N)` keeps `N` in the
class set. Storage textures carry `[[vk::image_format]]` for every format except
`float4`, which stays `Unknown` (format-less writes). Compilation passes
`-fvk-use-dx-layout` (D3D12 packing), `-fvk-use-dx-position-w`, and
`-fvk-invert-y` on vertex stages, so CPU-side layouts and clip conventions are
the ones D3D12 and Metal already use. Output is one `<package>_<vs|ps|cs>.spv`
per stage entry.

## Processing order

For each package, Akari:

1. Parses the `.akari` package and transitive `#import` modules.
2. Lowers AST → HIR → IR (specializing `const`, `fn` and optional parameters).
3. Emits pure MSL, HLSL, or Vulkan HLSL (bindings resolved; `#line` + stage metadata).
4. Validates emit purity (no leftover `HK_*` / `#pragma hikari`).
5. Invokes Metal, DXC, or DXC `-spirv`.
6. On failure, prints `@akari diag {"sev","msg"}` to **stderr** (machine), then human
   `error:` lines — metalc/DXC locations are remapped via structured line maps / `#line`
   to `.akari` when possible; Metal unknown-attribute / candidate-note noise is dropped.

Package failures are isolated. Akari completes every independent package in the
spec and exits non-zero only after reporting the failed set. Successful packages
publish atomically; a failed package does not replace its previous library. A
shared manifest or imported-module error still fails every package that actually
depends on that shared input.

Compile units own emitted source and line maps. Native compiler files are private
to each invocation, so concurrent builds never read shared generated files.
Native bytecode enters the cache from those private files before live artifact
publication, keeping fingerprints independent of concurrent output replacement.
Use `akari transpile --out=<directory>` to preserve source for inspection.

Native artifact names come from structured shader and entry metadata. Packages
with multiple entries of the same stage retain each entry's full name in the
artifact suffix, including when the package name differs from `#shader`.
Cache inventories record every artifact's name, size, and content hash. A hit
requires the complete verified set; a missing or corrupt member rebuilds the
package. Hits stream each blob once, checking its length and hash while writing
a private staging file; staging publishes only after the whole bundle verifies.
Bytecode blobs and inventories publish through unique temporary files
and atomic replacement, so readers never accept partially written cache data.

## Shipping shader packages

Standalone game sealing uses the cooked asset manifest and pack plan to retain
shader packages referenced by shipped materials (including generated materials
and `async_compute.package`), plus explicit runtime roots and their transitive
dependencies. Editors keep the complete compiled set. Particle-specialized
programs remain embedded in their cooked particle asset.

An Akari compilation entry may declare:

```json
{
  "shipping": { "root": true, "requires": ["water_sim", "_engine/gbuffer"] }
}
```

`root: true` retains shaders reached through runtime code or plugins even without
a material reference. `root: false` retains a package only through references or
another package's `requires`. Authored game packages default to `root: true`
when shipping metadata is omitted; material-generated specs explicitly use
`root: false`. Dependencies use the same package identities as
`AssetRef` of kind `shader`, with `_engine/` for engine packages. Every engine
entry **must** declare its shipping policy; a test keeps policies aligned across
the three backend specs. Current renderer packages are runtime roots because
quality settings and fallback paths can change while the game runs. Do not mark
an existing renderer root unreferenced merely because a startup setting is off.

Akari writes exact per-package artifact ownership to `shaders.akari.json`.
Shinra caches typed material references with the existing manifest hashes and
resolves shader closure in `packs.shinplan.json`. Kaji validates all retained
artifacts before staging them from the cook tree, for both loose and bundled
output; unresolved references fail sealing. No stage-filename prefix matching
or scene-only reachability inference is used. The report at
`<project>/.engine/build/shader-stripping.json` records package reasons and sizes.
This pass strips whole packages at seal time; compilation still processes the
specs, and native entry-point stripping is not performed.

## Validation

Akari can validate metadata, imports, bindings, stages, layouts, and specializations
without invoking DXC:

```sh
bin/akari/akari compile \
  --spec=src/hikari/shaders/build.akari.d3d12.json \
  --out=build/cache/shader-validation/out \
  --cache=build/cache/shader-validation/cache \
  --engine-sdk=. \
  --validate-only
```

`validate-only` does not type-check native HLSL. The Windows build remains the
authoritative DXC validation. The Vulkan spec has no such gap: without
`--validate-only` it runs the vendored DXC in SPIR-V mode on every host, and DXC
validates each module it writes, so the same command against
`build.akari.vulkan.json` is a full compile check.

**Validate the game's shaders against the engine's, not only the engine's.** A
game package that calls into an engine module is compiled against whatever that
module currently declares, so an engine-side change can break a game shader that
nobody touched — and the failure surfaces at cook time in the editor console,
not in any Zig build. Point the same command at the game's spec with
`--engine-sdk` naming the absolute repository or SDK root:

```sh
bin/akari/akari compile \
  --spec=../hikari-games/example/assets/shaders/build.akari.metal.json \
  --engine-sdk="$PWD" \
  --out=build/cache/shader-validation/out \
  --cache=build/cache/shader-validation/cache \
  --validate-only
```

Always pass `--out` and `--cache` pointing somewhere disposable. Without
`--validate-only` this recompiles every package and overwrites the staged
metallibs for the given config.

**`HikariRasterLightCtx` is a struct literal game shaders build, so a field
added to it is a breaking change.** Anything calling `evaluate_lighting_forward`
and friends constructs one by naming every field, and Akari rejects a literal
that misses one (`missing field 'x' in 'HikariRasterLightCtx' literal`) — there
is no default, because the pack holds textures and samplers and neither can be
zero-initialized. When the engine adds a resource to the light loops, every game
shader that lights a surface needs the new field; mirror what
`akari/packages/forward_lit.akari` does for it (the sample's `water.akari` is the
worked example).

Occlusion enters lighting as two separate factors. The light loops take
`ambient_occlusion: float3`, the diffuse occlusion of ambient-kind lights; build
it with `hikari_ambient_diffuse_occlusion(ao_pass, ao_baked, base_color)`, or
pass `float3(baked_ao)` from a forward shader, which has no AO pass.
`hikari_eval_ibl` takes the AO pass's visibility and the baked map as separate
arguments (`1.0` for the pass when none ran). Never pass their product: only the
pass's estimate takes the multi-bounce lift.

## Format

`akari fmt` reprints `.akari` source in the canonical style (4-space indent,
semicolons, `name: Type` spacing, wrapped parameter/argument lists). It parses
and pretty-prints; it does not type-check or compile. Comments are preserved.

```sh
bin/akari/akari fmt src/hikari/shaders/akari
bin/akari/akari fmt --check src/hikari/shaders/akari
bin/akari/akari fmt --stdin < foo.akari
```

Directories are walked recursively for `*.akari`. `--check` lists files that
would change and exits 1. Default writes in place.

## Authoring rules

- Author packages and modules as pure `.akari` only (`#shader` / `#module`,
  `#layout`, `#import`).
- Bind with `@bind(group.name)` / `@target(group.name)` — not raw registers.
  `@sampler(...)` picks a state of the engine's static sampler set; its
  register follows from the state.
- Keep CPU/GPU structure layouts synchronized with their Zig definitions
  (`akari transpile --dump-layout` / ABI rules in `docs/reference/akari-abi.md`).
- Add a binding to the shared manifest and this document in the same change.
- Treat missing imports and unknown bindings as errors; never substitute empty
  source.
- Prefer runtime map masks over compile-time permutations for material features;
  keep `const` instances finite and named (tonemap operator, etc.).
- Entry points that differ only by a constant (à-trous step, skinned/rigid,
  RT/raster) are one template with `const` parameters plus `fn name = template(...)`
  instances; the host still loads each instance by its name. Resources shared by
  a package's entries are declared once as package bindings
  (`@bind(group.name) let name: T`). See the
  [language reference](/docs/reference/akari-language.md#compile-time-parameters-const-optional-resources-and-instances).
