///|
/// Capability self-report. A feature only leaves `Gap` in the same commit as
/// the code that draws it, and a `Partial` row always names the half that is
/// missing: an optimistic row here is the silent-mis-draw failure the plan
/// rejects at L2. Naming it is a convention this module can check — every
/// `Partial` note ends with a `Remaining:` sentence listing its gaps, and
/// `metal_renderer_wbtest.mbt` fails any row that reports `Partial` without one,
/// because two rows once passed on 700-odd characters of description that named
/// nothing missing, which a length check cannot see. `docs/renderer-capability-report.md` mirrors these statuses and
/// `scripts/validate-renderer-capability-consistency.mjs` fails if they drift.
///
/// Both walks below name all 17 features and have no catch-all, so a feature
/// added to `@render.RendererFeature` is a compile error here rather than a row
/// that silently reports `gap`. The same drift is caught on every host by the
/// capability validator, which reads the report matrices.

///|
fn metal_feature_status(
  feature : @render.RendererFeature,
) -> @render.RendererFeatureStatus {
  match feature {
    @render.RendererFeature::Rect => Supported
    RoundedRect => Supported
    Gradient => Supported
    Shadow => Partial
    Text => Partial
    Image => Supported
    PathVector => Supported
    Clip => Partial
    Transform => Partial
    Opacity => Partial
    LayerCompositing => Partial
    BlendMode => Partial
    FilterEffect => Partial
    ShaderEffect => Partial
    AsyncImage => Partial
    TextShaping => HostForwarded
    EmojiText => Partial
  }
}

