Skip to content

Joxblox v1.5.0

Choose a tag to compare

@github-actions github-actions released this 08 May 01:31
· 2 commits to main since this release
a2d981d

Added

  • Scan: right-click any result row for a context menu with Copy (Asset ID / Asset Reference / SHA256) and Tag (Downscale, Duplicated, Decimate, Atlas, Remove Alpha, Remove). Active tags show in a new Tags column. A HTML Tag Report button on the scan toolbar then generates a self-contained HTML page with one section per tag, each showing the tagged assets as image cards (asset ID, type, dimensions, GPU texture memory, instance path). Tags live in memory for the session and are not persisted across rescans
  • Scan: Duplicated tag opens a picker dialog instead of toggling on a single row. The right-clicked asset is preselected at the top with its preview already showing; the dialog has a side-by-side layout (candidate table on the left, asset preview on the right). Single-click any row to swap the preview to that asset; double-click to add or remove it from the group. Candidates are sorted by perceptual-hash similarity (dHash Hamming distance) to the primary asset, with a Similarity column showing each row's 100% (0)-style score so the user can scan the top of the list for near-identical content; rows that can't be hashed (no decodable image bytes, or the primary itself is a non-image) fall back to SHA256 clustering. Editing an existing group reopens the same dialog with all current members preselected. The HTML Tag Report renders each curated group as its own Group N — N copies block so reviewers can see at a glance which copies are flagged as variants of which
  • Scan: new PDF Tag Report button next to HTML Tag Report — exports the same per-tag breakdown (and curated Group N — N copies sub-blocks for Duplicated) as a self-contained PDF with embedded thumbnails. Built with gofpdf so it ships in the binary; no Chrome / external renderer required. The asset card grid wraps at four cards per A4 row, paginates automatically, and falls back to a "(no preview)" placeholder when an asset's Resource isn't a PDF-embeddable format
  • Scan: SHA256-detected duplicate clusters auto-tag as Duplicated groups when a scan completes — the same grouping the picker dialog produces, but seeded from byte-identical files the loader already deduplicated. Manual groups created via the dialog always win: an auto-tag run skips any cluster whose members overlap a user-curated group, and re-running auto-tag on an already-grouped cluster is a no-op
  • Scan: new Show untagged filter checkbox next to Show duplicates and Show large textures — hides any row that already carries at least one tag, so triagers can drill into "rows that still need a decision" after burning through the obvious tags. The existing Show only duplicates checkbox is renamed to Show duplicates for symmetry
  • Scan: Path Filter checkbox now defaults on, scoped to Workspace.* and MaterialService.*, so fresh scans skip the editor-only / tooling tail of an rbxl by default. Untick it to walk the full instance tree
  • Scan: default sort is now GPU Memory (descending) instead of Self Size — the metric that drives in-game VRAM cost surfaces first, so triagers see the heaviest textures at the top without having to re-sort. Heatmap variant still defaults to Total Byte Size

Fixed

  • Scan: when an asset's full preview was lazy-loaded after row selection (because the streaming scan only got a thumbnail or no Source), the freshly-loaded Resource / DownloadBytes / FileSHA256 / dimensions weren't being persisted back into the explorer's master row list. That made the duplicate-picker dialog's Similarity column render - for every row even when the primary and candidates had matching SHA256s — the SHA fast-path saw an empty primary SHA and the dHash path saw an empty primary Resource. The lazy-load completion now writes the loaded result back into allResults (matched by AssetReferenceKey) so subsequent reads see the populated row
  • Scan: duplicate-picker dialog now synchronously lazy-fetches the primary's full preview when its bytes / SHA aren't already in allResults, so the Similarity column populates even on the first open before any async load has happened. The status line gained a Ranked X/Y candidates counter so users can tell at a glance whether the column is empty due to no matches versus a missing primary preview
  • Scan: similarity dHash now falls back to the rendered Resource.Content() (PNG/JPEG) when the raw DownloadBytes from AssetDelivery aren't decodable by Go's stdlib image package — Roblox sometimes ships textures in formats stdlib doesn't read (KTX2, custom containers), and the rendered preview is the reliable fallback. Previously every row whose primary fell into that bucket rendered "-" in the Similarity column even when matches existed
  • Scan: similarity ranking for normal-map slots now hashes the R and G channels independently and sums their Hamming distances, instead of using the standard luminance dHash. Normal maps encode XY surface deflection in R/G with the Z (blue) channel near-uniform, which dominated luminance and made every normal map look like every other one to the old hash; the dual-channel variant catches X-only vs Y-only deflection patterns that luminance collapses. Detected automatically from the row's PropertyName (NormalMap / NormalMapContent) and applied to every candidate so distances stay comparable; the Similarity column scales its percentage to the doubled bit space (112 vs 56) so identical files still read 100% (0)
  • Report Generation: Export JSON button — saves the full graded report (overall, per-grade letters/scores/values, summary stats, per-cell avg/p90/max metrics, mismatched-PBR / oversized-texture / duplicate-group drill-downs, asset type, source file metadata) as a single JSON file via the native save dialog. Versioned schema (format_version) so a future viewer / share-link server can render historical exports without depending on internal types

