From f76b0de8364168c16a2afa5cd5f8a10d1311c68e Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Fri, 25 Sep 2026 19:09:34 +0100
Subject: [PATCH] MONKEY (integration): fix the programme's test failures (warm
names, shader embed, dev door, CRLF/path-separator test bugs, float compare,
tooltip newlines) and declare the missing system orders
- ssao.wgsl registered in shaders::plugin and loaded by its embedded path
- wind/wetness/fog-model MonkeyFrame writers ordered; viewer publish before its readers;
sky clock + sky tier before LightingConsumeSet; zone skybox chain joins the consume set;
grass benders read GlobalTransform
- ambiguity ceiling re-measured with the reason (MatAnimTable allocator + exclusive-system pairs)
- verb_event_gate: Windows separators; AddSkillUp/RemoveSkillUp GAP rows
- ui_session portrait test: CRLF checkout
- spell envelope fuse assertion: f32 tolerance
- daylightWindowSplit tooltip: escaped \n (raw line breaks broke the Lua chunk)
Co-Authored-By: Claude Opus 5.5
---
crates/benilla-app/assets/ui/OptionsFrame.xml | 8 ++----
.../src/entities/spell_fx/lifecycle.rs | 3 +-
crates/benilla-app/src/game_plugins.rs | 10 ++++++-
crates/benilla-app/src/player.rs | 6 +++-
crates/benilla-app/src/shaders.rs | 4 ++-
crates/benilla-app/src/sky_quality.rs | 6 +++-
crates/benilla-app/src/ssao.rs | 7 +++--
.../src/ui_script/verb_event_gate.rs | 19 +++++++++++--
crates/benilla-app/src/ui_session.rs | 3 +-
crates/benilla-world/src/sky_fx.rs | 3 +-
crates/benilla-world/src/skybox.rs | 2 ++
crates/benilla-world/src/weather/wetness.rs | 10 ++++++-
crates/benilla-world/src/wind/mod.rs | 28 +++++++++++++++----
13 files changed, 85 insertions(+), 24 deletions(-)
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index d9ad6c90..77843a0c 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -693,9 +693,7 @@
DAYLIGHT_WINDOW_SPLIT =
"Large city buildings take daylight through each window on its own instead of "
.. "treating a whole wall of windows as one opening, so halls like the cathedral "
- .. "catch the sun pane by pane.
-
-Applies to buildings loaded after the change. "
+ .. "catch the sun pane by pane.\n\nApplies to buildings loaded after the change. "
.. "Off is the earlier look.",
-- MONKEY (lampfog)
LAMP_FOG =
@@ -921,9 +919,7 @@ Applies to buildings loaded after the change. "
DAYLIGHT_WINDOW_SPLIT =
"Большие городские здания пропускают дневной свет через каждое окно отдельно, "
.. "а не через всю стену окон как один проём, поэтому залы вроде собора "
- .. "освещаются солнцем окно за окном.
-
-Действует для зданий, загруженных после "
+ .. "освещаются солнцем окно за окном.\n\nДействует для зданий, загруженных после "
.. "изменения. Выключено — прежний вид.",
-- MONKEY (lampfog)
LAMP_FOG =
diff --git a/crates/benilla-app/src/entities/spell_fx/lifecycle.rs b/crates/benilla-app/src/entities/spell_fx/lifecycle.rs
index 3f4fbbfa..01e2c3ca 100644
--- a/crates/benilla-app/src/entities/spell_fx/lifecycle.rs
+++ b/crates/benilla-app/src/entities/spell_fx/lifecycle.rs
@@ -639,7 +639,8 @@ mod tests {
// The ONSET: a firework's fuse. Dark for the whole flight, up at the detonation.
let fuse = || SpellLight::new(1.0, 1.5, SpellLightMode::Burst { span: 0.6 });
assert_eq!(at(fuse(), 1.4), 0.0);
- assert_eq!(at(fuse(), 1.5 + SPELL_LIGHT_RAMP), 1.0);
+ // MONKEY (integration): (1.5 + ramp) - 1.5 is not exactly `ramp` in f32.
+ assert!((at(fuse(), 1.5 + SPELL_LIGHT_RAMP) - 1.0).abs() < 1e-6);
// The reap fade: a kit light goes dark on its instance's `FxDecay`, quicker than the
// model's own Decay sequence — the aura is over the moment the server said so.
diff --git a/crates/benilla-app/src/game_plugins.rs b/crates/benilla-app/src/game_plugins.rs
index 65d3b818..06ab6b77 100644
--- a/crates/benilla-app/src/game_plugins.rs
+++ b/crates/benilla-app/src/game_plugins.rs
@@ -1089,7 +1089,15 @@ pub(crate) mod schedule_tests {
/// the net drain's. Same tree, the class dropped, nothing else moved: 5,052 + 556.
/// **5,554 (decision 2345)** — lowered: the TAB scan stopped reading `Visibility` (the draw
/// election's verdict), which ended its pairs with every `Visibility` writer.
- const UPDATE_ACTIONABLE_CEILING: usize = 5_554;
+ /// **5,582 (MONKEY integration round 3, graphics programme)** — measured on the merged tree
+ /// after declaring the orders the lanes had left out (wind → wetness → fog-model writers of
+ /// `MonkeyFrame`; the viewer publish before its new readers; the sky clock and sky tier before
+ /// the consume set; the grass benders on `GlobalTransform` and after the camera-pose copy).
+ /// What remains, read off the dump: 13 pairs of `skybox::animate_skyboxes` against the
+ /// `MatAnimTable` allocators (attach/spawn/doodad systems) — the allocator case of 2300, the
+ /// skybox writes only the rows it owns — and one pair each of the new systems against the two
+ /// exclusive systems (`finish_colliders`, `apply_net_updates`), the 2343 class.
+ const UPDATE_ACTIONABLE_CEILING: usize = 5_582;
const UPDATE_ACTIONABLE_SLACK: usize = 40;
fn ratchet(what: &str, n: usize, ceiling: usize, slack: usize) {
diff --git a/crates/benilla-app/src/player.rs b/crates/benilla-app/src/player.rs
index 3f69801e..4eb4830e 100644
--- a/crates/benilla-app/src/player.rs
+++ b/crates/benilla-app/src/player.rs
@@ -328,7 +328,11 @@ impl Plugin for PlayerPlugin {
Update,
(world_focus::publish_viewer, world_focus::publish_view_focus)
.after(benilla_world::schedule::WorldStage::Input)
- .before(benilla_world::schedule::WorldStage::Stream),
+ .before(benilla_world::schedule::WorldStage::Stream)
+ // MONKEY (integration): the `Viewer` readers the programme added (grass benders,
+ // fog model, zone skybox) run after this frame's publish.
+ .before(benilla_world::wind::WindTick)
+ .before(benilla_world::lighting::LightingResolveSet),
)
.add_systems(
Update,
diff --git a/crates/benilla-app/src/shaders.rs b/crates/benilla-app/src/shaders.rs
index 55ab58a5..26f0fb24 100644
--- a/crates/benilla-app/src/shaders.rs
+++ b/crates/benilla-app/src/shaders.rs
@@ -26,6 +26,8 @@ pub(crate) fn plugin(app: &mut App) {
bevy::asset::embedded_asset!(app, "shaders/ui_slice_gamma.wgsl");
bevy::asset::embedded_asset!(app, "shaders/shadow_caster.wgsl");
bevy::asset::embedded_asset!(app, "shaders/shadow_caster_cutout_prepass.wgsl");
+ // MONKEY (ao): the contact-shadow pass.
+ bevy::asset::embedded_asset!(app, "shaders/ssao.wgsl");
}
#[cfg(test)]
@@ -61,6 +63,6 @@ mod tests {
`shaders::plugin`, or was registered from a file that is not directly under src/"
);
}
- assert_eq!(found, 7, "the game's shader set changed size");
+ assert_eq!(found, 8, "the game's shader set changed size");
}
}
diff --git a/crates/benilla-app/src/sky_quality.rs b/crates/benilla-app/src/sky_quality.rs
index e56503e7..0e54c03d 100644
--- a/crates/benilla-app/src/sky_quality.rs
+++ b/crates/benilla-app/src/sky_quality.rs
@@ -13,7 +13,11 @@ pub(crate) struct SkyQualityPlugin;
impl Plugin for SkyQualityPlugin {
fn build(&self, app: &mut App) {
- app.init_resource::().add_systems(Update, bridge);
+ app.init_resource::().add_systems(
+ Update,
+ // MONKEY (integration): the tier lands before the sky and cloud readers.
+ bridge.before(benilla_world::lighting::LightingConsumeSet),
+ );
}
}
diff --git a/crates/benilla-app/src/ssao.rs b/crates/benilla-app/src/ssao.rs
index 489ea9a7..c997cc96 100644
--- a/crates/benilla-app/src/ssao.rs
+++ b/crates/benilla-app/src/ssao.rs
@@ -112,10 +112,11 @@ impl Plugin for AmbientOcclusionPlugin {
if !app.is_plugin_added::() {
return;
}
+ // MONKEY (integration): the crate's shader convention — embedded by `shaders::plugin`.
let shader = app
- .world_mut()
- .resource_mut::>()
- .add(Shader::from_wgsl(include_str!("shaders/ssao.wgsl"), "ssao.wgsl"));
+ .world()
+ .resource::()
+ .load("embedded://benilla_app/shaders/ssao.wgsl");
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
diff --git a/crates/benilla-app/src/ui_script/verb_event_gate.rs b/crates/benilla-app/src/ui_script/verb_event_gate.rs
index 8fa3f854..790fc0e1 100644
--- a/crates/benilla-app/src/ui_script/verb_event_gate.rs
+++ b/crates/benilla-app/src/ui_script/verb_event_gate.rs
@@ -166,6 +166,20 @@ const GAP: &[(&str, &str, &str)] = &[
nothing moves and `ui_char.rs`'s feed has nothing to announce — the stock SkillFrame \
would only repaint under its own Close",
),
+ // MONKEY (integration): the two temp-point verbs, the same case as `CancelSkillUps` (pre-existing
+ // upstream gap, surfaced once the Windows path fix let this gate run).
+ (
+ "AddSkillUp",
+ "SKILL_LINES_CHANGED",
+ "the reference's helper 0x4d32d0 moves a temp point and fires; the temp-point table stays \
+ empty in this model (`skills.rs`), and `ui_char.rs`'s feed fires on any real block change",
+ ),
+ (
+ "RemoveSkillUp",
+ "SKILL_LINES_CHANGED",
+ "the reference's helper 0x4d3480, the same as AddSkillUp: no temp points to move here, and \
+ `ui_char.rs`'s feed fires on any real block change",
+ ),
(
"ClickTargetTradeButton",
"TRADE_REPLACE_ENCHANT",
@@ -279,7 +293,8 @@ fn quoted(text: &str, name: &str) -> bool {
}
fn short(p: &std::path::Path) -> String {
- let s = p.to_string_lossy();
+ // MONKEY (integration): Windows paths use a backslash; the rows are written with `/`.
+ let s = p.to_string_lossy().replace('\\', "/");
s.split_once("/crates/")
.map_or(s.to_string(), |(_, t)| t.to_string())
}
@@ -370,7 +385,7 @@ fn the_declared_rows_are_still_true() {
}
let named: Vec<&(PathBuf, String)> = src
.iter()
- .filter(|(p, _)| p.to_string_lossy().ends_with(file))
+ .filter(|(p, _)| short(p).ends_with(file)) // MONKEY (integration): separator-agnostic
.collect();
if named.is_empty() {
wrong.push(format!(
diff --git a/crates/benilla-app/src/ui_session.rs b/crates/benilla-app/src/ui_session.rs
index 3ef4700b..a496e5d5 100644
--- a/crates/benilla-app/src/ui_session.rs
+++ b/crates/benilla-app/src/ui_session.rs
@@ -514,7 +514,8 @@ mod tests {
// The chain's own source is the authority on what is wired — not a hand-copied list here,
// which would be the very drift this test exists to catch.
- let this_file = include_str!("ui_session.rs");
+ // MONKEY (integration): a CRLF checkout must still find the function's closing brace.
+ let this_file = include_str!("ui_session.rs").replace("\r\n", "\n");
let chain = this_file
.split_once("pub(crate) fn feed_interact_npc")
.expect("feed_interact_npc")
diff --git a/crates/benilla-world/src/sky_fx.rs b/crates/benilla-world/src/sky_fx.rs
index e8462579..c76ec84e 100644
--- a/crates/benilla-world/src/sky_fx.rs
+++ b/crates/benilla-world/src/sky_fx.rs
@@ -91,7 +91,8 @@ impl Plugin for SkyFxPlugin {
});
let quality = SkyQuality(SkyQuality::env_override().unwrap_or(0));
app.insert_resource(quality)
- .add_systems(Update, tick_sky_clock);
+ // MONKEY (integration): the clock ticks before its readers (sky colours, cloud FX).
+ .add_systems(Update, tick_sky_clock.before(crate::lighting::LightingConsumeSet));
}
}
diff --git a/crates/benilla-world/src/skybox.rs b/crates/benilla-world/src/skybox.rs
index bd750db4..f14b946b 100644
--- a/crates/benilla-world/src/skybox.rs
+++ b/crates/benilla-world/src/skybox.rs
@@ -196,6 +196,8 @@ impl Plugin for SkyboxPlugin {
// After the lighting resolve, so the weight is this frame's crossfade, as the
// fog's is.
.after(crate::lighting::LightingResolveSet)
+ // MONKEY (integration): a `WowLighting` reader joins the consume set.
+ .in_set(crate::lighting::LightingConsumeSet)
.in_set(SkyboxResolve),
)
// Camera-anchored placement runs after propagation, off this frame's camera pose.
diff --git a/crates/benilla-world/src/weather/wetness.rs b/crates/benilla-world/src/weather/wetness.rs
index ebcf73df..1dcca38b 100644
--- a/crates/benilla-world/src/weather/wetness.rs
+++ b/crates/benilla-world/src/weather/wetness.rs
@@ -124,7 +124,15 @@ fn wetness_tick(
pub(super) fn register(app: &mut App) {
app.init_resource::()
.init_resource::()
- .add_systems(Update, wetness_tick.after(WeatherTick));
+ // MONKEY (integration): after the wind rows and before the resolve, so the three
+ // `MonkeyFrame` writers run in one declared order.
+ .add_systems(
+ Update,
+ wetness_tick
+ .after(WeatherTick)
+ .after(crate::wind::WindTick)
+ .before(crate::lighting::LightingResolveSet),
+ );
}
#[cfg(test)]
diff --git a/crates/benilla-world/src/wind/mod.rs b/crates/benilla-world/src/wind/mod.rs
index f1e811cf..47676254 100644
--- a/crates/benilla-world/src/wind/mod.rs
+++ b/crates/benilla-world/src/wind/mod.rs
@@ -207,8 +207,10 @@ fn update_wind(
fn update_benders(
quality: Res,
viewer: Res,
- viewer_unit: Query<&Transform, (With, With)>,
- units: Query<(&Transform, &WorldUnit), Without>,
+ // MONKEY (integration): `GlobalTransform` (last propagation), not `Transform`, so the scan is
+ // not an undeclared order against every unit mover in `Update`.
+ viewer_unit: Query<&GlobalTransform, (With, With)>,
+ units: Query<(&GlobalTransform, &WorldUnit), Without>,
mut frame: ResMut,
) {
frame.bender_count = 0;
@@ -220,7 +222,7 @@ fn update_benders(
// no-avatar fallback that lets the parting instrument exercise the same receiver.
let Some(player) = viewer
.at
- .or_else(|| viewer_unit.single().ok().map(|t| t.translation))
+ .or_else(|| viewer_unit.single().ok().map(|t| t.translation()))
else {
return;
};
@@ -231,7 +233,7 @@ fn update_benders(
// unit just to retain seven entries.
let mut nearby = [(f32::INFINITY, Vec3::ZERO, 1.5); 7];
for (transform, unit) in &units {
- let at = transform.translation;
+ let at = transform.translation();
let d2 = (at.xz() - player.xz()).length_squared();
if d2 > 40.0 * 40.0 || d2 >= nearby[6].0 {
continue;
@@ -253,6 +255,11 @@ fn update_benders(
}
}
+/// MONKEY (integration): the wind writers' set, so other `MonkeyFrame` writers (wetness) and the
+/// viewer publish can order against it.
+#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct WindTick;
+
/// Installs the shared field after this frame's weather ramp has resolved.
pub struct WindPlugin;
@@ -260,7 +267,18 @@ impl Plugin for WindPlugin {
fn build(&self, app: &mut App) {
app.init_resource::()
.init_resource::()
- .add_systems(Update, (update_wind, update_benders).after(WeatherTick));
+ // MONKEY (integration): one ordered writer run of `MonkeyFrame`'s wind rows, before
+ // the lighting resolve (the fog model writes the same resource there).
+ .add_systems(
+ Update,
+ (update_wind, update_benders)
+ .chain()
+ .in_set(WindTick)
+ .after(WeatherTick)
+ // The camera pose copy also writes `GlobalTransform` (on the camera only).
+ .after(crate::view::publish_camera_pose)
+ .before(crate::lighting::LightingResolveSet),
+ );
}
}