Both limitations #526 shipped with, deliberately deferred and documented
in its limits section.
1. RGB16-direct Nx renderer was unwired. Wired for BOTH 1x and Nx rather
than only 2x: tier 1 does not present on the 1x RGB16 path either, so
wiring only the Nx one would make a pack look different at 2x than at
1x for a reason unrelated to resolution.
2. Static tiles aged out to flat colour. Nx blocks carry the surface's
frame epoch and stock supersampled content is trusted for
HIRES_EPOCH_WINDOW (16) presented frames; pack art is now exempt from
that age check, while the stock sub block behind an author alpha hole
still ages. Before this, a tile blitted once and left on screen -- a
HUD, a menu, a title card -- showed crisp Nx art for ~16 frames and
then degraded to the 1x representative's flat blocky colours.
The exemption adds no new failure class: the window bounds stale
structure (an NxN interior derived from a write RAM no longer holds),
and for pack art that class is already unbounded and already shipping
at 1x, where the tier-1 shadow carries no epoch at all and presents
the same tile's 1x representative at the same pixel on the same value
check. Refusing the Nx block prevented no wrong-tile artifact; it only
dropped that artifact's resolution.
Gates (test_texreplace):
hires_static_persist -- steps REAL presented frames between the battery
blits and the probe (harness_step drives retro_run drives
ShadowHiresFrameTick; nothing fakes the epoch), and first asserts the
destination RAM is unchanged so expiry cannot be confused with a
coherence miss. 24 frames against a window of 16.
rgb16_presents -- 1x 574/574 and 2x 575/575 delivered, 0 wrong, 0
non-pack entries wrongly claimed, and the no-pack run inert
(shadowFBReplActive == 0, 0 claims).
make TEST_EXPORTS=1 test: 1658 PASS, 0 FAIL, audio pair confirmed by name
(Clipping check: Iron Soldier 2; two presence passes). C89 lint clean.
Rebased onto libretro/develop after #531/#537/#539/#541 and re-verified --
the pre-rebase branch would have deleted test/tools/ir_ab.sh.
Co-Authored-By: Claude Opus 5 <[email protected]>
Two gaps found in self-review of the tier-3 change.
`expect_1x_hits(t, 1)` was written but never called: gate 7 asserted the
Nx blocks and gate 3 only covered the 1x run, so the "1x representative
is the pack's TOP-LEFT subpixel" rule -- chosen precisely because it is
trivially predictable and therefore assertable -- shipped unasserted.
Storing the bottom-right pixel, an average, or nothing at all would have
passed all eight gates. Gate 7 now checks rH->hits/wrong_rgb against it
(63 for the alpha-holed tile: the top-left of stock word (0,0) IS the
PNG (0,0) hole) and checks the 1x presentation path at 2x as well.
Documented, not asserted: Nx blocks carry the surface's frame epoch and
are trusted for HIRES_EPOCH_WINDOW (16) presented frames, while the 1x
shadow has no epoch at all. So a tile blitted once and left on screen
-- static HUD, menu, title card, exactly the content packs are most
attractive for -- shows crisp Nx art for ~16 frames and then ages out to
the 1x representative: flat blocky top-left colours, NOT stock. Which
is the load-bearing reason the 1x representative is recorded, previously
unstated. The battery cannot observe this (its blits run after the
frame loop and probe with a current epoch), so it goes in the limits
list rather than into a gate that would have to fake the epoch.
Also records why the shared SHADOWFB_HIRES_CAP_BYTES budget is safe by
arithmetic: at N=2 all 256 base pages are ~20.5 MB and all 256
replacement planes ~16.8 MB against a 64 MB cap, and MAX_N is 2 -- a
pack cannot starve base allocation.
Co-Authored-By: Claude Opus 5 <[email protected]>
Texture packs could only be authored at the dumped pixel dimensions,
and at Internal Resolution Nx they were worse than that: the hi-res
shadow line entry wins wherever it hits, so the 1x replacement surface
only showed through on a resolve MISS -- hi-res masked packs outright.
Mechanism. A `shadowfb_sub` is {value16, frac16}, a CRY decomposition,
and pack RGB is not a function of the stock word, so it cannot live in
one. Rather than widen every subpixel entry to 8 bytes for every user
(and change the blit memo's BLIT_MEMO_SH_BLOCK serialization), pack art
rides a PARALLEL per-page plane of N*N uint32 entries plus ONE extra bit
in the same word tag (HIRES_TAG_REPL). That buys three properties from
machinery that already exists:
- any ordinary ShadowHiresStoreCry/Block to the word rewrites the tag
WITHOUT the bit, so stale pack art self-clears -- no invalidation
hooks, the same argument that justified the tag scheme for true
colour;
- the value+epoch coherence check is unchanged and shared;
- a 1x run, or a 2x run with no pack, allocates nothing: planes are
allocated per page on first pack write, and both the OP resolve and
the Nx scanline renderer hoist a single shadowHiresReplActive test.
Entry encoding is SHADOWFB_HIRES_REPL_VALID | RGB888, or 0 for "keep
what is underneath", so an author's alpha hole falls through to the
Stage 2 supersampled content rather than to a flat colour. Where the
word's existing entry is not live for this value, the hole subpixels are
seeded with box replication first -- otherwise a hole would expose
uninitialised sub content behind the tag being stamped.
Dimension rule becomes exactly 1x or exactly Nx in both axes. Nothing
between: an arbitrary resize has no defined mapping onto stock pixel
words and would make the per-pixel straight-copy witness meaningless.
1x art is stored into the Nx surface too, replicated N*N, which is what
removes the masking above. The 1x representative of a word covered by
Nx art is the pack's TOP-LEFT subpixel (not an average -- an average
invents a colour the author never drew), so a hi-res resolve miss
degrades to pack colour instead of stock.
Three details that would each have been a silent bug:
- ShadowHiresRestampRamPage now preserves HIRES_TAG_REPL. It is
called by the blit memo, so dropping the bit there would make pack
art vanish on exactly the repeated identical blits the memo skips --
an intermittent "coherence" symptom with a masking cause.
- the RGB store is deliberately NOT logged to the blit memo (the
replacement walk re-runs on every launch, memo skips included, and
the memo's replay deserializes shadowfb_sub blocks -- feeding it RGB
words would silently reinterpret them). Same reasoning as
ShadowFBStoreRGB.
- replacement-plane allocation failure sets its OWN stopped flag, not
hiresAllocStopped: running out of budget for pack art must degrade
the PACK, never the base hi-res feature.
Unchanged: zero savestate fields, zero RAM writes, zero bus-model time.
The plane is a derived cache like every other shadow surface, dropped by
ShadowHiresInvalidate on savestate load and when a pack is unloaded.
Not wired in, deliberately: the RGB16-direct Nx renderer. Tier 1 does
not present on the 1x RGB16 path either, so wiring only the 2x one would
make a pack look different at 2x than at 1x for reasons unrelated to
resolution.
Still open on #369: tier 2, indexed (<=8bpp) sources.
Co-Authored-By: Claude Opus 5 <[email protected]>
Extends docs/texture-dump.md (one document for both #369 deliverables,
as specced): vj_texpacks layout mirroring the dump tree, the host-only
presentation-riding architecture and its consequences, tier status and
known limits (CRY 16bpp OP path, value-tag coherence edge, hi-res
interaction), the test gates, and a replacement authoring guide.
Part of #369.
Co-Authored-By: Claude Opus 5 <[email protected]>
Adds the pack-author section promised by the file plan (directory
layout, filename-is-the-contract, palette caveats, 16bpp preview
note) and records how each open item resolved: B_CMD source-channel
decode verified against docs/jtrm-blitter.md; miniz links cleanly
from outside the unity TU (no wrapper needed, define carried in
LIBCHDR_CFLAGS for the theos_ios path); golden-list population comes
from the synthetic blit battery because no in-tree public ROM is a
meaningful tripwire by itself.
Co-Authored-By: Claude Opus 5 <[email protected]>
Capture at the shared blit-launch site from the register-described
source window (engine-independent, pace-neutral by construction);
FNV-1a 64 bytes-only identity contract with a v1 version marker;
PNG via the already-vendored miniz encoder (one build define);
four inertness/determinism test gates including savestate-digest
proof that the emulated machine cannot see the feature.
Co-Authored-By: Claude Opus 5 <[email protected]>