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
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ object AreaGuideBuilder {
AreaGuidePointCategory.SERVICE,
AreaGuidePointCategory.UNKNOWN -> {
outputBudget.retain()
placesAndServices += point
placesAndServices += point.copy(
label = point.label?.takeUnless { it.equals(name, ignoreCase = true) },
)
}
AreaGuidePointCategory.AVAILABLE_ITEM,
AreaGuidePointCategory.COLLECTED_ITEM -> {
Expand Down Expand Up @@ -150,8 +152,8 @@ object AreaGuideBuilder {
val label = when {
!identified -> null
category == AreaGuidePointCategory.AVAILABLE_ITEM || category == AreaGuidePointCategory.COLLECTED_ITEM ->
normalizeText(poi.item?.displayName, trainer?.name, names[poi.baseAreaId])
else -> normalizeText(poiName(poi, trainer?.gender), trainer?.name, names[poi.baseAreaId])
normalizeText(poi.item?.displayName, trainer?.name, null)
else -> normalizeText(poiName(poi, trainer?.gender), trainer?.name, null)
}
outputBudget?.retain()
add(AreaGuidePoint(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.enrpau.dualscreendex.companion.map

import com.enrpau.dualscreendex.companion.model.AppSnapshot
import com.enrpau.dualscreendex.companion.model.CompanionSettings
import com.enrpau.dualscreendex.companion.model.KnowledgeMode
import com.enrpau.dualscreendex.parser.catalog.CatalogParser
import com.enrpau.dualscreendex.parser.io.RomImage
import java.nio.file.Files
import java.nio.file.Path
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assume.assumeTrue
import org.junit.Test

class AreaGuideBuilderRealControlTest {
@Test
fun `Modern Emerald keeps the Littleroot town sign on the map without duplicating the guide heading`() {
val configured = System.getenv("DUALDEX_MODERN_EMERALD_ROM")
assumeTrue("set DUALDEX_MODERN_EMERALD_ROM to run this real-ROM control", !configured.isNullOrBlank())
val path = Path.of(requireNotNull(configured))
assumeTrue("real ROM does not exist: $path", Files.isRegularFile(path))
val rom = RomImage(Files.readAllBytes(path))
assertEquals(MODERN_EMERALD_SHA, rom.sha256)
val catalog = requireNotNull(CatalogParser.parseCatching(rom).catalog).getOrThrow()

val projection = AreaGuideBuilder.project(
catalog,
AppSnapshot(
liveAreaBaseId = LITTLEROOT_TOWN,
settings = CompanionSettings(knowledgeMode = KnowledgeMode.DISCOVERED),
),
)
val sign = projection.points.single {
it.baseAreaId == LITTLEROOT_TOWN && it.tileX == 15 && it.tileY == 13
}

assertEquals("Littleroot Town", sign.label)
assertNull(
projection.guide.areas.single { it.baseAreaId == LITTLEROOT_TOWN }
.placesAndServices.single { it.key == sign.key }.label,
)
}

private companion object {
const val LITTLEROOT_TOWN = 0x0009
const val MODERN_EMERALD_SHA = "21a0306c4e5b5dc15ca70b74e713e3140612c1045aa298072993a6c5dd8d6895"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,35 @@ class AreaGuideBuilderTest {
assertNull(unnamed.label)
}

@Test
fun areaTitleSignRemainsLabeledOnMapWithoutAddingDuplicateGuideText() {
val catalog = catalog().let { original ->
val areaTitleSign = original.localMaps.pois.single { it.key == GENERIC }
.copy(displayName = "Quiet Corner\nA longer description")
original.copy(
localMaps = original.localMaps.copy(
pois = original.localMaps.pois.map { poi ->
if (poi.key == GENERIC) areaTitleSign else poi
},
),
)
}

val projection = AreaGuideBuilder.project(
catalog,
AppSnapshot(
liveAreaBaseId = EMPTY,
settings = CompanionSettings(knowledgeMode = KnowledgeMode.DISCOVERED),
),
)

assertEquals("Quiet Corner", projection.points.single { it.key == GENERIC }.label)
assertNull(
projection.guide.areas.single { it.baseAreaId == EMPTY }
.placesAndServices.single { it.key == GENERIC }.label,
)
}

@Test
fun objectivesAreAttachedOnlyToTheirKnowledgeVisibleArea() {
val objective = AreaGuideObjective("open-road", "Open Road")
Expand Down
2 changes: 1 addition & 1 deletion docs/reports/qa-hardening/stage-07-closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ A post-run inventory comparison found the same 333 eligible names and one intent
| Current release/governance gate after closure packaging and APK privacy correction | 85 Node tests passed. |
| Fresh corpus summarization | Streaming raw-report hash, source/generator lineage, canonical multiset, terminal outcomes, and persistence/reopen checks passed. |

The expensive parser and consolidated Gradle gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, acceptance-test stabilization, the later replacement of two Android API-33-only bounded reads with the shared API-30-compatible reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to use public nonsecret endpoints instead of an integration token lacking administration scope, binding the comparison range to the cache decision's prior parser schema, or tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path. Focused checkpoint/save tests, the source-contract regression, Android test compilation, app lint, the PR managed-device suite, the failed candidate's complete unsigned build and packaged Android gates, the reproduced APK privacy regression, and the release-governance tests passed after those corrections. `NONPARSER_REUSE` is explicit and does not permit parser, catalog, build, wrapper, or corpus-execution changes.
The expensive parser and consolidated Gradle gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, acceptance-test stabilization, the later replacement of two Android API-33-only bounded reads with the shared API-30-compatible reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to use public nonsecret endpoints instead of an integration token lacking administration scope, binding the comparison range to the cache decision's prior parser schema, tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path, or preserving area-title sign labels in the Local-map projection while suppressing only the duplicate Area Guide drawer text. Focused checkpoint/save tests, the source-contract regression, Android test compilation, app lint, the PR managed-device suite, the candidate workflows' complete unsigned build and packaged Android gates, the reproduced APK privacy regression, the complete Area Guide suite, the exact Modern Emerald sign control, and the release-governance tests passed after those corrections. `NONPARSER_REUSE` is explicit and does not permit parser, catalog, build, wrapper, or corpus-execution changes.

## Missing-feature classification

Expand Down
2 changes: 1 addition & 1 deletion docs/reports/qa-hardening/stage-08-closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The denominator correction does not waive an input. The audited physical invento
| Final corpus | 333/333 eligible inputs terminal; 0 parser/catalog/compatibility/persistence errors; 278/278 selected catalogs persisted and reopened | `PASS` |
| Downstream evidence hardening | Bounded 1.63 GB streaming summary, corrected denominator, duplicate-aware canonical contract, promotion/readiness/privacy regressions | `PASS` |

The consolidated Gradle and hours-long parser gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, deterministic acceptance-test corrections, the replacement of two Android API-33-only read calls with the already-tested API-30-compatible bounded reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to public nonsecret endpoints, binding the release comparison range to the cache decision's prior parser schema, or tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path. Focused checkpoint/save tests, source contracts, Android test compilation, app lint, the 7/7 PR managed-device suite, the failed candidate's complete unsigned build and packaged Android gates, the reproduced APK privacy regression, and the release-governance suite passed afterward. The evidence validator rejects reuse if parser/catalog sources, build logic, wrapper, or corpus-execution tooling changes.
The consolidated Gradle and hours-long parser gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, deterministic acceptance-test corrections, the replacement of two Android API-33-only read calls with the already-tested API-30-compatible bounded reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to public nonsecret endpoints, binding the release comparison range to the cache decision's prior parser schema, tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path, or preserving area-title sign labels in the Local-map projection while suppressing only the duplicate Area Guide drawer text. Focused checkpoint/save tests, source contracts, Android test compilation, app lint, the 7/7 PR managed-device suite, the candidate workflows' complete unsigned build and packaged Android gates, the reproduced APK privacy regression, the complete Area Guide suite, the exact Modern Emerald sign control, and the release-governance suite passed afterward. The evidence validator rejects reuse if parser/catalog sources, build logic, wrapper, or corpus-execution tooling changes.

No ad hoc emulator, ADB gesture, physical-device action, credential inspection, signing-material inspection, signing, tagging, or publication was performed for this closure.

Expand Down
55 changes: 55 additions & 0 deletions release/RELEASE_NOTES_1.1.0-rc.78-hotfix.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# DualDex 1.1.0-rc.78-hotfix.4

RC78 hotfix 4 is the release candidate for the completed project-wide QA hardening program. It closes the remaining Android setup, parser/catalog, runtime authority, companion transport, privacy, and release-governance gaps, then binds them to the final source-bound compatibility corpus. The hotfix qualifier preserves the immutable earlier RC78 tags while keeping cache validation bound to the correct prior parser-schema baseline, validating public assets without binary false positives, and restoring area-title sign labels to their real Local-map coordinates.

## Android setup and recovery

- Recover cleanly when a guide cannot be loaded instead of crashing the app or leaving the guide surface in a stale state.
- Reconcile direct-storage and folder-picker access without discarding the last valid index during a failed rescan.
- Quarantine revoked folder grants, route package-specific settings safely, and explain protected `Android/data` and `Android/obb` limitations accurately.
- Deliver overlay picker results exactly once across cold starts, new intents, retries, and activity recreation.
- Keep Area Guide projection failures local to that optional module.

## Parser and catalog resilience

- Bound complete-ROM probes, archive extraction, catalog payloads, concurrent corpus work, and detached Gen I species discovery.
- Add cancellation checks to long parser passes and replenish ordered corpus work after any completion so one slow input cannot stall unrelated inputs.
- Verify catalog identity and canonical content digests before activation, quarantine invalid snapshots, and fail closed on malformed optional data.
- Invalidate pre-hardening parser catalogs through schema revision 46 and retain seeded rebuild coverage.

## Runtime authority and companion safety

- Require verified ROM identity and monotonic session epochs before live memory or SaveRAM state can become authoritative.
- Fence queued mapper, socket, command, delayed-reply, and checkpoint work against stale sessions.
- Recover RetroArch configuration and command sockets transactionally while bounding memory reads, UDP drains, and retained snapshots.
- Bound Android and desktop companion transport, fence navigation/state/media responses, preserve structured retries, and isolate optional feature failures.
- Keep an area-title sign label at its real Local-map coordinate while suppressing only the redundant same-name entry in the Area Guide drawer; Organic discovery behavior remains unchanged.
- Remove private paths, reversible player-state fingerprints, raw failures, stacks, workspace identifiers, and device identifiers from normal diagnostics and public evidence.

## Source-bound QA closure

- Audit 334 supported-extension files while evaluating all 333 scanner-eligible mainline and hack inputs; one known spin-off remains intentionally excluded by scanner policy.
- Reach terminal parser outcomes for 333/333 inputs: 278 selected, 2 ambiguous, 53 without a family match, and 0 parser errors.
- Record 20 complete, 302 partial, and 11 unresolved data-compatibility outcomes with 0 compatibility errors.
- Materialize, persist, close, reopen, and decode all 278 selected catalogs with 0 catalog or persistence errors.
- Close QA Stages 7 and 8 with zero blockers and zero referrals.

## Measured validation

- Post-remediation Kotlin and Android gate: 65 tasks passed in 40m36s across parser, catalog, CLI, runtime, companion, and app unit suites.
- Companion web gate: 32 Vitest files and 268 tests passed; the TypeScript/Vite production build passed.
- Portable Chromium acceptance: 3/3 Playwright tests passed.
- Release and governance gate: 85/85 Node tests passed.
- Public QA evidence: 7/7 assets passed structural privacy validation.
- PR Android acceptance: 7/7 managed-device tests passed; min-SDK-30 app lint and focused bounded-read tests passed after the final compatibility correction.
- The complete Area Guide builder suite passed with the exact Modern Emerald v3.5 control; its Littleroot sign remained labeled at tile `(15, 13)` while the duplicate drawer label stayed suppressed.
- Public nonsecret repository-policy reads were verified against the live tag and environment configuration without querying signing material.
- Cache validation selects the latest prior tag whose parser schema matches the explicit cache decision, so failed immutable delivery tags cannot replace the evidence baseline.
- Public-asset validation still rejects complete private paths embedded in binary payloads while ignoring isolated drive-prefix byte sequences in compressed APK data.
- Protected release managed-device acceptance, signing, and Thor validation remain mandatory before candidate promotion.

## Delivery

- This candidate uses Android version code `1010082`.
- The candidate is built and signed only through the protected GitHub Actions environment; production signing material is never exposed to the repository or local workspace.
- DualDex remains read-only and sends no game commands or emulator-memory writes.
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, and binary-safe public-asset privacy validation 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, and the Area Guide projection correction changed afterward without changing parser, catalog, build, wrapper, or corpus-execution source."
},
"cacheDecision": {
"type": "BUMP_REQUIRED",
Expand Down
Loading