///|
fn metal_feature_note(feature : @render.RendererFeature) -> String {
  match feature {
    @render.RendererFeature::Text =>
      "A glyph run reaches the screen through the injected engine: the provider rasterizes coverage, this module shelf-packs it into 1024x1024 RGBA8 pages, and the word the item already carried says which page to bind — so one run whose glyphs no longer fit a page becomes two draws, and the 76-byte vertex and the text shader never mention pages (`metal_text_wbtest.mbt`: \"a glyph run that spills onto a second page draws both pages\"). `moui_tests/metal_renderer_smoke/macos` asserts the read-back ink ratio is glyph-shaped and that a noop engine paints nothing. Partial for three named reasons. One: capacity is a capped page set with clear-and-rebuild rather than per-glyph eviction — four 1024x1024 pages, allocated lazily so a text-light route still costs one 4 MB buffer and one 4 MB texture, and a frame that needs a fifth is repacked and counted (`metal_text_wbtest.mbt`: \"a frame denser than one page grows the atlas instead of dropping\", \"a frame whose text outlives the atlas is repacked, not dropped\", \"the atlas holds a retina page's distinct glyph boxes\"). Two: the ceiling is measured rather than asserted, and now on a grid — through the real CoreText provider one page takes ~818 distinct 14pt ideographs at device scale 2.0, and the smoke's capacity cells read (14pt, scale 1.0, 1600 glyphs) -> 1 text item, (14pt, 2.0, 1600) -> 2, (24pt, 2.0, 900) -> 3, every one with `text_dropped_glyphs` 0, where the same 14pt/2.0 frame pinned to a single page reported 782 drops. Three: colour glyphs are composited as straight alpha and evidenced through the `EmojiText` row, and the composition root wraps the platform provider with no second engine behind it, so a codepoint the system cascade cannot resolve has nowhere further to go. Remaining: capacity is a capped page set aged out by frame rather than a use-keyed eviction record, the measured grid is two sizes and two device scales rather than every font and script, and a colour glyph is composited as straight alpha with one engine behind the composition root."
    Rect =>
      "Fill and stroke both draw: a stroked rect is the SDF ring minus its inset box, pinned by pixel tests that assert the ring is inked and the interior is clean. Edge softness is a one-pixel smoothstep, not a gamma-correct ramp."
    RoundedRect =>
      "Filled and stroked rounded boxes are evaluated per fragment by the shared rounded-box SDF, with the corner arc asserted by pixel tests: corners transparent, arc partially covered, centre opaque."
    Gradient =>
      "Linear and radial brushes interpolate per fragment from the brush endpoints carried on each vertex, asserted by monotonic-along-axis and center-to-edge falloff pixel tests, including a gradient-filled path. A box fragment measures itself from its own rect origin while a path mesh carries absolute positions, so box endpoints are shifted into that space — a route that forgot to is pinned by `a radial gradient on an offset rect keeps its centre and its radius`, which also checks that a radial radius is a length and does not shift. The contract itself is two-stop, so no stop table is needed."
    Shadow =>
      "A shadow is one quad whose fragment evaluates the rounded box's distance field and turns it into coverage the way a Gaussian does: `0.5 * erfc(d / (sigma * sqrt 2))`, with `d` the signed distance from the shape inflated by `spread` and `sigma` the requested `blur_radius` -- the reading `moui_skia_renderer` hands its `MaskFilter`, since it passes `radius` straight to `ImageFilter::blur`. `erfc` is not available in this MSL build, so the error function comes from Abramowitz & Stegun 7.1.26, whose ~1.5e-7 bound is far under an 8-bit output. The quad is grown by `3 * sigma + spread` so the tail it paints has room to fall to 0.13% before the box edge. What this replaces is worth keeping in the record: the previous formulation filled the grown box solid out to 2 sigma and ramped across the last sigma, so a measured 12-pixel mark at `blur_radius = 8` came back as 255 from eight pixels outside its left edge to seven past its right, then dropped 76 levels in one pixel at the box rim -- a slab with a soft edge, not a shadow, and the earlier test passed because it only asked whether *some* ramp existed. `moui_l2_wbtest.mbt` now asserts the profile: half-covered at the shape's edge, about 16% one sigma out, under 10% at two, monotone to three sigma, and no single-pixel step carrying most of the remaining ink. Partial, with the halves named: the distance field is a one-sided approximation, so a shape thinner than its blur reads brighter at its centre than Skia's two-sided sum does (the same 12-pixel mark peaks at a measured 81% where the exact two-sided integral is 55% (Phi(6/8) - Phi(-6/8))), corners are the SDF's rather than a true 2-D convolution, and the route still does not convolve what the group actually painted -- a shadow of a textured or path-filled shape is a shadow of its bounding box. This also means Metal and `moui_wgpu_renderer` no longer agree on the look: that route keeps the ramp, and its note still claims the shared formulation, which is now the stale side of the pair. Remaining: the distance field is one-sided, so a shape thinner than its blur peaks brighter than Skia's two-sided convolution; corners follow the SDF rather than an exact 2-D blur; and the shape is a `RoundedRect` by contract — `ShadowSpec` carries a rect, a colour, an offset, a blur and a spread, so there is no path-shaped or texture-shaped shadow in the command model to get wrong; what is approximate is the corner falloff, which follows the SDF rather than an exact 2-D convolution."
    Image =>
      "A decoded image draws as one textured quad. ImageIO decodes PNG/JPEG/WebP on the system stack (no third-party codec); `fit` and `src_rect` resolve through the shared `image_placement`/`image_src_window`; the host async lifecycle is wired (`image_decoder`, `image_records`, `apply_image_load_completion`), so a first frame requests the load, paints nothing, and the frame after the completion paints the fixture's own red/blue halves. A failed source re-requests only once its bytes become readable, which is what lets a file that appears later draw without a restart."
    PathVector =>
      "Path fills and path strokes both draw from the shared `tessellate_path` mesh, and each sub-mesh keeps its own brush because the mesh carries the colour pair and gradient endpoints per vertex — a path with a gradient fill and a solid stroke renders both. Edge quality is the tessellation's, not an analytic curve evaluation."
    Clip =>
      "Rectangular clips become Metal scissors per draw range, and the rect is transformed before it becomes one: a clip pushed under a scale or a translation clips the region the transform puts it in, which is the rule `moui_wgpu_renderer/native_visual_state.mbt:48` pushes with too (`metal_state_wbtest.mbt`: \"a clip pushed under a transform clips where the transform puts it\", \"a rounded clip under a transform moves with it too\"). A `LayerSpec` mask is the deliberate exception and is not transformed, matching that route: a mask names a frame on the surface, not a shape in the caller's space. A `PushRoundedClip` whose arc the composite can still describe is exact rather than boxed: the scope becomes an offscreen pass whose composite rounds its corners with the same SDF a `LayerMask::RoundedMask` uses, so the corner pixel stays clear while two device pixels in is inked — a bounding-rect clip cannot produce that pair (`metal_state_wbtest.mbt`: \"a rounded clip rounds the corner instead of squaring it off\"), and the radius has to scale with the rect when a scale transform is open (\"a rounded clip under a scale rounds at the scaled radius\"). Partial for two remaining shapes that one rounded box cannot describe, both still falling back to the bounding-rect scissor and still counted in `approximated_clip_count`: a parent clip or surface edge that shrinks the rect (rounding the shrunk frame would round the wrong corners), and a rotated or skewed transform (its corners are ellipses) — `metal_state_wbtest.mbt`: \"a rounded clip that cannot stay a rounded box is still counted\". The exact path is not free: it costs one offscreen pass, which `report.layers` counts, so a page that round-clips every card pays a card-sized target per card — measured before that was written, no view or example in this repo calls `push_rounded_clip` or builds a `RoundedClip` today (`moui/views/controls/layout_helpers.mbt:77` is its only emitter), so the cost is prospective rather than already paid. Stencil clipping would be exact and free of that cost for every shape including rotated ones, and it is shared work with `moui_wgpu_renderer` rather than this module's to add alone. Remaining: a rotated or skewed transform (its corners become ellipses) and a parent clip or surface edge that shrinks the rect both fall back to the bounding-rect scissor and stay counted, and the stencil path that would be exact for every shape at no extra pass is shared work with `moui_wgpu_renderer`."
    Transform =>
      "The 2D affine stack is applied per vertex on the CPU and composed with the parent transform; there is no per-object matrix upload. Nothing had ever put a `PushTransform` through the device before the L4 state probes, and the first time it was pinned it found two real mis-draws, both fixed and both pinned now: a box was culled against the clip by its untransformed rect, so a rect a transform carried into a layer's masked frame drew nothing (\"a transform that moves a box into a masked layer's frame is not culled\"), and a clip pushed under a transform clipped where it was written rather than where the transform put it. The row is backed by pixels rather than by a reading of the code: replacing `transform_metal_point` with the identity fails all eight transform and clip probes in `metal_state_wbtest.mbt`. Remaining: because the stack reaches vertices only, anything measured as a *length* in the shape's own space -- a corner radius, a stroke width, a shadow's blur band -- is exact under a uniform scale and rotates with the transform, but cannot be right on both axes under a non-uniform scale or a skew, which is also why `PushRoundedClip` declines its exact path there; and a clip rect under a rotated transform contributes only its axis-aligned bounds to the scissor (`metal_state_with_clip` intersects the transformed bounds), so a rotated clip clips a larger region than it names."
    Opacity =>
      "Opacity multiplies into the premultiplied vertex colour, so nested opacity is exact, and the composited byte values are read off the device to separate the three ways that goes wrong: a dropped `PushOpacity` leaves 255 where 128 is due, a stack that replaced rather than multiplied leaves 128 where 64 is due, and a forgotten `PopOpacity` dims the next fill (\"a pushed opacity scales the fill's alpha at the vertex\", \"nested opacity multiplies rather than taking the innermost\", \"a popped opacity leaves the next fill at full strength\"). A `LayerSpec` group instead builds at full strength and takes its opacity once at the composite, which is what makes overlapping children group instead of double-blending. Remaining: opacity is multiplied into each vertex colour rather than taken once per draw, so a draw whose own triangles overlap -- a self-intersecting or multi-subpath fill whose mesh covers an area twice -- double-blends inside that overlap, where `moui_skia_renderer`'s save-layer builds the group at full strength and dims it once; and a group's own opacity is applied at the composite, so it inherits `BlendMode`'s exact-only-over-opaque-destination half."
    TextShaping =>
      "The injected platform engine shapes the run — CoreText, DirectWrite or Fontconfig each do it in their own provider — and this renderer only forwards the glyph placements it is handed, so no shaping is implemented here at all. The forwarding is now pinned from both ends: `metal_feature_probe_wbtest.mbt` (\"the shaping row is forwarded, not drawn by this route\") paints ink with the engine installed and nothing with the noop engine, and `metal_renderer_wbtest.mbt` (\"metal session gives the runtime the injected engine's text system\") reads the caret table back off the session's own text system, which is what catches a route wiring the painter to one engine and the widgets to another."
    LayerCompositing =>
      "`PushLayer`/`PopLayer` and `PushFilter`/`PopFilter` groups render into their own offscreen RGBA8 target sized to the group's device frame, and the group comes back as one full-bleed composite quad. Slots are claimed eagerly in post-order, so a parent always owns a higher slot than its children and the host can walk the flat list deepest-first without recursion; `metal_l2c_wbtest.mbt` pins that a rect mask really clips (`a rect-masked layer clips its group to the layer frame`), that a nested pair clips to the tighter frame (`nested layers clip to the tighter of the two frames`), and that group opacity is applied once at the composite so overlapping children group instead of double-blending. `BeginRetainedLayer` boundaries are now kept across frames: the host pools one pass target per slot and reuses it while the size matches, so a boundary that claims the same slot at the same device rect in the frame after the one that painted it composites that texture instead of rebuilding the group. The hit pushes a pass record with no children and consumes the child stream without drawing it, and the host's pass loop skips the encoder for exactly that record — opening one would clear the very pixels the composite is about to sample. Four records say what happened to a boundary: `retained_layers_cached`, `retained_layers_rebuilt`, `retained_layers_updated` (a revision bump on a boundary that was already there) and `retained_layers_evicted` (an entry too old for its texture to still be the group's). Partial for three named reasons. One: this is the pass pool reused as a cache, not a picture or pixel cache of its own, so an entry is valid only for the frame immediately after the one that painted or carried it, at the same slot and the same device rect — a page whose boundary list changes shape, order or size rebuilds, and a boundary that comes back after being offscreen for a frame pays one rebuild before it can hit again (`metal_retained_wbtest.mbt`: `a boundary that returns after a gap rebuilds rather than reusing a stale texture`, `a boundary whose rect moved repaints rather than compositing an old frame`). Two: content fidelity is delegated whole to `content_revision` — a group whose drawing changes without a bump keeps compositing the old texture — and `a cached boundary composites the pixels the previous frame painted` asserts exactly that stale read, because a hit that paints what the current stream asked for would be indistinguishable from re-rendering it. Three: entries are aged, never capped or prioritised, so there is no residency decision of the kind `moui_skia_renderer`'s picture cache or `moui_sun_renderer`'s pixel cache makes hit, update and eviction choices with; a page holding twenty boundaries holds twenty pass targets, which is what rebuilding them would have used anyway, but holds them for as long as the boundaries stay. The boundaries this covers are real in this repo's own apps: every diagnostics section the showcase can show wraps its scroll body and its header panel in `.repaint_boundary()` (`examples/showcase/app/diagnostics/components.mbt:24,32,80`) and the runtime gives each one a `key` and a `content_revision` (`moui/runtime/render_tree.mbt:98`, `:230`). The route's own evidence app is not one of them on its first frame — `examples/showcase/macos_metal/main.mbt` passes no route and lands on `Components/welcome` (`examples/showcase/app/workspace.mbt:84-86`), and a committed presentation smoke now launches it and requires a first present — so the frames that carry boundaries begin at the first navigation into a diagnostics section, which `scripts/macos-metal-presentation-smoke.sh` reaches and `benchmarks/app_cached_layer` drives, which is the section set `benchmarks/app_cached_layer/native/main.mbt:376` already drives. Remaining: an entry is trusted only for the frame immediately after the one that painted or carried it, at the same slot and device rect, so a boundary whose list or layout churns rebuilds; content fidelity is delegated whole to `content_revision`; and entries are aged, never capped or prioritised, so there is no residency decision of the kind Skia's picture cache and Sun's pixel cache make."
    BlendMode =>
      "Five of the six modes reach real Metal state at the composite: source-over, multiply, screen and (through `MTLBlendOperationMin`/`Max`) darken and lighten, which `blend modes composite the group against what the pass painted` pins against a mid-grey destination so the four cannot be confused with each other or with a plain source-over. Overlay needs a conditional and this route has no per-fragment blend program, so it is not drawn and lands in `blend_modes_unsupported`. Partial for a second reason as well: these are fixed-function factors that are exact only where the destination is opaque, and there is no backdrop read, so a group over a freshly cleared target or a translucent window blends toward black instead of falling back to source-over — every non-source-over composite is counted in `blend_modes_approximated` rather than presented as exact. Remaining: `Overlay`, which needs a conditional no fixed-function state provides and is counted rather than guessed, and any non-source-over composite over a destination that is not opaque, since this stage has no backdrop read."
    FilterEffect =>
      "A filter wraps its group in the same offscreen pass a layer uses and rewrites that target while compositing, so the filter applies to the group as a whole. Blur is separable and its sigma is the requested radius, the way `moui_skia_renderer` reads it (`ImageFilter::blur(radius, radius)`): the group is convolved horizontally into a second pass target of its own, and the composite that ends the scope reads that target vertically, with thirteen taps an axis, spaced `max(sigma / 6, 1)` device pixels so they span ~2 sigma at every radius, with Gaussian weights divided by their own sum. `moui_l2c_wbtest.mbt` pins the result as a profile rather than a value: light has to reach past a two-pixel mark on **both** axes, the falloff has to be non-increasing away from it in each direction, and the sample eight pixels out must not out-shine the ones between it and the mark -- that last one is what a spaced tap cannot pass, because before this leg the blur echoed the group at +-radius (`a blurred group falls off on both axes rather than echoing at the radius`). The intermediate counts as a pass, so a blurred group reports `layers` 2 while still counting one `filter_approximations`. Partial for what a bounded single-shader pass cannot be: the support stops at 12 taps a side, so a radius past six device pixels is truncated near 2 sigma and renormalised; the intermediate is an 8-bit texture, so a wide blur quantises twice; there is still no backdrop to apply a filter to; and the colour filters run on un-premultiplied colour while `moui_wgpu_renderer` filters in premultiplied space, so a translucent filtered layer can differ between the two routes. Two defects fell out of building it, each now pinned by its own test: the composite fragment's `else if (kind > 4.5)` was open above, so the new stage kinds 6 and 7 fell through into the colour-matrix branch, and `metal_identity_color_matrix` carried the numbers `moui_wgpu_renderer` uses -- correct for a `mat4x5f` indexed by column, wrong for this shader, which reads rows -- so that \"identity\" mapped every channel onto red. The branch is bounded at 5.5 now and the identity is row-major with each row's translation last (`an identity colour matrix really leaves the group alone`). Remaining: thirteen samples an axis discretise the tail, so a sigma-16 edge measures 212/119/32/10 at -sigma, the edge, +sigma and +1.5 sigma against the ideal 214/128/40/17 (the spaced kernel this replaces measured 255/122/0/0 -- it could not reach past 12 pixels at any radius); the intermediate is an 8-bit texture so a wide blur quantises twice; a blur cannot spill past the group's own frame, because that frame is the pass target; there is no backdrop to filter; and the colour filters run on un-premultiplied colour where `moui_wgpu_renderer` runs them premultiplied."
    ShaderEffect =>
      "The four names `builtin_shader_names()` declares are evaluated per fragment, each taking its two colours from the brush the spec carries as its `fallback` the way `moui_skia_renderer`'s `skia_shader_effect_colors` does: `checker` on square cells of `min(w,h)/amount` measured from the frame origin, `vignette` as a radial fade to black (at the fallback's own alpha) by `max(w,h)/2 * max(amount, 0.8)`, `linear-gradient-debug` across the frame's own width whatever axis the brush carried, and `solid` as one flat colour. `metal_l2c_wbtest.mbt` pins each against the fallback frame it replaced (\"a checker effect alternates its cells rather than painting its fallback\", \"a vignette effect fades to black before the frame edge\", \"a linear-gradient-debug effect runs across its own frame, not its brush axis\", \"a solid effect paints one colour across a gradient fallback\"), and the checker/vignette formulations are the ones Skia raster and Sun paint rather than WGPU's, which normalizes each axis and so gets rectangular cells on a non-square frame. Partial because a name outside that list still paints the fallback brush and is counted in `shader_effects_fallback_painted` (\"an effect name with no formulation is drawn with its fallback brush\"), because there is no registry for an app-defined effect to resolve to, because only `uniforms[0]` is read (defaulted per effect the way the mainline routes default it), and because `solid` follows `moui_wgpu_renderer`'s flat reading while `moui_skia_renderer` answers that name with the whole fallback brush, so the two diagnostic routes and the mainline differ there. Remaining: only `uniforms[0]` is read, an app-defined name has no registry to resolve to so it paints its fallback brush and is counted, and `solid` follows WGPU's flat reading while Skia answers that name with the whole fallback brush."
    AsyncImage =>
      "The route drives the shared image lifecycle record: the first frame emits one `RendererImageLoadRequest` per unresolved source and paints nothing, `apply_metal_image_completion` installs the decoded RGBA8 as the texture the next frame samples, and a failed source re-requests only once its bytes are readable. `metal_image_wbtest.mbt` pins all three (`async images request once, then wait for the completion`, `the frame after an async completion paints the image pixels`, `a failed image re-requests only once its bytes become readable`). Partial because the two-frame lifecycle is proven renderer-local, and the mid-frame arrival through a real window's event loop has never been observed on this route. The mechanism exists and one shipped app uses it: `examples/momark/macos_skia/main.mbt:24` installs `@image_remote.remote_image_starter` before launching and runs the worker as a sibling of the pump — and that is the only entrypoint in the repository that installs a starter (no `macos_wgpu`, `linux_wgpu` or in-repo app does), so every other native route, this one included, takes the synchronous `immediate` default (`moui/backend/common/window_host_workflows.mbt:11`). Two facts make the missing observation this module's to wait for but not its to build: the starter defers `https:` sources only and completes a local file or `data:` URI inline (`moui/image_remote/remote_image.mbt`, `window_image_source_is_remote`), and the in-repo app this route drives feeds `data:` URIs (`examples/showcase/app/diagnostics/formatting.mbt:129`), so installing it there would change nothing; while the app that does fetch over a network is a git submodule pinned to released versions (`examples/momark`, resolving published modules), and this renderer, though registered `publication: public` at `checks/release-modules.json:68`, is still untracked work in this tree until a release line publishes it. The ordering is evidenced where it can be — `moui/backend/common/image/window_image_tasks_test.mbt:134,:176` drive the deferred starter with injected `spawn`/`post` hooks and assert the completion lands on the posting turn, not the requesting one. Residency is bounded by the route itself: each frame enforces a resident-bytes budget over the installed textures (default 64 MB, `METAL_IMAGE_MAX_RESIDENT_BYTES`, and a `MetalFrameContext` field so the automatic path is the one tests drive), releasing the least-recently-used texture through `moui_metal_image_release` and dropping its lookup, with `image_textures_released` recording how many left that frame. `metal_image_wbtest.mbt` (`an evicted image is requested again while the resident one keeps painting`) proves the release is not a disappearance: the next build either resolves the source again or requests it, and after the completion the fixture paints. What the route still cannot do is learn that a source was thrown away: `ImageResourceLifecycle::evict_disposed` has zero callers repo-wide, so eviction happens under byte pressure rather than on notification, and an unreachable source keeps its entry until the budget asks for it. The two do differ at teardown — `moui_skia_renderer/renderer_image.mbt:630-634` marks every cached source `ImageDisposed` before clearing its cache, while `MetalRenderer::dispose` releases the context and leaves its records as they were — and mirroring it is deliberately not done here: both host dispose paths are terminal (`moui/backend/common/frame/window_frame_state.mbt:166-172` refuses further frames, `moui/backend/common/window_host_workflows.mbt:16` drops the session it just disposed) and `RendererSession::image_diagnostics` has no production caller, so a stale record has nothing to mislead yet. Remaining: a presented-host async completion observation, which needs this module published so the one network-image app in the ecosystem can bind it plus give that build a Metal variant — a release-line and product decision, not renderer code; and reclamation on notification rather than on pressure: a source the framework has disposed is dropped only when the budget happens to need its slot, because `evict_disposed` has no callers anywhere and deciding when to call it is a policy above this module."
    EmojiText =>
      "A colour glyph keeps its own pixels end to end. `metal_geometry.mbt` gives a non-coverage placement a white vertex colour, so the text fragment's multiply by the run colour cannot repaint it, and one identical 6x8 glyph box pinned twice with only the raster format changed shows the branch goes both ways (`metal_text_wbtest.mbt`: `a colour glyph paints its own pixels, not the run colour`, `the same footprint as a coverage mask takes the run colour`). `moui_tests/metal_renderer_smoke/macos` puts two real CoreText samples through the route — a single-codepoint smile and a ZWJ sequence — each drawn once under a pure-red run and once under a pure-green run: 698 and 1011 inked pixels at 32pt, of which 686 and 993 light the green channel and 698 and 1011 the red; the check now walks 24, 32 and 48pt per sample (the ZWJ sequence inks 576, 1011 and 2290 with 571, 993 and 2240 of them green) and draws each through a layer composite too (2098 inked, 2048 green, `layers` 1), and a noop engine lights nothing at any size, on either pass. Partial because the evidence samples the matrix rather than covering it, and whether a codepoint arrives as colour at all is the shared provider's `kCTFontColorGlyphsTrait` decision (`moui_text_providers/coretext`), which this module sees only as a format flag. A colour glyph inside a group is no longer one of the missing halves: `metal_text_wbtest.mbt` puts it through a plain layer composite (\"a colour glyph keeps its pixels through a layer composite\"), through a filter that rewrites colour (\"a filter group rewrites a colour glyph as colour, not as coverage\") and through a rect-masked layer that has to clip it as well as tint it (\"a masked layer clips its colour glyph as well as its pixels\"); a rounded-mask group (\"a rounded-mask group rounds a colour glyph as well as tinting it\") and a doubly nested pair (\"two nested groups carry a colour glyph's own pixels\") have now — the first of which is also this route's only device probe of a `LayerMask::RoundedMask` carried by a `PushLayer`, since until now the rounded mask SDF was reached only from inside `PushRoundedClip`. The limit is stated rather than padded: all five of these group cases share one mechanism (the colour branch is taken at the vertex, and a composite neither reads nor can re-tint it), so they widen the contexts in which the claim holds rather than proving a new path; each is nevertheless pinned twice, because the rounded case also asserts the corner the radius must clear (dropping the mask's kind fails that pixel while the colour asserts stay green) and the nested case counts two composites reaching the screen. Remaining: the matrix is sampled, not covered -- Retina read-back colour is proven for one run per sample on the main pass, while the 24/32/48pt sizes and the layer composite are proven at scale 1.0, so a combination nobody has read back is a combination nobody has promised -- and whether a codepoint arrives as colour at all is the shared provider's `kCTFontColorGlyphsTrait` decision, not this module's."
  }
}

///|
pub fn metal_feature_capabilities() -> Array[
  @render.RendererBackendFeatureCapability,
] {
  let all_features = [
    @render.RendererFeature::Rect,
    RoundedRect,
    Gradient,
    Shadow,
    Text,
    Image,
    Clip,
    Transform,
    Opacity,
    LayerCompositing,
    BlendMode,
    FilterEffect,
    PathVector,
    ShaderEffect,
    TextShaping,
    EmojiText,
    AsyncImage,
  ]
  all_features.map(feature => {
    @render.RendererBackendFeatureCapability::new(
      provider_id="metal-direct",
      feature~,
      backend=NativeDirectGpu,
      status=metal_feature_status(feature),
      note=metal_feature_note(feature),
    )
  })
}