feat(mods): adjust final world fog parameters

Co-Authored-By: Codex <[email protected]>
This commit is contained in:
[email protected]andCodex committed 2026-11-01 15:13:38 +01:00
1 parent 885e287a2e
commit e0c64b5cc8
4 files changed
+121

No files matched your search

@@ -1331,6 +1331,17 @@ impl Game<'_> {
pub(super) fn native_zoom(&self, zoom: &mut Option<NativeZoom>) {
let _ = zoom;
}
/// Final world fog, after ordinary calculations and Horizon's fog option, before upload.
/// Title panoramas skip it. Invalid color or distance values retain the original whole fog.
pub(super) fn native_render_fog(
&self,
partial: f32,
camera: [f64; 3],
fog_type: horizon_client::fog::FogType,
fog: &mut horizon_client::fog::FogData,
) {
let _ = (partial, camera, fog_type, fog);
}
/// The camera shake native mods ask for in this frame, `partial` ticks
/// past the last tick (0 to 1): add your pitch, yaw and roll in degrees
/// and your offset in blocks, along the camera's right, up and back, to
@@ -140,8 +140,43 @@ impl Game<'_> {
if !self.settings.horizon.fog {
horizon_client::fog::clear_distance_fog(&mut fog, &input);
}
if self.title.is_none() {
fog = self.native_fog_now(self.clock.fraction() as f32, at, fog_type, fog);
}
fog
}
fn native_fog_now(
&self,
partial: f32,
camera: [f64; 3],
fog_type: FogType,
original: FogData,
) -> FogData {
let mut fog = original;
self.native_render_fog(partial, camera, fog_type, &mut fog);
let distances = [
fog.environmental_start,
fog.environmental_end,
fog.render_distance_start,
fog.render_distance_end,
fog.sky_end,
fog.cloud_end,
];
if fog
.color
.iter()
.all(|v| v.is_finite() && (0.0..=1.0).contains(v))
&& distances.iter().all(|v| v.is_finite())
&& fog.environmental_end > fog.environmental_start
&& fog.render_distance_end > fog.render_distance_start
&& fog.sky_end >= 0.0
&& fog.cloud_end >= 0.0
{
fog
} else {
original
}
}
/// `FogRenderer.computeFogColor`'s night vision brightenFactor:
/// `GameRenderer.nightVisionScale`, but not while Darkness is on.
fn fog_night_vision(&self) -> f32 {
+63
View File
@@ -0,0 +1,63 @@
"""Exercise the actual final-fog validation/dispatch with a fake mod."""
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from test_world_input_capture import method
ROOT = Path(__file__).resolve().parents[1]
class RenderFogHookTests(unittest.TestCase):
@unittest.skipUnless(shutil.which("rustc"), "requires Rust")
def test_valid_parameters_and_atomic_invalid_fallback(self):
source = (ROOT / "crates/horizon-client/src/game/render_tick.rs").read_text(encoding="utf-8")
actual = method(source, "native_fog_now")
fixture = r"""
#[derive(Clone, Copy)]
enum FogType { Water }
#[derive(Clone, Copy, Debug, PartialEq)]
struct FogData {
color: [f32; 4], environmental_start: f32, environmental_end: f32,
render_distance_start: f32, render_distance_end: f32, sky_end: f32, cloud_end: f32,
}
struct Game { replacement: Option<FogData> }
impl Game {
fn native_render_fog(&self, partial: f32, camera: [f64; 3], kind: FogType, fog: &mut FogData) {
assert_eq!((partial, camera), (0.5, [1.0, 2.0, 3.0]));
assert!(matches!(kind, FogType::Water));
assert_eq!(fog.environmental_start, -8.0);
if let Some(value) = self.replacement { *fog = value; }
}
METHOD
}
#[test]
fn changed_unchanged_and_invalid() {
let original = FogData { color: [0.2, 0.3, 0.4, 1.0], environmental_start: -8.0,
environmental_end: 24.0, render_distance_start: 230.0, render_distance_end: 256.0,
sky_end: 24.0, cloud_end: 24.0 };
let run = |replacement| Game { replacement }.native_fog_now(0.5, [1.0, 2.0, 3.0], FogType::Water, original);
assert_eq!(run(None), original);
let changed = FogData { color: [1.0, 0.0, 0.5, 0.5], environmental_start: -16.0,
environmental_end: 48.0, render_distance_start: 512.0, render_distance_end: 1024.0,
sky_end: 0.0, cloud_end: 80.0 };
assert_eq!(run(Some(changed)), changed);
for invalid in [FogData { environmental_start: 48.0, ..changed },
FogData { environmental_end: f32::NAN, ..changed },
FogData { render_distance_end: 512.0, ..changed },
FogData { render_distance_start: f32::NEG_INFINITY, ..changed },
FogData { sky_end: -1.0, ..changed }, FogData { cloud_end: f32::INFINITY, ..changed },
FogData { cloud_end: -1.0, ..changed },
FogData { color: [f32::NAN, 0.0, 0.0, 1.0], ..changed },
FogData { color: [0.0, 0.0, 0.0, 1.1], ..changed }] {
assert_eq!(run(Some(invalid)), original);
}
}
""".replace("METHOD", actual)
with tempfile.TemporaryDirectory() as folder:
src, exe = Path(folder) / "fog.rs", Path(folder) / "fog.exe"
src.write_text(fixture, encoding="utf-8")
subprocess.run(["rustc", "--edition=2024", "--test", str(src), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)
@@ -0,0 +1,12 @@
{
"hooks": [
{
"name": "render_fog",
"target": "horizon_client::game::native_hooks::Game<'_>::native_render_fog",
"neoforge": "ViewportEvent.RenderFog",
"side": "client"
}
],
"rust_summary": "a final world fog hook (`native_render_fog`).",
"doc_summary": "`native_render_fog(partial, camera, fog_type, fog)` adjusts final FogData after normal environment/effect/render-distance calculation and Horizon's fog option, before GPU upload. Title panoramas skip it. The whole request is accepted only when color components are finite in 0..=1, all distances are finite, environmental and render-distance ends exceed their starts, and sky/cloud ends are nonnegative. Negative near planes remain valid (water/lava). Invalid requests retain the original whole fog. Bundles need \"hooks\": @API:render_fog@."
}