Skip to content

Feature: Calibration tool for measuring actual Rust paint values#10

Open
VoX wants to merge 1 commit intomainfrom
feature/calibration-tool
Open

Feature: Calibration tool for measuring actual Rust paint values#10
VoX wants to merge 1 commit intomainfrom
feature/calibration-tool

Conversation

@VoX
Copy link
Copy Markdown
Owner

@VoX VoX commented Apr 5, 2026

Summary

Calibration tool to measure the actual circle sizes, alpha values, and circle shapes from Rust game screenshots and compare against Bob-Rust's hardcoded assumptions.

New files

  • CalibrationPatternGenerator — generates a 6x6 calibration grid (size x opacity), white on black
  • ScreenshotAnalyzer — analyzes a screenshot of the painted pattern, auto-detects grid, measures diameter/alpha/shape match per cell
  • CalibrationRoundTripTest — 5 tests including round-trip verification and 2x scale detection

Usage

  1. Run CalibrationPatternGenerator to create reference pattern
  2. Paint the pattern in Rust (or use debug mode)
  3. Screenshot it
  4. Run ScreenshotAnalyzer path/to/screenshot.png
  5. Get corrected SIZES/ALPHAS arrays + diff images

Output includes

  • Console table of measured vs expected values
  • Suggested corrected Java constants
  • Color-coded diff image (green=correct, red=missing, blue=false positive)

All 41 tests pass.

Provides a CalibrationPatternGenerator that creates a 6x6 reference
grid (sizes x alphas) and a ScreenshotAnalyzer that measures painted
circles from Rust screenshots to detect mismatches with hardcoded
SIZES and ALPHAS constants. Includes grid auto-detection via
brightness projections, shape diff image generation, and a
copy-paste Java snippet for corrected values.

Also makes CircleCache and Scanline public so the calibration
package can access the scanline masks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 5, 2026 03:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a calibration workflow to generate a known size/opacity grid pattern and analyze Rust screenshots to measure actual brush diameters, effective alpha, and circle shape fidelity vs the current scanline masks.

Changes:

  • Introduces CalibrationPatternGenerator to generate a size×opacity grid reference image.
  • Introduces ScreenshotAnalyzer to auto-detect the grid in a screenshot, measure per-cell diameter/alpha/shape match, and emit a diff image + suggested constants.
  • Adds CalibrationRoundTripTest to validate round-trip behavior (including 2x scaling) and exposes generator internals needed by the calibration code.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/test/java/com/bobrust/calibration/CalibrationRoundTripTest.java Adds round-trip + scaling tests for the calibration toolchain
src/main/java/com/bobrust/generator/Scanline.java Makes Scanline public for cross-package calibration usage
src/main/java/com/bobrust/generator/CircleCache.java Makes CircleCache public so calibration code can access scanline masks
src/main/java/com/bobrust/calibration/ScreenshotAnalyzer.java New screenshot analysis CLI + measurement and diff-image generation
src/main/java/com/bobrust/calibration/CalibrationPatternGenerator.java New reference-pattern generator CLI (grid of size/opacity circles)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* <li>Shape match percentage against Bob-Rust's scanline masks</li>
* </ul>
*
* Usage: java -cp ... com.bobrust.calibration.ScreenshotAnalyzer screenshot.png [reference.png]
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class-level Javadoc says the CLI usage is screenshot.png [reference.png], but main actually treats the optional second argument as the diff output path. Please update the Javadoc usage string to match the implemented arguments (or implement the reference-image parameter if that was intended).

