/airdef command tree with typed arguments, live scenario names and generated help (#98, fork patch 36)
build / mod (push) Successful in 3m52s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 15:43:11 +03:00
parent 9bc94fa8aa
commit f16bc2387f
39 changed files with 2260 additions and 42 deletions
+275
View File
@@ -0,0 +1,275 @@
//! The `/airdef` command tree: the one description the game's chat box (colours, errors,
//! Tab completion), the help line and the tests read (#98). The handler in `lib.rs` still
//! splits its line itself; this tree decides what reaches it.
use crate::sim::{DroneKind, SamKind};
use horizon_mod_sdk::tree::{Node, Opt, Tree, Value};
/// The suggestion list of the saved scenarios' names (`command::set_suggestions`).
pub const SCENARIOS: &str = "scenarios";
fn on_off() -> Value {
Value::choice(["on", "off"])
}
/// `/airdef ...` as `lib.rs` accepts it.
pub fn tree() -> Tree {
let wave_kinds: Vec<&str> = DroneKind::ALL.iter().map(|k| k.spec().key).chain(["mix"]).collect();
let deploy_kinds: Vec<&str> = SamKind::ALL.iter().map(|k| k.key()).collect();
let armed: Vec<&str> = SamKind::ARMED.iter().map(|k| k.key()).chain(["all"]).collect();
let toggle = |word: &str| Node::lit(word).then(Node::arg("on/off", on_off()).runs());
Tree::new("airdef").runs().with([
Node::lit("menu").runs(),
Node::lit("monitor").runs(),
Node::lit("grid").runs(),
Node::lit("replay").runs().with([
Node::lit("map").runs(),
Node::lit("full").runs(),
Node::lit("reel").runs(),
Node::lit("film").runs().with([
Node::arg("режим", Value::choice(["best", "all"])).runs(),
Node::arg("N", Value::int_from(1)).runs(),
]),
// The words after `export` come in any order: a name, best/all/N, key=value options.
Node::lit("export").runs().then(
Node::options(
"параметры",
vec![
Opt::new("fps", Value::int_from(1)),
Opt::new("size", Value::word()),
Opt::new("crf", Value::int_from(0)),
Opt::new("sec", Value::Number(Some(0.0), None)),
Opt::flag("noaudio"),
],
Some(vec!["best".into(), "all".into()]),
)
.runs(),
),
Node::lit("stop").runs(),
]),
Node::lit("scenario").with([
Node::lit("save").then(
Node::arg("имя", Value::word()).runs().then(Node::arg("fresh", Value::choice(["fresh"])).runs()),
),
Node::lit("load").then(
Node::arg("имя", Value::names(SCENARIOS)).runs().then(Node::arg("force", Value::choice(["force"])).runs()),
),
Node::lit("list").runs(),
Node::lit("delete").then(Node::arg("имя", Value::names(SCENARIOS)).runs()),
]),
Node::lit("why").runs().then(Node::arg("номер", Value::int_from(1)).runs()),
Node::lit("raid").runs(),
Node::lit("repair").runs(),
Node::lit("import").runs(),
toggle("ew"),
Node::lit("flare").runs(),
Node::lit("move").then(Node::arg("номер", Value::int_from(1)).runs()),
Node::lit("target").then(Node::lit("here").runs()),
Node::lit("give").runs(),
Node::lit("deploy").then(Node::arg("комплекс", Value::choice(deploy_kinds)).runs()),
Node::lit("wave").then(
Node::arg("тип", Value::choice(wave_kinds)).runs().then(
Node::arg("кол-во", Value::int(1, 80)).runs().then(Node::arg("дальность", Value::number(60.0, 800.0)).runs()),
),
),
Node::lit("clear").runs(),
Node::lit("targets").then(Node::lit("clear").runs()),
toggle("damage"),
toggle("truth"),
Node::lit("shader").then(Node::arg("режим", Value::choice(["on", "off", "hd"])).runs()),
toggle("hold"),
Node::lit("roles").runs().then(
Node::arg("комплекс", Value::choice(armed)).then(
Node::arg(
"классы",
Value::Word(["default", "all", "drone", "jet", "cruise", "supersonic", "ballistic"].map(String::from).to_vec()),
)
.runs(),
),
),
Node::lit("stats").runs(),
])
}
/// The help line, from the same tree.
pub fn help() -> String {
tree().usage()
}
/// The scenario names after `name` is saved (once, in the order saved).
pub fn names_after_save(mut all: Vec<String>, name: &str) -> Vec<String> {
if !all.iter().any(|n| n == name) {
all.push(name.to_string());
}
all
}
/// The scenario names after `name` is deleted.
pub fn names_after_delete(mut all: Vec<String>, name: &str) -> Vec<String> {
all.retain(|n| n != name);
all
}
/// Registers the tree with the game, and the scenario names it suggests.
pub fn register(scenarios: &[String]) {
horizon_mod_sdk::command::set_suggestions(SCENARIOS, scenarios);
if let Err(e) = tree().register() {
horizon_mod_sdk::warn(&format!("airdef: command tree refused: {e}"));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ok(line: &str) {
if let Err(e) = tree().parse(line) {
panic!("`{line}` should parse: {e}");
}
}
fn bad_at(line: &str) -> usize {
match tree().parse(line) {
Ok(_) => panic!("`{line}` should not parse"),
Err(e) => e.at,
}
}
fn complete(line: &str, scenarios: &[&str]) -> Vec<String> {
let names: Vec<String> = scenarios.iter().map(|s| s.to_string()).collect();
tree().complete(line, &|_| names.clone())
}
/// Every branch of the old `HELP` line and of the handler's match parses, with its
/// options.
#[test]
fn every_branch_of_the_handler_parses() {
for line in [
"", "menu", "monitor", "grid", "give", "raid", "repair", "import", "flare", "clear", "stats", "roles",
"replay", "replay map", "replay full", "replay reel", "replay film", "replay film best", "replay film all",
"replay film 3", "replay export", "replay export mine", "replay export best", "replay export all", "replay export 3",
"replay export mine all fps=30 size=1280x720 crf=18 sec=12.5 noaudio", "replay export fps=60", "replay stop",
"scenario save a", "scenario save a fresh", "scenario load a", "scenario load a force", "scenario list",
"scenario delete a", "why", "why 2", "ew on", "ew off", "move 4", "target here", "targets clear",
"deploy patriot", "deploy light", "deploy s400", "wave mix", "wave shahed", "wave shahed 20", "wave kh101 80 800",
"wave oreshnik 1 60", "damage on", "damage off", "truth on", "shader hd", "shader off", "shader on", "hold on",
"roles all default", "roles patriot drone,jet", "roles pantsir all", "roles tor ballistic",
] {
ok(line);
}
}
#[test]
fn the_trees_kinds_are_the_enums_own() {
for k in SamKind::ALL {
ok(&format!("deploy {}", k.key()));
}
for k in DroneKind::ALL {
ok(&format!("wave {}", k.spec().key));
}
for k in SamKind::ARMED {
ok(&format!("roles {} all", k.key()));
}
}
#[test]
fn numbers_and_words_outside_what_the_handler_takes_are_refused_where_they_are() {
assert_eq!(bad_at("wave shahed 0"), 12);
assert_eq!(bad_at("wave shahed 81"), 12);
assert_eq!(bad_at("wave shahed 5 59"), 14);
assert_eq!(bad_at("wave shahed 5 801"), 14);
assert_eq!(bad_at("wave shahed x"), 12);
assert_eq!(bad_at("wave scud"), 5);
assert_eq!(bad_at("wave"), 4);
assert_eq!(bad_at("deploy tank"), 7);
assert_eq!(bad_at("deploy"), 6);
assert_eq!(bad_at("ew maybe"), 3);
assert_eq!(bad_at("shader ultra"), 7);
assert_eq!(bad_at("why 0"), 4);
assert_eq!(bad_at("why x"), 4);
assert_eq!(bad_at("move"), 4);
assert_eq!(bad_at("move 0"), 5);
assert_eq!(bad_at("scenario"), 8);
assert_eq!(bad_at("scenario load"), 13);
assert_eq!(bad_at("scenario wipe"), 9);
assert_eq!(bad_at("scenario save a keep"), 16);
assert_eq!(bad_at("target there"), 7);
assert_eq!(bad_at("roles tank all"), 6);
assert_eq!(bad_at("roles patriot"), 13);
assert_eq!(bad_at("replay sideways"), 7);
assert_eq!(bad_at("replay film best now"), 17);
assert_eq!(bad_at("nothing"), 0);
assert_eq!(bad_at("menu 1"), 5);
}
/// The key=value options of `replay export` stay what `start_export` reads.
#[test]
fn export_options_are_checked_by_key_and_value() {
assert_eq!(bad_at("replay export fps=0"), 14);
assert_eq!(bad_at("replay export fps=x"), 14);
assert_eq!(bad_at("replay export fps"), 14);
assert_eq!(bad_at("replay export noaudio=1"), 14);
assert_eq!(bad_at("replay export fps=30 fps=60"), 21);
assert_eq!(bad_at("replay export sec=-1"), 14);
assert_eq!(bad_at("replay export crf=-2"), 14);
assert_eq!(bad_at("replay export size="), 14);
let p = tree().parse("replay export mine fps=30 noaudio").unwrap();
assert_eq!(p.get("параметры"), Some("mine fps=30 noaudio"));
}
#[test]
fn completion_offers_the_next_word_only() {
let top = complete("", &[]);
for w in ["menu", "monitor", "grid", "replay", "scenario", "why", "raid", "repair", "import", "ew", "flare", "move",
"target", "give", "deploy", "wave", "clear", "targets", "damage", "truth", "shader", "hold", "roles", "stats"] {
assert!(top.contains(&w.to_string()), "{w} in {top:?}");
}
assert_eq!(top.len(), 24);
assert_eq!(complete("rep", &[]), ["repair", "replay"]);
assert_eq!(complete("replay ", &[]), ["export", "film", "full", "map", "reel", "stop"]);
assert_eq!(complete("replay film ", &[]), ["all", "best"]);
assert_eq!(complete("scenario ", &[]), ["delete", "list", "load", "save"]);
assert_eq!(complete("wave ", &[]).len(), 14);
assert!(complete("wave s", &[]).contains(&"shahed".to_string()));
assert_eq!(complete("shader ", &[]), ["hd", "off", "on"]);
assert_eq!(complete("replay export ", &[]), ["all", "best", "crf=", "fps=", "noaudio", "sec=", "size="]);
assert_eq!(complete("replay export fps=30 no", &[]), ["noaudio"]);
assert!(complete("menu ", &[]).is_empty());
assert!(complete("wave nothing ", &[]).is_empty());
}
/// The names follow the data: after a save the new name is offered, after a delete it is gone.
#[test]
fn scenario_names_follow_a_save_and_a_delete() {
let mut all: Vec<String> = Vec::new();
let names = |all: &[String]| -> Vec<String> { tree().complete("scenario load ", &|_| all.to_vec()) };
assert!(names(&all).is_empty());
all = names_after_save(all, "base");
all = names_after_save(all, "night");
all = names_after_save(all, "base");
assert_eq!(names(&all), ["base", "night"]);
let of = |all: &[String], line: &str| tree().complete(line, &|_| all.to_vec());
assert_eq!(of(&all, "scenario delete n"), ["night"]);
all = names_after_delete(all, "base");
assert_eq!(names(&all), ["night"]);
assert_eq!(of(&all, "scenario delete "), ["night"]);
all = names_after_delete(all, "night");
assert!(names(&all).is_empty());
}
#[test]
fn the_help_line_is_the_trees() {
let help = help();
assert!(help.starts_with("/airdef menu | monitor | grid | replay "), "{help}");
assert!(help.contains("scenario save <имя> [fresh]"), "{help}");
assert!(help.contains("wave <тип> [<кол-во> [<дальность>]]"), "{help}");
assert!(help.ends_with("| stats"), "{help}");
assert!(help.contains("export [fps=… size=… crf=… sec=… noaudio]"), "{help}");
assert!(help.contains("deploy <комплекс>"), "{help}");
}
/// The nodes handed to the game are a tree the game accepts.
#[test]
fn the_nodes_are_flat_and_ordered() {
let nodes = tree().nodes();
assert!(nodes.len() < 512, "{}", nodes.len());
assert!(nodes.iter().enumerate().all(|(i, n)| n.children.iter().all(|&c| c as usize > i)));
}
}
+8 -2
View File
@@ -13,6 +13,7 @@ use horizon_mod_sdk::{
};
mod cinema;
mod commands;
mod layout;
mod film;
mod monitor;
@@ -1368,7 +1369,10 @@ impl AirDef {
}
}
const HELP: &str = "/airdef menu | monitor | grid | replay [map|full|reel|film [best|all|N]|export [имя] [best|all|N] [fps=60 size=1920x1080 crf=14 sec=N noaudio]|stop] | scenario save|load|list|delete | why [номер] | raid | repair | import | ew on|off | flare | move <номер> | target here | give | deploy <комплекс> | wave <тип|mix> [кол-во] [дальность] | clear | targets clear | damage on|off | truth on|off | shader on|off|hd | hold on|off | roles [комплекс классы] | stats";
/// The help line: generated from the command tree (`commands.rs`), which the game checks lines with.
fn help() -> String {
commands::help()
}
impl Mod for AirDef {
fn init() -> Result<(Self, Registration), String> {
@@ -1422,6 +1426,8 @@ impl Mod for AirDef {
me.block_log = Some(render::world_log_start());
me.sim.shader = true;
me.apply_far();
// Subcommands, typed arguments and the saved scenarios' names for the chat box (#98).
commands::register(&scenario::saved_names());
Ok((me, Registration::new().command("airdef").on_tick()))
}
@@ -1946,7 +1952,7 @@ impl Mod for AirDef {
self.sim.crashes,
self.ground.world.text()
)),
_ => return Err(HELP.to_string()),
_ => return Err(help()),
};
self.emit(out);
Ok(reply)
+17 -5
View File
@@ -20,6 +20,16 @@ fn valid(name: &str) -> Result<String, String> {
Ok(n)
}
/// The saved scenarios' names.
pub(crate) fn saved_names() -> Vec<String> {
names()
}
/// Tells the chat box the names now saved, so Tab offers them.
fn publish(all: &[String]) {
command::set_suggestions(commands::SCENARIOS, all);
}
fn names() -> Vec<String> {
storage::get(INDEX).map(|b| String::from_utf8_lossy(&b).lines().map(str::to_string).filter(|l| !l.is_empty()).collect()).unwrap_or_default()
}
@@ -82,11 +92,12 @@ impl AirDef {
let fresh = flags.contains(&"fresh");
let text = self.scenario_text(&name, fresh);
storage::set(&key(&name), text.as_bytes()).map_err(|e| format!("Не удалось сохранить: {e}"))?;
let mut all = names();
if !all.contains(&name) {
all.push(name.clone());
let before = names();
let all = commands::names_after_save(before.clone(), &name);
if all != before {
storage::set(INDEX, all.join("\n").as_bytes()).map_err(|e| format!("Не удалось записать список: {e}"))?;
}
publish(&all);
Ok(format!(
"Сценарий «{name}» сохранён: комплексов {}, объектов {}{}",
self.sim.sams.len(),
@@ -109,13 +120,14 @@ impl AirDef {
}
["delete", name] => {
let name = valid(name)?;
let mut all = names();
let all = names();
if !all.contains(&name) {
return Err(format!("Нет сценария «{name}»"));
}
all.retain(|n| *n != name);
let all = commands::names_after_delete(all, &name);
storage::delete(&key(&name));
storage::set(INDEX, all.join("\n").as_bytes()).map_err(|e| format!("Не удалось записать список: {e}"))?;
publish(&all);
Ok(format!("Сценарий «{name}» удалён"))
}
_ => Err("/airdef scenario save <имя> [fresh] | load <имя> [force] | list | delete <имя>".into()),
@@ -1,7 +1,7 @@
From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 12:37:45 +0300
Subject: [PATCH 01/35] Mod API (AirDef fork): world models, client particles,
Subject: [PATCH 01/36] Mod API (AirDef fork): world models, client particles,
sounds, ground heights
render.set-models draws block models with an interpolated pose per id;
@@ -1,7 +1,7 @@
From a6d64e6415bf618539af464e521091e72b72d72f Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 12:39:45 +0300
Subject: [PATCH 02/35] cargo-horizon-mod: report particles and sounds
Subject: [PATCH 02/36] cargo-horizon-mod: report particles and sounds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
@@ -1,7 +1,7 @@
From ee79b3562feebe51959a01f1585a9e85bd53ccb5 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 13:33:10 +0300
Subject: [PATCH 03/35] Mod API (AirDef fork): full-screen post pass for mods
Subject: [PATCH 03/36] Mod API (AirDef fork): full-screen post pass for mods
render.set-post compiles a mod's WGSL after a prelude (camera matrices,
time, 256 vec4 of params, the world's colour and depth) and runs it after
@@ -1,7 +1,7 @@
From 2b137dbbedd540daeebc7f5e387847aeb56a1355 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 22:09:30 +0300
Subject: [PATCH 04/35] Mod models: draw far models as scaled impostors past
Subject: [PATCH 04/36] Mod models: draw far models as scaled impostors past
the far plane, cull at 4096
---
@@ -1,7 +1,7 @@
From c8ebca6e0af68630e65211844b34d25c5b985c03 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 22:31:00 +0300
Subject: [PATCH 05/35] Mod cameras: render.set-views draws the world from
Subject: [PATCH 05/36] Mod cameras: render.set-views draws the world from
other points into pictures that HUD view shapes show (API level 9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 8d2261fc4f8072c32e454139cfea7a8c0cd9d664 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 23:08:21 +0300
Subject: [PATCH 06/35] Mods: more fuel per tick and per event (AirDef plans
Subject: [PATCH 06/36] Mods: more fuel per tick and per event (AirDef plans
raids)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 6c548de34def8e4a624d734243418090798bd16a Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Wed, 28 Oct 2026 23:25:29 +0300
Subject: [PATCH 07/35] Mod cameras up to 1920x1080 (full-screen replays)
Subject: [PATCH 07/36] Mod cameras up to 1920x1080 (full-screen replays)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
@@ -1,7 +1,7 @@
From 9b9e93269d67401cc620567a884d5ce97f68d76e Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 11:15:11 +0300
Subject: [PATCH 08/35] Mod cameras: distant terrain tiles, fog measured from
Subject: [PATCH 08/36] Mod cameras: distant terrain tiles, fog measured from
the camera, no impostors while one is shown
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 065d9fa02966238a5bd9ff460392a1a809db0b11 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 11:25:45 +0300
Subject: [PATCH 09/35] Mods: game.world-id, so mods keep state per world (API
Subject: [PATCH 09/36] Mods: game.world-id, so mods keep state per world (API
level 9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 1bf28135fd9a31f837323dc3a8d81ed73343d816 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 12:01:08 +0300
Subject: [PATCH 10/35] cargo-horizon-mod: trial runs behind feature full, so a
Subject: [PATCH 10/36] cargo-horizon-mod: trial runs behind feature full, so a
packing-only tool skips wasmtime
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 0a56f5616df4699588cbcc529d4720661baa810f Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 13:18:43 +0300
Subject: [PATCH 11/35] HD lighting: G-buffer pass, per-pixel mod point lights
Subject: [PATCH 11/36] HD lighting: G-buffer pass, per-pixel mod point lights
in HDR, bloom and tone curve; render.set-lights and render.set-lighting (API
level 10)
@@ -1,7 +1,7 @@
From 9ebf0031fe9ba6b7f02afb26e4fd3a1ba4ddb0d0 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 13:59:43 +0300
Subject: [PATCH 12/35] HD lighting stage 2: sun and moon shadow cascades, the
Subject: [PATCH 12/36] HD lighting stage 2: sun and moon shadow cascades, the
world relit (sun by face angle and shadow, sky and block light through the
lightmap), light shafts and halos, fog over it again, a light colour grade
@@ -1,7 +1,7 @@
From 7a5a38f68923aec62f10bfa0df50b436474ed612 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 16:19:53 +0300
Subject: [PATCH 13/35] HD: bind groups cached until a resize or a new source,
Subject: [PATCH 13/36] HD: bind groups cached until a resize or a new source,
shadow passes draw only the entities that cast shadows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 8020811e57d4b5a9ea1cd72d30ce859dc5095d35 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 16:25:44 +0300
Subject: [PATCH 14/35] HD: lights culled per 16x16 tile (surface and halo
Subject: [PATCH 14/36] HD: lights culled per 16x16 tile (surface and halo
lists built on the CPU), a naga test for the lighting shaders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 72d3fddfea329e578162656151350f0aa2efd323 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 16:43:36 +0300
Subject: [PATCH 15/35] HD: GPU time per pass in the F3 timing list,
Subject: [PATCH 15/36] HD: GPU time per pass in the F3 timing list,
half-resolution GTAO with a depth-aware blur
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 1fd83a821bf270879d8ece5aa583f200ed920d89 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 17:02:02 +0300
Subject: [PATCH 16/35] HD: shadows draw only sun-facing quads and skip buried
Subject: [PATCH 16/36] HD: shadows draw only sun-facing quads and skip buried
sections, the far cascade every second frame; TAA (Halton jitter,
reprojection by depth, YCoCg variance clipping) before bloom
@@ -1,7 +1,7 @@
From 1502d359d599228fe0f44ce6b99a3fded2ac5ea0 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 17:02:02 +0300
Subject: [PATCH 17/35] Mods: model handles (render.model-id, model-generation,
Subject: [PATCH 17/36] Mods: model handles (render.model-id, model-generation,
set-models-by-id; API level 11) so mods send no model names each tick;
clear-post and set-lighting documented
@@ -1,7 +1,7 @@
From 0f9d99b8ad3e4b72e621d84c2396cf1d3d2b7a33 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 17:19:57 +0300
Subject: [PATCH 18/35] HD: temporal upscaling (HORIZON_HD_SCALE: world,
Subject: [PATCH 18/36] HD: temporal upscaling (HORIZON_HD_SCALE: world,
G-buffer and lighting at a lower size, rebuilt to the window in the TAA
pass); fix flickering shadows near the player (the near cascade lost its
sections on frames the far one was held, the held far matrix ignored the
@@ -1,7 +1,7 @@
From 38250c4392e27d5700842ca6ee47291dd1831b72 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 17:47:52 +0300
Subject: [PATCH 19/35] HD water: its own pass (colour, normal, depth), the
Subject: [PATCH 19/36] HD water: its own pass (colour, normal, depth), the
floor under it lit, a composite with world-space waves, refraction with depth
absorption in the biome colour, screen-space reflections, Fresnel and a
shadowed sun glint (HORIZON_HD_WATER=0 to switch off)
@@ -1,7 +1,7 @@
From dbc6f8a6e9c2eeabe1de15f1794013c90be7faa5 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 17:56:04 +0300
Subject: [PATCH 20/35] HD water: no vanilla texture on the surface (the biome
Subject: [PATCH 20/36] HD water: no vanilla texture on the surface (the biome
colour only), a darker, deeper colour
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 13e74537dd6e63482e3613e0e416de1158944dfe Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 18:38:19 +0300
Subject: [PATCH 21/35] HD sky and light: a physical atmosphere (Rayleigh, Mie,
Subject: [PATCH 21/36] HD sky and light: a physical atmosphere (Rayleigh, Mie,
ozone; constants and transmittance after Photon, MIT) for the sky, haze, fog
and water reflections; warmer sunlight from the atmosphere's transmittance, a
gentle sky-coloured ambient, soft PCSS-lite shadows near the player, a haze
@@ -1,7 +1,7 @@
From 6277e051a89c2537570982f6ef275887193c05cf Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 19:02:16 +0300
Subject: [PATCH 22/35] HD water: a darker blue, a little clearer, ripples a
Subject: [PATCH 22/36] HD water: a darker blue, a little clearer, ripples a
little higher
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 982265d21bb9d74ec0040db03feb5a55762286ad Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 21:41:48 +0300
Subject: [PATCH 23/35] HD: Photon-style sunsets (Rec. 2020 atmosphere, sun
Subject: [PATCH 23/36] HD: Photon-style sunsets (Rec. 2020 atmosphere, sun
tint, blue hour; MIT), volumetric clouds with ground shadows instead of
vanilla's in HD, volumetric fog with sun shafts and morning mist
(HORIZON_HD_CLOUDS=0, HORIZON_HD_VOLFOG=0)
@@ -1,7 +1,7 @@
From 24971b3c67d8c87673eb2e0d204cedf8a2f46d1c Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 21:43:22 +0300
Subject: [PATCH 24/35] Mods: world queries for simulations (render.query-rays,
Subject: [PATCH 24/36] Mods: world queries for simulations (render.query-rays,
query-sections, world-changes; API level 12): block-exact rays and section
occupancy by collision, sight or sound, answered next tick within budgets,
and a log of changed sections
@@ -1,7 +1,7 @@
From fdec4960983bdff82f3793460f9a5234f0cd3f49 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 22:56:15 +0300
Subject: [PATCH 25/35] HD fixes: rain level from the world not from rain
Subject: [PATCH 25/36] HD fixes: rain level from the world not from rain
geometry (#41), TAA history on the jittered grid (#38), vanilla cloud tint
without a depth heuristic (#35), air fog once over water (#37), translucent
things in front of HD water (#36), cheaper sunrise fan (#39), halos in thick
@@ -1,7 +1,7 @@
From 21391afb26f49505d6cdaf454a6355cd4e747503 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 23:19:53 +0300
Subject: [PATCH 26/35] HD split into modules (gfx/hd/: one Rust file and one
Subject: [PATCH 26/36] HD split into modules (gfx/hd/: one Rust file and one
WGSL file per pass, a shared prelude), contracts for colour, depth, alpha,
time and coordinates written down, tests that the frame layout and bindings
match between Rust and WGSL (#45)
@@ -1,7 +1,7 @@
From 8d4cadbf8534309ac847d34e229f6d764770475d Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Thu, 29 Oct 2026 23:22:19 +0300
Subject: [PATCH 27/35] HD clouds: noise scales that divide the camera's
Subject: [PATCH 27/36] HD clouds: noise scales that divide the camera's
65536-block wrap, so the clouds and their shadows no longer jump at x or z =
0
@@ -1,7 +1,7 @@
From 0fb59c782abf269fe555faa3241b31acf41e26d7 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 03:17:11 +0300
Subject: [PATCH 28/35] HD: volumetric smoke and missile trails with clear
Subject: [PATCH 28/36] HD: volumetric smoke and missile trails with clear
tunnels through fog and cloud (render.set-smoke, API level 14; #54); sun
speculars by material, wet surfaces and puddles in rain, wind in foliage
(HORIZON_HD_SPECULAR=0, HORIZON_HD_WIND=0, HORIZON_HD_SMOKE=0); HD shaders
@@ -1,7 +1,7 @@
From e4bc29da45d0d38244c6eb2ba3ffb9ab853540c0 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 03:26:09 +0300
Subject: [PATCH 29/35] HD clouds composited by depth so terrain and vehicles
Subject: [PATCH 29/36] HD clouds composited by depth so terrain and vehicles
inside or behind the layer are hidden and those in front stay in front (#67);
world-anchored noise everywhere (cloud detail by height above the layer, fog,
smoke and puddle scales dividing the wrap) (#68); the replay's time is visual
@@ -1,7 +1,7 @@
From 20f699796afb12b600236a4e8859cd97430f86f0 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 03:44:45 +0300
Subject: [PATCH 30/35] HD smoke: young trails drawn at least as wide as a grid
Subject: [PATCH 30/36] HD smoke: young trails drawn at least as wide as a grid
cell (density scaled to keep the mass), gentler bulges, so a trail is a
smooth ribbon and not a chain of puffs
@@ -1,7 +1,7 @@
From 35ffbaa26dd91855c8b93446646ba8c58448d3d0 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 03:59:41 +0300
Subject: [PATCH 31/35] Mods: render.set-main-view (API level 15) renders the
Subject: [PATCH 31/36] Mods: render.set-main-view (API level 15) renders the
screen's world from a mod camera with the full pipeline, so replays get HD
and the post effects; small mod views get the post pass with their own camera
@@ -1,7 +1,7 @@
From 27e24527550942695164dd3a769a42a659e797ca Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 09:28:05 +0300
Subject: [PATCH 32/35] Spot lights for mods (render.set-spot-lights, API level
Subject: [PATCH 32/36] Spot lights for mods (render.set-spot-lights, API level
16): surfaces lit in the cone, a volumetric beam stopped by depth (#60); film
capture (render.capture-*, set-film-quality, API level 17): fixed-step clock,
frames piped to ffmpeg or PNGs, offline audio, no chat in the frames (#58);
@@ -1,7 +1,7 @@
From 615f5c3fa7c19a9895f8972a88333c6f250ad78a Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 14:30:39 +0300
Subject: [PATCH 33/35] HD clouds and fog polish: cloud panorama, cirrus,
Subject: [PATCH 33/36] HD clouds and fog polish: cloud panorama, cirrus,
terrain-aware fog, far smoke cascade; replay world log and film clock;
material overlays by sprite name
@@ -1,7 +1,7 @@
From 0254984fdcce53a4a0f2bff15f9be3b78c93842e Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 15:01:09 +0300
Subject: [PATCH 34/35] HD: coloured sun through glass and leaves, rain ripples
Subject: [PATCH 34/36] HD: coloured sun through glass and leaves, rain ripples
on water, film motion blur and depth of field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,7 +1,7 @@
From 9645d3f1f47d8bb358d5630288bdd801bd874911 Mon Sep 17 00:00:00 2001
From: chexit <chexit@duck.com>
Date: Fri, 30 Oct 2026 15:19:32 +0300
Subject: [PATCH 35/35] Film export: every frame in flight reaches the writer,
Subject: [PATCH 35/36] Film export: every frame in flight reaches the writer,
listener follows the main view, cloud wind on film frames, set-smoke reports
real activity
File diff suppressed because it is too large Load Diff