Skip to content

feat(godot,#1091): brighter, clearer iso hero sprite#1144

Open
100yenadmin wants to merge 1 commit into
mainfrom
feat/godot-better-hero
Open

feat(godot,#1091): brighter, clearer iso hero sprite#1144
100yenadmin wants to merge 1 commit into
mainfrom
feat/godot-better-hero

Conversation

@100yenadmin

Copy link
Copy Markdown
Member

What

Replaces the dim/murky generic fighter iso bake (merged in #1142) with a bright, readable, detailed armored-knight hero at the same scope (sprite-fighter-iso8). Fully additive — no engine/python/scope-wiring change.

Why the old sheet was a dim grey blob — and the fix

The biggest lever was the bake lighting + color management in godot/tools/bake_sprites.py:

Problem Fix
No view transform set → Blender's default AgX (Filmic on older builds) desaturated + crushed midtones into grey view_transform="Standard", look="None", exposure +0.35
World ambient near-black (0.05) → shadow sides went dark Bright neutral-cool ambient (0.45..0.52) @ 1.0
Weak/uneven lights (sun=3 + 2 weak area fills) 4× SUN rig (direction-only → identical across all 8 turntable yaws): warm key 5.0, cool opposite fill 2.6, top wash 1.6, cool back/rim 3.0; EEVEE Fast-GI AO (5.x) w/ legacy GTAO fallback; 64 TAA samples

Art source: Meshy meshy-5 PBR text→3D (polished steel plate + royal-blue tabard + gold trim) → brightened Blender bake → pack_sheet.py @ sprite-fighter-iso8.

Validation (local, mirrors godot.yml)

  • godot --headless --path godot --import → clean (exit 0)
  • boot smoke → anims=32, WorldView location present
  • served-finals smokeserved-atlas-applied=true resolver_cached=true anims=32
  • combat-tokens → ok=true count=5/5 all_zoned=true team_tinted=true
  • smoke-intent → frozen-vocab only
  • Bake: 192/192 frames, dimetric ratio exact 2.0 (elev 29.531°), anchor (64,109), 0 missing.

Same scope key ⇒ godot.yml served-finals smoke needs no change — it cps this committed sheet. Avoids the multi-point scope-rename trap that broke #1142's first run.

Honest verdict (ceiling)

Brightness / readability / detail vs the old blob: decisively better — bar met. Style-match with the painterly tavern backdrop is ~80% — this is a clean 3D PBR render (the Meshy→bake ceiling), readable and grounded on the floor, but not pixel-identical to the hand-painted oil look. The head reads as a pointed helm/hood (A-pose derived from the T-pose source); fine at 128px token scale. A true painterly match would need a 2D path: Scenario character art + manual 8-facing iso framing + a paint post-process.

Diff

sheet.png + sheet.json (attribution prompt only) + bake_sprites.py (lighting). No engine/python/scope change.

Replace the dim/murky generic fighter iso bake with a bright, readable
armored-knight hero at the SAME scope (sprite-fighter-iso8) — fully additive,
no engine/python/scope-wiring change.

Root cause of the old murk (in bake_sprites.py):
- No view transform set -> Blender's default AgX (Filmic on older builds)
  desaturated + crushed midtones into a grey blob. Now: Standard transform,
  look=None, +0.35 exposure.
- World ambient was near-black (0.05); shadow sides went dark. Now a bright
  neutral-cool ambient (0.45..0.52 @ 1.0).
- Weak/uneven lights (sun=3 + 2 weak area fills). Now a 4x SUN rig
  (direction-only so it reads the same across all 8 turntable yaws): warm key
  5.0, cool opposite fill 2.6, top wash 1.6, cool back/rim 3.0. Added EEVEE
  Fast-GI AO (5.x) with legacy GTAO fallback + 64 TAA samples.

Art: Meshy meshy-5 PBR text->3D (polished steel plate + royal-blue tabard +
gold trim) -> brightened Blender bake (192/192 frames, dimetric 2:1 exact,
anchor 64,109) -> pack_sheet @ sprite-fighter-iso8.

Validated locally: godot --import clean; boot anims=32; served-finals
served-atlas-applied=true resolver_cached=true anims=32; combat-tokens 5/5;
smoke-intent frozen-vocab. Same scope key => godot.yml served-finals smoke
needs no change (it cp's this committed sheet).
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Priority Level: P1

Critical Issue

Overly Broad Exception Handling in Cross-Version Error Recovery — Required for merge

What is wrong: The _try_set() function catches Exception universally, which masks programming errors like typos in property names, incorrect types, or other legitimate failures that should be caught during development.

Why it matters: Silent failure on misspelled attributes (e.g., taa_render_sampless instead of taa_render_samples) prevents developers from discovering bugs. It defeats the purpose of exception handling and makes debugging harder when the Blender render settings aren't actually being applied.

Location: godot/tools/bake_sprites.py, lines 79-83

Recommended fix: Narrow the exception catch to only version/property-existence errors:

def _try_set(obj, attr: str, val) -> None:
    """Set obj.attr = val, swallowing AttributeError/TypeError for cross-version safety."""
    try:
        setattr(obj, attr, val)
    except (AttributeError, TypeError):
        pass

Required before merge: Yes — this allows legitimate property-not-found errors to be handled gracefully across Blender versions while preserving visibility of actual bugs.


Implementation Verification — Passing

Color Management: View transform "Standard" with look=None and +0.35 exposure correctly prevents AgX/Filmic desaturation.

World Ambient: Bright neutral-cool (0.45, 0.47, 0.52, 1.0) @ 1.0 intensity properly lifts shadow sides off near-black.

Lighting Rig: Four-SUN direction-only configuration maintains consistency across all 8 turntable yaws:

  • Key: 5.0 (warm, 1.0/0.97/0.92)
  • Fill: 2.6 (cool, 0.90/0.94/1.0)
  • Top: 1.6 (neutral white)
  • Rim: 3.0 (cool, 0.95/0.97/1.0)

EEVEE Quality: TAA set to 64 samples; Fast-GI AO enabled with legacy GTAO fallback for cross-version compatibility.

Asset Integrity: JSON valid; PNG present (1.1M); attribution updated with final heroic fantasy knight prompt.

No Scope Changes: Modifications fully additive to bake pipeline; same sprite key (sprite-fighter-iso8) requires no scope-wiring updates.

Walkthrough

bake_sprites.py gains explicit color management (Standard view transform, exposure 0.35), a brighter neutral-cool world ambient, and best-effort EEVEE TAA/AO tuning via new helpers. The lighting rig in _add_lights is replaced with four SUN lights (Key/Fill/Top/Rim). The fighter sheet.json attribution string is updated to match a new Meshy prompt.

Changes

Sprite Bake Pipeline Improvements

Layer / File(s) Summary
Color management, EEVEE quality, and lighting rig
godot/tools/bake_sprites.py
_reset_scene forces Standard view transform with exposure 0.35/gamma 1.0, replaces the near-black ambient with a brighter neutral-cool world background, and adds _boost_eevee_quality/_try_set helpers that attempt to set TAA samples and enable AO via Fast GI or legacy GTAO (swallowing version errors). _add_lights removes the old warm key SUN + two weak AREA fills and replaces them with a 4-SUN rig (Key, Fill, Top, Rim) with explicit energies, colors, and rotations.
Fighter sheet attribution
godot/assets/characters/fighter/sheet.json
Single-line update to the attribution field to reference the new "heroic fantasy knight / bright polished steel plate armor" Meshy prompt, keeping the bake/pack tool references intact.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • electricsheephq/WorldOS#1142: Also modifies godot/assets/characters/fighter/sheet.json, directly sharing the fighter asset manifest that this PR's attribution update targets.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately captures the primary change: replacing a dim fighter sprite with a brighter, clearer isometric hero sprite, directly matching the changeset scope.
Description check ✅ Passed Description is comprehensive and exceeds template requirements with detailed problem analysis, fix justification, validation results, and scope confirmation; however, CLA checkbox requirement is not explicitly marked as complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/godot-better-hero

Comment @coderabbitai help to get the list of available commands and usage tips.

"""Set obj.attr = val, swallowing AttributeError/TypeError for cross-version safety."""
try:
setattr(obj, attr, val)
except Exception:
@100yenadmin 100yenadmin enabled auto-merge (squash) June 21, 2026 19:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@godot/tools/bake_sprites.py`:
- Around line 121-124: The Background node inputs access in lines 123-124 (where
bg.inputs[0] and bg.inputs[1] are assigned) lacks error handling and could fail
with IndexError if the node structure differs across Blender versions. Wrap the
background input assignments (both the default_value assignments for inputs[0]
and inputs[1]) in a try/except block to match the defensive error-handling
pattern used for view_settings configuration earlier in the _reset_scene
function, catching IndexError or KeyError exceptions and logging a warning
instead of crashing when the expected node structure is missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b83c1b07-ead0-4df5-b7ed-ce03563e7c5f

📥 Commits

Reviewing files that changed from the base of the PR and between b732533 and fe5e1ba.

⛔ Files ignored due to path filters (1)
  • godot/assets/characters/fighter/sheet.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • godot/assets/characters/fighter/sheet.json
  • godot/tools/bake_sprites.py
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: test
  • GitHub Check: viewer-tests
  • GitHub Check: Analyze (python)
  • GitHub Check: viewer-tests
  • GitHub Check: test
🧰 Additional context used
🪛 Ruff (0.15.17)
godot/tools/bake_sprites.py

[warning] 112-112: Do not catch blind exception: Exception

(BLE001)


[warning] 113-113: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


[warning] 143-143: Boolean positional value in function call

(FBT003)


[warning] 147-147: Boolean positional value in function call

(FBT003)


[warning] 153-156: Use contextlib.suppress(Exception) instead of try-except-pass

Replace try-except-pass with with contextlib.suppress(Exception): ...

(SIM105)


[error] 155-156: try-except-pass detected, consider logging the exception

(S110)


[warning] 155-155: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (3)
godot/tools/bake_sprites.py (2)

99-156: LGTM!


223-265: LGTM!

godot/assets/characters/fighter/sheet.json (1)

55-55: LGTM!

Comment on lines 121 to +124
bg = world.node_tree.nodes.get("Background")
if bg:
bg.inputs[0].default_value = (0.05, 0.05, 0.06, 1.0)
bg.inputs[1].default_value = 0.6
bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0) # bright neutral-cool ambient
bg.inputs[1].default_value = 1.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Background node inputs access could be wrapped for defensive consistency (90% confidence).

Lines 123-124 access bg.inputs[0] and bg.inputs[1] without error handling, unlike the view_settings configuration (lines 106-113) which wraps property access in try/except. While the freshly created factory-settings world should have a predictable Background node structure, wrapping this access would align with the defensive cross-version pattern established elsewhere in _reset_scene.

Impact: If a future or legacy Blender version creates world nodes with a different structure, the script would crash with IndexError/KeyError during scene setup rather than gracefully degrading.

Root cause: Assumption that factory settings always create a Background node with standard inputs.

Code path: _reset_scene → world creation → Background node access → potential crash if node structure differs.

🛡️ Defensive wrapper to match the view_settings error-handling pattern
 bg = world.node_tree.nodes.get("Background")
-if bg:
+if bg and hasattr(bg, "inputs") and len(bg.inputs) >= 2:
     bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0)  # bright neutral-cool ambient
     bg.inputs[1].default_value = 1.0

Alternatively, wrap in try/except for full consistency:

 bg = world.node_tree.nodes.get("Background")
-if bg:
-    bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0)  # bright neutral-cool ambient
-    bg.inputs[1].default_value = 1.0
+try:
+    if bg:
+        bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0)  # bright neutral-cool ambient
+        bg.inputs[1].default_value = 1.0
+except Exception:
+    pass  # Factory world will use default background
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bg = world.node_tree.nodes.get("Background")
if bg:
bg.inputs[0].default_value = (0.05, 0.05, 0.06, 1.0)
bg.inputs[1].default_value = 0.6
bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0) # bright neutral-cool ambient
bg.inputs[1].default_value = 1.0
bg = world.node_tree.nodes.get("Background")
if bg and hasattr(bg, "inputs") and len(bg.inputs) >= 2:
bg.inputs[0].default_value = (0.45, 0.47, 0.52, 1.0) # bright neutral-cool ambient
bg.inputs[1].default_value = 1.0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@godot/tools/bake_sprites.py` around lines 121 - 124, The Background node
inputs access in lines 123-124 (where bg.inputs[0] and bg.inputs[1] are
assigned) lacks error handling and could fail with IndexError if the node
structure differs across Blender versions. Wrap the background input assignments
(both the default_value assignments for inputs[0] and inputs[1]) in a try/except
block to match the defensive error-handling pattern used for view_settings
configuration earlier in the _reset_scene function, catching IndexError or
KeyError exceptions and logging a warning instead of crashing when the expected
node structure is missing.

100yenadmin added a commit that referenced this pull request Jun 21, 2026
) (#1146)

Adds the --preview-scene Godot entrypoint (additive, non-interactive) that
loads a JSON spec, builds a dimetric 2:1 diamond grid overlay via NavOverlay.gd,
solves an A* path with AStarGrid2D, captures the viewport to a PNG, and writes
a nav.json sidecar — gen→place→render→view in one command.

Four additive pieces:
1. Main.gd — _run_preview_scene() + _arg_value() helper; dispatched via --preview-scene
2. RenderProfile.gd — load_inline(profile) so a preview can inject a profile dict
3. WorldView.gd — apply_local_backdrop(path) for zero-HTTP local PNG loading
4. NavOverlay.gd (new Node2D) — _draw(): diamond grid + walkable/blocked tints +
   zone-anchor rings + solved A* polyline + actor foot-dots + facing arrows

Spec format and qa/preview_scene.sh usage documented in both files.
Verified locally: proof-A path_found=true (10-cell corridor), proof-B path_found=false
(column wall). Existing headless conformance unchanged.

Co-authored-by: Eva <arncalso@gmail.com>
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.

1 participant