Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ All notable changes to Lumenwright are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
[Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

- A stated sky can stand at a tick and ask for the game's look: `StatedSky::tick` takes a
`StatedTick` (`time`, `look`, `render_distance`), set by `StatedSky::at`. At it the
environment is sampled at the tick, not at the world's `DayTime`; quick mode and the
game's look draw the tick's sunrise glow, moon and stars through the same code as
`Sky::Vanilla`; and HQ mode draws the stated look, weighted for `SkyLook::ByTime` by the
one `sky::day_weight`, with `StatedSky::hq` as Chunky's part. A stated sky at
`Sky::Vanilla`'s colours and sun draws its frame byte for byte.
- `QuickSkyColours::vanilla`: the colours `Sky::Vanilla` derives at a tick, for a caller to
state.

### Changed

- `StatedSky` and the new `StatedTick` are `#[non_exhaustive]`: build them with
`StatedSky::new(quick, hq)` and `StatedTick::new(time, look)`. A struct literal of
`StatedSky` no longer compiles, so this is a breaking change for 0.2.x. A stated sky at
no tick (`StatedSky::new`) draws what 0.2.0 drew, byte for byte.

### Not expressed

- Weather: the game's look, under `Sky::Vanilla` and at a stated tick, draws a clear sky; it
applies no rain or thunder.

## [0.2.0] - 2026-10-21

### Added
Expand Down
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["crates/lumenwright", "crates/lumenwright-cli", "crates/lumenwright-kernel"]
members = ["crates/lumenwright", "crates/lumenwright-cli", "crates/lumenwright-compat", "crates/lumenwright-kernel"]
# The PTX crate only builds for nvptx64-nvidia-cuda; see scripts/build-ptx.sh.
exclude = ["crates/lumenwright-ptx"]

Expand Down
85 changes: 79 additions & 6 deletions crates/lumenwright-cli/tests/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use lumenwright::render::{BackendKind, Mode};
use lumenwright::scene::camera::Camera;
use lumenwright::world::Dimension;
use lumenwright::{
ChunkRect, EntityPolicy, HqOptions, HqSkyModel, QuickOptions, Rendered, Request, RequestError,
Sky, WorldSource,
ChunkRect, EntityPolicy, HqOptions, HqSkyModel, QuickOptions, QuickSkyColours, Rendered,
Request, RequestError, Sky, SkyLook, StatedSky, StatedTick, WorldSource,
};
use lumenwright_cli::fixture;
use lumenwright_cli::jar;
Expand Down Expand Up @@ -199,15 +199,15 @@ fn a_stated_sky_is_drawn_as_given() {
let mut req = request(&world, &jar, Mode::Quick);
// Look straight up: the frame is all sky.
req.camera.pitch = -90.0;
req.sky = Sky::Stated(lumenwright::StatedSky {
quick: lumenwright::QuickSkyColours {
req.sky = Sky::Stated(lumenwright::StatedSky::new(
lumenwright::QuickSkyColours {
sky: Vec3::new(1.0, 0.0, 0.0),
fog: Vec3::new(1.0, 0.0, 0.0),
sun_dir: Vec3::new(0.0, -1.0, 0.0),
sky_factor: 1.0,
},
hq: sky::hq_sky(Vec3::Y, 1.5, 1.0),
});
sky::hq_sky(Vec3::Y, 1.5, 1.0),
));
let out = lumenwright::render(&req).unwrap();
let red = out
.rgba
Expand All @@ -217,6 +217,79 @@ fn a_stated_sky_is_drawn_as_given() {
assert_eq!(red, 192 * 108, "the stated sky colour fills the frame");
}

/// A stated sky standing at a tick is drawn as `Sky::Vanilla` at that `time` is, when the
/// caller states the colours and suns Lumenwright derives there
/// (`QuickSkyColours::vanilla`, `sky::hq_sky`): byte for byte on the CPU, in quick mode
/// (the sunrise glow, the moon and the stars of the tick) and in HQ mode under the game's
/// look and under the look by time of day (its weight, the sky light and the lens's sky).
/// The tick moves the frame: without it the same stated sky draws something else.
#[test]
fn a_stated_sky_at_a_tick_is_the_vanilla_sky_at_it() {
let jar = require_jar!();
let dir = tmp("stated-tick");
let world = dir.join("world");
fixture::block_entity_gallery().write(&world).unwrap();
let store = lumenwright::assets::load_resources(&jar, None, &[]).unwrap();
let distance = (RADIUS * 16) as f32;
let forward = camera().basis().0;
// 12800: the sun just set, the glow up; 18000: midnight, the moon and the stars out.
for (time, mode, look) in [
(12800, Mode::Quick, SkyLook::ByTime),
(18000, Mode::Quick, SkyLook::ByTime),
(12800, Mode::Hq, SkyLook::Game),
(12800, Mode::Hq, SkyLook::ByTime),
(18000, Mode::Hq, SkyLook::Game),
] {
let env = lumenwright::assets::environment::sample(
&store,
"minecraft:overworld",
"minecraft:plains",
time,
)
.expect("a 1.21.11 jar carries environment attributes");
let sun = sky::celestial_direction(env.sun_angle);
let mut vanilla = request(&world, &jar, mode);
vanilla.sky = Sky::Vanilla {
time: Some(time),
sun_dir: Some(sun),
hq: HqSkyModel {
look,
render_distance: distance,
..HqSkyModel::default()
},
};
let colours = QuickSkyColours::vanilla(
Some(&env),
"minecraft:plains",
time,
Some(sun),
forward,
distance,
);
let untimed = StatedSky::new(colours, sky::hq_sky(sun, sky::DEFAULT_SUN_INTENSITY, 1.0));
let mut tick = StatedTick::new(time, look);
tick.render_distance = distance;
let mut stated = vanilla;
stated.sky = Sky::Stated(untimed.at(tick));
let mut bare = vanilla;
bare.sky = Sky::Stated(untimed);
let name = format!("{mode:?} {look:?} at {time}");
let want = lumenwright::render(&vanilla).unwrap().rgba;
let got = lumenwright::render(&stated).unwrap().rgba;
let differing = want
.chunks(4)
.zip(got.chunks(4))
.filter(|(a, b)| a != b)
.count();
assert_eq!(differing, 0, "{name}: pixels differ from Sky::Vanilla's");
assert_ne!(
lumenwright::render(&bare).unwrap().rgba,
want,
"{name}: the tick moved nothing"
);
}
}

/// A JSON file from the client jar.
fn jar_json(jar: &Path, name: &str) -> serde_json::Value {
let f = std::fs::File::open(jar).unwrap();
Expand Down
18 changes: 18 additions & 0 deletions crates/lumenwright-compat/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "lumenwright-compat"
description = "Tests that hold the library to a published release's frames; never published"
edition.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true
rust-version = "1.90"
publish = false

[dependencies]

[dev-dependencies]
lumenwright = { path = "../lumenwright", default-features = false }
lumenwright-cli = { path = "../lumenwright-cli", default-features = false }
# 0.2.0 as published: the oracle a stated sky at no stated tick is held to. A published
# crate cannot depend on one crate version under two names, so the oracle lives here.
lumenwright_020 = { package = "lumenwright", version = "=0.2.0", default-features = false }
4 changes: 4 additions & 0 deletions crates/lumenwright-compat/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Holds the `lumenwright` library to the frames a published release draws. The crate has
//! no code; its tests render one request through this tree and through the release as
//! published on crates.io and compare the bytes. It is never published: a published crate
//! cannot depend on one crate version under two names.
Loading
Loading