Added

  • Report Generation: per-asset-type hard cap on individual texture GPU-memory footprint. DE Map reports flag any texture whose largest per-slot GPU memory (BC1 or BC3, with mips) exceeds the configured limit and force the overall grade to F regardless of every other metric. A red banner above the grade table shows the offender count with a View button listing each texture's asset ID, resolution, GPU-bytes/format, and instance path, plus an Ignore button that suppresses the F override and reveals the natural grade (with a Restore button to undo)
  • Scan: top-bar Asset type selector. When set to a type with a configured GPU-memory ban (e.g. DE Map), result rows whose largest per-slot GPU footprint exceeds the limit are highlighted in red so banned textures are immediately obvious in scan-mode triage
  • Scan: new Materials sub-tab next to the asset table. Shows engine-deduplicated PBR materials — one row per unique (color/normal/metalness/roughness) asset combo — with the authored slot sizes, the engine-effective normal size (upscaled to its largest paired color), the MR-pack size (max of group normal/color/M/R), the BC1+BC3 GPU bytes the engine actually allocates for that material's share, and how many SurfaceAppearance instances reuse the bundle. A header summary shows the engine GPU-memory total — dedupes shared normal assets, accounts for blank-MR packs, so it lines up with the report tab's GPU Texture Memory headline rather than the asset-level sum
  • Material variant warnings: Ignore button next to the existing View button so the warning banner can be dismissed for the current load without dismissing the underlying data

Changed

  • Report Generation: View in Scan no longer re-fetches every asset — the previews already loaded for the report are handed off directly to the Scan tab, so the table appears immediately. The Scan Asset type selector is auto-set to match the report
  • Report Generation: spatial grades now show two columns — typical (content-weighted average, Σv²/Σv — the cell value an average unit of content sits in, ignoring the long tail of near-empty cells) and max/cell. The avg/cell and p90/cell columns are gone
  • Report Generation: row grades are now the average of the typical-cell sub-score and the p90-cell sub-score (max no longer contributes to the grade — it's informational only)
  • Report Generation: Mismatched PBR Maps now counts unique asset combos rather than raw SurfaceAppearance instances — multiple instances sharing the same (color/normal/metalness/roughness) bundle collapse to one entry, since fixing the asset fixes every usage. The grade threshold grades against the unique-combo count, the value cell shows e.g. 3 (8.00 MB), and the View dialog shows one row per combo with an ×N instances / save N MB suffix, sorted by per-combo savings desc (then instance count desc) so the highest-impact fixes float to the top
  • Report Generation: Mismatched PBR Maps value cell now also shows the GPU-memory savings from downscaling each mismatched combo's bigger-than-color slots to match its color map. Computed via the engine allocation model with proper asset deduplication: a single oversized texture shared across many combos counts once, not once per usage; a shared asset still needed at full size by any matched combo correctly contributes zero
  • Scan: result table now lives under an Assets sub-tab. The asset table itself is unchanged — every row keeps showing the literal authored size and per-asset GPU footprint

Full Changelog: v1.4.2...v1.5.0