一键导入
fs-skia-layout-readability
Work on generated game HUD/status layout readability, gameplay-region bounds, and public scene/host/update naming guidance.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Work on generated game HUD/status layout readability, gameplay-region bounds, and public scene/host/update naming guidance.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Consumer-facing guide to hosting an interactive FS.Skia.UI app — the keyboard/pointer input surface, the preview-vs-tree render distinction, and the windowed-fullscreen blur caveat.
Build Skia-rendered FS.Skia.UI Controls, rich text, chart controls, graph controls, DataGrid, custom wrappers, and generated product examples.
Generated product guidance for Skia-rendered FS.Skia.UI Controls, rich text, chart controls, graph controls, DataGrid, and custom wrappers.
Wire a generated FS.Skia.UI product to the desktop viewer host.
Understand and work with the internal keyed VDOM diff over the lowered Control<'msg> IR (feature 067) — its key-first-then-positional matching, the NodePatch/ChildOp operation set, the totality/determinism/identity-at-rest/round-trip invariants, and the module's disposition (internal, property-tested, wired onto the live render path via RetainedRender in feature 091 and current through feature 103 — layout/bounds cache, injected-delta animation clock, visual-state cross-fade). Use when reading the diff invariants, extending the property tests, or working on the wired retained render path.
Maintainer-facing guide to the FS.Skia.UI.Controls.Elmish interactive-host seam — how runInteractiveApp drives the retained render structure each frame (RetainedRender.step over the keyed diff), advances per-identity animation clocks from an injected Tick delta, stamps runtime visual state pre-reconcile, routes keys focus-first through routeFocusedKey, and resolves pointer hits to a stable identity via retainedHitTest. Use when reading or extending the live controls host loop, the per-frame retained-state/clock/visual-state wiring, or the key/pointer routing seam.
| name | fs-skia-layout-readability |
| description | Work on generated game HUD/status layout readability, gameplay-region bounds, and public scene/host/update naming guidance. |
Use this skill for tasks that change generated game HUD/status layout,
gameplay-region bounds, readable-layout proof, generated layout validation, or
public scene/host/update naming guidance. This is the layout-design half of
the former fs-skia-layout-evidence skill; the deterministic-evidence-mode and
host-warning-classification half lives in fs-skia-evidence-mode.
Such tasks must declare fs-skia-layout-readability in tasks.deps.yml and
mirror it on the matching tasks.md line. Resolve this skill before
implementation starts and record the resolved path in the active feature's
readiness evidence.
Public evidence contracts must start in .fsi files before implementation.
Readable-layout evidence must report HUD region, gameplay region, text or entity
bounds, and overlap status.
Use the ReadableLayout proof level only when HUD region, gameplay region, HUD
text bounds, gameplay entity bounds, and non-overlap diagnostics are all
present. When only render hashes or scene metadata exist — not readability —
defer to fs-skia-evidence-mode and its DeterministicRenderOnly level.
Public guidance must use app-owned names when showing consumer signatures or tests:
Product.Program.view for the scene-returning function.Product.Program.generatedHost for the generated host value.Product.Program.update for reducer tests or signatures.Capture and assert scene layout bounds against the readable-layout contract. The driven surface is the Layout/Scene product packages; an evidence check confirms the HUD region and gameplay entity bounds do not overlap:
open FS.Skia.UI.Scene
open FS.Skia.UI.Testing
// Render the app scene and capture readable-layout evidence.
let scene = Product.Program.view Product.Program.initialModel
let evidence = LayoutEvidence.capture scene
// Assert reserved HUD region and gameplay entity bounds, then prove non-overlap.
let hud = evidence |> LayoutEvidence.requireRegion "hud"
let gameplay = evidence |> LayoutEvidence.requireRegion "gameplay"
match LayoutEvidence.proofLevel evidence with
| ReadableLayout when not (Bounds.intersects hud gameplay) ->
printfn "ReadableLayout proven: HUD %A clear of gameplay %A" hud gameplay
| _ -> failwith "not readable-layout proof; classify with fs-skia-evidence-mode"
Reach the readable-layout proof by design, not by gate-driven trial and error. The intended pattern has three parts, applied in order:
hud) whose bounds are derived from the surface size, not from
where text happens to land.gameplay region. Every gameplay coordinate —
spawning, movement, wrapping, clamping, collision, and active-entity bounds —
must be expressed in and clamped to the gameplay region, so entities can never
stray under the HUD band. Clamp at the policy boundary, not after rendering.This pattern makes ReadableLayout the natural outcome: the HUD region and gameplay
region are distinct named rectangles by construction, and the clamp keeps
NoLayoutOverlap true at both default and constrained sizes. A layout that proves
readable only after nudging specific pixel offsets has skipped step 1 or step 2.
FS.Skia.UI.SkillSupport.Hud.reserveHudBandEvery arcade demo re-implemented step 1 the same way, so as of feature 062 (FR-010)
it is shipped real API in FS.Skia.UI.SkillSupport.Hud — no longer a
re-derived per-game convention. Reach for it before hand-rolling the band math:
open FS.Skia.UI.SkillSupport
// surface = full extent along the axis (e.g. window height); bandSize = HUD thickness.
let layout = Hud.reserveHudBand 600.0 48.0 Hud.Top
// layout.HudBand = { Offset = 0.0; Size = 48.0 }
// layout.Gameplay = { Offset = 48.0; Size = 552.0 } // clamp all gameplay to this
It returns plain float bands (no Scene.Rect dependency — convert to your own
geometry at the call site), clamps HudBand.Size = min bandSize surface, and
guarantees Gameplay.Size = surface − HudBand.Size ≥ 0 with the two bands
partitioning the surface. Clamp every gameplay coordinate to layout.Gameplay
(step 2) and overdraw the HUD last (step 3). The reference implementation below
shows the same split if you need a Scene.Rect-typed variant in your own code:
// gameplayRegion = surface − reserved band; the band is anchored to one edge.
let reserveHudBand (edge: Edge) (bandHeight: float) (surface: Rect) : Rect * Rect =
let band, gameplay =
match edge with
| Edge.Top -> { surface with Height = bandHeight },
{ surface with Y = surface.Y + bandHeight; Height = surface.Height - bandHeight }
| Edge.Bottom -> { surface with Y = surface.Bottom - bandHeight; Height = bandHeight },
{ surface with Height = surface.Height - bandHeight }
band, gameplay // name them `hud` / `gameplay`; clamp all gameplay to `gameplay`; overdraw `hud` last
Derive the band height from the surface size (not from where text lands), clamp
every gameplay coordinate to the returned gameplay rectangle (step 2), and
overdraw the hud rectangle last (step 3). Prefer the shipped
Hud.reserveHudBand above for the band math; this Rect-typed form is only the
reference for a consumer that wants the result already in Scene.Rect geometry.
FS.Skia.UI.SkillSupport.Wrap.wrapDeltaXToroidal (wrap-around) worlds — Asteroids, Space Invaders, Lunar Lander — kept
re-deriving the same shortest-wrap-aware delta, so as of feature 063 (FR-010) it is
shipped real API in FS.Skia.UI.SkillSupport.Wrap. Reach for it instead of
hand-rolling the seam arithmetic in your update:
open FS.Skia.UI.SkillSupport
// Shortest signed delta from fromX to toX on a toroidal axis of width worldWidth.
Wrap.wrapDeltaX 100.0 90.0 10.0 // 20.0 (wraps forward across the seam, not -80)
Wrap.wrapDeltaX 100.0 10.0 90.0 // -20.0 (wraps backward, not +80)
Wrap.wrapDeltaX 100.0 42.0 42.0 // 0.0 (identity)
It is pure scalar arithmetic (no state, no I/O, no Scene/Layout dependency, so
SkillSupport stays dependency-light), deterministic, and returns the signed distance of
least magnitude in (-worldWidth/2, worldWidth/2]. Thread it through your pure Elmish
update (e.g. camera-relative or AI targeting on a wrap-around world).
A camera-centered projection (world → screen with the camera following the player,
e.g. LunarLander1/src/LunarLander1/View.fs:60-61) recurred too, but it is documented
here and deliberately NOT shipped. Rationale: it is a closure over per-game state
(player position, view scale, screen center), it returns a Scene.Point (a soft Scene
dependency SkillSupport deliberately avoids), and its shape varies per game
(zoom-centered here, parallax or fixed elsewhere) — it is a View concern, not a
shippable dependency-light scalar helper. Next-recurrence bar: ship a camera helper
only if a stable, game-agnostic projection signature (one that does not pull Scene
into SkillSupport) recurs across ≥3 demos; until then, write it inline in your View.
Prefer repository targets over ad-hoc command sequences:
./fake.sh build -t GeneratedGuidanceCheck
./fake.sh build -t GeneratedProductCheck
./fake.sh build -t TemplateCheck
./fake.sh build -t EvidenceGraph
Generated game samples that claim readability must reserve a named HUD/status region, keep active gameplay entities in the gameplay region, validate default and constrained sizes, and fail when HUD/HUD or HUD/gameplay overlap is detected. Unsupported host or font/layout facts must be explicit and must not be reported as readable layout proof.
Once a HUD region is reserved, movement, wrapping, spawning, clamping, collisions, and active entity bounds must use gameplay-region coordinates.
Before you swap the scaffold game model, read docs/scaffold-map.md — it names
which generated src/Product/** files are durable vs replaceable, the
GovernanceTests-durable / BehaviorTests-replaceable split, the must-survive
source-scan strings, and the pre-design fs-skia-scene record-label-collision
pointer.
Keep pure layout classifiers in Scene or Testing contracts. Do not move viewer launch, filesystem, package restore, process, font host, or window-system effects into pure layout helpers. When layout evidence collection needs I/O, model the request and result explicitly and keep execution at the interpreter or build-target edge.
When a problem outlasts reasonable in-repo attempts, extensive external research is
mandatory — consult official online docs first (the F#/.NET docs and the driven
library's own documentation/API reference), then community sources (forums, Reddit, Q&A
sites, issue trackers and changelogs). Record the findings and resolving links in the
feature's specs/<feature>/feedback/ folder and, for durable lessons, in this skill's
Sources line. Offline, the mandate degrades to recording "research blocked — "
rather than hard-failing the phase.