Suggested change
* Usage: java -cp ... com.bobrust.calibration.ScreenshotAnalyzer screenshot.png [reference.png]
* Usage: java -cp ... com.bobrust.calibration.ScreenshotAnalyzer screenshot.png [diff.png]

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +152
// Find the total energy to set a threshold
long totalEnergy = 0;
for (long v : proj) totalEnergy += v;
if (totalEnergy == 0) return null;

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says totalEnergy is used to set a threshold, but the code only uses it to early-return when the projection is empty. Either add the intended thresholding (to avoid noise peaks) or update the comment to reflect the actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +321 to +327
int totalChecked = 0;
for (int dy = -halfSize; dy <= halfSize; dy++) {
for (int dx = -halfSize; dx <= halfSize; dx++) {
int px = cx + dx;
int py = cy + dy;
if (px < 0 || px >= imgW || py < 0 || py >= imgH) continue;
totalChecked++;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totalChecked is incremented but never used. Please remove it (or use it to report/normalize something) to avoid dead code and confusion about what the loop is measuring.

Suggested change
int totalChecked = 0;
for (int dy = -halfSize; dy <= halfSize; dy++) {
for (int dx = -halfSize; dx <= halfSize; dx++) {
int px = cx + dx;
int py = cy + dy;
if (px < 0 || px >= imgW || py < 0 || py >= imgH) continue;
totalChecked++;
for (int dy = -halfSize; dy <= halfSize; dy++) {
for (int dx = -halfSize; dx <= halfSize; dx++) {
int px = cx + dx;
int py = cy + dy;
if (px < 0 || px >= imgW || py < 0 || py >= imgH) continue;

Copilot uses AI. Check for mistakes.
Comment on lines +336 to +340
if (totalExpected == 0) return 0.0;
// F1-like score: penalize both misses and false positives
int errors = (totalExpected - matched) + falsePositives;
int possible = totalExpected + falsePositives;
return possible > 0 ? 100.0 * (possible - errors) / possible : 100.0;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The score labeled as "F1-like" is effectively matched / (totalExpected + falsePositives) (IoU/Jaccard-style), not an F1 score. Either rename the metric/comment to what it is, or change the formula to a true F1 (2TP/(2TP+FP+FN)) so the reported percentage matches the description.

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +102
// White with given alpha, composited onto black background:
// result = alpha/255 * 255 = alpha
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment describes drawing "white with given alpha" composited onto black, but the code actually writes an opaque pixel with RGB set to alpha (gray). Please update the comment to match what is written, since this affects how consumers interpret the generated pattern (alpha channel vs brightness).

Suggested change
// White with given alpha, composited onto black background:
// result = alpha/255 * 255 = alpha
// Store the composited-on-black result directly as an opaque grayscale pixel:
// RGB = alpha, with a fully opaque alpha channel.

Copilot uses AI. Check for mistakes.
Comment on lines 5 to 9
import org.apache.logging.log4j.Logger;

class CircleCache {
public class CircleCache {
private static final Logger LOGGER = LogManager.getLogger(BobRustPainter.class);

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this class public exposes CIRCLE_CACHE/CIRCLE_CACHE_LENGTH as part of the public API, and those arrays remain mutable from outside the package. If this is only needed for calibration tooling, consider keeping the class/package internals non-public and exposing a narrow accessor (or returning defensive copies / unmodifiable views) to avoid accidental mutation by external callers.

Copilot uses AI. Check for mistakes.
Comment on lines 4 to 6
public int y;
public int x1;
public int x2; // inclusive
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing Scanline to public exposes a mutable data type (public fields) as API surface. Consider making the fields final (or using an immutable type like a record) so callers can’t mutate cached scanlines and corrupt shape masks at runtime.

Suggested change
public int y;
public int x1;
public int x2; // inclusive
public final int y;
public final int x1;
public final int x2; // inclusive

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +82
// All bottom-row detected cells should have correct alpha
int bottomRowDetected = 0;
for (int col = 0; col < numSizes; col++) {
if (detected[numAlphas - 1][col]) bottomRowDetected++;
}
assertTrue(alphaMatches >= bottomRowDetected * 0.8,
"Too few alpha matches in bottom row: " + alphaMatches + "/" + bottomRowDetected);
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "All bottom-row detected cells should have correct alpha", but the assertion only requires 80% (alphaMatches >= bottomRowDetected * 0.8). Please align the comment/message with the actual tolerance, or tighten the assertion if the intent is truly 100% for the round-trip case.

Suggested change
// All bottom-row detected cells should have correct alpha
int bottomRowDetected = 0;
for (int col = 0; col < numSizes; col++) {
if (detected[numAlphas - 1][col]) bottomRowDetected++;
}
assertTrue(alphaMatches >= bottomRowDetected * 0.8,
"Too few alpha matches in bottom row: " + alphaMatches + "/" + bottomRowDetected);
// At least 80% of bottom-row detected cells should have correct alpha
int bottomRowDetected = 0;
for (int col = 0; col < numSizes; col++) {
if (detected[numAlphas - 1][col]) bottomRowDetected++;
}
assertTrue(alphaMatches >= bottomRowDetected * 0.8,
"Too few alpha matches in bottom row (expected at least 80%): "
+ alphaMatches + "/" + bottomRowDetected);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants