Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions .github/workflows/promote-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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[] | {
Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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%**.

Expand Down Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions docs/current-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the readiness-index assertion for RC86

When CI or the protected release workflow runs node --test tools/release/*.test.mjs, tools/release/readiness-index.test.mjs:17 still requires v1.1.0-rc.77, so this RC86 marker makes the suite fail before the release can be built or signed. Update that assertion alongside the active marker.

Useful? React with 👍 / 👎.

- **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

Expand All @@ -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

Expand Down
29 changes: 29 additions & 0 deletions release/RELEASE_NOTES_1.1.0-rc.86.md
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 16 additions & 2 deletions release/candidate-promotions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion release/compatibility-evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions release/v1-ready.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion tools/release/readiness-index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion tools/release/release-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"/);
Expand Down
Loading