2 Commits
Author SHA1 Message Date
Valentin Popov ff7993223f docs: align renderer and evidence status
Docs Deploy / Build and Deploy MkDocs (push) Successful in 34s
Test / Lint (push) Failing after 1m49s
Test / Test (push) Has been skipped
Test / Render parity (push) Has been skipped
2026-07-04 01:54:25 +04:00
Valentin Popov 08550849c7 chore: clarify planning acceptance semantics 2026-07-04 01:50:59 +04:00
12 changed files with 412 additions and 30 deletions
+11 -1
View File
@@ -24,7 +24,17 @@ Open source проект с реализацией компонентов игр
- [crates/fparkan-rsli](crates/fparkan-rsli) — чтение, lookup и lossless roundtrip архивов RsLi. - [crates/fparkan-rsli](crates/fparkan-rsli) — чтение, lookup и lossless roundtrip архивов RsLi.
- [crates/fparkan-msh](crates/fparkan-msh) — validated static MSH geometry. - [crates/fparkan-msh](crates/fparkan-msh) — validated static MSH geometry.
- [crates/fparkan-runtime](crates/fparkan-runtime) — transactional mission loading и headless runtime foundation. - [crates/fparkan-runtime](crates/fparkan-runtime) — transactional mission loading и headless runtime foundation.
- [apps/fparkan-cli](apps/fparkan-cli), [apps/fparkan-viewer](apps/fparkan-viewer), [apps/fparkan-headless](apps/fparkan-headless), [apps/fparkan-game](apps/fparkan-game) — composition roots. - [apps/fparkan-cli](apps/fparkan-cli) — CLI для архивов, графов и acceptance-отчетов.
- [apps/fparkan-viewer](apps/fparkan-viewer) — inspection-only CLI для archive/model/texture/map без live Vulkan draw path.
- [apps/fparkan-headless](apps/fparkan-headless) — headless runtime composition root.
- [apps/fparkan-game](apps/fparkan-game) — render-planning composition root; сейчас выдает planning report, а не живой отрисованный кадр.
## Текущий статус рендера
- `fparkan-vulkan-smoke` доказывает живой Stage 0 Vulkan triangle path с native window, swapchain и validation telemetry.
- `VulkanPlanningBackend` и `fparkan-game` подтверждают только deterministic command planning/capture, а не draw пикселей.
- `fparkan-viewer` пока является инспектором ассетов. Stage 3 GPU vertical slice для оригинального `MSH`/`Texm`/`WEAR`/`MAT0`/terrain еще не закрыт.
- Truth table и evidence-артефакты вынесены в [`docs/rendering/renderer_truth_table.md`](docs/rendering/renderer_truth_table.md) и [`docs/evidence/`](docs/evidence).
## Тестирование ## Тестирование
+4 -4
View File
@@ -19,7 +19,7 @@
) )
)] )]
#![allow(clippy::print_stderr, clippy::print_stdout)] #![allow(clippy::print_stderr, clippy::print_stdout)]
//! `FParkan` rendered game composition root. //! `FParkan` render-planning composition root.
use fparkan_assets::PreparedVisual; use fparkan_assets::PreparedVisual;
use fparkan_platform::WindowPort; use fparkan_platform::WindowPort;
@@ -98,7 +98,7 @@ fn run(args: &[String]) -> Result<String, String> {
let capture_report = backend.report(); let capture_report = backend.report();
Ok(format!( Ok(format!(
"{{\"mission\":{},\"objects\":{},\"frames\":{},\"tick\":{},\"draws\":{},\"captures\":{},\"last_capture_bytes\":{},\"hash\":{}}}", "{{\"report_kind\":\"render-planning\",\"backend\":\"vulkan-planning\",\"window\":\"synthetic\",\"mission\":{},\"objects\":{},\"frames\":{},\"tick\":{},\"draws\":{},\"submission_plans\":{},\"last_command_capture_bytes\":{},\"hash\":{}}}",
json_string(&args.mission), json_string(&args.mission),
loaded.object_count, loaded.object_count,
args.frames, args.frames,
@@ -325,12 +325,12 @@ mod tests {
RenderCase { RenderCase {
root: "IS", root: "IS",
mission: "MISSIONS/CAMPAIGN/CAMPAIGN.00/Mission.01/data.tma", mission: "MISSIONS/CAMPAIGN/CAMPAIGN.00/Mission.01/data.tma",
expected: "{\"mission\":\"MISSIONS/CAMPAIGN/CAMPAIGN.00/Mission.01/data.tma\",\"objects\":33,\"frames\":1,\"tick\":1,\"draws\":33,\"captures\":1,\"last_capture_bytes\":810,\"hash\":\"ca17cc76e55c45e83c1c9c1c088e84bf1a698be91a7730943210fe27596af841\"}", expected: "{\"report_kind\":\"render-planning\",\"backend\":\"vulkan-planning\",\"window\":\"synthetic\",\"mission\":\"MISSIONS/CAMPAIGN/CAMPAIGN.00/Mission.01/data.tma\",\"objects\":33,\"frames\":1,\"tick\":1,\"draws\":33,\"submission_plans\":1,\"last_command_capture_bytes\":810,\"hash\":\"ca17cc76e55c45e83c1c9c1c088e84bf1a698be91a7730943210fe27596af841\"}",
}, },
RenderCase { RenderCase {
root: "IS2", root: "IS2",
mission: "MISSIONS/Campaign/CAMPAIGN.00/Mission.02/data.tma", mission: "MISSIONS/Campaign/CAMPAIGN.00/Mission.02/data.tma",
expected: "{\"mission\":\"MISSIONS/Campaign/CAMPAIGN.00/Mission.02/data.tma\",\"objects\":10,\"frames\":1,\"tick\":1,\"draws\":10,\"captures\":1,\"last_capture_bytes\":235,\"hash\":\"5d720b3ab690076a398a79a404850bbeaee2e33811b5bb570ec8a96d4a7a2fc4\"}", expected: "{\"report_kind\":\"render-planning\",\"backend\":\"vulkan-planning\",\"window\":\"synthetic\",\"mission\":\"MISSIONS/Campaign/CAMPAIGN.00/Mission.02/data.tma\",\"objects\":10,\"frames\":1,\"tick\":1,\"draws\":10,\"submission_plans\":1,\"last_command_capture_bytes\":235,\"hash\":\"5d720b3ab690076a398a79a404850bbeaee2e33811b5bb570ec8a96d4a7a2fc4\"}",
}, },
] { ] {
assert_eq!( assert_eq!(
+9 -9
View File
@@ -19,7 +19,7 @@
) )
)] )]
#![allow(clippy::print_stderr, clippy::print_stdout)] #![allow(clippy::print_stderr, clippy::print_stdout)]
//! `FParkan` asset viewer composition root. //! `FParkan` asset inspection composition root.
use fparkan_inspection::{ use fparkan_inspection::{
inspect_land_file, inspect_model_from_root, inspect_texture_from_root, ArchiveInspection, inspect_land_file, inspect_model_from_root, inspect_texture_from_root, ArchiveInspection,
@@ -68,14 +68,14 @@ fn inspect_archive(args: &[String]) -> Result<String, String> {
lookup_order_valid, lookup_order_valid,
sample, sample,
} => Ok(format!( } => Ok(format!(
"{{\"kind\":\"NRes\",\"path\":{},\"entries\":{},\"lookup_order_valid\":{},\"sample\":[{}]}}", "{{\"report_kind\":\"archive-inspection\",\"kind\":\"NRes\",\"path\":{},\"entries\":{},\"lookup_order_valid\":{},\"sample\":[{}]}}",
json_string(&file.display().to_string()), json_string(&file.display().to_string()),
entries, entries,
lookup_order_valid, lookup_order_valid,
render_nres_entries(&sample) render_nres_entries(&sample)
)), )),
ArchiveInspection::Rsli { entries } => Ok(format!( ArchiveInspection::Rsli { entries } => Ok(format!(
"{{\"kind\":\"RsLi\",\"path\":{},\"entries\":{}}}", "{{\"report_kind\":\"archive-inspection\",\"kind\":\"RsLi\",\"path\":{},\"entries\":{}}}",
json_string(&file.display().to_string()), json_string(&file.display().to_string()),
entries entries
)), )),
@@ -92,7 +92,7 @@ fn inspect_model(args: &[String]) -> Result<String, String> {
let inspection = inspect_model_from_root(&query.root, &query.archive, &query.name)?; let inspection = inspect_model_from_root(&query.root, &query.archive, &query.name)?;
Ok(format!( Ok(format!(
"{{\"kind\":\"model\",\"archive\":{},\"name\":{},\"streams\":{},\"nodes\":{},\"slots\":{},\"positions\":{},\"indices\":{},\"batches\":{}}}", "{{\"report_kind\":\"model-inspection\",\"kind\":\"model\",\"archive\":{},\"name\":{},\"streams\":{},\"nodes\":{},\"slots\":{},\"positions\":{},\"indices\":{},\"batches\":{}}}",
json_string(&query.archive), json_string(&query.archive),
json_string(&query.name), json_string(&query.name),
inspection.streams, inspection.streams,
@@ -136,7 +136,7 @@ impl ViewerModelService {
.count(); .count();
Ok(format!( Ok(format!(
"{{\"kind\":\"model\",\"fixture\":{},\"service\":\"synthetic-model\",\"draw_commands\":{draw_commands}}}", "{{\"report_kind\":\"model-inspection\",\"kind\":\"model\",\"fixture\":{},\"service\":\"synthetic-model-inspection\",\"draw_commands\":{draw_commands}}}",
json_string(fixture) json_string(fixture)
)) ))
} }
@@ -147,7 +147,7 @@ fn inspect_texture(args: &[String]) -> Result<String, String> {
let inspection = inspect_texture_from_root(&query.root, &query.archive, &query.name)?; let inspection = inspect_texture_from_root(&query.root, &query.archive, &query.name)?;
Ok(format!( Ok(format!(
"{{\"kind\":\"texture\",\"archive\":{},\"name\":{},\"width\":{},\"height\":{},\"format\":{},\"mips\":{},\"pages\":{}}}", "{{\"report_kind\":\"texture-inspection\",\"kind\":\"texture\",\"archive\":{},\"name\":{},\"width\":{},\"height\":{},\"format\":{},\"mips\":{},\"pages\":{}}}",
json_string(&query.archive), json_string(&query.archive),
json_string(&query.name), json_string(&query.name),
inspection.width, inspection.width,
@@ -180,7 +180,7 @@ fn inspect_map(args: &[String]) -> Result<String, String> {
fn render_map_inspection_json(path: &str, kind: &str, inspection: &MapInspection) -> String { fn render_map_inspection_json(path: &str, kind: &str, inspection: &MapInspection) -> String {
match kind { match kind {
"land-msh" => format!( "land-msh" => format!(
"{{\"kind\":\"land-msh\",\"path\":{},\"streams\":{},\"positions\":{},\"faces\":{},\"slots\":{}}}", "{{\"report_kind\":\"map-inspection\",\"kind\":\"land-msh\",\"path\":{},\"streams\":{},\"positions\":{},\"faces\":{},\"slots\":{}}}",
json_string(path), json_string(path),
inspection.streams, inspection.streams,
inspection.positions, inspection.positions,
@@ -188,7 +188,7 @@ fn render_map_inspection_json(path: &str, kind: &str, inspection: &MapInspection
inspection.slots inspection.slots
), ),
"land-map" => format!( "land-map" => format!(
"{{\"kind\":\"land-map\",\"path\":{},\"areals\":{},\"declared_areals\":{},\"grid_width\":{},\"grid_height\":{}}}", "{{\"report_kind\":\"map-inspection\",\"kind\":\"land-map\",\"path\":{},\"areals\":{},\"declared_areals\":{},\"grid_width\":{},\"grid_height\":{}}}",
json_string(path), json_string(path),
inspection.areals, inspection.areals,
inspection.declared_areals, inspection.declared_areals,
@@ -342,7 +342,7 @@ mod tests {
fn model_fixture_uses_viewer_service_and_render_commands() -> Result<(), String> { fn model_fixture_uses_viewer_service_and_render_commands() -> Result<(), String> {
assert_eq!( assert_eq!(
run(&strings(&["model", "--fixture", "synthetic/model-basic"]))?, run(&strings(&["model", "--fixture", "synthetic/model-basic"]))?,
"{\"kind\":\"model\",\"fixture\":\"synthetic/model-basic\",\"service\":\"synthetic-model\",\"draw_commands\":1}" "{\"report_kind\":\"model-inspection\",\"kind\":\"model\",\"fixture\":\"synthetic/model-basic\",\"service\":\"synthetic-model-inspection\",\"draw_commands\":1}"
); );
Ok(()) Ok(())
} }
+5
View File
@@ -389,6 +389,11 @@ pub trait RenderBackend {
fn execute(&mut self, commands: &RenderCommandList) -> Result<FrameOutput, RenderError>; fn execute(&mut self, commands: &RenderCommandList) -> Result<FrameOutput, RenderError>;
} }
/// Marker trait for backends that execute draws against a live GPU.
///
/// Planning and capture-only backends must not implement this trait.
pub trait GpuRenderBackend: RenderBackend {}
/// Backend that validates commands and intentionally produces no pixels. /// Backend that validates commands and intentionally produces no pixels.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct NullBackend; pub struct NullBackend;
+56
View File
@@ -0,0 +1,56 @@
# Original Engine Hashes
Страница фиксирует минимальный статический baseline, на который должны
ссылаться capture fixtures и Stage 4 evidence.
## Scope
- Источник: локальная статическая сверка Part 1 (`IS`) и Part 2 (`IS2`).
- Метод: SHA-256, export/import tables, `objdump -p`, `strings`.
- Эта страница не заменяет динамические traces: она задаёт only-if-match
binary baseline для дальнейших runtime captures.
## Stable binaries across Part 1 / Part 2
| Binary | SHA-256 | Size | Notes |
| --- | --- | ---: | --- |
| `Ngi32.dll` | `bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5` | 253952 | Shared low-level render/resource/audio boundary |
| `World3D.dll` | `17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e` | 208896 | Shared gameplay/world/render lifecycle baseline |
| `Terrain.dll` | `6d3e68f0e15b297f6c184af3113baf1f31e19c3326c18f0150dec659242ed667` | 708608 | Shared terrain/shade/world baseline |
| `iron_3d.exe` / `iron_3d_p2.exe` | `f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7` | 36864 | Shared launcher binary |
## Divergent binaries across Part 1 / Part 2
Эти модули нельзя автоматически считать behavior-compatible между частями:
- `AniMesh.dll`
- `Effect.dll`
- `iron3d.dll`
- `services.dll`
- `Control.dll`
- `ArealMap.dll`
## Practical use
1. Frame-order traces для `World3D.dll`, `Terrain.dll` и `Ngi32.dll` можно
привязывать к shared profile, пока hash совпадает.
2. Animation and FX captures обязаны храниться раздельно для Part 1 и Part 2,
потому что `AniMesh.dll` и `Effect.dll` отличаются.
3. Любой runtime fixture должен записывать минимум:
- `game_part`
- `module_name`
- `module_sha256`
- `mission`
- `frame_or_tick`
- `schema_version`
## Export / import focus for Stage 4
- `World3D.dll`: `stdCalculateGame`, `stdRenderGame`, `sendEndOfRender`
- `Terrain.dll`: `GetShade`, `GetWorld`, `stdSetCurrentCamera2`
- `AniMesh.dll`: `LoadAgent`, `LoadAniMesh`
- `Effect.dll`: `CreateFxManager`, `InitializeSettings`
- `Ngi32.dll`: `niGet3DRender`, `n3dPrimitive`, `n3dEndScene`, `rsLoadTexture`, `rsLoadMultiTexture`
Если будущий capture fixture не указывает, к какому hash он относится, такой
fixture нельзя считать acceptance evidence.
+107
View File
@@ -0,0 +1,107 @@
# Stage 4 Capture Schema
Stage 4 нельзя закрывать набором ad-hoc логов. Нужна схема, по которой
animation, FX и rendered frame captures сравниваются между Part 1, Part 2 и
современной реализацией.
## Goals
- сделать captures пригодными для автоматического diff;
- не хранить host-specific пути, временные каталоги и нестабильные handles;
- связывать frame traces, command captures и pixel artifacts общим identity.
## Common envelope
```json
{
"schema_version": "fparkan-stage4-capture-v1",
"capture_kind": "frame-trace | animation-pose | fx-lifecycle | render-frame",
"game_part": "part1 | part2",
"mission": "MISSIONS/.../data.tma",
"frame_id": 123,
"tick": 123,
"module_hashes": {
"World3D.dll": "sha256...",
"Terrain.dll": "sha256...",
"AniMesh.dll": "sha256...",
"Effect.dll": "sha256..."
},
"tool_version": "codex/manual/fixture version",
"notes": []
}
```
## Capture kinds
### `frame-trace`
Используется для порядка фаз и внешних вызовов.
Required fields:
- `events`: ordered list of `{ phase, symbol, sequence, object_id?, fx_id?, camera_id? }`
- `queue_counters`: deferred operations, visible objects, emitted FX, UI callbacks
- `rng_state`: optional, if recoverable
### `animation-pose`
Используется для x87 / portable sampler parity.
Required fields:
- `clip_id`
- `node_index`
- `sample_time`
- `numeric_profile`
- `translation`
- `rotation_quat`
- `scale`
- `matrix_hash`
### `fx-lifecycle`
Используется для create/update/emit/stop parity.
Required fields:
- `fx_id`
- `instance_id`
- `time`
- `opcode_events`
- `rng_calls`
- `resource_refs`
- `emissions`
### `render-frame`
Связывает backend-neutral snapshot с live Vulkan output.
Required fields:
- `camera`
- `visible_object_ids`
- `draws`
- `pipeline_keys`
- `resource_ids`
- `validation`
- `pixel_artifact`
## Stability rules
1. Не записывать абсолютные host paths.
2. Не записывать raw pointer addresses как identity fields.
3. `frame_id` должен совпадать между trace, command capture и pixel artifact.
4. GPU-specific transient handles допустимы только внутри diagnostics fields и
не участвуют в canonical equality.
5. Любой capture without `module_hashes` считается informational, а не
acceptance-grade.
## Acceptance mapping
- `S4-TRACE-*` rows читают `frame-trace`
- `S4-ANIM-*` rows читают `animation-pose`
- `S4-FX-*` rows читают `fx-lifecycle`
- `S4-VK-*` и `S4-PIXEL-*` rows читают `render-frame`
Эта схема intentionally минимальна. Новые поля можно добавлять, но нельзя
ломать перечисленные identity and parity anchors без смены `schema_version`.
+39
View File
@@ -0,0 +1,39 @@
# Renderer Truth Table
Эта страница нужна для одной вещи: не позволять путям smoke, planning и capture
выглядеть как «почти готовый renderer». Каждый путь доказывает разный класс
свойств, и acceptance не должен смешивать их.
## Краткая матрица
| Path | Native window / swapchain | Draws pixels | Uses original assets | Acceptance class | Что доказывает | Чего не доказывает |
| --- | --- | --- | --- | --- | --- | --- |
| `fparkan-vulkan-smoke` / `VulkanSmokeRenderer` | Yes | Yes | No | `covered-gpu` for Stage 0 smoke-only IDs | Loader, instance, surface, swapchain, submit/present, validation-clean triangle path | Model upload, texture sampling, descriptors, terrain, gameplay rendering |
| `VulkanPlanningBackend` | No | No | Optional CPU-side IDs only | `covered-planning` | Deterministic command validation, canonical capture, frame submission planning | Любой live GPU draw, pixel parity, validation-clean asset frame |
| `RecordingBackend` | No | No | Optional CPU-side IDs only | `covered-planning` | Stable command capture for backend-neutral tests | Native window, Vulkan, GPU resource lifetime, pixels |
| `NullBackend` | No | No | Optional CPU-side IDs only | Usually `covered` for validation-only rows | Command stream framing and bounds validation | Capture stability, GPU execution, pixels |
| `VulkanAssetRenderer` | Yes | Yes | Yes | `covered-gpu` | Static original asset rendering: MSH/Texm/WEAR/MAT0/terrain through Vulkan | Animation/FX parity unless explicitly wired |
| Future rendered `fparkan-game` mode | Yes | Yes | Yes | `covered-gpu` plus original-evidence IDs | Mission-driven render snapshot execution and pixel capture | Original-runtime parity for animation/FX/x87 without dedicated captures |
## Rules
1. IDs со смыслом `VK`, `GPU`, `DRAW`, `PIXEL`, `VALIDATION` или `RENDERED`
на Stage 3+ не могут закрываться через `NullBackend`, `RecordingBackend`
или `VulkanPlanningBackend`.
2. `covered-planning` означает command planning/capture evidence. Этот статус
никогда не считается доказательством draw пикселей.
3. `covered-stub` зарезервирован для явно помеченных `STUB` acceptance rows и
не считается compatibility closure для FX lifecycle.
4. `covered-gpu` требует live native handles, реальный draw path и связанный
renderer artifact: report, capture или approved pixel.
## Current repository status
- Реальный Vulkan в репозитории есть, но только как smoke triangle path.
- `apps/fparkan-game` сейчас выдает `render-planning` JSON report поверх
synthetic window descriptor и `VulkanPlanningBackend`.
- `apps/fparkan-viewer` сейчас inspection-only CLI и не открывает live Vulkan
asset viewer.
- Следующий реальный milestone для rendered acceptance: `VulkanAssetRenderer`
с upload/draw/capture path для хотя бы одной оригинальной модели и одного
terrain slice.
+14
View File
@@ -219,6 +219,13 @@ graph.
текстурированную статическую модель из исходного архива. Красивый viewer всё ещё текстурированную статическую модель из исходного архива. Красивый viewer всё ещё
означает только asset compatibility, а не готовую игру. означает только asset compatibility, а не готовую игру.
Текущее состояние репозитория нужно формулировать строже. `apps/fparkan-viewer`
сейчас является inspection CLI и synthetic command producer, а не live Vulkan
asset viewer. Реальный Vulkan в репозитории сегодня доказан только через
Stage 0 smoke triangle path; Stage 3 GPU vertical slice для оригинального
`MSH` + `Texm` + `WEAR/MAT0` + terrain остаётся блокером. Для различения
smoke, planning и live GPU путей используйте [таблицу правды renderer paths](../rendering/renderer_truth_table.md).
### Этап 4. Анимация и эффекты ### Этап 4. Анимация и эффекты
- реализовать MSH type 8/type 19 sampling и hierarchy; - реализовать MSH type 8/type 19 sampling и hierarchy;
@@ -232,6 +239,13 @@ graph.
923/1 065 FXID создаются без parser errors; перезапуск одинакового effect seed 923/1 065 FXID создаются без parser errors; перезапуск одинакового effect seed
даёт идентичный список emitted primitives. даёт идентичный список emitted primitives.
Текущее состояние репозитория опять же уже, чем целевой этап. В коде есть
portable reference sampler и детерминированный FX reference stub, но нет
runtime-captured parity для lifecycle/opcode semantics и нет Stage 4 rendered
acceptance поверх live Vulkan asset renderer. Поэтому rendered Stage 4 следует
считать заблокированным входным gate Stage 3, а parallel Stage 4 work вести
через captures, schemas и backend-neutral snapshots.
### Этап 5. Карта и мир ### Этап 5. Карта и мир
- реализовать `Land.msh` и corrected `TerrainFace28` layout; - реализовать `Land.msh` и corrected `TerrainFace28` layout;
+7
View File
@@ -215,6 +215,13 @@ RVA используются только для сопоставления и
implementation не должна встраивать их как постоянные игровые идентификаторы. implementation не должна встраивать их как постоянные игровые идентификаторы.
Таблица внутренних RVA хранится по SHA-256 конкретного модуля. Таблица внутренних RVA хранится по SHA-256 конкретного модуля.
Операционные evidence-артефакты, которые должны оставаться синхронизированными
с кодом и acceptance, вынесены в отдельные страницы:
- [Hashes и import/export summary оригинального движка](../evidence/original_engine_hashes.md)
- [Stage 4 capture schema](../evidence/stage4_capture_schema.md)
- [Renderer truth table](../rendering/renderer_truth_table.md)
Подтверждённые hashes неизменённых DLL: Подтверждённые hashes неизменённых DLL:
```text ```text
+9 -9
View File
@@ -1,5 +1,5 @@
# Acceptance coverage manifest. # Acceptance coverage manifest.
# Format: <acceptance-id>\t<covered|partial|blocked|omitted>\t<evidence> # Format: <acceptance-id>\t<covered|covered-planning|covered-stub|covered-gpu|partial|blocked|omitted>\t<evidence>
# Scope note: Stage 0 is closed for the macOS/codebase review scope. # Scope note: Stage 0 is closed for the macOS/codebase review scope.
# Scope note: cross-platform native runtime and hosted CI evidence remain deferred. # Scope note: cross-platform native runtime and hosted CI evidence remain deferred.
L0-COPYRIGHT-001 covered cargo test -p fparkan-corpus --offline report_json_contains_metrics_and_hashes_not_paths_or_payloads L0-COPYRIGHT-001 covered cargo test -p fparkan-corpus --offline report_json_contains_metrics_and_hashes_not_paths_or_payloads
@@ -32,7 +32,7 @@ S0-PLAT-001 covered cargo test -p fparkan-platform-winit --offline window_port_r
S0-PLAT-002 covered cargo clippy -p fparkan-platform -p fparkan-platform-winit --all-targets --all-features --locked -- -D warnings S0-PLAT-002 covered cargo clippy -p fparkan-platform -p fparkan-platform-winit --all-targets --all-features --locked -- -D warnings
S0-PLAT-003 covered cargo test -p fparkan-platform-winit --offline smoke_window_plan_requires_native_handles_and_nonzero_extent smoke_window_plan_rejects_zero_extent S0-PLAT-003 covered cargo test -p fparkan-platform-winit --offline smoke_window_plan_requires_native_handles_and_nonzero_extent smoke_window_plan_rejects_zero_extent
S0-PLAT-004 covered cargo test -p fparkan-platform-winit --offline smoke_window_app_requires_created_native_window smoke_window_app_rejects_synthetic_window_without_native_handles S0-PLAT-004 covered cargo test -p fparkan-platform-winit --offline smoke_window_app_requires_created_native_window smoke_window_app_rejects_synthetic_window_without_native_handles
S0-VK-001 covered cargo test -p fparkan-render-vulkan --offline planning_backend_tracks_render_request_and_simulated_present S0-VK-001 covered-planning cargo test -p fparkan-render-vulkan --offline planning_backend_tracks_render_request_and_simulated_present
S0-VK-002 covered cargo test -p fparkan-render-vulkan --offline device_scoring_is_deterministic_and_prefers_discrete_unified_queue S0-VK-002 covered cargo test -p fparkan-render-vulkan --offline device_scoring_is_deterministic_and_prefers_discrete_unified_queue
S0-VK-003 covered cargo test -p fparkan-render-vulkan --offline portability_subset_is_reported_and_enabled_when_exposed S0-VK-003 covered cargo test -p fparkan-render-vulkan --offline portability_subset_is_reported_and_enabled_when_exposed
S0-VK-004 covered cargo test -p fparkan-render-vulkan --offline rejects_missing_graphics_present_swapchain_and_format S0-VK-004 covered cargo test -p fparkan-render-vulkan --offline rejects_missing_graphics_present_swapchain_and_format
@@ -52,8 +52,8 @@ S0-VK-017 covered cargo test -p fparkan-render-vulkan --offline swapchain_plan_j
S0-VK-018 covered cargo test -p fparkan-render-vulkan --offline triangle_shader_manifest_hashes_are_stable checked_in_shader_manifest_matches_generated_report S0-VK-018 covered cargo test -p fparkan-render-vulkan --offline triangle_shader_manifest_hashes_are_stable checked_in_shader_manifest_matches_generated_report
S0-VK-019 covered cargo test -p fparkan-render-vulkan --offline shader_manifest_report_json_is_stable S0-VK-019 covered cargo test -p fparkan-render-vulkan --offline shader_manifest_report_json_is_stable
S0-VK-020 covered cargo test -p fparkan-render-vulkan --offline shader_manifest_rejects_invalid_spirv_containers S0-VK-020 covered cargo test -p fparkan-render-vulkan --offline shader_manifest_rejects_invalid_spirv_containers
S0-VK-021 covered cargo test -p fparkan-render-vulkan --offline frame_submission_plan_json_is_stable S0-VK-021 covered-planning cargo test -p fparkan-render-vulkan --offline frame_submission_plan_json_is_stable
S0-VK-022 covered cargo test -p fparkan-render-vulkan --offline planning_backend_tracks_render_request_and_simulated_present S0-VK-022 covered-planning cargo test -p fparkan-render-vulkan --offline planning_backend_tracks_render_request_and_simulated_present
S0-VK-023 covered cargo test -p fparkan-vulkan-smoke --offline rejects_false_pass_without_full_evidence blocked_report_includes_shader_manifest_and_bootstrap_status S0-VK-023 covered cargo test -p fparkan-vulkan-smoke --offline rejects_false_pass_without_full_evidence blocked_report_includes_shader_manifest_and_bootstrap_status
S0-VK-024 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_loader_probe formats_vulkan_api_version S0-VK-024 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_loader_probe formats_vulkan_api_version
S0-VK-025 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_instance_probe parses_instance_probe_as_loader_probe S0-VK-025 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_instance_probe parses_instance_probe_as_loader_probe
@@ -233,8 +233,8 @@ L3-P1-WEAR-001 covered cargo test -p fparkan-material --offline licensed_corpus_
L3-P2-WEAR-001 covered cargo test -p fparkan-material --offline licensed_corpus_mat0_and_wear_parse L3-P2-WEAR-001 covered cargo test -p fparkan-material --offline licensed_corpus_mat0_and_wear_parse
L3-P1-ASSET-001 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations L3-P1-ASSET-001 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations
L3-P2-ASSET-001 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations L3-P2-ASSET-001 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations
S2-PLANNING-CAPTURE-P1-001 covered cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures S2-PLANNING-CAPTURE-P1-001 covered-planning cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures
S2-PLANNING-CAPTURE-P2-001 covered cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures S2-PLANNING-CAPTURE-P2-001 covered-planning cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures
S3-WEAR-001 covered cargo test -p fparkan-material --offline wear_preserves_legacy_id_but_selects_by_index S3-WEAR-001 covered cargo test -p fparkan-material --offline wear_preserves_legacy_id_but_selects_by_index
S3-WEAR-002 covered cargo test -p fparkan-material --offline wear_requires_declared_rows S3-WEAR-002 covered cargo test -p fparkan-material --offline wear_requires_declared_rows
S3-WEAR-003 covered cargo test -p fparkan-material --offline wear_preserves_legacy_id_but_selects_by_index S3-WEAR-003 covered cargo test -p fparkan-material --offline wear_preserves_legacy_id_but_selects_by_index
@@ -292,7 +292,7 @@ S2-PLANNING-RENDER-004 covered cargo test -p fparkan-render --offline command_or
S2-PLANNING-RENDER-005 covered cargo test -p fparkan-render --offline command_capture_independent_of_snapshot_construction_order S2-PLANNING-RENDER-005 covered cargo test -p fparkan-render --offline command_capture_independent_of_snapshot_construction_order
S2-PLANNING-RENDER-006 covered cargo test -p fparkan-render --offline invalid_range_returns_contextual_error S2-PLANNING-RENDER-006 covered cargo test -p fparkan-render --offline invalid_range_returns_contextual_error
S2-PLANNING-RENDER-007 covered cargo test -p fparkan-render --offline capture_is_stable S2-PLANNING-RENDER-007 covered cargo test -p fparkan-render --offline capture_is_stable
S2-PLANNING-RENDER-008 covered cargo test -p fparkan-render --offline recording_backend_stores_captures S2-PLANNING-RENDER-008 covered-planning cargo test -p fparkan-render --offline recording_backend_stores_captures
S2-PLANNING-RENDER-009 covered cargo xtask policy S2-PLANNING-RENDER-009 covered cargo xtask policy
S3-VK-MESH-UPLOAD-001 blocked awaits Stage 3 Vulkan asset renderer and GPU upload path S3-VK-MESH-UPLOAD-001 blocked awaits Stage 3 Vulkan asset renderer and GPU upload path
S3-VK-TEXM-UPLOAD-001 blocked awaits Stage 3 Vulkan texture upload path S3-VK-TEXM-UPLOAD-001 blocked awaits Stage 3 Vulkan texture upload path
@@ -441,7 +441,7 @@ L5-P1-MISSION-002 covered cargo test -p fparkan-runtime --offline licensed_corpo
L5-P2-MISSION-002 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations L5-P2-MISSION-002 covered cargo test -p fparkan-runtime --offline licensed_corpora_load_all_mission_foundations
L5-P1-HEADLESS-001 covered cargo test -p fparkan-runtime --offline selected_is_and_is2_missions_execute_10000_deterministic_ticks L5-P1-HEADLESS-001 covered cargo test -p fparkan-runtime --offline selected_is_and_is2_missions_execute_10000_deterministic_ticks
L5-P2-HEADLESS-001 covered cargo test -p fparkan-runtime --offline selected_is_and_is2_missions_execute_10000_deterministic_ticks L5-P2-HEADLESS-001 covered cargo test -p fparkan-runtime --offline selected_is_and_is2_missions_execute_10000_deterministic_ticks
L5-P1-RENDER-001 covered cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures L5-P1-RENDER-001 covered-planning cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures
L5-P2-RENDER-001 covered cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures L5-P2-RENDER-001 covered-planning cargo test -p fparkan-game --offline selected_is_and_is2_missions_produce_approved_render_captures
L3-DEVICE-001 omitted outside the current macOS-focused goal scope; RG40XX-capable device/profile evidence remains documented for the portable target scope L3-DEVICE-001 omitted outside the current macOS-focused goal scope; RG40XX-capable device/profile evidence remains documented for the portable target scope
L5-RG40-001 omitted outside the current macOS-focused goal scope; RG40XX 640x480 on-device mission smoke/performance/memory evidence remains documented for the portable target scope L5-RG40-001 omitted outside the current macOS-focused goal scope; RG40XX 640x480 on-device mission smoke/performance/memory evidence remains documented for the portable target scope
1 # Acceptance coverage manifest.
2 # Format: <acceptance-id>\t<covered|partial|blocked|omitted>\t<evidence> # Format: <acceptance-id>\t<covered|covered-planning|covered-stub|covered-gpu|partial|blocked|omitted>\t<evidence>
3 # Scope note: Stage 0 is closed for the macOS/codebase review scope.
4 # Scope note: cross-platform native runtime and hosted CI evidence remain deferred.
5 L0-COPYRIGHT-001
32 S0-PLAT-002
33 S0-PLAT-003
34 S0-PLAT-004
35 S0-VK-001
36 S0-VK-002
37 S0-VK-003
38 S0-VK-004
52 S0-VK-018
53 S0-VK-019
54 S0-VK-020
55 S0-VK-021
56 S0-VK-022
57 S0-VK-023
58 S0-VK-024
59 S0-VK-025
233 L3-P2-WEAR-001
234 L3-P1-ASSET-001
235 L3-P2-ASSET-001
236 S2-PLANNING-CAPTURE-P1-001
237 S2-PLANNING-CAPTURE-P2-001
238 S3-WEAR-001
239 S3-WEAR-002
240 S3-WEAR-003
292 S2-PLANNING-RENDER-005
293 S2-PLANNING-RENDER-006
294 S2-PLANNING-RENDER-007
295 S2-PLANNING-RENDER-008
296 S2-PLANNING-RENDER-009
297 S3-VK-MESH-UPLOAD-001
298 S3-VK-TEXM-UPLOAD-001
441 L5-P2-MISSION-002
442 L5-P1-HEADLESS-001
443 L5-P2-HEADLESS-001
444 L5-P1-RENDER-001
445 L5-P2-RENDER-001
446 L3-DEVICE-001
447 L5-RG40-001
+3
View File
@@ -73,6 +73,9 @@ nav:
- WEAR и MAT0: reference/materials.md - WEAR и MAT0: reference/materials.md
- Texm: reference/texm.md - Texm: reference/texm.md
- Render frame: reference/render-frame.md - Render frame: reference/render-frame.md
- Renderer Truth Table: rendering/renderer_truth_table.md
- Original Engine Hashes: evidence/original_engine_hashes.md
- Stage 4 Capture Schema: evidence/stage4_capture_schema.md
- Приложения: - Приложения:
- Глоссарий: appendices/glossary.md - Глоссарий: appendices/glossary.md
- Границы знания: appendices/knowledge-boundaries.md - Границы знания: appendices/knowledge-boundaries.md
+148 -7
View File
@@ -2340,6 +2340,9 @@ struct CoverageEntry {
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CoverageStatus { enum CoverageStatus {
Covered, Covered,
CoveredPlanning,
CoveredStub,
CoveredGpu,
Partial, Partial,
Blocked, Blocked,
Omitted, Omitted,
@@ -2349,12 +2352,27 @@ impl CoverageStatus {
fn parse(value: &str) -> Result<Self, String> { fn parse(value: &str) -> Result<Self, String> {
match value { match value {
"covered" => Ok(Self::Covered), "covered" => Ok(Self::Covered),
"covered-planning" => Ok(Self::CoveredPlanning),
"covered-stub" => Ok(Self::CoveredStub),
"covered-gpu" => Ok(Self::CoveredGpu),
"partial" => Ok(Self::Partial), "partial" => Ok(Self::Partial),
"blocked" => Ok(Self::Blocked), "blocked" => Ok(Self::Blocked),
"omitted" => Ok(Self::Omitted), "omitted" => Ok(Self::Omitted),
_ => Err(format!("unknown coverage status: {value}")), _ => Err(format!("unknown coverage status: {value}")),
} }
} }
const fn as_str(self) -> &'static str {
match self {
Self::Covered => "covered",
Self::CoveredPlanning => "covered-planning",
Self::CoveredStub => "covered-stub",
Self::CoveredGpu => "covered-gpu",
Self::Partial => "partial",
Self::Blocked => "blocked",
Self::Omitted => "omitted",
}
}
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@@ -2366,22 +2384,34 @@ struct AcceptanceAudit {
msrv: String, msrv: String,
required_total: usize, required_total: usize,
covered: Vec<String>, covered: Vec<String>,
covered_planning: Vec<String>,
covered_stub: Vec<String>,
covered_gpu: Vec<String>,
partial: Vec<String>, partial: Vec<String>,
blocked: Vec<String>, blocked: Vec<String>,
omitted: Vec<String>, omitted: Vec<String>,
missing: Vec<String>, missing: Vec<String>,
invalid_coverage: Vec<String>,
unknown_coverage: Vec<String>, unknown_coverage: Vec<String>,
coverage_evidence: BTreeMap<String, String>, coverage_evidence: BTreeMap<String, String>,
by_stage: BTreeMap<String, usize>, by_stage: BTreeMap<String, usize>,
} }
impl AcceptanceAudit { impl AcceptanceAudit {
fn covered_total(&self) -> usize {
self.covered.len()
+ self.covered_planning.len()
+ self.covered_stub.len()
+ self.covered_gpu.len()
}
fn unverified(&self) -> Vec<String> { fn unverified(&self) -> Vec<String> {
self.partial self.partial
.iter() .iter()
.chain(&self.blocked) .chain(&self.blocked)
.chain(&self.omitted) .chain(&self.omitted)
.chain(&self.missing) .chain(&self.missing)
.chain(&self.invalid_coverage)
.cloned() .cloned()
.collect() .collect()
} }
@@ -2392,6 +2422,7 @@ impl AcceptanceAudit {
.chain(&self.blocked) .chain(&self.blocked)
.chain(&self.omitted) .chain(&self.omitted)
.chain(&self.missing) .chain(&self.missing)
.chain(&self.invalid_coverage)
.cloned() .cloned()
.collect() .collect()
} }
@@ -2469,6 +2500,9 @@ fn build_acceptance_audit(
coverage: &BTreeMap<String, CoverageEntry>, coverage: &BTreeMap<String, CoverageEntry>,
) -> AcceptanceAudit { ) -> AcceptanceAudit {
let mut covered = Vec::new(); let mut covered = Vec::new();
let mut covered_planning = Vec::new();
let mut covered_stub = Vec::new();
let mut covered_gpu = Vec::new();
let mut partial = Vec::new(); let mut partial = Vec::new();
let mut blocked = Vec::new(); let mut blocked = Vec::new();
let mut omitted = Vec::new(); let mut omitted = Vec::new();
@@ -2487,6 +2521,9 @@ fn build_acceptance_audit(
*by_stage.entry(stage).or_insert(0) += 1; *by_stage.entry(stage).or_insert(0) += 1;
match coverage.get(id).map(|entry| entry.status) { match coverage.get(id).map(|entry| entry.status) {
Some(CoverageStatus::Covered) => covered.push(id.clone()), Some(CoverageStatus::Covered) => covered.push(id.clone()),
Some(CoverageStatus::CoveredPlanning) => covered_planning.push(id.clone()),
Some(CoverageStatus::CoveredStub) => covered_stub.push(id.clone()),
Some(CoverageStatus::CoveredGpu) => covered_gpu.push(id.clone()),
Some(CoverageStatus::Partial) => partial.push(id.clone()), Some(CoverageStatus::Partial) => partial.push(id.clone()),
Some(CoverageStatus::Blocked) => blocked.push(id.clone()), Some(CoverageStatus::Blocked) => blocked.push(id.clone()),
Some(CoverageStatus::Omitted) => omitted.push(id.clone()), Some(CoverageStatus::Omitted) => omitted.push(id.clone()),
@@ -2497,6 +2534,11 @@ fn build_acceptance_audit(
} }
} }
let invalid_coverage = coverage
.iter()
.filter_map(|(id, entry)| coverage_semantic_failure(id, entry.status))
.collect();
let unknown_coverage = coverage let unknown_coverage = coverage
.keys() .keys()
.filter(|id| { .filter(|id| {
@@ -2516,16 +2558,63 @@ fn build_acceptance_audit(
msrv: WORKSPACE_MSRV.to_string(), msrv: WORKSPACE_MSRV.to_string(),
required_total: required.len(), required_total: required.len(),
covered, covered,
covered_planning,
covered_stub,
covered_gpu,
partial, partial,
blocked, blocked,
omitted, omitted,
missing, missing,
invalid_coverage,
unknown_coverage, unknown_coverage,
coverage_evidence, coverage_evidence,
by_stage, by_stage,
} }
} }
fn coverage_semantic_failure(id: &str, status: CoverageStatus) -> Option<String> {
if is_stage3_or_later(id)
&& matches!(
status,
CoverageStatus::CoveredPlanning | CoverageStatus::CoveredStub
)
&& has_gpu_delivery_token(id)
{
return Some(format!(
"{id}: {} cannot satisfy Stage 3+ GPU/pixel acceptance; use covered-gpu or leave the row non-green",
status.as_str()
));
}
if matches!(status, CoverageStatus::CoveredStub)
&& is_fx_lifecycle_compatibility_id(id)
&& !id.contains("STUB")
{
return Some(format!(
"{id}: covered-stub is reserved for explicit STUB-only FX lifecycle acceptance rows"
));
}
None
}
fn is_stage3_or_later(id: &str) -> bool {
id.as_bytes().get(1).is_some_and(|stage| *stage >= b'3')
}
fn has_gpu_delivery_token(id: &str) -> bool {
id.split('-').any(|token| {
matches!(
token,
"VK" | "GPU" | "DRAW" | "PIXEL" | "VALIDATION" | "RENDERED"
)
})
}
fn is_fx_lifecycle_compatibility_id(id: &str) -> bool {
id.starts_with("S4-FX-") || id.starts_with("L4-P1-EFFECT-") || id.starts_with("L4-P2-EFFECT-")
}
#[derive(Serialize)] #[derive(Serialize)]
struct AcceptanceAuditJson<'a> { struct AcceptanceAuditJson<'a> {
schema_version: &'static str, schema_version: &'static str,
@@ -2536,18 +2625,26 @@ struct AcceptanceAuditJson<'a> {
msrv: &'a str, msrv: &'a str,
required_total: usize, required_total: usize,
covered_total: usize, covered_total: usize,
covered_planning_total: usize,
covered_stub_total: usize,
covered_gpu_total: usize,
partial_total: usize, partial_total: usize,
blocked_total: usize, blocked_total: usize,
omitted_total: usize, omitted_total: usize,
missing_total: usize, missing_total: usize,
unverified_total: usize, unverified_total: usize,
invalid_coverage_total: usize,
unknown_coverage_total: usize, unknown_coverage_total: usize,
by_stage: &'a BTreeMap<String, usize>, by_stage: &'a BTreeMap<String, usize>,
covered: &'a [String], covered: &'a [String],
covered_planning: &'a [String],
covered_stub: &'a [String],
covered_gpu: &'a [String],
partial: &'a [String], partial: &'a [String],
blocked: &'a [String], blocked: &'a [String],
omitted: &'a [String], omitted: &'a [String],
missing: &'a [String], missing: &'a [String],
invalid_coverage: &'a [String],
unknown_coverage: &'a [String], unknown_coverage: &'a [String],
coverage_evidence: &'a BTreeMap<String, String>, coverage_evidence: &'a BTreeMap<String, String>,
} }
@@ -2562,19 +2659,27 @@ fn render_audit_json(audit: &AcceptanceAudit) -> Result<String, String> {
rust_toolchain: &audit.rust_toolchain, rust_toolchain: &audit.rust_toolchain,
msrv: &audit.msrv, msrv: &audit.msrv,
required_total: audit.required_total, required_total: audit.required_total,
covered_total: audit.covered.len(), covered_total: audit.covered_total(),
covered_planning_total: audit.covered_planning.len(),
covered_stub_total: audit.covered_stub.len(),
covered_gpu_total: audit.covered_gpu.len(),
partial_total: audit.partial.len(), partial_total: audit.partial.len(),
blocked_total: audit.blocked.len(), blocked_total: audit.blocked.len(),
omitted_total: audit.omitted.len(), omitted_total: audit.omitted.len(),
missing_total: audit.missing.len(), missing_total: audit.missing.len(),
unverified_total: unverified.len(), unverified_total: unverified.len(),
invalid_coverage_total: audit.invalid_coverage.len(),
unknown_coverage_total: audit.unknown_coverage.len(), unknown_coverage_total: audit.unknown_coverage.len(),
by_stage: &audit.by_stage, by_stage: &audit.by_stage,
covered: &audit.covered, covered: &audit.covered,
covered_planning: &audit.covered_planning,
covered_stub: &audit.covered_stub,
covered_gpu: &audit.covered_gpu,
partial: &audit.partial, partial: &audit.partial,
blocked: &audit.blocked, blocked: &audit.blocked,
omitted: &audit.omitted, omitted: &audit.omitted,
missing: &audit.missing, missing: &audit.missing,
invalid_coverage: &audit.invalid_coverage,
unknown_coverage: &audit.unknown_coverage, unknown_coverage: &audit.unknown_coverage,
coverage_evidence: &audit.coverage_evidence, coverage_evidence: &audit.coverage_evidence,
}; };
@@ -2936,7 +3041,7 @@ mod tests {
#[test] #[test]
fn builds_acceptance_audit_counts() { fn builds_acceptance_audit_counts() {
let required = ["S0-ARCH-001", "S0-ARCH-002", "L3-DEVICE-001", "L5-RG40-001"] let required = ["S0-ARCH-001", "S0-ARCH-002", "L3-GPU-001", "L5-RG40-001"]
.into_iter() .into_iter()
.map(str::to_string) .map(str::to_string)
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
@@ -2949,10 +3054,10 @@ mod tests {
}, },
), ),
( (
"L3-DEVICE-001".to_string(), "L3-GPU-001".to_string(),
CoverageEntry { CoverageEntry {
status: CoverageStatus::Omitted, status: CoverageStatus::CoveredPlanning,
evidence: "outside macos scope".to_string(), evidence: "planning-only evidence".to_string(),
}, },
), ),
( (
@@ -2976,14 +3081,46 @@ mod tests {
let audit = build_acceptance_audit(&required, &coverage); let audit = build_acceptance_audit(&required, &coverage);
assert_eq!(audit.covered, ["S0-ARCH-001"]); assert_eq!(audit.covered, ["S0-ARCH-001"]);
assert_eq!(audit.covered_planning, ["L3-GPU-001"]);
assert_eq!(audit.blocked, ["L5-RG40-001"]); assert_eq!(audit.blocked, ["L5-RG40-001"]);
assert_eq!(audit.omitted, ["L3-DEVICE-001"]);
assert_eq!(audit.missing, ["S0-ARCH-002"]); assert_eq!(audit.missing, ["S0-ARCH-002"]);
assert_eq!(audit.unknown_coverage, ["S0-ARCH-099"]); assert_eq!(audit.unknown_coverage, ["S0-ARCH-099"]);
assert_eq!(audit.by_stage.get("S0"), Some(&2)); assert_eq!(audit.by_stage.get("S0"), Some(&2));
assert_eq!( assert_eq!(
audit.strict_failures(), audit.strict_failures(),
strings(&["L5-RG40-001", "L3-DEVICE-001", "S0-ARCH-002"]) vec![
"L5-RG40-001".to_string(),
"S0-ARCH-002".to_string(),
"L3-GPU-001: covered-planning cannot satisfy Stage 3+ GPU/pixel acceptance; use covered-gpu or leave the row non-green".to_string(),
]
);
}
#[test]
fn covered_stub_requires_explicit_stub_fx_acceptance_row() {
let required = ["S4-FX-018"]
.into_iter()
.map(str::to_string)
.collect::<BTreeSet<_>>();
let coverage = [(
"S4-FX-018".to_string(),
CoverageEntry {
status: CoverageStatus::CoveredStub,
evidence: "reference stub output".to_string(),
},
)]
.into_iter()
.collect::<BTreeMap<_, _>>();
let audit = build_acceptance_audit(&required, &coverage);
assert_eq!(audit.covered_stub, ["S4-FX-018"]);
assert_eq!(
audit.invalid_coverage,
vec![
"S4-FX-018: covered-stub is reserved for explicit STUB-only FX lifecycle acceptance rows"
.to_string()
]
); );
} }
@@ -2997,10 +3134,14 @@ mod tests {
msrv: WORKSPACE_MSRV.to_string(), msrv: WORKSPACE_MSRV.to_string(),
required_total: 1, required_total: 1,
covered: vec!["S0-ARCH-001".to_string()], covered: vec!["S0-ARCH-001".to_string()],
covered_planning: Vec::new(),
covered_stub: Vec::new(),
covered_gpu: Vec::new(),
partial: Vec::new(), partial: Vec::new(),
blocked: Vec::new(), blocked: Vec::new(),
omitted: Vec::new(), omitted: Vec::new(),
missing: Vec::new(), missing: Vec::new(),
invalid_coverage: Vec::new(),
unknown_coverage: Vec::new(), unknown_coverage: Vec::new(),
coverage_evidence: BTreeMap::new(), coverage_evidence: BTreeMap::new(),
by_stage: BTreeMap::new(), by_stage: BTreeMap::new(),