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 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-25 19:09:34 +01:00
co-authored by Claude Opus 5.5
parent 89b68ed26b
commit f76b0de836
13 changed files with 85 additions and 24 deletions
@@ -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 =
@@ -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.
+9 -1
View File
@@ -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) {
+5 -1
View File
@@ -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,
+3 -1
View File
@@ -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");
}
}
+5 -1
View File
@@ -13,7 +13,11 @@ pub(crate) struct SkyQualityPlugin;
impl Plugin for SkyQualityPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SkyQuality>().add_systems(Update, bridge);
app.init_resource::<SkyQuality>().add_systems(
Update,
// MONKEY (integration): the tier lands before the sky and cloud readers.
bridge.before(benilla_world::lighting::LightingConsumeSet),
);
}
}
+4 -3
View File
@@ -112,10 +112,11 @@ impl Plugin for AmbientOcclusionPlugin {
if !app.is_plugin_added::<AssetPlugin>() {
return;
}
// MONKEY (integration): the crate's shader convention — embedded by `shaders::plugin`.
let shader = app
.world_mut()
.resource_mut::<Assets<Shader>>()
.add(Shader::from_wgsl(include_str!("shaders/ssao.wgsl"), "ssao.wgsl"));
.world()
.resource::<AssetServer>()
.load("embedded://benilla_app/shaders/ssao.wgsl");
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
@@ -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!(
+2 -1
View File
@@ -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")
+2 -1
View File
@@ -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));
}
}
+2
View File
@@ -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.
+9 -1
View File
@@ -124,7 +124,15 @@ fn wetness_tick(
pub(super) fn register(app: &mut App) {
app.init_resource::<RainSurfaces>()
.init_resource::<Wetness>()
.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)]
+23 -5
View File
@@ -207,8 +207,10 @@ fn update_wind(
fn update_benders(
quality: Res<FoliageWind>,
viewer: Res<crate::view::Viewer>,
viewer_unit: Query<&Transform, (With<ViewerUnit>, With<WorldUnit>)>,
units: Query<(&Transform, &WorldUnit), Without<ViewerUnit>>,
// 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<ViewerUnit>, With<WorldUnit>)>,
units: Query<(&GlobalTransform, &WorldUnit), Without<ViewerUnit>>,
mut frame: ResMut<MonkeyFrame>,
) {
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::<WindField>()
.init_resource::<FoliageWind>()
.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),
);
}
}