From c78e68df7fa6d65b1b82cce1559e799eaed59ad6 Mon Sep 17 00:00:00 2001 From: Darkaxt Date: Tue, 1 Sep 2026 10:38:54 +0200 Subject: [PATCH 1/2] release: prepare v1.1.0-rc.86 Co-Authored-By: Claude --- .github/workflows/promote-candidate.yml | 26 +++++++++++++++++----- .github/workflows/release.yml | 1 + README.md | 4 ++-- docs/current-readiness.md | 8 +++---- release/RELEASE_NOTES_1.1.0-rc.86.md | 29 +++++++++++++++++++++++++ release/candidate-promotions/README.md | 18 +++++++++++++-- release/compatibility-evidence.json | 2 +- release/v1-ready.json | 5 +++++ tools/release/release-workflow.test.mjs | 25 ++++++++++++++++++++- 9 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 release/RELEASE_NOTES_1.1.0-rc.86.md diff --git a/.github/workflows/promote-candidate.yml b/.github/workflows/promote-candidate.yml index c4e69cdb..4dc4f95e 100644 --- a/.github/workflows/promote-candidate.yml +++ b/.github/workflows/promote-candidate.yml @@ -73,8 +73,13 @@ jobs: assets="$RUNNER_TEMP/candidate-assets" mkdir -p "$assets" release_json_file="$RUNNER_TEMP/initial-release.json" + release_pages="$RUNNER_TEMP/release-pages.json" initial_release_assets="$RUNNER_TEMP/initial-release-assets.json" - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" > "$release_json_file" + gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" > "$release_pages" + jq -s -e --arg tag "$RELEASE_TAG" ' + [.[][] | select(.tag_name == $tag)] | + if length == 1 then .[0] else error("expected exactly one release for candidate tag") end + ' "$release_pages" > "$release_json_file" jq -e '.draft == true and .prerelease == true' "$release_json_file" >/dev/null release_id="$(jq -r '.id' "$release_json_file")" jq -e '[.assets[] | { @@ -85,8 +90,17 @@ jobs: "$release_json_file" > "$initial_release_assets" test -n "$release_id" test "$(jq 'length' "$initial_release_assets")" -gt 0 - - gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir "$assets" + jq -e ' + .assets as $assets | + ($assets | map(.name) | length) == ($assets | map(.name) | unique | length) and + ($assets | all(.name | test("^[A-Za-z0-9._-]+$"))) + ' "$release_json_file" >/dev/null + while IFS=$'\t' read -r asset_id asset_name; do + [[ "$asset_id" =~ ^[1-9][0-9]*$ ]] + gh api "repos/$GITHUB_REPOSITORY/releases/assets/$asset_id" \ + --header "Accept: application/octet-stream" > "$assets/$asset_name" + test -s "$assets/$asset_name" + done < <(jq -r '.assets[] | [.id, .name] | @tsv' "$release_json_file") echo "assets=$assets" >> "$GITHUB_OUTPUT" echo "release_id=$release_id" >> "$GITHUB_OUTPUT" @@ -269,10 +283,12 @@ jobs: set -euo pipefail current_release="$RUNNER_TEMP/current-release.json" current_release_assets="$RUNNER_TEMP/current-release-assets.json" - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" > "$current_release" + gh api "repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID" > "$current_release" jq -e \ --argjson release_id "$RELEASE_ID" \ - '.id == $release_id and .draft == true and .prerelease == true' \ + --arg release_tag "$RELEASE_TAG" \ + '.id == $release_id and .tag_name == $release_tag and + .draft == true and .prerelease == true' \ "$current_release" >/dev/null jq -e '[.assets[] | { name, diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24d3622b..2d8d517f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -299,6 +299,7 @@ jobs: jq -e '.v11SaveSynchronizedKnowledgeCheckpoints == true' release/v1-ready.json >/dev/null jq -e '.v11Rc84ThorLowerDisplayRemediation == true and .v11Rc84ReleaseQaIsolation == true' release/v1-ready.json >/dev/null jq -e '.v11Rc85ActiveHeaderDestinations == true' release/v1-ready.json >/dev/null + jq -e '.v11Rc86MapPlayerAccessibility == true and .v11Rc86TruthfulHabitatAtlas == true and .v11Rc86PartyExperienceContrast == true and .v11Rc86CompactPokedexAlignment == true and .v11Rc86DraftCandidatePromotion == true' release/v1-ready.json >/dev/null if grep -Eiq '[A-Z]:\\|dualdex-expanded-corpus|ExtractedPath' \ reports/dualdex-parser-compatibility.json \ reports/dualdex-parser-compatibility.md \ diff --git a/README.md b/README.md index da06e640..486907c4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ DualDex is a passive Pokédex companion for mainline-family Pokémon games runni The game remains on the primary display. DualDex detects the active GB, GBC, or GBA content, parses the user's ROM into a local SQLite Pokédex, and reads supported transient game state through RetroArch's read-only Network Commands. One unified snapshot supplies every feature; live fields are authoritative and validated save/checkpoint recovery fills only fields that are unavailable live. It does this without OCR, screenshots, cheats, memory writes, or per-hack profiles. A separately isolated issue-report tool can export read-only evidence for unsupported layouts, but its dumps never feed the production Pokédex. > [!IMPORTANT] -> The pure-Kotlin ROM parser, materialized SQLite catalog, Gen I–III SaveRAM readers, validated live-WRAM paths, loopback web host, Thor-first UI, passive RetroArch activation, Docked/Overlay modes, and isolated read-only issue reports are implemented. Stable `v1.0.0` provides the complete v1 baseline. Candidate `v1.1.0-rc.67` retains one unified snapshot as the sole transient-state authority and corrects Pokédex Height Comparison scaling so visible trainer and Pokémon artwork—not transparent image padding—represents the stated physical height. Unsupported features remain explicit instead of aborting otherwise valid catalogs. +> The pure-Kotlin ROM parser, materialized SQLite catalog, Gen I–III SaveRAM readers, validated live-WRAM paths, loopback web host, Thor-first UI, passive RetroArch activation, Docked/Overlay modes, and isolated read-only issue reports are implemented. Stable `v1.0.0` provides the complete v1 baseline. Candidate `v1.1.0-rc.86` completes the Thor lower-display pass with consistent active destinations, accessibility-gated Local Map player emphasis, truthful habitat-only Atlas shortcuts, clearer Party experience bars, and compact Pokédex card alignment. Unsupported features remain explicit instead of aborting otherwise valid catalogs. > > Current static compatibility evidence comes from the exact [331-ROM Gen I–III full-corpus review](docs/reports/2026-08-26-gen1-gen3-full-corpus-status.md) and its [machine-readable JSON](docs/reports/2026-08-26-gen1-gen3-full-corpus-status.json): 256 catalogs select and persist, overall applicable-table coverage is **73.80%**, and shared-table coverage improves from RC21's **67.13%** to **73.91%**. @@ -348,7 +348,7 @@ SaveRAM evidence is reported separately for [Generations I/II](docs/reports/gen1 | Multi-folder ROM/config/SaveRAM storage | Implemented with Android All files access; SAF folder grants remain fallbacks | | Optional Docked / resizable 4:3 Overlay Android display modes | Implemented in the RC13 candidate; floating-ball/4:3 smoke passed, physical resizing acceptance pending | | Replacement of inherited OCR Android app | Implemented through the current staged Android host | -| Signed candidate target | `v1.1.0-rc.77` is the signed Stage 7 cross-feature UI conformance draft prerelease created by [protected workflow run 33118408017](https://github.com/Darkaxt/DualScreenDex/actions/runs/33118408017) after project-wide QA hardening landed on `master`. Source, build, packaged-app, signing, checksum, certificate, provenance, QA-convergence, compatibility, and UI-evidence gates passed; physical acceptance remains with the user. | +| Final signed candidate target | `v1.1.0-rc.86` contains the completed Thor lower-display remediation and the exact source intended for protected candidate signing and stable `v1.1.0` authorization. Candidate and stable APKs are built only by the protected GitHub workflow; promotion revalidates the signed APK, certificate, provenance, source-bound workflow evidence, and immutable release asset set without replacing any asset. | ## Parser development diff --git a/docs/current-readiness.md b/docs/current-readiness.md index 5640916a..df8ad8f4 100644 --- a/docs/current-readiness.md +++ b/docs/current-readiness.md @@ -4,10 +4,10 @@ This is the canonical reviewer entry point for DualDex release readiness. ## Active marker -- **Latest published repository release marker:** `v1.1.0-rc.77` -- **Release notes:** [`release/RELEASE_NOTES_1.1.0-rc.77.md`](../release/RELEASE_NOTES_1.1.0-rc.77.md) +- **Final signed candidate marker:** `v1.1.0-rc.86` +- **Release notes:** [`release/RELEASE_NOTES_1.1.0-rc.86.md`](../release/RELEASE_NOTES_1.1.0-rc.86.md) - **Machine-readable readiness marker:** [`release/v1-ready.json`](../release/v1-ready.json) -- **Current state:** project-wide QA Stages 7–8 are closed with zero blockers and zero referrals; source-bound compatibility evidence is tracked and the repository is ready for protected stable-release preparation. +- **Current state:** project-wide QA Stages 7–8 are closed with zero blockers and zero referrals; RC86 completes the Thor lower-display usability pass and is the exact candidate source for protected signing, promotion, and stable `v1.1.0` authorization. ## Final QA evidence @@ -19,7 +19,7 @@ The final corpus evaluated all 333 scanner-eligible mainline/hack inputs in the - [Stage 7 closure](reports/qa-hardening/stage-07-closure.md) - [Stage 8 integrated closure](reports/qa-hardening/stage-08-closure.md) -The release workflow validates source lineage, generator and raw-report digests, the canonical denominator/multiset digest, cache decision, exact closure state, protected tag rules, and both protected GitHub environments before signing can begin. No stable tag or signed artifact has been created by QA closure. +The release workflow validates source lineage, generator and raw-report digests, the canonical denominator/multiset digest, cache decision, exact closure state, protected tag rules, and both protected GitHub environments before signing can begin. Stable `v1.1.0` may be authorized only from the exact promoted RC86 source, APK digest, signer, provenance, and immutable release asset set. ## Historical records diff --git a/release/RELEASE_NOTES_1.1.0-rc.86.md b/release/RELEASE_NOTES_1.1.0-rc.86.md new file mode 100644 index 00000000..e2c2b54d --- /dev/null +++ b/release/RELEASE_NOTES_1.1.0-rc.86.md @@ -0,0 +1,29 @@ +# DualDex 1.1.0-rc.86 + +RC86 is the final signed candidate for the Thor lower-display usability pass. It preserves RC85's consistent active-destination headers and completes the small presentation corrections identified during physical testing. + +## Local Map and habitat truthfulness + +- Show the normal ROM-derived player sprite without a pulse or outline by default. +- Offer a device-global, default-off **High-visibility map player** toggle under Accessibility for users who want the pulsing outline. +- Offer Pokémon Atlas shortcuts only when catalogued habitat evidence exists; Organic observations cannot invent a habitat outside that catalogue. +- Preserve Atlas recovery when a truthful habitat exists but cannot be placed on the normalized habitat map. + +## Party and Pokédex readability + +- Increase the compact Party experience-bar thickness and separate its bright blue fill from the neutral gray track. +- Increase compact Pokédex rows to 76 CSS pixels and keep virtualization geometry synchronized with the rendered cards. +- Keep type labels fully inside compact species cards at the exact Thor packaged-WebView viewport. +- Refresh the affected public Pokédex screenshot from the packaged Android WebView at `1241×1027`. + +## Release governance + +- Resolve draft candidate releases through the authenticated release list rather than the public-by-tag endpoint, which does not expose drafts. +- Download every draft asset by its immutable asset ID, then recheck the same release ID and complete asset set immediately before promotion. +- Preserve protected GitHub-only production signing, pinned-certificate verification, immutable checksums, provenance, and non-replacing publication. + +## Validation and delivery + +- Pull request CI, CodeQL, public Chromium acceptance, Gradle dependency submission, and packaged Android managed-device acceptance passed for the exact merged source. +- This candidate uses Android version code `1010086`. +- DualDex remains read-only, includes no ROM or private memory data, and sends no game commands or emulator-memory writes. diff --git a/release/candidate-promotions/README.md b/release/candidate-promotions/README.md index 09a08847..196fc69c 100644 --- a/release/candidate-promotions/README.md +++ b/release/candidate-promotions/README.md @@ -10,12 +10,19 @@ The `release-promotion` environment must allow only the default branch, require ## Packaged Android and physical-device mode -Use this mode after the reusable packaged Android workflow and the physical Thor checklist pass: +Use this mode after the reusable packaged Android workflow and the physical Thor checklist pass. The JSON below is an abbreviated schema fragment, not a valid promotion record: its `releaseAssets` array deliberately shows only three entries. Generate the committed record from the draft release and include every asset returned by GitHub. ```json { "schema": 1, "candidateTag": "v1.1.0-rc.73", + "sourceCommit": "1111111111111111111111111111111111111111", + "candidateProvenanceSha256": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "releaseAssets": [ + { "name": "DualDex-v1.1.0-rc.73.apk", "id": 1001, "sha256": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, + { "name": "provenance.json", "id": 1002, "sha256": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" }, + { "name": "SHA256SUMS.txt", "id": 1003, "sha256": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" } + ], "apkSha256": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "validatedSignerSha256": "C5A02CECB47CDA41B618817EA684CBB6CCFDCC17A3E7D8243448175C8E3B2FBA", "releaseWorkflowRunUrl": "https://github.com/Darkaxt/DualScreenDex/actions/runs/123", @@ -39,12 +46,19 @@ Replace the sample tag, APK hash, run URL, and artifact digest with the candidat ## Authorized passive-catalog substitution -A change limited to passive catalog generation and presentation may use the existing user-authorized substitution instead of claiming device validation: +A change limited to passive catalog generation and presentation may use the existing user-authorized substitution instead of claiming device validation. This is also an abbreviated schema fragment with an intentionally incomplete `releaseAssets` array; it cannot be committed as-is. ```json { "schema": 1, "candidateTag": "v1.1.0-rc.73", + "sourceCommit": "1111111111111111111111111111111111111111", + "candidateProvenanceSha256": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "releaseAssets": [ + { "name": "DualDex-v1.1.0-rc.73.apk", "id": 1001, "sha256": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, + { "name": "provenance.json", "id": 1002, "sha256": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" }, + { "name": "SHA256SUMS.txt", "id": 1003, "sha256": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" } + ], "apkSha256": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "validatedSignerSha256": "C5A02CECB47CDA41B618817EA684CBB6CCFDCC17A3E7D8243448175C8E3B2FBA", "releaseWorkflowRunUrl": "https://github.com/Darkaxt/DualScreenDex/actions/runs/123", diff --git a/release/compatibility-evidence.json b/release/compatibility-evidence.json index 18df5c42..b8022b0d 100644 --- a/release/compatibility-evidence.json +++ b/release/compatibility-evidence.json @@ -12,7 +12,7 @@ }, "scopeDecision": { "type": "NONPARSER_REUSE", - "attestation": "Fresh raw parser evidence was generated at the named source commit; downstream release packaging, acceptance tests, API-30-compatible Android bounded reads, repository-policy alignment, public nonsecret GitHub policy access, schema-bound comparison selection, binary-safe public-asset privacy validation, the Area Guide projection correction, CRC-less RetroArch session activation with evidence-bound epochs, Thor lower-display companion UI remediation, Android loopback disconnect containment, the debug-only RetroArch-free QA harness and clean-start heap provisioning, active root-destination header consistency, packaged Thor screenshot refresh, and asynchronous acceptance deadline hardening changed afterward without changing parser, catalog, build, wrapper, or corpus-execution source." + "attestation": "Fresh raw parser evidence was generated at the named source commit; downstream release packaging, acceptance tests, API-30-compatible Android bounded reads, repository-policy alignment, public nonsecret GitHub policy access, schema-bound comparison selection, binary-safe public-asset privacy validation, the Area Guide projection correction, CRC-less RetroArch session activation with evidence-bound epochs, Thor lower-display companion UI remediation, Android loopback disconnect containment, the debug-only RetroArch-free QA harness and clean-start heap provisioning, active root-destination header consistency, packaged Thor screenshot refresh, asynchronous acceptance deadline hardening, accessibility-gated Local Map player emphasis, habitat-bound Atlas shortcuts, Party experience-bar contrast, compact Pokedex row geometry, and draft-candidate promotion lookup hardening changed afterward without changing parser, catalog, build, wrapper, or corpus-execution source." }, "cacheDecision": { "type": "BUMP_REQUIRED", diff --git a/release/v1-ready.json b/release/v1-ready.json index ba3094c8..16618ebe 100644 --- a/release/v1-ready.json +++ b/release/v1-ready.json @@ -161,6 +161,11 @@ "v11Rc84ThorLowerDisplayRemediation": true, "v11Rc84ReleaseQaIsolation": true, "v11Rc85ActiveHeaderDestinations": true, + "v11Rc86MapPlayerAccessibility": true, + "v11Rc86TruthfulHabitatAtlas": true, + "v11Rc86PartyExperienceContrast": true, + "v11Rc86CompactPokedexAlignment": true, + "v11Rc86DraftCandidatePromotion": true, "gen1CompiledEvolutionCoveragePercent": 96.62, "gen1CompiledLearnsetCoveragePercent": 93.56, "gen1CompiledTypeChartCoveragePercent": 95.79, diff --git a/tools/release/release-workflow.test.mjs b/tools/release/release-workflow.test.mjs index b43683e8..94100474 100644 --- a/tools/release/release-workflow.test.mjs +++ b/tools/release/release-workflow.test.mjs @@ -46,13 +46,36 @@ test("keeps candidates draft until protected exact-artifact promotion", () => { assert.match(promotionWorkflow, /--apk-signer-verification/); assert.match(promotionWorkflow, /actions\/runs\/\$run_id/); assert.match(promotionWorkflow, /\.conclusion == "success"/); - assert.match(promotionWorkflow, /gh release download/); + assert.match(promotionWorkflow, /gh api --paginate "repos\/\$GITHUB_REPOSITORY\/releases\?per_page=100"/); + assert.match(promotionWorkflow, /\[\.\[\]\[\] \| select\(\.tag_name == \$tag\)\]/); + assert.match(promotionWorkflow, /if length == 1 then \.\[0\] else error\("expected exactly one release for candidate tag"\) end/); + assert.match(promotionWorkflow, /\.draft == true and \.prerelease == true/); + assert.match(promotionWorkflow, /map\(\.name\) \| unique \| length/); + assert.match(promotionWorkflow, /all\(\.name \| test\("\^\[A-Za-z0-9\._-\]\+\$"\)\)/); + assert.match(promotionWorkflow, /releases\/assets\/\$asset_id/); + assert.match(promotionWorkflow, /--header "Accept: application\/octet-stream"/); + assert.match(promotionWorkflow, /releases\/\$RELEASE_ID/); + assert.match(promotionWorkflow, /--arg release_tag "\$RELEASE_TAG"/); + assert.match(promotionWorkflow, /\.id == \$release_id and \.tag_name == \$release_tag/); + assert.doesNotMatch(promotionWorkflow, /releases\/tags\/\$RELEASE_TAG/); assert.match(promotionWorkflow, /initial-release-assets\.json/); assert.match(promotionWorkflow, /current-release-assets\.json/); assert.match(promotionWorkflow, /-F draft=false/); assert.match(promotionWorkflow, /-F prerelease=true/); }); +test("requires every RC86 release readiness gate", () => { + for (const field of [ + "v11Rc86MapPlayerAccessibility", + "v11Rc86TruthfulHabitatAtlas", + "v11Rc86PartyExperienceContrast", + "v11Rc86CompactPokedexAlignment", + "v11Rc86DraftCandidatePromotion", + ]) { + assert.match(workflow, new RegExp(`\\.${field} == true`)); + } +}); + test("runs reusable installed-app acceptance before signing or promotion", () => { assert.match(gradleBuild, /localDevices\s*\{[\s\S]*create\("qaApi35"\)/); assert.match(gradleBuild, /testInstrumentationRunnerArguments\["useTestStorageService"\]\s*=\s*"true"/); From 9eed6a51c89f4a50027e05fac14e944811983c6b Mon Sep 17 00:00:00 2001 From: Darkaxt Date: Tue, 1 Sep 2026 10:45:22 +0200 Subject: [PATCH 2/2] test: align readiness index with RC86 Co-Authored-By: Claude --- tools/release/readiness-index.test.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/release/readiness-index.test.mjs b/tools/release/readiness-index.test.mjs index fabdc5fc..b352432b 100644 --- a/tools/release/readiness-index.test.mjs +++ b/tools/release/readiness-index.test.mjs @@ -14,7 +14,8 @@ test("current reviewer entry points agree on source-bound zero-gap closure", () const marker = JSON.parse(read("release/v1-ready.json")); const canonical = JSON.parse(read("release/canonical-corpus.json")); - assert.match(index, /v1\.1\.0-rc\.77/); + assert.match(index, /v1\.1\.0-rc\.86/); + assert.equal(existsSync(join(repositoryRoot, "release/RELEASE_NOTES_1.1.0-rc.86.md")), true); assert.match(index, /Stages 7[–-]8 are closed with zero blockers and zero referrals/i); assert.match(index, /333 scanner-eligible.*334-file physical inventory/i); assert.equal(canonical.schemaVersion, 2);