Fix: background splitting bug
This commit is contained in:
35
assets/sprite-packs/demogorgon/manifest.json
Normal file
35
assets/sprite-packs/demogorgon/manifest.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"id": "demogorgon",
|
||||||
|
"version": "1",
|
||||||
|
"image": "sprite.png",
|
||||||
|
"frame_width": 512,
|
||||||
|
"frame_height": 512,
|
||||||
|
"animations": [
|
||||||
|
{
|
||||||
|
"name": "idle",
|
||||||
|
"fps": 6,
|
||||||
|
"frames": [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"fps": 10,
|
||||||
|
"frames": [1, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "success",
|
||||||
|
"fps": 10,
|
||||||
|
"frames": [0, 1, 0],
|
||||||
|
"one_shot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "error",
|
||||||
|
"fps": 8,
|
||||||
|
"frames": [1, 0, 1],
|
||||||
|
"one_shot": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
assets/sprite-packs/demogorgon/sprite.png
Normal file
BIN
assets/sprite-packs/demogorgon/sprite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 MiB |
@@ -10,6 +10,7 @@
|
|||||||
"core:window:allow-set-position",
|
"core:window:allow-set-position",
|
||||||
"core:window:allow-inner-size",
|
"core:window:allow-inner-size",
|
||||||
"core:window:allow-outer-position",
|
"core:window:allow-outer-position",
|
||||||
|
"core:window:allow-current-monitor",
|
||||||
"core:event:allow-listen",
|
"core:event:allow-listen",
|
||||||
"core:event:allow-unlisten"
|
"core:event:allow-unlisten"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"default":{"identifier":"default","description":"Default capability for sprimo-tauri main window runtime APIs.","local":true,"windows":["*"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-size","core:window:allow-set-position","core:window:allow-inner-size","core:window:allow-outer-position","core:event:allow-listen","core:event:allow-unlisten"]}}
|
{"default":{"identifier":"default","description":"Default capability for sprimo-tauri main window runtime APIs.","local":true,"windows":["*"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-size","core:window:allow-set-position","core:window:allow-inner-size","core:window:allow-outer-position","core:window:allow-current-monitor","core:event:allow-listen","core:event:allow-unlisten"]}}
|
||||||
@@ -134,6 +134,14 @@ Bevy window-management verification workflow: `docs/BEVY_WINDOW_VERIFICATION.md`
|
|||||||
- run repeated character switch cycles (`default` <-> `ferris`) and move scale slider each cycle
|
- run repeated character switch cycles (`default` <-> `ferris`) and move scale slider each cycle
|
||||||
- ensure no runtime frontend exception is shown (debug overlay/console)
|
- ensure no runtime frontend exception is shown (debug overlay/console)
|
||||||
- ensure no visible magenta fringe remains around sprite edges after chroma-key conversion
|
- ensure no visible magenta fringe remains around sprite edges after chroma-key conversion
|
||||||
|
12. Verify packaged tauri scale anchoring and bounds:
|
||||||
|
- repeated scale changes resize around window center (no consistent bottom-right drift)
|
||||||
|
- window remains visible on the current monitor (no off-screen drift)
|
||||||
|
- no runtime scale-path exception appears (for example monitor lookup API errors)
|
||||||
|
- no runtime position-arg exceptions appear during scale (e.g. float passed to integer position API)
|
||||||
|
- at large scale values (>= 1.8), full sprite remains visible without clipping
|
||||||
|
13. Verify packaged tauri frontend freshness:
|
||||||
|
- confirm package run reflects latest `frontend/tauri-ui` changes (no stale embedded UI bundle)
|
||||||
|
|
||||||
### Packaged Mode (Required Once Tauri Packaging Exists)
|
### Packaged Mode (Required Once Tauri Packaging Exists)
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,13 @@ An issue touching Tauri runtime behaviors must satisfy all requirements before `
|
|||||||
- scaling behavior remains responsive after each pack switch
|
- scaling behavior remains responsive after each pack switch
|
||||||
8. Chroma-key quality check:
|
8. Chroma-key quality check:
|
||||||
- verify no visible magenta (`#FF00FF`) fringe remains around sprite edges in normal runtime view
|
- verify no visible magenta (`#FF00FF`) fringe remains around sprite edges in normal runtime view
|
||||||
|
9. Scale anchor and bounds check:
|
||||||
|
- repeated scale changes should keep window centered without directional drift
|
||||||
|
- window must remain within current monitor bounds during scale adjustments
|
||||||
|
- no runtime error is allowed from monitor lookup during scale operations (e.g. `currentMonitor`
|
||||||
|
API mismatch)
|
||||||
|
- verify window resize uses consistent coordinate units (no accumulated drift over 20 scale changes)
|
||||||
|
- no runtime command/type error from position updates (e.g. `set_position` expects integer coords)
|
||||||
|
|
||||||
## Settings Window Checklist
|
## Settings Window Checklist
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { LogicalPosition, LogicalSize, getCurrentWindow } from "@tauri-apps/api/window";
|
import {
|
||||||
|
PhysicalPosition,
|
||||||
|
PhysicalSize,
|
||||||
|
currentMonitor,
|
||||||
|
getCurrentWindow,
|
||||||
|
monitorFromPoint
|
||||||
|
} from "@tauri-apps/api/window";
|
||||||
import { PixiPetRenderer, type UiSpritePack, type UiSnapshot } from "./renderer/pixi_pet";
|
import { PixiPetRenderer, type UiSpritePack, type UiSnapshot } from "./renderer/pixi_pet";
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
@@ -45,29 +51,58 @@ function fittedWindowSize(
|
|||||||
scale: number
|
scale: number
|
||||||
): { width: number; height: number } {
|
): { width: number; height: number } {
|
||||||
const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
||||||
const width = Math.max(frameWidth * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE);
|
const width = Math.round(Math.max(frameWidth * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE));
|
||||||
const height = Math.max(frameHeight * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE);
|
const height = Math.round(Math.max(frameHeight * safeScale + WINDOW_PADDING, MIN_WINDOW_SIZE));
|
||||||
return { width, height };
|
return { width, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fitWindowForScale(pack: UiSpritePack, scale: number): Promise<void> {
|
async function fitWindowForScale(pack: UiSpritePack, scale: number): Promise<void> {
|
||||||
const window = getCurrentWindow();
|
const window = getCurrentWindow();
|
||||||
const [outerPosition, innerSize] = await Promise.all([window.outerPosition(), window.innerSize()]);
|
const [outerPosition, innerSize] = await Promise.all([window.outerPosition(), window.innerSize()]);
|
||||||
|
|
||||||
const target = fittedWindowSize(pack.frame_width, pack.frame_height, scale);
|
const target = fittedWindowSize(pack.frame_width, pack.frame_height, scale);
|
||||||
const widthChanged = Math.abs(target.width - innerSize.width) > SIZE_EPSILON;
|
const centerX = outerPosition.x + innerSize.width / 2;
|
||||||
const heightChanged = Math.abs(target.height - innerSize.height) > SIZE_EPSILON;
|
const centerY = outerPosition.y + innerSize.height / 2;
|
||||||
|
let targetWidth = target.width;
|
||||||
|
let targetHeight = target.height;
|
||||||
|
let targetX = centerX - targetWidth / 2;
|
||||||
|
let targetY = centerY - targetHeight / 2;
|
||||||
|
let monitor:
|
||||||
|
| {
|
||||||
|
position: { x: number; y: number };
|
||||||
|
size: { width: number; height: number };
|
||||||
|
workArea: { position: { x: number; y: number }; size: { width: number; height: number } };
|
||||||
|
}
|
||||||
|
| null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
monitor = (await monitorFromPoint(centerX, centerY)) ?? (await currentMonitor());
|
||||||
|
} catch {
|
||||||
|
monitor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor !== null) {
|
||||||
|
targetWidth = Math.min(targetWidth, monitor.workArea.size.width);
|
||||||
|
targetHeight = Math.min(targetHeight, monitor.workArea.size.height);
|
||||||
|
targetX = centerX - targetWidth / 2;
|
||||||
|
targetY = centerY - targetHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widthChanged = Math.abs(targetWidth - innerSize.width) > SIZE_EPSILON;
|
||||||
|
const heightChanged = Math.abs(targetHeight - innerSize.height) > SIZE_EPSILON;
|
||||||
if (!widthChanged && !heightChanged) {
|
if (!widthChanged && !heightChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaWidth = target.width - innerSize.width;
|
await window.setSize(new PhysicalSize(targetWidth, targetHeight));
|
||||||
const deltaHeight = target.height - innerSize.height;
|
if (monitor !== null) {
|
||||||
const targetX = outerPosition.x - deltaWidth / 2;
|
const minX = Math.round(monitor.workArea.position.x);
|
||||||
const targetY = outerPosition.y - deltaHeight;
|
const minY = Math.round(monitor.workArea.position.y);
|
||||||
|
const maxX = Math.round(monitor.workArea.position.x + monitor.workArea.size.width - targetWidth);
|
||||||
await window.setSize(new LogicalSize(target.width, target.height));
|
const maxY = Math.round(monitor.workArea.position.y + monitor.workArea.size.height - targetHeight);
|
||||||
await window.setPosition(new LogicalPosition(targetX, targetY));
|
targetX = maxX < minX ? minX : Math.min(Math.max(targetX, minX), maxX);
|
||||||
|
targetY = maxY < minY ? minY : Math.min(Math.max(targetY, minY), maxY);
|
||||||
|
}
|
||||||
|
await window.setPosition(new PhysicalPosition(Math.round(targetX), Math.round(targetY)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function MainOverlayWindow(): JSX.Element {
|
function MainOverlayWindow(): JSX.Element {
|
||||||
|
|||||||
@@ -33,6 +33,22 @@ export type UiSnapshot = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type AnimationMap = Map<string, UiAnimationClip>;
|
type AnimationMap = Map<string, UiAnimationClip>;
|
||||||
|
const KEY_R = 0xff;
|
||||||
|
const KEY_G = 0x00;
|
||||||
|
const KEY_B = 0xff;
|
||||||
|
const FALLBACK_MIN_CONNECTED_RATIO = 0.005;
|
||||||
|
const CONNECTED_HUE_MIN = 270;
|
||||||
|
const CONNECTED_HUE_MAX = 350;
|
||||||
|
const CONNECTED_SAT_MIN = 0.25;
|
||||||
|
const CONNECTED_VAL_MIN = 0.08;
|
||||||
|
const FALLBACK_HUE_MIN = 255;
|
||||||
|
const FALLBACK_HUE_MAX = 355;
|
||||||
|
const FALLBACK_SAT_MIN = 0.15;
|
||||||
|
const FALLBACK_VAL_MIN = 0.04;
|
||||||
|
const HALO_HUE_MIN = 245;
|
||||||
|
const HALO_HUE_MAX = 355;
|
||||||
|
const HALO_SAT_MIN = 0.15;
|
||||||
|
const HALO_VAL_MIN = 0.04;
|
||||||
|
|
||||||
export class PixiPetRenderer {
|
export class PixiPetRenderer {
|
||||||
private app: Application;
|
private app: Application;
|
||||||
@@ -101,28 +117,144 @@ export class PixiPetRenderer {
|
|||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
const frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
const data = frame.data;
|
const data = frame.data;
|
||||||
const keyR = 0xff;
|
const width = canvas.width;
|
||||||
const keyG = 0x00;
|
const height = canvas.height;
|
||||||
const keyB = 0xff;
|
const pixelCount = width * height;
|
||||||
const hardTolerance = 22;
|
const isKeyLike = new Uint8Array(pixelCount);
|
||||||
const softTolerance = 46;
|
const removedBg = new Uint8Array(pixelCount);
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
const queue = new Int32Array(pixelCount);
|
||||||
const dr = Math.abs(data[i] - keyR);
|
let head = 0;
|
||||||
const dg = Math.abs(data[i + 1] - keyG);
|
let tail = 0;
|
||||||
const db = Math.abs(data[i + 2] - keyB);
|
|
||||||
const maxDistance = Math.max(dr, dg, db);
|
const indexFor = (x: number, y: number): number => y * width + x;
|
||||||
if (maxDistance <= hardTolerance) {
|
const channelOffset = (index: number): number => index * 4;
|
||||||
data[i + 3] = 0;
|
const enqueueIfKeyLike = (x: number, y: number): void => {
|
||||||
|
const idx = indexFor(x, y);
|
||||||
|
if (isKeyLike[idx] === 1 && removedBg[idx] === 0) {
|
||||||
|
removedBg[idx] = 1;
|
||||||
|
queue[tail] = idx;
|
||||||
|
tail += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||||
|
const offset = channelOffset(idx);
|
||||||
|
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||||
|
data[offset],
|
||||||
|
data[offset + 1],
|
||||||
|
data[offset + 2]
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
PixiPetRenderer.isHueInRange(h, CONNECTED_HUE_MIN, CONNECTED_HUE_MAX) &&
|
||||||
|
s >= CONNECTED_SAT_MIN &&
|
||||||
|
v >= CONNECTED_VAL_MIN
|
||||||
|
) {
|
||||||
|
isKeyLike[idx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let x = 0; x < width; x += 1) {
|
||||||
|
enqueueIfKeyLike(x, 0);
|
||||||
|
enqueueIfKeyLike(x, height - 1);
|
||||||
|
}
|
||||||
|
for (let y = 1; y < height - 1; y += 1) {
|
||||||
|
enqueueIfKeyLike(0, y);
|
||||||
|
enqueueIfKeyLike(width - 1, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (head < tail) {
|
||||||
|
const idx = queue[head];
|
||||||
|
head += 1;
|
||||||
|
const x = idx % width;
|
||||||
|
const y = Math.floor(idx / width);
|
||||||
|
if (x > 0) {
|
||||||
|
enqueueIfKeyLike(x - 1, y);
|
||||||
|
}
|
||||||
|
if (x + 1 < width) {
|
||||||
|
enqueueIfKeyLike(x + 1, y);
|
||||||
|
}
|
||||||
|
if (y > 0) {
|
||||||
|
enqueueIfKeyLike(x, y - 1);
|
||||||
|
}
|
||||||
|
if (y + 1 < height) {
|
||||||
|
enqueueIfKeyLike(x, y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectedRemovedCount = tail;
|
||||||
|
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||||
|
if (removedBg[idx] !== 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (maxDistance <= softTolerance) {
|
const offset = channelOffset(idx);
|
||||||
const alphaScale =
|
data[offset + 3] = 0;
|
||||||
(maxDistance - hardTolerance) / (softTolerance - hardTolerance);
|
}
|
||||||
const suppress = 1 - alphaScale;
|
|
||||||
data[i + 3] = Math.round(data[i + 3] * alphaScale);
|
const needsFallback =
|
||||||
// Remove magenta spill from antialiased edges after alpha reduction.
|
connectedRemovedCount / Math.max(pixelCount, 1) < FALLBACK_MIN_CONNECTED_RATIO;
|
||||||
data[i] = Math.round(data[i] * (1 - 0.4 * suppress));
|
if (needsFallback) {
|
||||||
data[i + 2] = Math.round(data[i + 2] * (1 - 0.4 * suppress));
|
for (let idx = 0; idx < pixelCount; idx += 1) {
|
||||||
|
const offset = channelOffset(idx);
|
||||||
|
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||||
|
data[offset],
|
||||||
|
data[offset + 1],
|
||||||
|
data[offset + 2]
|
||||||
|
);
|
||||||
|
const maxDistanceFromHardKey = PixiPetRenderer.maxColorDistance(
|
||||||
|
data[offset],
|
||||||
|
data[offset + 1],
|
||||||
|
data[offset + 2],
|
||||||
|
KEY_R,
|
||||||
|
KEY_G,
|
||||||
|
KEY_B
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
(PixiPetRenderer.isHueInRange(h, FALLBACK_HUE_MIN, FALLBACK_HUE_MAX) &&
|
||||||
|
s >= FALLBACK_SAT_MIN &&
|
||||||
|
v >= FALLBACK_VAL_MIN) ||
|
||||||
|
maxDistanceFromHardKey <= 96
|
||||||
|
) {
|
||||||
|
data[offset + 3] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y += 1) {
|
||||||
|
for (let x = 0; x < width; x += 1) {
|
||||||
|
const idx = indexFor(x, y);
|
||||||
|
if (data[channelOffset(idx) + 3] === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let touchesBackground = false;
|
||||||
|
if (x > 0 && data[channelOffset(indexFor(x - 1, y)) + 3] === 0) {
|
||||||
|
touchesBackground = true;
|
||||||
|
} else if (x + 1 < width && data[channelOffset(indexFor(x + 1, y)) + 3] === 0) {
|
||||||
|
touchesBackground = true;
|
||||||
|
} else if (y > 0 && data[channelOffset(indexFor(x, y - 1)) + 3] === 0) {
|
||||||
|
touchesBackground = true;
|
||||||
|
} else if (y + 1 < height && data[channelOffset(indexFor(x, y + 1)) + 3] === 0) {
|
||||||
|
touchesBackground = true;
|
||||||
|
}
|
||||||
|
if (!touchesBackground) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = channelOffset(idx);
|
||||||
|
const [h, s, v] = PixiPetRenderer.rgbToHsv(
|
||||||
|
data[offset],
|
||||||
|
data[offset + 1],
|
||||||
|
data[offset + 2]
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!PixiPetRenderer.isHueInRange(h, HALO_HUE_MIN, HALO_HUE_MAX) ||
|
||||||
|
s < HALO_SAT_MIN ||
|
||||||
|
v < HALO_VAL_MIN
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data[offset] = Math.round(data[offset] * 0.72);
|
||||||
|
data[offset + 2] = Math.round(data[offset + 2] * 0.72);
|
||||||
|
data[offset + 3] = Math.round(data[offset + 3] * 0.86);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.putImageData(frame, 0, 0);
|
ctx.putImageData(frame, 0, 0);
|
||||||
@@ -135,6 +267,53 @@ export class PixiPetRenderer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static maxColorDistance(
|
||||||
|
r: number,
|
||||||
|
g: number,
|
||||||
|
b: number,
|
||||||
|
keyR: number,
|
||||||
|
keyG: number,
|
||||||
|
keyB: number
|
||||||
|
): number {
|
||||||
|
const dr = Math.abs(r - keyR);
|
||||||
|
const dg = Math.abs(g - keyG);
|
||||||
|
const db = Math.abs(b - keyB);
|
||||||
|
return Math.max(dr, dg, db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static rgbToHsv(r: number, g: number, b: number): [number, number, number] {
|
||||||
|
const rf = r / 255;
|
||||||
|
const gf = g / 255;
|
||||||
|
const bf = b / 255;
|
||||||
|
const max = Math.max(rf, gf, bf);
|
||||||
|
const min = Math.min(rf, gf, bf);
|
||||||
|
const delta = max - min;
|
||||||
|
|
||||||
|
let hue = 0;
|
||||||
|
if (delta > 0) {
|
||||||
|
if (max === rf) {
|
||||||
|
hue = 60 * (((gf - bf) / delta) % 6);
|
||||||
|
} else if (max === gf) {
|
||||||
|
hue = 60 * ((bf - rf) / delta + 2);
|
||||||
|
} else {
|
||||||
|
hue = 60 * ((rf - gf) / delta + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hue < 0) {
|
||||||
|
hue += 360;
|
||||||
|
}
|
||||||
|
const saturation = max === 0 ? 0 : delta / max;
|
||||||
|
const value = max;
|
||||||
|
return [hue, saturation, value];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static isHueInRange(hue: number, min: number, max: number): boolean {
|
||||||
|
if (min <= max) {
|
||||||
|
return hue >= min && hue <= max;
|
||||||
|
}
|
||||||
|
return hue >= min || hue <= max;
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
if (this.disposed) {
|
if (this.disposed) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ P1
|
|||||||
- `issues/screenshots/issue4.png`
|
- `issues/screenshots/issue4.png`
|
||||||
- `issues/screenshots/issue4-b.png`
|
- `issues/screenshots/issue4-b.png`
|
||||||
- `issues/screenshots/issue4-c.png`
|
- `issues/screenshots/issue4-c.png`
|
||||||
|
- `issues/screenshots/issue4-after-fix2-2026-02-14-145819.png`
|
||||||
|
- `issues/screenshots/issue4-after-fix4-2026-02-14-153233.png`
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
@@ -57,6 +59,17 @@ and scaling becomes ineffective after the error.
|
|||||||
the view in a broken/cropped state on creation failures.
|
the view in a broken/cropped state on creation failures.
|
||||||
5. Chroma-key conversion tolerance removed most `#FF00FF` background but still left magenta fringe
|
5. Chroma-key conversion tolerance removed most `#FF00FF` background but still left magenta fringe
|
||||||
on anti-aliased edges.
|
on anti-aliased edges.
|
||||||
|
6. Scale fit used repeated position deltas and caused directional drift during repeated resizing.
|
||||||
|
7. API mismatch in tauri window module:
|
||||||
|
- runtime used `getCurrentWindow().currentMonitor()` but this API version exposes monitor lookup as
|
||||||
|
module function (`currentMonitor`), causing `TypeError` and skipping window fit.
|
||||||
|
8. Scale position math mixed physical window metrics (`outerPosition`/`innerSize`) with logical
|
||||||
|
set operations (`LogicalSize`/`LogicalPosition`), reintroducing cumulative drift in some DPI
|
||||||
|
contexts.
|
||||||
|
9. Ferris background keying needed adaptive key detection; fixed `#FF00FF` assumptions were still
|
||||||
|
too brittle for packaged atlas variants.
|
||||||
|
10. Scale-path physical positioning used non-integer coordinates in `setPosition`, triggering
|
||||||
|
runtime arg errors (`expected i32`) and bypassing window fit updates.
|
||||||
|
|
||||||
## Fix Plan
|
## Fix Plan
|
||||||
|
|
||||||
@@ -93,6 +106,32 @@ Implemented:
|
|||||||
- Made pack reload transactional (keep old renderer until new renderer creation succeeds).
|
- Made pack reload transactional (keep old renderer until new renderer creation succeeds).
|
||||||
- Improved fit-window flow so scale apply continues after reload retries.
|
- Improved fit-window flow so scale apply continues after reload retries.
|
||||||
- Added targeted diagnostics for reload failures.
|
- Added targeted diagnostics for reload failures.
|
||||||
|
6. `frontend/tauri-ui/src/main.tsx`
|
||||||
|
- Changed scaling anchor to window center and clamped resized window position within current
|
||||||
|
monitor bounds.
|
||||||
|
7. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||||
|
- Replaced tolerance-only chroma key with border-connected `#FF00FF` background flood-fill removal
|
||||||
|
and localized edge halo suppression.
|
||||||
|
8. `crates/sprimo-tauri/capabilities/default.json`
|
||||||
|
- Added `core:window:allow-current-monitor` permission for monitor bounds clamping.
|
||||||
|
9. `frontend/tauri-ui/src/main.tsx`
|
||||||
|
- switched monitor lookup to module-level `currentMonitor()` with safe fallback so window scaling
|
||||||
|
still applies even if monitor introspection is unavailable.
|
||||||
|
10. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||||
|
- added fallback global key cleanup when border-connected background detection is too sparse.
|
||||||
|
11. `frontend/tauri-ui/src/main.tsx`
|
||||||
|
- moved scale resizing and positioning to physical units (`PhysicalSize`/`PhysicalPosition`) and
|
||||||
|
monitor selection at window-center point (`monitorFromPoint`).
|
||||||
|
12. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||||
|
- added adaptive border-derived key color selection with fallback key cleanup pass.
|
||||||
|
13. `scripts/package_windows.py`
|
||||||
|
- tauri packaging now explicitly rebuilds UI bundle to avoid stale embedded `dist` output.
|
||||||
|
14. `frontend/tauri-ui/src/main.tsx`
|
||||||
|
- enforced integer physical positioning and monitor work-area size clamping to prevent set-position
|
||||||
|
arg failures and large-scale clipping.
|
||||||
|
15. `frontend/tauri-ui/src/renderer/pixi_pet.ts`
|
||||||
|
- switched ferris cleanup to hue/saturation/value magenta-band masking with connected background
|
||||||
|
removal and stronger fallback cleanup.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
@@ -122,6 +161,14 @@ Implemented:
|
|||||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - applied 8x7 generic splitter policy and pack-ID correction.
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - applied 8x7 generic splitter policy and pack-ID correction.
|
||||||
- `2026-02-14 00:00` - reporter - `In Progress` - reported `issue4-after-fix1` still failing in packaged runtime.
|
- `2026-02-14 00:00` - reporter - `In Progress` - reported `issue4-after-fix1` still failing in packaged runtime.
|
||||||
- `2026-02-14 00:00` - codex - `Fix Implemented` - hardened renderer reload/dispose and chroma-key edge cleanup.
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - hardened renderer reload/dispose and chroma-key edge cleanup.
|
||||||
|
- `2026-02-14 00:00` - reporter - `In Progress` - remaining magenta ferris edge + scale drift reported.
|
||||||
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - switched to border-connected chroma-key removal and center-anchored, monitor-clamped scale fit.
|
||||||
|
- `2026-02-14 00:00` - reporter - `In Progress` - reported `currentMonitor` TypeError and ferris magenta background still visible.
|
||||||
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - corrected monitor API call and added fallback chroma cleanup pass.
|
||||||
|
- `2026-02-14 00:00` - reporter - `In Progress` - reported ferris magenta background still visible and scale drift recurrence.
|
||||||
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - switched to physical-unit resize math, adaptive key detection, and packaging UI refresh enforcement.
|
||||||
|
- `2026-02-14 00:00` - reporter - `In Progress` - reported default clipping, ferris background still present, and set_position float arg error.
|
||||||
|
- `2026-02-14 00:00` - codex - `Fix Implemented` - added integer-safe physical setPosition and HSV magenta cleanup strategy.
|
||||||
|
|
||||||
## Closure
|
## Closure
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ def sha256_file(path: Path) -> str:
|
|||||||
def package(frontend: FrontendLayout) -> PackageLayout:
|
def package(frontend: FrontendLayout) -> PackageLayout:
|
||||||
version = read_version()
|
version = read_version()
|
||||||
ensure_assets()
|
ensure_assets()
|
||||||
|
if frontend.id == "tauri":
|
||||||
|
run(["npm", "--prefix", "frontend/tauri-ui", "run", "build"])
|
||||||
binary = ensure_release_binary(frontend)
|
binary = ensure_release_binary(frontend)
|
||||||
runtime_files = ensure_runtime_files(frontend, binary.parent)
|
runtime_files = ensure_runtime_files(frontend, binary.parent)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user