/airdef command tree with typed arguments, live scenario names and generated help (#98, fork patch 36)
build / mod (push) Successful in 3m52s
build / mod (push) Successful in 3m52s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user