154 lines
8.7 KiB
Python
154 lines
8.7 KiB
Python
"""Audit saved REA pixels and a frozen YCode fixture; never access the original app.
|
|
|
|
Run with a Python runtime providing Pillow. The exported buddy image is explicitly
|
|
only the screenshot-visible fragment, not a recovered original source asset.
|
|
"""
|
|
from collections import deque
|
|
from hashlib import sha256
|
|
from pathlib import Path
|
|
import json
|
|
from PIL import Image, ImageChops
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / ".tmp/rea-home-static-20261016"
|
|
OUTPUT = ROOT / ".tmp/home-visual-audit-20261016"
|
|
ASSETS = ROOT / "src/renderer/src/components/source-home-assets"
|
|
EXPECTED_SHA = "360b076811ef19381f0670c314b24b06252310bf24fdc245f529f4894cfd7241"
|
|
|
|
|
|
def digest(file):
|
|
return sha256(file.read_bytes()).hexdigest()
|
|
|
|
|
|
def write_json(file, value):
|
|
file.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main():
|
|
OUTPUT.mkdir(parents=True, exist_ok=True)
|
|
source_path = SOURCE / "workbuddy.png"
|
|
assert digest(source_path) == EXPECTED_SHA, "REA evidence screenshot changed"
|
|
layout = json.loads((SOURCE / "layout.json").read_text(encoding="utf-8"))
|
|
nodes = layout["layout"]["documents"][0]["nodes"]
|
|
by_index = {node["node_index"]: node for node in nodes}
|
|
assert by_index[919]["bounds"] == [1085.125, 286.5, 120, 120]
|
|
assert by_index[922]["bounds"] == [1065.125, 276.5, 140, 140]
|
|
source = Image.open(source_path).convert("RGB")
|
|
assert source.size == (1336, 901)
|
|
# Source composer begins y364.5. Row364 may contain antialias/overlap.
|
|
# Exclude that entire row; never invent the occluded body below it.
|
|
box = (1085, 286, 1205, 364)
|
|
crop = source.crop(box)
|
|
crop.save(OUTPUT / "buddy-visible-raw-rgb.png")
|
|
width, height = crop.size
|
|
pixels = crop.load()
|
|
outside = set()
|
|
queue = deque((x, y) for x in range(width) for y in (0, height - 1))
|
|
queue.extend((x, y) for y in range(height) for x in (0, width - 1))
|
|
while queue:
|
|
x, y = queue.popleft()
|
|
if (x, y) in outside or pixels[x, y] != (255, 255, 255):
|
|
continue
|
|
outside.add((x, y))
|
|
for xx, yy in ((x-1, y), (x+1, y), (x, y-1), (x, y+1)):
|
|
if 0 <= xx < width and 0 <= yy < height and (xx, yy) not in outside:
|
|
queue.append((xx, yy))
|
|
reference = crop.convert("RGBA")
|
|
alpha = Image.new("L", crop.size, 255)
|
|
for point in outside:
|
|
alpha.putpixel(point, 0)
|
|
reference.putalpha(alpha)
|
|
asset = ASSETS / "workbuddy-home-buddy-visible-reference.png"
|
|
reference.save(asset)
|
|
alpha.save(OUTPUT / "buddy-visible-derived-alpha.png")
|
|
# Exact equality on the observed white backdrop is the only alpha claim.
|
|
recomposed = Image.new("RGBA", crop.size, (255, 255, 255, 255))
|
|
recomposed.alpha_composite(reference)
|
|
diff = ImageChops.difference(recomposed.convert("RGB"), crop)
|
|
assert diff.getbbox() is None
|
|
foreground_box = alpha.getbbox()
|
|
manifest = {
|
|
"kind": "screenshot-visible-static-reference-fragment",
|
|
"evidenceId": layout["evidenceId"], "capturedAt": layout["captured_at"],
|
|
"originalProduct": "WorkBuddy", "observedVersion": "5.8.2 (screenshot sidebar)",
|
|
"sourceScreenshot": str(source_path.relative_to(ROOT)).replace("\\", "/"),
|
|
"sourceScreenshotSha256": EXPECTED_SHA, "viewport": [1336, 901], "deviceScale": 1,
|
|
"sourceGrowthWrapperNode": 919, "sourceGrowthWrapperBounds": by_index[919]["bounds"],
|
|
"sourceImageNode": 922, "sourceImageBounds": by_index[922]["bounds"],
|
|
"cropPixelBoxExclusiveRightBottom": list(box), "size": [width, height],
|
|
"cropOffsetFromGrowthWrapper": [-0.125, -0.5],
|
|
"foregroundBoxWithinCropExclusiveRightBottom": list(foreground_box),
|
|
"assetSha256": digest(asset),
|
|
"alphaDerivation": "Binary alpha: remove only exact-white pixels connected to crop edges. Preserve every other observed RGB pixel and enclosed white highlight.",
|
|
"transparentPixelCount": len(outside), "whiteBackdropCompositeChangedPixels": 0,
|
|
"knownBackdrop": [255, 255, 255],
|
|
"limitations": [
|
|
"Not the original thumbnail file, not a full robot, and not a recovered original alpha channel.",
|
|
"Only observed pixels y286..363 are included; composer overlap row364 and all occluded content are excluded.",
|
|
"Antialiased edge pixels retain their white matte; dark-background appearance is unverified.",
|
|
"No animation or hover frame is included. Original video URL and thumbnail URL have not been observed.",
|
|
"Use as a 120x78 static fragment at cropOffsetFromGrowthWrapper; applying the original 140x140 image style rescales and misplaces it.",
|
|
"Pixel placement is tied to this viewport and raster phase. Retest after viewport/sidebar/DPR changes.",
|
|
],
|
|
"sourceSelectionEvidence": {
|
|
"runtimeScriptEvidenceId": "ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb",
|
|
"script": "contexts-BMeWSUHk.js",
|
|
"scriptSha256": "5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b",
|
|
"growthBuddyApproxOffset": 7027087, "fallbackWwnApproxOffset": 7018348,
|
|
"getGrowthBuddyApproxOffset": 4295749,
|
|
"finding": "GrowthBuddy selects adapter buddy.thumbnail_url unless absent/image-error; fallback is Wwn default-miao. Exact rendered URL is omitted by existing REA layout capture.",
|
|
"fallbackEvidenceId": "ev_60a03f3376f0e0c04e16d12c7798e25401d1e697f33b39e4b5f7027794b09223",
|
|
"fallbackAssetSha256": "d164ac0e6e54438ea2a26a26e8ef622de3d911a674f55086d465ce10b011641d",
|
|
},
|
|
}
|
|
write_json(ASSETS / "workbuddy-home-buddy-visible-reference.json", manifest)
|
|
# Static-region comparison is diagnostic only: integer x-translation leaves
|
|
# 0.125px raster-phase error. No full-page equality percentage is reported.
|
|
own_path = OUTPUT / "native.png"
|
|
region_metrics = []
|
|
if own_path.exists():
|
|
own = Image.open(own_path).convert("RGB")
|
|
assert own.size == source.size
|
|
regions = {"title": (660, 158, 950, 201), "scene-tabs": (692, 216, 918, 253),
|
|
"first-chip": (405, 316, 487, 349), "composer-top-border": (435, 364, 1055, 376)}
|
|
for name, region in regions.items():
|
|
x1, y1, x2, y2 = region
|
|
a, b = source.crop(region), own.crop((x1 - 2, y1, x2 - 2, y2))
|
|
distances = list(ImageChops.difference(a, b).getdata())
|
|
region_metrics.append({"name": name, "sourceBox": list(region), "ownTranslation": [-2, 0],
|
|
"residualRasterPhaseX": 0.125, "pixels": len(distances),
|
|
"changedPixels": sum(any(p) for p in distances),
|
|
"meanAbsoluteChannelDifference": sum(sum(p) for p in distances)/(len(distances)*3),
|
|
"maxChannelDifference": max(max(p) for p in distances)})
|
|
native_report = OUTPUT / "native-layout.json"
|
|
geometry = []
|
|
if native_report.exists():
|
|
own_nodes = json.loads(native_report.read_text(encoding="utf-8"))["nodes"]
|
|
for own_node in own_nodes:
|
|
selector = own_node["selector"]
|
|
if selector == ".source-home-robot img":
|
|
source_node = by_index[922]
|
|
else:
|
|
matches = [n for n in nodes if selector[1:] in n.get("attributes", {}).get("class", "").split()]
|
|
if len(matches) <= own_node["index"]:
|
|
continue
|
|
source_node = matches[own_node["index"]]
|
|
geometry.append({"selector": selector, "index": own_node["index"],
|
|
"sourceNodeIndex": source_node["node_index"], "source": source_node["bounds"],
|
|
"own": own_node["bounds"],
|
|
"delta": [b-a for a,b in zip(source_node["bounds"], own_node["bounds"])],
|
|
"styleDifferences": {key: {"source": source_node["styles"].get(key), "own": value}
|
|
for key, value in own_node["styles"].items()
|
|
if source_node["styles"].get(key) != value}})
|
|
write_json(OUTPUT / "audit.json", {"sourceEvidenceId": layout["evidenceId"],
|
|
"sourceScreenshotSha256": EXPECTED_SHA, "ownScreenshotSha256": digest(own_path) if own_path.exists() else None,
|
|
"geometry": geometry, "diagnosticRegionMetrics": region_metrics, "buddyReference": manifest,
|
|
"limitations": ["Frozen fixture data differs from original. Not a production/UI parity pass.",
|
|
"Source capture does not report actual platform fonts. Matching declared fonts does not prove glyph identity."]})
|
|
print(json.dumps({"asset": str(asset), "size": [width, height], "whiteCompositeChangedPixels": 0,
|
|
"foregroundBox": list(foreground_box), "geometrySamples": len(geometry)}, ensure_ascii=False))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|