mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Compare audio environment attributes and Musics with the vanilla data
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
13e434d49d
commit
6eee2e3262
4 files changed
+249
No files matched your search
@@ -0,0 +1,85 @@
|
||||
//! Audio environment attributes from Horizon's `MusicTables` and
|
||||
//! `AmbientTables` for `tools/test_audio_attributes_parity.py`:
|
||||
//! `audio_attributes_fixture ROOT INPUT OUTPUT`.
|
||||
use horizon_client::ambience::AmbientTables;
|
||||
use horizon_client::music::{Music, MusicTables};
|
||||
use horizon_client::sound_events::normalize;
|
||||
use std::io::Write;
|
||||
|
||||
fn music(m: Option<&Music>) -> String {
|
||||
m.map_or("-".to_owned(), |m| {
|
||||
format!(
|
||||
"{},{},{},{}",
|
||||
normalize(&m.sound),
|
||||
m.min_delay,
|
||||
m.max_delay,
|
||||
m.replace_current_music
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let root = std::path::Path::new(&args[1]);
|
||||
let tables = MusicTables::load(root);
|
||||
let ambient = AmbientTables::load(root);
|
||||
let input = std::fs::read_to_string(&args[2]).expect("input");
|
||||
let mut out = std::io::BufWriter::new(std::fs::File::create(&args[3]).expect("output"));
|
||||
for (name, m) in [
|
||||
("MENU", Music::menu()),
|
||||
("CREDITS", Music::credits()),
|
||||
("END_BOSS", Music::end_boss()),
|
||||
("CREATIVE", Music::creative()),
|
||||
("END", Music::end()),
|
||||
("UNDER_WATER", Music::under_water()),
|
||||
("GAME", Music::game()),
|
||||
] {
|
||||
writeln!(out, "{name} {}", music(Some(&m))).expect("write");
|
||||
}
|
||||
for line in input.lines() {
|
||||
let f: Vec<&str> = line.split(' ').collect();
|
||||
let (dimension, biome) = (f[0], f[1]);
|
||||
let bg = tables.background(dimension, biome);
|
||||
let mut s = format!(
|
||||
"{line} default={} creative={} underwater={}",
|
||||
music(bg.default.as_ref()),
|
||||
music(bg.creative.as_ref()),
|
||||
music(bg.underwater.as_ref())
|
||||
);
|
||||
for creative in [false, true] {
|
||||
for underwater in [false, true] {
|
||||
s.push_str(&format!(
|
||||
" select={}",
|
||||
music(bg.select(creative, underwater))
|
||||
));
|
||||
}
|
||||
}
|
||||
s.push_str(&format!(
|
||||
" volume={:08x}",
|
||||
tables.volume(dimension, biome).to_bits()
|
||||
));
|
||||
let a = ambient.get(dimension, biome);
|
||||
s.push_str(&format!(
|
||||
" loop={}",
|
||||
a.r#loop.as_deref().map_or("-".to_owned(), normalize)
|
||||
));
|
||||
s.push_str(&format!(
|
||||
" mood={}",
|
||||
a.mood.as_ref().map_or("-".to_owned(), |m| format!(
|
||||
"{},{},{},{:016x}",
|
||||
normalize(&m.sound),
|
||||
m.tick_delay,
|
||||
m.block_search_extent,
|
||||
m.offset.to_bits()
|
||||
))
|
||||
));
|
||||
for (sound, chance) in &a.additions {
|
||||
s.push_str(&format!(
|
||||
" addition={},{:016x}",
|
||||
normalize(sound),
|
||||
chance.to_bits()
|
||||
));
|
||||
}
|
||||
writeln!(out, "{s}").expect("write");
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,22 @@ impl Music {
|
||||
pub fn end_boss() -> Self {
|
||||
Self::new("minecraft:music.dragon", 0, 0, true)
|
||||
}
|
||||
/// `Musics.CREATIVE`.
|
||||
pub fn creative() -> Self {
|
||||
Self::new("minecraft:music.creative", 12000, 24000, false)
|
||||
}
|
||||
/// `Musics.END`.
|
||||
pub fn end() -> Self {
|
||||
Self::new("minecraft:music.end", 6000, 24000, true)
|
||||
}
|
||||
/// `Musics.UNDER_WATER`.
|
||||
pub fn under_water() -> Self {
|
||||
Self::new("minecraft:music.under_water", 12000, 24000, false)
|
||||
}
|
||||
/// `Musics.GAME`.
|
||||
pub fn game() -> Self {
|
||||
Self::new("minecraft:music.game", 12000, 24000, false)
|
||||
}
|
||||
/// `Music.CODEC`.
|
||||
fn parse(v: &Value) -> Option<Self> {
|
||||
let sound = v["sound"].as_str()?;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package dev.horizonlab.parity;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.RegistryDataLoader;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.RegistryLayer;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.repository.ServerPacksSource;
|
||||
import net.minecraft.server.packs.resources.MultiPackResourceManager;
|
||||
import net.minecraft.sounds.Music;
|
||||
import net.minecraft.sounds.Musics;
|
||||
import net.minecraft.tags.TagLoader;
|
||||
import net.minecraft.world.attribute.*;
|
||||
import net.minecraft.world.clock.*;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* The audio environment attributes as the vanilla EnvironmentAttributeSystem
|
||||
* evaluates them from the vanilla data pack (dimension layer, the biome's
|
||||
* positional layer, the dimension timelines and weather layers):
|
||||
* BACKGROUND_MUSIC with BackgroundMusic.select for every creative/underwater
|
||||
* combination, MUSIC_VOLUME and AMBIENT_SOUNDS, for lines "dimension biome
|
||||
* ticks"; plus the Musics constants.
|
||||
*/
|
||||
public final class VanillaAudioAttributesOracle {
|
||||
static String music(Music m) {
|
||||
return m == null ? "-" : m.sound().value().location() + "," + m.minDelay() + "," + m.maxDelay() + "," + m.replaceCurrentMusic();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static void addBiome(EnvironmentAttributeSystem.Builder b, EnvironmentAttribute attr, Biome biome) {
|
||||
b.addPositionalLayer(attr, (base, pos, w) -> biome.getAttributes().applyModifier(attr, base));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
var repo = ServerPacksSource.createVanillaTrustedRepository();
|
||||
repo.reload();
|
||||
repo.setSelected(List.of("vanilla"));
|
||||
var resources = new MultiPackResourceManager(PackType.SERVER_DATA, repo.openAllSelected());
|
||||
var layers = RegistryLayer.createRegistryAccess();
|
||||
var staticTags = TagLoader.loadTagsForExistingRegistries(resources, layers.getLayer(RegistryLayer.STATIC));
|
||||
var context = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLD), staticTags);
|
||||
RegistryAccess.Frozen world = RegistryDataLoader.load(resources, context, RegistryDataLoader.WORLD_REGISTRIES, Runnable::run).join();
|
||||
staticTags.forEach(Registry.PendingTags::apply);
|
||||
var dimensions = world.lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||
var biomes = world.lookupOrThrow(Registries.BIOME);
|
||||
try (var w = new PrintWriter(Files.newBufferedWriter(Path.of(args[1]), StandardCharsets.UTF_8))) {
|
||||
w.println("MENU " + music(Musics.MENU));
|
||||
w.println("CREDITS " + music(Musics.CREDITS));
|
||||
w.println("END_BOSS " + music(Musics.END_BOSS));
|
||||
w.println("CREATIVE " + music(Musics.CREATIVE));
|
||||
w.println("END " + music(Musics.END));
|
||||
w.println("UNDER_WATER " + music(Musics.UNDER_WATER));
|
||||
w.println("GAME " + music(Musics.GAME));
|
||||
for (String line : Files.readAllLines(Path.of(args[0]), StandardCharsets.UTF_8)) {
|
||||
String[] f = line.split(" ");
|
||||
DimensionType dim = dimensions.getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, Identifier.parse(f[0]))).value();
|
||||
Biome biome = biomes.getOrThrow(ResourceKey.create(Registries.BIOME, Identifier.parse(f[1]))).value();
|
||||
long ticks = Long.parseLong(f[2]);
|
||||
ClockManager clocks = h -> new ClockInstance() {
|
||||
public long totalTicks() { return ticks; }
|
||||
public float partialTick() { return 0.0F; }
|
||||
public float rate() { return 1.0F; }
|
||||
public boolean isPaused() { return false; }
|
||||
};
|
||||
var b = EnvironmentAttributeSystem.builder();
|
||||
b.addConstantLayer(dim.attributes());
|
||||
for (EnvironmentAttribute<?> attr : biome.getAttributes().keySet()) addBiome(b, attr, biome);
|
||||
dim.timelines().forEach(t -> b.addTimelineLayer(t, clocks));
|
||||
if (dim.hasSkyLight() && !dim.hasCeiling()) {
|
||||
WeatherAttributes.addBuiltinLayers(b, new WeatherAttributes.WeatherAccess() {
|
||||
public float rainLevel() { return 0.0F; }
|
||||
public float thunderLevel() { return 0.0F; }
|
||||
});
|
||||
}
|
||||
var system = b.build();
|
||||
BackgroundMusic bg = system.getValue(EnvironmentAttributes.BACKGROUND_MUSIC, Vec3.ZERO);
|
||||
StringBuilder sb = new StringBuilder(line);
|
||||
sb.append(" default=").append(music(bg.defaultMusic().orElse(null)))
|
||||
.append(" creative=").append(music(bg.creativeMusic().orElse(null)))
|
||||
.append(" underwater=").append(music(bg.underwaterMusic().orElse(null)));
|
||||
for (boolean creative : new boolean[]{false, true}) {
|
||||
for (boolean underwater : new boolean[]{false, true}) {
|
||||
sb.append(" select=").append(music(bg.select(creative, underwater).orElse(null)));
|
||||
}
|
||||
}
|
||||
sb.append(String.format(" volume=%08x", Float.floatToRawIntBits(system.getValue(EnvironmentAttributes.MUSIC_VOLUME, Vec3.ZERO))));
|
||||
AmbientSounds ambient = system.getValue(EnvironmentAttributes.AMBIENT_SOUNDS, Vec3.ZERO);
|
||||
sb.append(" loop=").append(ambient.loop().map(s -> s.value().location().toString()).orElse("-"));
|
||||
sb.append(" mood=").append(ambient.mood().map(m -> m.soundEvent().value().location() + "," + m.tickDelay() + ","
|
||||
+ m.blockSearchExtent() + "," + String.format("%016x", Double.doubleToRawLongBits(m.soundPositionOffset()))).orElse("-"));
|
||||
for (AmbientAdditionsSettings a : ambient.additions()) {
|
||||
sb.append(" addition=").append(a.soundEvent().value().location()).append(String.format(",%016x", Double.doubleToRawLongBits(a.tickChance())));
|
||||
}
|
||||
w.println(sb);
|
||||
}
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the audio environment attributes with the vanilla 26.3 data pack.
|
||||
|
||||
The oracle evaluates vanilla's `EnvironmentAttributeSystem` (dimension layer,
|
||||
biome positional layer, dimension timelines, weather layers) for every
|
||||
dimension type and biome and prints `BACKGROUND_MUSIC` (all three tracks and
|
||||
`BackgroundMusic.select` for each creative/underwater combination),
|
||||
`MUSIC_VOLUME` and `AMBIENT_SOUNDS` (loop, mood, additions), plus the
|
||||
`Musics` constants. Horizon's `MusicTables` and `AmbientTables` answer the same.
|
||||
"""
|
||||
try:
|
||||
from . import audio_harness as h
|
||||
except ImportError:
|
||||
import audio_harness as h
|
||||
|
||||
|
||||
def fixtures():
|
||||
data = h.RESOURCES / 'data/minecraft'
|
||||
biomes = sorted('minecraft:' + p.stem for p in (data / 'worldgen/biome').glob('*.json'))
|
||||
dimensions = sorted('minecraft:' + p.stem for p in (data / 'dimension_type').glob('*.json'))
|
||||
return [f'{d} {b} {t}' for d in dimensions for b in biomes for t in (0, 13000)]
|
||||
|
||||
|
||||
def main():
|
||||
lines = fixtures()
|
||||
matched, report = h.run_harness(
|
||||
'audio-attributes', 'VanillaAudioAttributesOracle', 'audio_attributes_fixture', ''.join(l + '\n' for l in lines),
|
||||
extra_sources=[h.lab.ROOT / 'crates/horizon-client/src/music.rs', h.lab.ROOT / 'crates/horizon-client/src/ambience.rs'],
|
||||
rust_args=[h.RESOURCES],
|
||||
scope='Vanilla EnvironmentAttributeSystem values of audio/background_music (with BackgroundMusic.select), audio/music_volume and '
|
||||
'audio/ambient_sounds for every dimension type x biome at two times of day, and the Musics constants.')
|
||||
print(f'{matched:,} audio attribute lines match the pinned data ({len(lines):,} cases): {report}', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user