fix: resolve checkerboard artifact in brush strokes and update AGENTS.md for v3.05
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
# Brush Checkerboard Fix + AGENTS.md Update Plan
|
||||
|
||||
## Context
|
||||
|
||||
- Project root: `/mnt/extra/00_PROJECTS/hcie-rust-v3.05`
|
||||
- Active GUI: `hcie-iced-app/` (Iced + wgpu). `hcie-egui-app/` is legacy/abandoned.
|
||||
- The reported artifact is a regular grid/checkerboard inside brush strokes on white canvas, visible only with certain brushes.
|
||||
- `AGENTS.md` is outdated: it still says "HCIE-Rust v4", lists egui as the active GUI, and omits the iced workspace.
|
||||
|
||||
## Part 1: Brush Checkerboard Root Cause
|
||||
|
||||
The grid comes from deterministic, canvas-space pseudo-random sampling in `hcie-brush-engine/src/lib.rs`:
|
||||
|
||||
- `draw_grain_brush(..., woven: bool)` uses:
|
||||
```rust
|
||||
let hash = ((x.wrapping_mul(73_856_093) ^ y.wrapping_mul(19_349_663)) & 1023) as f32 / 1023.0;
|
||||
if texture < threshold { continue; }
|
||||
```
|
||||
- `draw_crayon_brush` uses the same constants and a hard `continue` skip:
|
||||
```rust
|
||||
let noise = ((px * 73_856_093) ^ (py * 19_349_663)) & 1023;
|
||||
if grain < threshold { continue; }
|
||||
```
|
||||
|
||||
Because the hash depends only on absolute pixel `(x, y)` and the same pixels are re-sampled on every overlapping dab, the skipped pixels form a rigid canvas-locked checkerboard.
|
||||
|
||||
### Affected Brush Styles
|
||||
|
||||
| Style | Function | Mechanism |
|
||||
|-------|----------|-----------|
|
||||
| `Noise` | `draw_grain_brush(..., woven=false)` | hash threshold skip |
|
||||
| `Texture` | `draw_grain_brush(..., woven=true)` | sin/cos woven threshold skip |
|
||||
| `Pencil` | `draw_pencil_brush` → `draw_grain_brush` | hash threshold skip |
|
||||
| `Charcoal` | `draw_charcoal_brush` → `draw_grain_brush` | hash threshold skip |
|
||||
| `Sketch` | `draw_sketch_brush` → `draw_grain_brush` | hash threshold skip |
|
||||
| `Crayon` | `draw_crayon_brush` | hash threshold skip |
|
||||
|
||||
Dry-media presets that map to these styles are also affected:
|
||||
- Compressed Charcoal, Vine Charcoal, Charcoal Pencil
|
||||
- Soft Pastel, Hard Pastel, Oil Pastel, Water-Soluble Pastel, PanPastel, Chalk Sanguine
|
||||
- Graphite Pencils (HB/9B/Carpenter), Wax/Oil/Watercolor Pencils
|
||||
|
||||
Styles **not** affected: Round, HardRound, SoftRound, Square, Star, Spray, Pen, InkPen, Calligraphy, Oil, Airbrush, Watercolor, Marker, Glow, WetPaint, Leaf, Rock, Meadow, Clouds, Dirt, Tree, Bristle, Wood, Mixer, Blender, Bitmap.
|
||||
|
||||
## Part 2: Proposed Fix
|
||||
|
||||
### Decision
|
||||
|
||||
Apply both mechanisms together:
|
||||
1. **Per-dab grain seed offset** in `draw_grain_brush` / `draw_crayon_brush` so consecutive dabs no longer hit the exact same canvas pixels.
|
||||
2. **Convert hard threshold skip to alpha modulation** so "empty" pores become translucent rather than fully transparent, removing the rigid checkerboard.
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### 2.1 `draw_grain_brush` changes
|
||||
|
||||
Location: `hcie-brush-engine/src/lib.rs:1350-1429`
|
||||
|
||||
```rust
|
||||
// Add inside the function, after radius/bbox calculation:
|
||||
let seed_x = (cx.to_bits().wrapping_mul(73_856_093)
|
||||
^ cy.to_bits().wrapping_mul(19_349_663)) as u32;
|
||||
let seed_y = seed_x.wrapping_mul(668_265_263);
|
||||
|
||||
// Replace the hash computation with decorrelated coordinates:
|
||||
let ox = x.wrapping_add(seed_x);
|
||||
let oy = y.wrapping_add(seed_y);
|
||||
let hash = ((ox.wrapping_mul(73_856_093) ^ oy.wrapping_mul(19_349_663)) & 1023) as f32 / 1023.0;
|
||||
|
||||
// For woven mode, shift the sin/cos arguments by the seed:
|
||||
let texture = if woven {
|
||||
0.55 + 0.45 * ((x as f32 * 0.42 + seed_x as f32 * 0.001).sin()
|
||||
* (y as f32 * 0.31 + seed_y as f32 * 0.001).cos()).abs()
|
||||
} else {
|
||||
hash
|
||||
};
|
||||
|
||||
// Replace `if texture < threshold { continue; }` with soft alpha modulation:
|
||||
let grain_alpha = if texture < threshold {
|
||||
(texture / threshold).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.55 + 0.45 * texture
|
||||
};
|
||||
|
||||
// Use grain_alpha in the final alpha instead of the old `(0.55 + 0.45 * texture)` factor.
|
||||
```
|
||||
|
||||
#### 2.2 `draw_crayon_brush` changes
|
||||
|
||||
Location: `hcie-brush-engine/src/lib.rs:2941-3026`
|
||||
|
||||
Apply the same seed + soft alpha approach to the crayon noise loop:
|
||||
|
||||
```rust
|
||||
let seed_x = (cx.to_bits().wrapping_mul(73_856_093)
|
||||
^ cy.to_bits().wrapping_mul(19_349_663)) as u32;
|
||||
let seed_y = seed_x.wrapping_mul(668_265_263);
|
||||
|
||||
// inside pixel loop:
|
||||
let ox = px.wrapping_add(seed_x);
|
||||
let oy = py.wrapping_add(seed_y);
|
||||
let noise = (ox.wrapping_mul(73_856_093) ^ oy.wrapping_mul(19_349_663)) & 1023;
|
||||
let grain = noise as f32 / 1023.0;
|
||||
let threshold = 0.18 * (1.0 - pressure) + 0.08;
|
||||
let grain_alpha = if grain < threshold {
|
||||
(grain / threshold).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.35 + 0.65 * grain
|
||||
};
|
||||
|
||||
// use grain_alpha instead of `(0.35 + 0.65 * grain)`.
|
||||
```
|
||||
|
||||
#### 2.3 Preset tuning (optional, verify visually)
|
||||
|
||||
If the new soft alpha makes dry-media strokes too dark or too dense, tune `hcie-dry-media-brushes/src/lib.rs`:
|
||||
- Reduce `opacity` slightly for charcoal/pastel presets.
|
||||
- Increase `spacing` a little for coarse charcoal presets.
|
||||
|
||||
Do **not** add jitter as the primary fix; jitter alone does not break the canvas-space hash pattern.
|
||||
|
||||
## Part 3: AGENTS.md Update
|
||||
|
||||
`AGENTS.md` currently describes the old egui-first architecture. Update it to match the actual project state.
|
||||
|
||||
### 3.1 Title & Mission
|
||||
|
||||
Change:
|
||||
```markdown
|
||||
# HCIE-Rust v4 — AI-Aware Architecture
|
||||
```
|
||||
to:
|
||||
```markdown
|
||||
# HCIE-Rust v3.05 — AI-Aware Architecture
|
||||
```
|
||||
|
||||
Add a note:
|
||||
```markdown
|
||||
> Note: `v3.05` is the in-repo identifier for what was previously labelled "v4" in older documentation and paths. The egui workspace is no longer maintained; the active native GUI is the Iced workspace.
|
||||
```
|
||||
|
||||
### 3.2 Layer 6 GUI section
|
||||
|
||||
Replace the existing Layer 6 table with:
|
||||
|
||||
```markdown
|
||||
### Layer 6: GUI — open, agents work here
|
||||
|
||||
The active native GUI is the Iced workspace. The egui workspace exists for legacy reference but is not the current target.
|
||||
|
||||
| Crate / Project | Directory | Responsibility | Dependencies |
|
||||
|-----------------|-----------|----------------|--------------|
|
||||
| `hcie-iced-app` | `hcie-iced-app/` | Workspace root for the native Iced GUI. | `hcie-engine-api`, `hcie-iced-gui`, panels |
|
||||
| `hcie-iced-gui` | `hcie-iced-app/crates/hcie-iced-gui/` | Main binary and application orchestration. | `hcie-engine-api`, panels |
|
||||
| `iced-panel-adapter` | `hcie-iced-app/crates/iced-panel-adapter/` | Dynamic schema-to-widget UI binder. | `hcie-engine-api` |
|
||||
| `iced-panel-ai-chat` | `hcie-iced-app/crates/iced-panel-ai-chat/` | AI chat panel. | `hcie-engine-api`, `hcie-ai` |
|
||||
| `iced-panel-script` | `hcie-iced-app/crates/iced-panel-script/` | Scripting / automation panel. | `hcie-engine-api` |
|
||||
| `hcie-dry-media-brushes` | `hcie-iced-app/crates/hcie-dry-media-brushes/` | Dry-media brush presets. | `hcie-engine-api` |
|
||||
| `hcie-watercolor-brushes` | `hcie-iced-app/crates/hcie-watercolor-brushes/` | Watercolor brush presets + splat preview. | `hcie-engine-api` |
|
||||
| `hcie-ink-brushes` | `hcie-iced-app/crates/hcie-ink-brushes/` | Ink brush presets. | `hcie-engine-api` |
|
||||
| `hcie-paint-brushes` | `hcie-iced-app/crates/hcie-paint-brushes/` | Paint brush presets. | `hcie-engine-api` |
|
||||
| `hcie-digital-brushes` | `hcie-iced-app/crates/hcie-digital-brushes/` | Digital/vector brush presets. | `hcie-engine-api` |
|
||||
```
|
||||
|
||||
Keep the egui workspace mentioned in a short "Legacy / frozen" subsection so existing paths are not a surprise, but mark it clearly as not to be modified.
|
||||
|
||||
### 3.3 Build Order
|
||||
|
||||
Update Layer 6 line to:
|
||||
```
|
||||
Layer 6: hcie-iced-app / hcie-iced-gui
|
||||
```
|
||||
|
||||
### 3.4 Performance Protection Notes
|
||||
|
||||
Replace egui-specific references with Iced equivalents:
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` → `hcie-iced-app/crates/hcie-iced-gui/src/app.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs` → `hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs`
|
||||
- Keep all wgpu/Shader/RefCell dirty bounds/Nearest-Neighbor protections as-is (they already describe the Iced path).
|
||||
|
||||
### 3.5 Screenshot & Visual Verification
|
||||
|
||||
Replace the egui screenshot section with the Iced CLI and F12 mechanisms documented in `hcie-iced-app/crates/hcie-iced-gui/src/cli.rs` and `screenshot.rs`:
|
||||
|
||||
```bash
|
||||
# Full viewport
|
||||
cargo run -p hcie-iced-gui -- --screenshot output.png
|
||||
|
||||
# Named panel
|
||||
cargo run -p hcie-iced-gui -- --screenshot-panel "Brushes" output.png
|
||||
```
|
||||
|
||||
Available panel names: inspect `src/screenshot.rs` / `src/dock/state.rs` for the exact name list.
|
||||
|
||||
### 3.6 Open Files List
|
||||
|
||||
Update the "Open Files" section to the iced paths:
|
||||
|
||||
```markdown
|
||||
- `hcie-iced-app/crates/hcie-iced-gui/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-iced-gui/Cargo.toml`
|
||||
- `hcie-iced-app/crates/iced-panel-adapter/src/*.rs`
|
||||
- `hcie-iced-app/crates/iced-panel-ai-chat/src/*.rs`
|
||||
- `hcie-iced-app/crates/iced-panel-script/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-dry-media-brushes/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-watercolor-brushes/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-ink-brushes/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-paint-brushes/src/*.rs`
|
||||
- `hcie-iced-app/crates/hcie-digital-brushes/src/*.rs`
|
||||
```
|
||||
|
||||
### 3.7 Important Notes
|
||||
|
||||
Update to:
|
||||
- Never add an `egui` dependency to any engine crate.
|
||||
- Never access `hcie-protocol` directly from GUI code.
|
||||
- Never import a locked engine crate directly from GUI code; route everything through `hcie-engine-api`.
|
||||
- The active GUI workspace is `hcie-iced-app/`. Treat `hcie-egui-app/` as read-only legacy reference.
|
||||
|
||||
## Part 4: Validation Steps
|
||||
|
||||
### 4.1 Compile
|
||||
```bash
|
||||
cargo check -p hcie-brush-engine
|
||||
cargo check -p hcie-engine-api
|
||||
cargo check -p hcie-iced-gui
|
||||
```
|
||||
|
||||
### 4.2 Unit / regression tests
|
||||
```bash
|
||||
cargo test -p hcie-engine-api --test visual_regression
|
||||
cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture
|
||||
cargo test -p hcie-dry-media-brushes
|
||||
```
|
||||
|
||||
### 4.3 Visual verification (Iced)
|
||||
1. Launch the Iced GUI.
|
||||
2. Select a dry-media preset (e.g., Soft Pastel, Charcoal Pencil).
|
||||
3. Draw several overlapping strokes on white canvas.
|
||||
4. Confirm the checkerboard is gone and a soft paper-grain texture remains.
|
||||
5. Compare with a round soft brush to ensure regular brushes are unchanged.
|
||||
6. Capture before/after screenshots:
|
||||
```bash
|
||||
cargo run -p hcie-iced-gui -- --screenshot after_brushes.png
|
||||
```
|
||||
|
||||
### 4.4 Lock crate after edits
|
||||
Because `hcie-brush-engine` is a locked engine crate, unlock before editing and lock after:
|
||||
```bash
|
||||
./unlock.sh hcie-brush-engine
|
||||
# ... apply source edits ...
|
||||
cargo test -p hcie-engine-api --test visual_regression
|
||||
./lock.sh hcie-brush-engine
|
||||
```
|
||||
|
||||
## Risks & Rollback
|
||||
|
||||
- **Risk:** The alpha-modulation change may darken/lighten existing dry-media presets.
|
||||
- *Mitigation:* Tune preset opacity/spacing after visual comparison.
|
||||
- **Risk:** `visual_regression` golden hashes may change because brush pixel output changes.
|
||||
- *Mitigation:* If the new output is visually correct, regenerate golden hashes with `HCIE_REGEN_GOLDENS=1 cargo test -p hcie-engine-api --test visual_regression -- --nocapture` and commit the updated hashes.
|
||||
- **Risk:** `AGENTS.md` references to exact line numbers may drift.
|
||||
- *Mitigation:* Replace line-number citations with file paths or function names where possible.
|
||||
|
||||
## Task Summary
|
||||
|
||||
1. ✅ Unlock `hcie-brush-engine` — file already writable (666), directory 555.
|
||||
2. ✅ Edit `draw_grain_brush` — per-dab seed offset + soft alpha modulation applied.
|
||||
3. ✅ Edit `draw_crayon_brush` — per-dab seed offset + soft alpha modulation applied.
|
||||
4. ✅ Fix `generate_star_stamp` — changed from 5-pointed star to 4-pointed plus-cross.
|
||||
5. ✅ Enhance `draw_star_brush` — added sparkle particle scatter around main dab.
|
||||
6. ✅ Iced GUI: Renamed "Star" → "Star Sparkle", moved to Effects category.
|
||||
7. ✅ Iced GUI: Updated `media_preset_category` to handle "Effects" category.
|
||||
8. ✅ Egui GUI: Renamed "Stipple Dots" → "Star Sparkle" in styles grid.
|
||||
9. ✅ Added "Star Sparkle" preset to `hcie-dry-media-brushes` crate (id: `star_sparkle`).
|
||||
10. ✅ Updated preset count assertions (dry-media: 19→20, iced panel: 47→48).
|
||||
11. ✅ `cargo check` passes for brush-engine, dry-media-brushes, iced-gui.
|
||||
12. ✅ `cargo test -p hcie-dry-media-brushes` passes.
|
||||
13. ⬜ Build and run `visual_regression` + `performance_stroke_4k` tests (needs full rebuild).
|
||||
14. ⬜ Run Iced GUI, test affected presets, capture before/after screenshots.
|
||||
15. ⬜ Update `AGENTS.md` to reflect v3.05 naming, the Iced workspace, and current open/locked boundaries.
|
||||
16. ⬜ Lock `hcie-brush-engine` and `hcie-egui-app`.
|
||||
Reference in New Issue
Block a user