341 lines
19 KiB
Markdown
341 lines
19 KiB
Markdown
# Steam Frame port: progress
|
||
|
||
**Status (2026-10-07): iteration 1 implemented. Next: test checklist T1–T11 on the Frame
|
||
(at the end of this file), then assessment. Getting started: [README.md](README.md).**
|
||
|
||
## Iteration 1
|
||
|
||
### Step 1: build fixes, bytecode-only QML (2026-10-07)
|
||
|
||
**Changes:**
|
||
- **`CMakeLists.txt`:**
|
||
- `Core5Compat` removed (unused, not on the Frame).
|
||
- `GuiPrivate` requested explicitly. This is temporary, until step 3 replaces QRhi.
|
||
- `QT_QMLCACHEGEN_ARGUMENTS --only-bytecode`.
|
||
- OpenVR library directory per architecture (`linux64` / `linuxarm64` / `win64`), for
|
||
linking and for the copy next to the binary.
|
||
- `RPATH $ORIGIN`.
|
||
- `libXtst` linked.
|
||
- **`overlaycontroller.cpp`:** `d3d11.h` and `case QRhi::D3D11` only under `_WIN32`.
|
||
- **Logging:** 8 active easylogging `LOG(INFO)` calls became `qInfo()`
|
||
(`overlaycontroller.cpp` 6, `ChaperoneTabController.cpp` 1,
|
||
`MoveCenterTabController.cpp` 1). Commented-out `LOG` lines are left unchanged.
|
||
- Only changed lines are formatted (`git clang-format`).
|
||
|
||
**Results (x86_64, Arch, Qt 6.11.1, GCC):**
|
||
- The build succeeds, with 1463 compiler warnings, as in `qt6`.
|
||
- `objdump -T`: 7 `Qt_6_PRIVATE_API` symbols, all `QRhi…` (step 3). The
|
||
`QQmlPrivate::AOTCompiledContext` symbols are gone (bytecode-only QML).
|
||
- `libopenvr_api.so` is resolved from the binary's directory (`ldd`).
|
||
- Not started: without a running SteamVR the start hangs in OpenVR init (expected; tested
|
||
on the Frame in step 5).
|
||
|
||
### Step 2: platform option, R7, tick (2026-10-07)
|
||
|
||
**Changes:**
|
||
- **Platform option:**
|
||
- CMake option `OVRAS_STEAM_FRAME` (default OFF) sets the compile definition
|
||
`OVRAS_STEAM_FRAME=1`.
|
||
- New `src/utils/platform_capabilities.h` evaluates it as the only place. It holds
|
||
`k_chaperoneCommitSupported`, `k_defaultVsyncDisabled` and `k_defaultCustomTickRateMs`.
|
||
- **Tick (R6):**
|
||
- In "vsync disabled" mode, `m_pumpEventsTimer` runs directly at `customTickRateMs`
|
||
(`applyTickInterval()`, `Qt::PreciseTimer`); the 1 ms counting is gone.
|
||
- Changing the setting restarts the timer.
|
||
- Setting defaults in `settings_controller.h` come from the constants: Frame `true`/11 ms,
|
||
otherwise unchanged `false`/20 ms.
|
||
- **R7:** with `!k_chaperoneCommitSupported`:
|
||
- `updateChaperoneResetData()` and `applyChaperoneResetData()` skip
|
||
`CommitWorkingCopy`/`RevertWorkingCopy`. Each logs once per function.
|
||
- The init autosave profile is skipped.
|
||
|
||
**Results:**
|
||
- Both variants (default and `-DOVRAS_STEAM_FRAME=ON`) build on x86_64. The definition is
|
||
only set in the Frame variant (`compile_commands.json`).
|
||
- Not tested at runtime yet (Frame, step 5).
|
||
|
||
**Notes:**
|
||
- Another commit at start-up remains, but only on opt-in. `SetWidget()` applies a chaperone
|
||
profile and commits when "auto-apply chaperone" (`autoApplyChaperoneToggle`, default
|
||
`false`) is on. That's a chaperone-profile feature, so it belongs to R5 (iteration 2).
|
||
- clang-format reflows comment blocks next to new comments. Separate new comments from
|
||
existing commented-out code with a blank line, otherwise the diff grows.
|
||
|
||
### Step 3: rendering through public API, `--graphics-api`, mouse y, shutdown order (2026-10-07)
|
||
|
||
**Changes:**
|
||
- **`src/render/`** (new): `RenderBackend`, `OpenGLRenderBackend`, `VulkanRenderBackend` from
|
||
the template (`4a5b090`).
|
||
- Changes to the template code: `VRDASH_*` → `OVRAS_*` names, origin note in the file
|
||
header, references to `OverlayController`.
|
||
- Formatted completely in OVRAS style.
|
||
- One comment in `VulkanRenderBackend.h` was wrapped by hand, because the automatic
|
||
wrapping had broken its column layout.
|
||
- **`OverlayController`:**
|
||
- QRhi block replaced by `configureWindow` → `renderControl.initialize` →
|
||
`attachWindow` → `createRenderTarget`.
|
||
- `renderOverlay()` wraps the frame in `beginFrame`/`finishFrame`. It submits
|
||
`vrTextureBounds` + `vrTexture` while the context is current.
|
||
- `SetOverlayFromQRhiTexture`, the QRhi members and `QVulkanInstance` are removed.
|
||
- Render delay 5 → 16 ms (about 60 fps).
|
||
- **Shutdown:** `Shutdown()` runs only once. It calls `VR_Shutdown()`, then frees the
|
||
backend's render target (`releaseRenderTarget`, `detachWindow`).
|
||
- **Mouse y:** always flipped; the Linux un-flip and its TODO are removed.
|
||
- **`openvr_init::renderInterop()`:** Vulkan extensions and HMD GPU from SteamVR.
|
||
- **`main.cpp`:** parses `--graphics-api`, then `createRenderBackend` → `initialize` →
|
||
`QQuickWindow::setGraphicsApi` → `OverlayController`.
|
||
- **`setup.*`:** `--graphics-api opengl|vulkan` (default `opengl`). Unknown or not built in
|
||
→ error, exit code `GENERAL_FAILURE`.
|
||
- **CMake:**
|
||
- `GuiPrivate` is gone.
|
||
- The Vulkan backend is built when `QT_FEATURE_vulkan` is set (`OVRAS_HAS_VULKAN`).
|
||
|
||
**Results (x86_64, Qt 6.11.1):**
|
||
- The build succeeds. The Vulkan backend is built in.
|
||
- `objdump -T`: only `QVulkanDeviceFunctions::*` / `QVulkanFunctions::*` remain as
|
||
`Qt_6_PRIVATE_API`. Acceptance criterion R1 (symbols) is met.
|
||
- Warnings 1463 → 1630. The new code itself has none; the additional ones come from
|
||
`openvr.h` in the three new translation units.
|
||
- **Not tested yet:**
|
||
- running both backends, Vulkan validation, the snapshot test (step 5, with the
|
||
containers)
|
||
- mouse direction (Frame)
|
||
|
||
**Risk:**
|
||
- `MyTextField` opens the SteamVR keyboard on `activeFocus`. The template had to send the
|
||
offscreen window an explicit `FocusIn`, otherwise `activeFocusItem` stayed null. The `qt6`
|
||
branch doesn't send it. If text fields don't respond on the Frame, that is the cause (R4).
|
||
|
||
### Step 4: manifest and installation (2026-10-07)
|
||
|
||
**Changes:**
|
||
- **`manifest.vrmanifest`:** `binary_path_linux` and `binary_path_linux_arm` →
|
||
`AdvancedSettings_app`.
|
||
- **`manifest::handleManifests`:**
|
||
- `VR_Init(Utility)`; on `VRInitError_Init_NotAvailableToUtilityApps`,
|
||
`VR_Init(Background)`.
|
||
- Other errors: message and exit code `OPENVR_INIT_ERROR`, instead of
|
||
`initializeOpenVR`'s retry loop.
|
||
- **`main.cpp`:** `--graphics-api` is checked before connecting to OpenVR. A wrong value
|
||
used to fail only after `initializeOpenVR`'s retry loop.
|
||
|
||
**Results (x86_64, without SteamVR):**
|
||
- `--force-install-manifest` exits right away with `OPENVR_INIT_ERROR` (−2).
|
||
- `--graphics-api foo` exits right away with `GENERAL_FAILURE` (−1).
|
||
- The build tree contains the binary, `libopenvr_api.so`, both manifests,
|
||
`default_action_manifests/` and `res/`.
|
||
- Not tested yet: `Background` on the Frame (step 5).
|
||
|
||
### Step 5: Frame build, package, tests (2026-10-07)
|
||
|
||
**Changes:**
|
||
- **`build_scripts/steamframe/`** (new, see DESIGN.md section 9): containers, toolchain, build,
|
||
test, package and deploy scripts.
|
||
- **`test/render/`** (new): render test for both backends (`OVRAS_BUILD_TESTS=ON`).
|
||
- **`CMakeLists.txt`:**
|
||
- option `OVRAS_BUILD_TESTS`
|
||
- RPATH only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`). Before, the cross binary also had the
|
||
host's absolute link directory in its RUNPATH.
|
||
- **`.gitignore`:** `dist/`.
|
||
|
||
**Results:**
|
||
- **Render tests:**
|
||
- locally (Arch, Qt 6.11, Intel Iris Xe): OpenGL and Vulkan, 4/4 passed each
|
||
- in the container (Debian trixie, Qt 6.8, same GPU via `/dev/dri`): 2/2 passed,
|
||
**no Vulkan validation messages**
|
||
- Proof that the layer was active: `VK_LOADER_DEBUG=layer` shows
|
||
"Inserted device layer VK_LAYER_KHRONOS_validation".
|
||
- **Cross build (aarch64, Qt 6.8 Debian):** succeeds once the container has `libxt-dev`
|
||
(`input_sender_X11.cpp` includes `X11/Intrinsic.h`).
|
||
- `Qt_6_PRIVATE_API` without `QVulkan*`: 0 → **R1 symbols met**.
|
||
- Qt version tag: `Qt_6.8` (fits the Frame's 6.8.0).
|
||
- GLIBC ≤ 2.38 (the Frame has 2.39); GLIBCXX ≤ 3.4.32 (not checked against the Frame).
|
||
- NEEDED: Qt6 Core/Gui/Qml/Quick/Widgets/Network/DBus/Multimedia/WebSockets, libX11,
|
||
libXtst, libpulse, libopenvr_api, libstdc++/libc.
|
||
- **Package** `dist/AdvancedSettings-5.7.4-linux-arm64.tar.gz`: 3.0 MB (9.1 MB unpacked); since the version fix below `…-5.8.17-…`.
|
||
The version 5.7.4 comes from `qt6`'s `project()` (master is at 5.8.17).
|
||
|
||
**Open (needs the Frame):**
|
||
- Are the QML modules OVRAS imports present in the Frame's system Qt? QtQuick.Controls
|
||
(Material), Layouts, Dialogs, Multimedia.
|
||
- Do libXt/libXtst exist on SteamOS? Is libstdc++ new enough?
|
||
|
||
### Version from `compile_version_string.txt` (2026-10-07)
|
||
|
||
- **Problem:** `qt6`'s `CMakeLists.txt` had the version hard-coded as 5.7.4. The qmake build
|
||
read it from `build_scripts/compile_version_string.txt` (`5.8.17-release`). `master` is
|
||
fully merged into `qt6`, so only the number was stale. Consequence: the version check
|
||
(comparison with `ver/versioncheck.json`, 5.8.17) would have reported an update.
|
||
- **Fix:** CMake reads the file.
|
||
- `APPLICATION_VERSION` = full string, as in qmake.
|
||
- `project(VERSION)` = numeric part.
|
||
- **Result:** `APPLICATION_VERSION="5.8.17-release"`, `CMAKE_PROJECT_VERSION=5.8.17`, package
|
||
`dist/AdvancedSettings-5.8.17-linux-arm64.tar.gz` (3.0 MB).
|
||
|
||
### Fix: manifest commands without a display (2026-10-07)
|
||
|
||
- **Problem (first deploy to the Frame):** `install.sh` aborted with a core dump in
|
||
`./AdvancedSettings_app --force-install-manifest`. Log: "could not connect to display",
|
||
then "no Qt platform plugin could be initialized" (fatal).
|
||
- **Cause:** `main.cpp` creates the `QApplication` before it handles the manifest options.
|
||
Qt's default platform plugin (`xcb`) aborts without a display, and over SSH there is
|
||
none. `install.sh` set `DISPLAY=:0` only for the start, not for registering.
|
||
`uninstall.sh` (`--force-remove-manifest`) had the same problem.
|
||
- **Fix (`package-release.sh`, generated scripts only):** both manifest calls run with
|
||
`QT_QPA_PLATFORM=offscreen`. Registering draws nothing, so it needs no display at all.
|
||
The normal start keeps `DISPLAY=:0` (`xcb`), because rendering needs a real OpenGL/Vulkan
|
||
context. No change to the program.
|
||
- **Result:** package rebuilt (`--no-build`), generated scripts checked with `sh -n`.
|
||
- **Not tested yet:** the fix itself on the Frame (T1, T11). No local x86 build was at hand
|
||
for a test without a display.
|
||
|
||
### Fix: OpenVR library for aarch64 (2026-10-07)
|
||
|
||
- **Problem (second deploy):** with the display fix, `--force-install-manifest` got as far
|
||
as `VR_Init` and failed there: "Installation path could not be located (110)", and on
|
||
stderr "Unable to read VR Path Registry from /data/work/openvrpaths.vrpath".
|
||
`~/.config/openvr/openvrpaths.vrpath` on the Frame is correct (runtime `/opt/steamvr`).
|
||
- **Cause:** the package shipped `third-party/openvr/lib/linuxarm64/libopenvr_api.so`. It is
|
||
byte-identical to the OpenVR SDK 2.15.6's `lib/linuxarm64` and looks for the path registry
|
||
in the fixed path `/data/work/openvrpaths.vrpath`. The SDK's `bin/linuxarm64` library
|
||
(used by the template, verified on the Frame) and SteamVR's own one in
|
||
`/opt/steamvr/bin/linuxarm64` don't contain that path. OVRAS's `third-party/openvr` had no
|
||
`bin/linuxarm64`.
|
||
- **Fix:**
|
||
- New file `third-party/openvr/bin/linuxarm64/libopenvr_api.so` from the SDK v2.15.6
|
||
(`raw.githubusercontent.com/ValveSoftware/openvr/v2.15.6/bin/linuxarm64/…`, SHA-256
|
||
`cc3671d2…47e9`).
|
||
- `CMakeLists.txt`: `OPENVR_LIB_DIR` = `bin/linuxarm64` for aarch64 only. x86_64 and
|
||
Windows are unchanged.
|
||
- Rejected: setting `VR_PATHREG_OVERRIDE` in the scripts. It wouldn't help when SteamVR
|
||
autostarts OVRAS.
|
||
- **Result:** cross build and package succeed. The package contains the new library (no
|
||
`/data/work`), `Qt_6_PRIVATE_API` without `QVulkan*`: 0, RUNPATH `$ORIGIN`. Package
|
||
1.6 MB (was 3.0 MB, the old library was 5.4 MB).
|
||
- **Not tested yet:** on the Frame (T1).
|
||
|
||
### Finding: app key already registered by Steam (2026-10-07)
|
||
|
||
- **Third try of T1** (by hand on the Frame, with the two fixes above): `VR_Init` works, as
|
||
`Utility` (vrserver.txt: "New Connect message … (VRApplication_Utility)"; SteamVR did
|
||
**not** refuse Utility this time, unlike with the template). Then: "Manifest Previously
|
||
Installed", "Could not find working directory of already installed application:
|
||
VRApplicationError_PropertyNotSet".
|
||
- **Cause:** Steam's generated `steamapps.vrmanifest` lists every owned VR app, including
|
||
OVRAS (`steam.overlay.1009850`, launch via `steam://` URL), although the Steam version is
|
||
uninstalled. Not a leftover of the uninstallation; it can't be removed permanently.
|
||
- **Next:** design DESIGN.md section 2a (approved 2026-10-07).
|
||
- **Implementation (`setup.cpp`, `setup.h`):** as designed: key without working directory →
|
||
nothing removed, `AddApplicationManifest` directly (error name on refusal);
|
||
`logApplicationEntry()` after "Manifest reinstalled." logs source, launch type, working
|
||
directory, binary, URL and auto-launch for the key.
|
||
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
|
||
- **Not tested yet:** on the Frame. The log decides: `AppKeyAlreadyExists` or Steam's entry
|
||
(`steam://` URL) → own app key needed (option 2); our working directory and binary →
|
||
enough.
|
||
|
||
**Result on the Frame (2026-10-07, deploy with `ec82b5f`):** SteamVR accepts our manifest
|
||
(no error) but keeps using Steam's entry for the key:
|
||
`source: "steam" launch type: "url" working dir: PropertyNotSet binary: PropertyNotSet
|
||
URL: "steam://launch/1009850/openvroverlay" auto-launch: true`.
|
||
- **Consequence:** the native build needs its own app key (option 2, DESIGN.md 2a,
|
||
follow-up). Design pending.
|
||
- **Side effect:** `--force-install-manifest` switched auto-launch on for Steam's key. At the
|
||
next SteamVR start, SteamVR may launch `steam://launch/1009850` (the uninstalled Steam
|
||
version). To be switched off again.
|
||
- OVRAS itself was started by `install.sh` and keeps running (not yet checked in the
|
||
dashboard, T2).
|
||
|
||
### Fix: debug output only with debugSymbolsAndLogs (2026-10-07)
|
||
|
||
- **Problem:** the log filled with `[D] updateAllPulseData done.` about every 11 ms (8399
|
||
lines in 6 s). `AudioTabController::eventLoopTick()` reads the microphone's mute state on
|
||
every tick, and every read calls `updateAllPulseData()`
|
||
(`AudioManagerPulse_internal.h:312`).
|
||
- **Cause (regression in `qt6`):** on `master` the message was easylogging's `LOG(DEBUG)`,
|
||
and the Debug level was disabled unless built with `debugSymbolsAndLogs`
|
||
(`ENABLE_DEBUG_LOGGING`). `qt6` switched to `qDebug()` and lost that; the constant
|
||
`debugLoggingEnabled` in `setup.cpp` was left unused, so all 46 `qDebug()` calls were
|
||
always active.
|
||
- **Fix (`setup.cpp`, `setUpLogging()`):** without `ENABLE_DEBUG_LOGGING`,
|
||
`QLoggingCategory::setFilterRules("*.debug=false")`. `QT_LOGGING_RULES="*.debug=true"`
|
||
takes precedence and turns debug output on again without a rebuild.
|
||
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
|
||
- **Not tested yet:** on the Frame (log without `[D]` lines; with `QT_LOGGING_RULES` with
|
||
them).
|
||
- **Open for R6/T10:** each tick makes three synchronous PulseAudio queries (server info,
|
||
all sinks, all sources) for the mute state. Not changed now.
|
||
|
||
### Layout cut off on the Frame; diagnosis tools (2026-10-08)
|
||
|
||
- **Observed (headset photo):** the dashboard tab appears, navigation and laser work. The
|
||
fixed 1200×800 layout overflows: about the bottom quarter is cut off (left: Statistics,
|
||
Bindings, Settings; right: Microphone half, Push-to-Talk). Navigation icons overlap their
|
||
text. The right edge is complete.
|
||
- **Ruled out by experiment on the Frame:** `QT_FONT_DPI=96` (verified in the process
|
||
environment): no change. `QT_QUICK_CONTROLS_STYLE=Basic` (Qt 5's "Default" style; `qt6`
|
||
sets no style, so Qt 6 uses Fusion on Linux; only `mainwidget.qml` imports Material): no
|
||
visible change. `QT_QUICK_CONTROLS_MATERIAL_VARIANT=Dense`: slightly better, still cut
|
||
off.
|
||
- **Facts:** Frame X screen 1920×1080, `Xft.dpi: 144`; default font Noto Sans
|
||
(`fc-match sans-serif`). Template renders 1600×1000.
|
||
- **Also seen:** after removing the Steam license, Steam's `steamapps.vrmanifest` still
|
||
lists `steam.overlay.1009850` (not yet regenerated); the debug-output fix works (0 `[D]`
|
||
lines).
|
||
- **Diagnosis tools (DESIGN.md section 10):** font/screen metrics in the log, option
|
||
`--snapshot <file>`, `run.sh` passes further options.
|
||
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0,
|
||
generated `run.sh` checked with `sh -n`. No new warnings in the changed lines.
|
||
- **Not tested yet:** on the Frame (log line "Font: …", PNG written).
|
||
|
||
### Fix: Quick Controls style Fusion (2026-10-08)
|
||
|
||
- **Measurements on the Frame** (snapshots of the root page, OpenGL):
|
||
- Log: `Font: "Noto Sans" 9 pt = 12 px (requested "Sans Serif" 9 pt); 20 pt = 27 px;
|
||
screen "gamescope" logical DPI 96 physical DPI 63.972 device pixel ratio 1`. Fonts, DPI
|
||
and scaling are as intended.
|
||
- The PNG itself is cut off: OVRAS renders the content too large; SteamVR doesn't crop.
|
||
- Navigation buttons: as `qt6` is, 65 px (pitch 75) = Material. With
|
||
`QT_QUICK_CONTROLS_STYLE=Basic` (verified in the process environment): identical, the
|
||
import overrides it. Test build without the Material import (not committed): 45 px
|
||
(pitch 55) = Fusion, **everything fits**. Same build with Basic: 49 px (pitch 59), the
|
||
last button is cut off.
|
||
- **Fix (DESIGN.md section 11):** `mainwidget.qml` imports `QtQuick.Controls` again;
|
||
`main.cpp` sets `QQuickStyle::setStyle("Fusion")`.
|
||
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
|
||
- **Frame test of the committed build (`ff4ea8f`, 2026-10-08, OpenGL):** root page fits
|
||
completely (12 navigation buttons 45 px, pitch 55, Microphone, Push-to-Talk, version
|
||
visible), same as the test build; "Chaperone Settings" fits completely too. No `QT_`
|
||
variables set, so Fusion comes from the code. Log: no new errors.
|
||
- **Not tested yet:** the other pages, Vulkan.
|
||
- **Seen, not changed:** on "Chaperone" and "Space Fix" the icon still overlaps the text
|
||
slightly (`MyPushButton`: icon as wide as the button is high, text only indented by
|
||
spaces).
|
||
|
||
### Test checklist on the Frame (step 5)
|
||
|
||
Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR running).
|
||
|
||
| # | Check | Criterion | Result |
|
||
|---|---|---|---|
|
||
| T1 | `install.sh`: registration works (log: "Manifest reinstalled", via Background) | R2 | first try failed: abort without a display (fixed, see above); retest open |
|
||
| T2 | Tab appears in the dashboard, UI correct (OpenGL) | R2, R3 | open |
|
||
| T3 | `./run.sh vulkan`: UI correct | R3 | open |
|
||
| T4 | Laser right way up, click, scrolling on several pages (both APIs) | R4 | open |
|
||
| T5 | Text field (e.g. new profile name): SteamVR keyboard opens, input arrives | R4 | open |
|
||
| T6 | An OVRAS action arrives (e.g. push-to-talk, log/notification) | R4 | open |
|
||
| T7 | Play area before/after start unchanged; log: "CommitWorkingCopy skipped" | R7 | open |
|
||
| T8 | Quit app (OVRAS "Exit" and Quit in the SteamVR dashboard): no crash (both APIs) | R2 | open |
|
||
| T9 | Auto-start after a SteamVR restart / reboot | R2 | open |
|
||
| T10 | `pidstat -p $(pgrep -f AdvancedSettings_app) 10 6`: dashboard closed / OVRAS visible / other tab visible | R6 | open |
|
||
| T11 | `uninstall.sh` unregisters it | R2 | open |
|
||
|
||
### Wrap-up (2026-10-07)
|
||
|
||
- **Remotes:** `origin` = `git@git.matzman.net:SteamFrame/OpenVR-AdvancedSettings-frame.git`
|
||
(branches `steamframe`, `qt6`, `master`); `upstream` = GitHub OVRAS.
|
||
- **Handover documentation:** `docs/steamframe/README.md` (entry point), `CLAUDE.md`
|
||
(working rules, conventions), `docs/steamframe/OVRAS-FEATURES.md` (feature analysis).
|
||
- **Still open in iteration 1:** phase 4 (tests on the Frame, T1–T11) and phase 5
|
||
(assessment).
|