gates: clippy clean again, and the mail repro that never was for commit
`scripts/check.sh` has been failing before it ran a single test, on 62 clippy findings in code none of the recent rounds touched — 32 in `benilla-world`, 30 in `benilla-app` — so the in-loop gate could not tell anyone anything. Every one is a newer clippy noticing older code (`as_chunks` and `is_multiple_of` are both lints younger than the lines they fire on), and they are fixed, not silenced, except where the silence is the point: - **Argued allows, the house idiom.** `clippy::type_complexity` on ten Bevy system signatures and two inline caster queries, each with the one-line reason beside it, as the other 67 in this workspace have. `clippy::approx_constant` on `liquid/waves.rs`'s `TWO_PI`, whose own doc already says why it is spelled `6.2831853`: it mirrors a shader line, and `f32::consts::TAU` would stop reading against the file it copies. - **Two runtime assertions became compile-time ones** (`EXT_SEL_EMPTY is index 255`, the endless-stream frame count): a claim about constants should fail the build that breaks it, not a test run after it. - **Three real, if harmless, redundancies gone**: `len > MAX_TILT_TAN && len > 0.0` (the constant is `tan(4°)`, so the second test never spoke), `u32::from(..) + 1 | flags` now parenthesised where `+` binds tighter than `|`, and `palette.get(0)`. - The rest is mechanical: `chunks_exact(3)` → `as_chunks::<3>()`, `map_or(true, ..)` → `is_none_or`, two `Default::default()`-then-assign test fixtures, a doc line that began `1.` and so turned its own paragraph into a list item, and `local_utc_offset_secs` moved above the test module it had drifted below. `tests/repro_mail.rs` is deleted. Its first line says "TEMPORARY repro harness — not for commit"; it reads a loose mirror of FrameXML out of `/tmp/opencode/ vanilla-ui`, which no longer exists, so it has been failing on a missing directory rather than telling anyone about mail. What is left of the gate's red is one test: `world_api_wall` counts 174 crossings of the `benilla-world` boundary against a ceiling of 107 — 90 of them already filed with a close plan, 84 unsorted. That is not cleanup; the wall's own message says the remedies are to close the crossing or to publish it as engine API with the record that says so, and 84 of those is a design pass on what the engine's public surface is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -51,6 +51,7 @@ struct CharacterLane {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn collect_character_shadows(
|
||||
video: Res<VideoConfig>,
|
||||
time: Res<Time>,
|
||||
|
||||
@@ -3124,8 +3124,8 @@ mod tests {
|
||||
apply_lighting_preset(&mut cvars, name);
|
||||
// Somewhere else on the row's own scale: the flags flip, the numbers move by one.
|
||||
let moved = match v.trim().parse::<f32>() {
|
||||
Ok(x) if x == 0.0 => "1".to_string(),
|
||||
Ok(x) if x == 1.0 => "0".to_string(),
|
||||
Ok(0.0) => "1".to_string(),
|
||||
Ok(1.0) => "0".to_string(),
|
||||
Ok(x) => (x + 1.0).to_string(),
|
||||
Err(_) => format!("{v}x"),
|
||||
};
|
||||
|
||||
@@ -453,6 +453,7 @@ impl CarriedLightMotion {
|
||||
/// carried light is a child of a moving parent, so its `GlobalTransform` is only this frame's once
|
||||
/// `Propagate` has run. `update_torch_shadows` reads the verdict in `Last`, i.e. later in the same
|
||||
/// frame — never one stale.
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
pub(crate) fn track_carried_light_motion(
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
|
||||
@@ -468,7 +468,7 @@ fn spawn_screen(
|
||||
LoginAction::ToggleSave,
|
||||
CheckMark::Save,
|
||||
form.save,
|
||||
&strings.text("SAVE_ACCOUNT_NAME", "Remember Account Name"),
|
||||
strings.text("SAVE_ACCOUNT_NAME", "Remember Account Name"),
|
||||
);
|
||||
// Remember Password, one row below. Not a reference control at all — 1.12 stores an
|
||||
// account name and never a secret — so the label is literal rather than a `GlueStrings`
|
||||
|
||||
@@ -3860,8 +3860,9 @@ mod stage_latch_tests {
|
||||
}
|
||||
|
||||
/// The second half of the handover, and the reason [`super::sync_body_booth`] clears the
|
||||
/// latch: a pane's bake lives only as long as its room, and every pane numbers its bakes from
|
||||
/// 1. A window that closes and reopens brings the *same* pane back at revision 1, so a latch
|
||||
/// latch: a pane's bake lives only as long as its room, and every pane numbers its bakes
|
||||
/// from one. A window that closes and reopens brings the *same* pane back at revision 1, so a
|
||||
/// latch
|
||||
/// still holding that pane's revision 1 from its previous life reads as "already current"
|
||||
/// while the booth actually holds whatever the mirror left in it. Cleared, it is fresh again.
|
||||
#[test]
|
||||
|
||||
@@ -82,6 +82,7 @@ impl Plugin for SunShaftsPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn update_views(
|
||||
mut commands: Commands,
|
||||
video: Res<VideoConfig>,
|
||||
|
||||
@@ -456,6 +456,7 @@ fn apply_shadow_quality(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn manage_rig(
|
||||
state: Res<State<ClientState>>,
|
||||
video: Res<VideoConfig>,
|
||||
@@ -695,7 +696,7 @@ pub(crate) fn append_triangles(
|
||||
base: u32,
|
||||
indices: &mut Vec<u32>,
|
||||
) {
|
||||
for triangle in source.chunks_exact(3) {
|
||||
for triangle in source.as_chunks::<3>().0 {
|
||||
if triangle.iter().all(|index| *index < vertex_count) {
|
||||
indices.extend(triangle.iter().map(|index| base + *index));
|
||||
}
|
||||
@@ -753,6 +754,7 @@ pub(crate) fn empty_cutout_mesh() -> Mesh {
|
||||
/// `(admitted, occluder-rejected)` for the trace. Range-gates before any vertex work. The `parts`
|
||||
/// query is written inline (not aliased) so a lane can pass its own identically-typed query.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)] // the caster query a lane passes in, spelled out
|
||||
pub(crate) fn collect_entity_geometry(
|
||||
parts: &Query<
|
||||
(
|
||||
@@ -868,7 +870,7 @@ fn append_skinned(
|
||||
let (Some(joints), Some(weights)) = (pick.0.joints.get(index), pick.0.weights.get(index))
|
||||
else {
|
||||
let fallback = palette
|
||||
.get(0)
|
||||
.first()
|
||||
.map(|matrix| matrix.transform_point3(wow_to_bevy(*position)))
|
||||
.unwrap_or(Vec3::ZERO);
|
||||
positions.push(fallback.to_array());
|
||||
|
||||
@@ -434,7 +434,7 @@ mod tests {
|
||||
fn the_endless_frame_count_is_a_day_at_the_streams_own_rate() {
|
||||
assert_eq!(ENDLESS_SECS * 44_100, 3_810_240_000);
|
||||
assert_eq!(ENDLESS_SECS * 48_000, 4_147_200_000);
|
||||
assert!(
|
||||
const _: () = assert!(
|
||||
ENDLESS_SECS * 48_000 < usize::MAX / 1024,
|
||||
"nowhere near the arithmetic's edge"
|
||||
);
|
||||
|
||||
@@ -647,6 +647,7 @@ fn entity_gather_due(rate: u32, elapsed: f64, mask: u32, last_mask: u32) -> bool
|
||||
/// Not tidiness — [`update_torch_shadows`] sat at 15 of Bevy's 16 system params before this change
|
||||
/// added the player anchor and the interior knobs, and three separate members would not compile.
|
||||
#[derive(bevy::ecs::system::SystemParam)]
|
||||
#[allow(clippy::type_complexity)] // the caster query, written inline by the lane's own contract
|
||||
struct EntityCasters<'w, 's> {
|
||||
/// The SAME query the world lane passes to `collect_entity_geometry` (written inline by that
|
||||
/// contract so a lane can pass its own).
|
||||
@@ -760,6 +761,7 @@ fn cube_view_projs(fixture: Vec3) -> [Mat4; CUBE_FACES] {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn update_torch_shadows(
|
||||
video: Res<VideoConfig>,
|
||||
state: Res<State<ClientState>>,
|
||||
@@ -1226,15 +1228,17 @@ fn update_torch_shadows(
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
let census = (slot_count > 0)
|
||||
.then(|| PartCensus {
|
||||
let census = if slot_count > 0 {
|
||||
PartCensus {
|
||||
gx: gx
|
||||
.as_ref()
|
||||
.map_or(0, |gx| gx.torch_residency_generation())
|
||||
.wrapping_add(terrain_gen),
|
||||
parts: static_part_census(&ents),
|
||||
})
|
||||
.unwrap_or_default();
|
||||
}
|
||||
} else {
|
||||
PartCensus::default()
|
||||
};
|
||||
// A slot that has NEVER been fingerprinted jumps the queue: with one scan a frame a fresh
|
||||
// promotion would otherwise wait a whole sweep for its first map, and a slot with no map
|
||||
// publishes none (`map_publishable`), so that wait is a shadow that is simply missing. There
|
||||
@@ -1542,87 +1546,85 @@ fn update_torch_shadows(
|
||||
|
||||
// `WOW_TORCH_TRACE=1` — once a second, what the lane published: fixture distances + whether a
|
||||
// caster mesh exists. The app-side half of the debug (the shader group-3 sample is the GPU half).
|
||||
if std::env::var_os("WOW_TORCH_TRACE").is_some() {
|
||||
if now - *last_trace >= 1.0 {
|
||||
*last_trace = now;
|
||||
// MONKEY (outdoor torch shadows): the header carries the lane budget too — "6 of the
|
||||
// 12 slots may be exterior, 3 currently are, the sun is at 0.00" is the whole state of
|
||||
// the split in one line, and it is the first thing to read when an outdoor shadow is
|
||||
// missing (no EXT slots at sun 1.00 is correct; no EXT slots at sun 0.00 is a bug).
|
||||
if std::env::var_os("WOW_TORCH_TRACE").is_some() && now - *last_trace >= 1.0 {
|
||||
*last_trace = now;
|
||||
// MONKEY (outdoor torch shadows): the header carries the lane budget too — "6 of the
|
||||
// 12 slots may be exterior, 3 currently are, the sun is at 0.00" is the whole state of
|
||||
// the split in one line, and it is the first thing to read when an outdoor shadow is
|
||||
// missing (no EXT slots at sun 1.00 is correct; no EXT slots at sun 0.00 is a bug).
|
||||
info!(
|
||||
"torch-trace: {}/{} slots of {} (ext {}/{}, sun {:.2}, {} candidates, caster {}, {} entity casters)",
|
||||
promoted.len(),
|
||||
want,
|
||||
MAX_TORCH_CASTERS,
|
||||
promoted.iter().filter(|(.., ext)| *ext).count(),
|
||||
ext_cap,
|
||||
sun_w,
|
||||
cands.len(),
|
||||
if lane.slots.iter().any(|s| s.mesh.is_some()) {
|
||||
"built"
|
||||
} else {
|
||||
"none"
|
||||
},
|
||||
lane.trace_parts,
|
||||
);
|
||||
// MONKEY (torch lane perf): the FIXED per-frame cost, in the two counters that bound
|
||||
// it. `gather parts N (rate R Hz, reach X yd)` is the CPU-skinned moving-caster set and
|
||||
// the cadence + union radius it was taken at; `fingerprint scans S` is how many full
|
||||
// resident-scene walks the census gate actually let through in the last second (a
|
||||
// still room should read 0, a streaming one a handful). Both are per SECOND, so they
|
||||
// are directly comparable to the frame rate beside them: `gathers` equal to the fps is
|
||||
// `interiorShadowEntityRate 0`, and `scans` equal to the fps is the old behaviour with
|
||||
// the gate defeated.
|
||||
info!(
|
||||
"torch-perf: gather parts {} (rate {} Hz, {} gathers/s, reach {:.0} yd), fingerprint scans {}/s",
|
||||
lane.trace_parts,
|
||||
video.interior_shadow_entity_rate,
|
||||
lane.trace_gathers,
|
||||
lane.trace_reach,
|
||||
lane.trace_scans,
|
||||
);
|
||||
lane.trace_gathers = 0;
|
||||
lane.trace_scans = 0;
|
||||
// MONKEY (torch caster selection): per SLOT, the three numbers the selection is made
|
||||
// of — which fixture, what it scores at the player, and how far its cross-fade has
|
||||
// ramped. A shadow that pops is a `w` that jumped; a shadow that should be there and
|
||||
// is not is a fixture missing from this list (then read `cands`, above).
|
||||
// MONKEY (torch caster reach): scientific scores expose the small distant tail;
|
||||
// distance is still from the player, and eligibility now extends to 4R + 3 yd.
|
||||
for (i, e, p, sc, w, ext) in &promoted {
|
||||
// MONKEY (torch owner exclusion): `owner` + `own body` are the fix's own readout —
|
||||
// "who this fixture belongs to" and "how many retained items / entity parts its
|
||||
// LAST rebuild dropped as that owner's body". A fixture standing in its own black
|
||||
// square with `own body 0/0` is a missing owner tag; one with a plausible count and
|
||||
// a shadow still under it is a caster arriving by some third route.
|
||||
let slot = lane.slots.iter().find(|s| s.cache_slot == *i);
|
||||
let (gx_excl, part_excl) = slot.map_or((0, 0), |s| s.trace_excluded);
|
||||
// MONKEY (moving fixture): `moving` is this feature's readout — `live` is a slot
|
||||
// being re-rendered from scratch every frame, `dropped` one that is moving but past
|
||||
// TORCH_MOVING_MAX and fast-fading out, `settled` the ordinary cached path. An imp
|
||||
// whose shadow lags is a slot reading `settled` while it walks (then look at
|
||||
// `still_since` / the owner tag); a room whose static shadows freeze is two slots
|
||||
// stuck on `live`.
|
||||
info!(
|
||||
"torch-trace: {}/{} slots of {} (ext {}/{}, sun {:.2}, {} candidates, caster {}, {} entity casters)",
|
||||
promoted.len(),
|
||||
want,
|
||||
MAX_TORCH_CASTERS,
|
||||
promoted.iter().filter(|(.., ext)| *ext).count(),
|
||||
ext_cap,
|
||||
sun_w,
|
||||
cands.len(),
|
||||
if lane.slots.iter().any(|s| s.mesh.is_some()) {
|
||||
"built"
|
||||
} else {
|
||||
"none"
|
||||
" slot {i}: lane {} {e} at [{:.1},{:.1},{:.1}] d(player) {:.1} score {:.4e} w {:.2} owner {:?} moving {} own body {gx_excl} gx + {part_excl} parts (camera <= shadowDistance AND player <= 4R+3) static {} dynamic {} requested_live_rank {:?}",
|
||||
if *ext { "EXT" } else { "INT" },
|
||||
p.x,
|
||||
p.y,
|
||||
p.z,
|
||||
p.distance(anchor),
|
||||
sc,
|
||||
w,
|
||||
slot.and_then(|s| s.owner),
|
||||
match slot {
|
||||
Some(s) if s.moving_live => "live",
|
||||
Some(s) if s.fast_fade => "dropped",
|
||||
_ => "settled",
|
||||
},
|
||||
lane.trace_parts,
|
||||
if static_rebuilt & (1 << i) != 0 { "rebuilt (GPU pending)" } else { "cached/requested" },
|
||||
if dynamic & (1 << i) != 0 { "yes" } else { "no" },
|
||||
(dynamic & (1 << i) != 0).then(|| TorchShadowViews::live_rank(dynamic, *i)),
|
||||
);
|
||||
// MONKEY (torch lane perf): the FIXED per-frame cost, in the two counters that bound
|
||||
// it. `gather parts N (rate R Hz, reach X yd)` is the CPU-skinned moving-caster set and
|
||||
// the cadence + union radius it was taken at; `fingerprint scans S` is how many full
|
||||
// resident-scene walks the census gate actually let through in the last second (a
|
||||
// still room should read 0, a streaming one a handful). Both are per SECOND, so they
|
||||
// are directly comparable to the frame rate beside them: `gathers` equal to the fps is
|
||||
// `interiorShadowEntityRate 0`, and `scans` equal to the fps is the old behaviour with
|
||||
// the gate defeated.
|
||||
info!(
|
||||
"torch-perf: gather parts {} (rate {} Hz, {} gathers/s, reach {:.0} yd), fingerprint scans {}/s",
|
||||
lane.trace_parts,
|
||||
video.interior_shadow_entity_rate,
|
||||
lane.trace_gathers,
|
||||
lane.trace_reach,
|
||||
lane.trace_scans,
|
||||
);
|
||||
lane.trace_gathers = 0;
|
||||
lane.trace_scans = 0;
|
||||
// MONKEY (torch caster selection): per SLOT, the three numbers the selection is made
|
||||
// of — which fixture, what it scores at the player, and how far its cross-fade has
|
||||
// ramped. A shadow that pops is a `w` that jumped; a shadow that should be there and
|
||||
// is not is a fixture missing from this list (then read `cands`, above).
|
||||
// MONKEY (torch caster reach): scientific scores expose the small distant tail;
|
||||
// distance is still from the player, and eligibility now extends to 4R + 3 yd.
|
||||
for (i, e, p, sc, w, ext) in &promoted {
|
||||
// MONKEY (torch owner exclusion): `owner` + `own body` are the fix's own readout —
|
||||
// "who this fixture belongs to" and "how many retained items / entity parts its
|
||||
// LAST rebuild dropped as that owner's body". A fixture standing in its own black
|
||||
// square with `own body 0/0` is a missing owner tag; one with a plausible count and
|
||||
// a shadow still under it is a caster arriving by some third route.
|
||||
let slot = lane.slots.iter().find(|s| s.cache_slot == *i);
|
||||
let (gx_excl, part_excl) = slot.map_or((0, 0), |s| s.trace_excluded);
|
||||
// MONKEY (moving fixture): `moving` is this feature's readout — `live` is a slot
|
||||
// being re-rendered from scratch every frame, `dropped` one that is moving but past
|
||||
// TORCH_MOVING_MAX and fast-fading out, `settled` the ordinary cached path. An imp
|
||||
// whose shadow lags is a slot reading `settled` while it walks (then look at
|
||||
// `still_since` / the owner tag); a room whose static shadows freeze is two slots
|
||||
// stuck on `live`.
|
||||
info!(
|
||||
" slot {i}: lane {} {e} at [{:.1},{:.1},{:.1}] d(player) {:.1} score {:.4e} w {:.2} owner {:?} moving {} own body {gx_excl} gx + {part_excl} parts (camera <= shadowDistance AND player <= 4R+3) static {} dynamic {} requested_live_rank {:?}",
|
||||
if *ext { "EXT" } else { "INT" },
|
||||
p.x,
|
||||
p.y,
|
||||
p.z,
|
||||
p.distance(anchor),
|
||||
sc,
|
||||
w,
|
||||
slot.and_then(|s| s.owner),
|
||||
match slot {
|
||||
Some(s) if s.moving_live => "live",
|
||||
Some(s) if s.fast_fade => "dropped",
|
||||
_ => "settled",
|
||||
},
|
||||
if static_rebuilt & (1 << i) != 0 { "rebuilt (GPU pending)" } else { "cached/requested" },
|
||||
if dynamic & (1 << i) != 0 { "yes" } else { "no" },
|
||||
(dynamic & (1 << i) != 0).then(|| TorchShadowViews::live_rank(dynamic, *i)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1714,7 +1716,7 @@ fn collect_torch_entities(
|
||||
if added == 0 {
|
||||
continue;
|
||||
}
|
||||
for tri in pick.0.indices.chunks_exact(3) {
|
||||
for tri in pick.0.indices.as_chunks::<3>().0 {
|
||||
if tri.iter().all(|i| *i < added) {
|
||||
indices.extend(tri.iter().map(|i| base + *i));
|
||||
}
|
||||
|
||||
@@ -200,7 +200,6 @@ fn an_emote_argument_targets_the_named_player_and_not_the_selection() {
|
||||
let sel = Selection {
|
||||
target: Some(Entity::from_raw_u32(11).unwrap()),
|
||||
guid: Some(0x5e1),
|
||||
..Default::default()
|
||||
};
|
||||
let find = |n: &str| (n == "Bob").then_some((bob, 0xb0b));
|
||||
assert_eq!(emote_target(Some("Bob"), &sel, find, Some(me)), 0xb0b);
|
||||
|
||||
@@ -198,9 +198,11 @@ mod tests {
|
||||
#[test]
|
||||
fn a_vm_built_after_boot_still_fills_pfquests_file_scope_class_bitmask() {
|
||||
let mut app = App::new();
|
||||
app.insert_resource(ChrClassTable(ChrClasses::from_name_rows(
|
||||
[(1u32, "Warrior".to_owned(), "WARRIOR".to_owned())].into_iter(),
|
||||
)));
|
||||
app.insert_resource(ChrClassTable(ChrClasses::from_name_rows([(
|
||||
1u32,
|
||||
"Warrior".to_owned(),
|
||||
"WARRIOR".to_owned(),
|
||||
)])));
|
||||
// No VM yet — exactly the ordering a world-entry VM meets: the resource is long since
|
||||
// loaded, and `PostStartup` has already come and gone.
|
||||
app.add_systems(PostStartup, feed_class_info);
|
||||
|
||||
@@ -109,7 +109,7 @@ fn inspect_slot_view(
|
||||
.player_visible_item_enchant(slot0, u8::try_from(j).unwrap_or(0))
|
||||
.unwrap_or(0) as i32
|
||||
}),
|
||||
random_properties_id: u32::from(roll),
|
||||
random_properties_id: roll,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
||||
@@ -968,9 +968,8 @@ mod tests {
|
||||
};
|
||||
use benilla_ui::script::EQUIPMENT_BAG;
|
||||
|
||||
/// Both ends of every rung of `GetKeyRingSize` (`ContainerFrame.lua:773`).
|
||||
|
||||
/// The wire's 16 positions (vmangos `KEYRING_SLOT_END` 97), not the level-gated count.
|
||||
/// Both ends of every rung of `GetKeyRingSize` (`ContainerFrame.lua:773`), on the wire's 16
|
||||
/// positions (vmangos `KEYRING_SLOT_END` 97) rather than the level-gated count.
|
||||
#[test]
|
||||
fn wire_pos_maps_the_keyring_onto_the_player_grid() {
|
||||
assert_eq!(wire_pos(KEYRING_CONTAINER, 1), Some((255, 81)));
|
||||
|
||||
@@ -120,7 +120,7 @@ pub(crate) fn trace_visibility_churn(
|
||||
// Worst first: the driver is usually the top one, and its children follow it.
|
||||
// Worst first. The tie-break is the resolved name, so the line is stable run to run; it
|
||||
// resolves only after the truncation, which is the whole point of carrying handles.
|
||||
loud.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
loud.sort_by_key(|row| std::cmp::Reverse(row.1));
|
||||
loud.truncate(12);
|
||||
let mut named: Vec<(String, u32)> = loud
|
||||
.into_iter()
|
||||
|
||||
@@ -856,6 +856,30 @@ pub(crate) fn shutdown_on_exit(
|
||||
/// player's own create (`0x5deb60 call 0x4908c0`). This fires `PLAYER_LOGIN` at the end of the
|
||||
/// load on both, the `/reload` shape; `PLAYER_ENTERING_WORLD` fires from [`crate::ui_unit`] once
|
||||
/// the self descriptor lands.
|
||||
/// The player's UTC offset in seconds, for `date()` and `C_DateAndTime.GetServerTimeLocal`.
|
||||
///
|
||||
/// `std` has no local-time support, so this is `localtime_r` on the current instant: the offset
|
||||
/// *as it stands now*, DST included, rather than the zone's standard offset. Read once at VM setup
|
||||
/// — a session that crosses a DST boundary keeps the offset it booted with, which is one hour
|
||||
/// wrong for the rest of that session and is the same thing every client that caches it does.
|
||||
///
|
||||
/// 0 on failure, which is UTC — the behaviour this tree had everywhere before, so a broken or
|
||||
/// missing zone database leaves the clock exactly where it used to be rather than off by a
|
||||
/// garbage amount.
|
||||
fn local_utc_offset_secs() -> i32 {
|
||||
// SAFETY: `localtime_r` writes a caller-owned `tm` and takes a pointer to a live `time_t`;
|
||||
// both live for the call, and the `_r` form keeps no global state.
|
||||
unsafe {
|
||||
let now: libc::time_t = libc::time(std::ptr::null_mut());
|
||||
let mut tm: libc::tm = std::mem::zeroed();
|
||||
if libc::localtime_r(&now, &mut tm).is_null() {
|
||||
return 0;
|
||||
}
|
||||
// `tm_gmtoff` is seconds EAST of UTC, which is the sign `date()` wants to add.
|
||||
i32::try_from(tm.tm_gmtoff).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn finish_ui_load(script: &mut UiScript) {
|
||||
finish_ui_load_with(script, |_| {}, |_| {});
|
||||
@@ -1099,27 +1123,3 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The player's UTC offset in seconds, for `date()` and `C_DateAndTime.GetServerTimeLocal`.
|
||||
///
|
||||
/// `std` has no local-time support, so this is `localtime_r` on the current instant: the offset
|
||||
/// *as it stands now*, DST included, rather than the zone's standard offset. Read once at VM setup
|
||||
/// — a session that crosses a DST boundary keeps the offset it booted with, which is one hour
|
||||
/// wrong for the rest of that session and is the same thing every client that caches it does.
|
||||
///
|
||||
/// 0 on failure, which is UTC — the behaviour this tree had everywhere before, so a broken or
|
||||
/// missing zone database leaves the clock exactly where it used to be rather than off by a
|
||||
/// garbage amount.
|
||||
fn local_utc_offset_secs() -> i32 {
|
||||
// SAFETY: `localtime_r` writes a caller-owned `tm` and takes a pointer to a live `time_t`;
|
||||
// both live for the call, and the `_r` form keeps no global state.
|
||||
unsafe {
|
||||
let now: libc::time_t = libc::time(std::ptr::null_mut());
|
||||
let mut tm: libc::tm = std::mem::zeroed();
|
||||
if libc::localtime_r(&now, &mut tm).is_null() {
|
||||
return 0;
|
||||
}
|
||||
// `tm_gmtoff` is seconds EAST of UTC, which is the sign `date()` wants to add.
|
||||
i32::try_from(tm.tm_gmtoff).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ fn select_fog_lamps(
|
||||
selected
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn update_fog(
|
||||
mut commands: Commands,
|
||||
video: Res<VideoConfig>,
|
||||
|
||||
@@ -761,6 +761,6 @@ mod tests {
|
||||
assert_eq!(VolLightOverride(None).tier(&video), 2);
|
||||
assert_eq!(VolLightOverride(Some(1)).tier(&video), 1);
|
||||
assert_eq!(STEPS[0], 0.0);
|
||||
assert!(STEPS[2] > STEPS[1]);
|
||||
const _: () = assert!(STEPS[2] > STEPS[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +435,7 @@ fn occlusion_probe(eye: Vec3, unit_origin: Vec3) -> Option<(Dir3, f32)> {
|
||||
(reach > 0.0).then_some((dir, reach))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn drive_vplates(
|
||||
mode: Res<VPlateMode>,
|
||||
mut plates: ResMut<VPlates>,
|
||||
|
||||
@@ -156,6 +156,7 @@ impl WorldLane {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)] // one Bevy system's full input set
|
||||
fn update_world_shadows(
|
||||
video: Res<VideoConfig>,
|
||||
time: Res<Time>,
|
||||
@@ -271,9 +272,9 @@ fn update_world_shadows(
|
||||
lane.static_mesh = Some(mesh);
|
||||
lane.static_rebuilt_at = None;
|
||||
}
|
||||
let due = lane.static_rebuilt_at.map_or(true, |at| {
|
||||
at.distance(frame.light_position) > STATIC_REBUILD_STEP
|
||||
});
|
||||
let due = lane
|
||||
.static_rebuilt_at
|
||||
.is_none_or(|at| at.distance(frame.light_position) > STATIC_REBUILD_STEP);
|
||||
if !due {
|
||||
return;
|
||||
}
|
||||
@@ -400,9 +401,11 @@ mod streamed_refresh_tests {
|
||||
#[test]
|
||||
fn shadow_cache_refresh_runs_without_volumetric_fog_and_is_quiet_when_unchanged() {
|
||||
let mut app = App::new();
|
||||
let mut video = VideoConfig::default();
|
||||
video.volumetric_fog = 0;
|
||||
video.world_shadows = false;
|
||||
let video = VideoConfig {
|
||||
volumetric_fog: 0,
|
||||
world_shadows: false,
|
||||
..Default::default()
|
||||
};
|
||||
app.insert_resource(video)
|
||||
.init_resource::<StaticGx>()
|
||||
.init_resource::<WorldLane>()
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
//! TEMPORARY repro harness — not for commit. Loads the stock MailFrame chain from loose mirror
|
||||
//! files under /tmp/opencode/vanilla-ui and drives the open-letter path, printing Lua errors.
|
||||
|
||||
use benilla_ui::script::{MailInboxRow, MailState, UiScript};
|
||||
|
||||
const UI: &str = "/tmp/opencode/vanilla-ui";
|
||||
|
||||
const FILES: &[&str] = &[
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
r"Interface\FrameXML\MoneyInputFrame.lua",
|
||||
r"Interface\FrameXML\MoneyInputFrame.xml",
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
r"Interface\FrameXML\UIParent.xml",
|
||||
"ScrollTemplates.xml",
|
||||
r"Interface\FrameXML\UIPanelTemplates.lua",
|
||||
r"Interface\FrameXML\UIPanelTemplates.xml",
|
||||
"Interface\\FrameXML\\BasicControls.xml",
|
||||
"Interface\\FrameXML\\LocaleProperties.lua",
|
||||
"Interface\\FrameXML\\StaticPopup.xml",
|
||||
"Interface\\FrameXML\\GameTooltip.xml",
|
||||
r"Interface\FrameXML\ItemButtonTemplate.xml",
|
||||
"Interface\\FrameXML\\UIDropDownMenu.xml",
|
||||
"Interface\\FrameXML\\CharacterFrameTemplates.xml",
|
||||
"Interface\\FrameXML\\FriendsFrame.xml",
|
||||
"Interface\\FrameXML\\MailFrame.xml",
|
||||
];
|
||||
|
||||
fn load_ui(script: &UiScript) {
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/ui");
|
||||
let read = |req: &str| -> Option<Vec<u8>> {
|
||||
let p = std::path::Path::new(UI).join(req.replace('\\', "/"));
|
||||
if let Ok(b) = std::fs::read(&p) {
|
||||
return Some(b);
|
||||
}
|
||||
std::fs::read(dir.join(req)).ok()
|
||||
};
|
||||
for entry in FILES {
|
||||
let bytes = read(entry).unwrap_or_else(|| panic!("{entry}: not found"));
|
||||
if entry.to_ascii_lowercase().ends_with(".lua") {
|
||||
script
|
||||
.run_chunk_named(&bytes, &format!("@{entry}"))
|
||||
.unwrap_or_else(|e| panic!("{entry}: {e}"));
|
||||
continue;
|
||||
}
|
||||
let doc = benilla_ui::framexml::parse(&benilla_ui::source::decode(&bytes))
|
||||
.unwrap_or_else(|e| panic!("{entry}: {e}"));
|
||||
let report = benilla_ui::loader::load_in(script, &doc, &entry.replace('\\', "/"), &read);
|
||||
assert!(
|
||||
report.errors.is_empty(),
|
||||
"{entry}: loader errors: {:#?}",
|
||||
report.errors
|
||||
);
|
||||
let leaf = entry.rsplit(['\\', '/']).next().unwrap_or("");
|
||||
if leaf.eq_ignore_ascii_case("UIParent.xml") && entry.contains('\\') {
|
||||
script
|
||||
.run(
|
||||
"FCF_OnUpdate = FCF_OnUpdate or function() end \
|
||||
FCF_DockUpdate = FCF_DockUpdate or function() end \
|
||||
UnitPopup_OnUpdate = UnitPopup_OnUpdate or function() end \
|
||||
BattlefieldFrame_OnUpdate = BattlefieldFrame_OnUpdate or function() end \
|
||||
MultiActionBar_Update = MultiActionBar_Update or function() end \
|
||||
RaidOptionsFrame_UpdatePartyFrames = RaidOptionsFrame_UpdatePartyFrames or function() end \
|
||||
LocalizeFrames = LocalizeFrames or function() end \
|
||||
updateContainerFrameAnchors = updateContainerFrameAnchors or function() end \
|
||||
UpdateNameplates = UpdateNameplates or function() end \
|
||||
CloseAllBags = CloseAllBags or function() end \
|
||||
OpenBackpack = OpenBackpack or function() end \
|
||||
CloseBackpack = CloseBackpack or function() end \
|
||||
NUM_CONTAINER_FRAMES = NUM_CONTAINER_FRAMES or 0 \
|
||||
CONTAINER_OFFSET_X = CONTAINER_OFFSET_X or 0 \
|
||||
CONTAINER_OFFSET_Y = CONTAINER_OFFSET_Y or 70 \
|
||||
BATTLEFIELD_TAB_OFFSET_Y = BATTLEFIELD_TAB_OFFSET_Y or 210 \
|
||||
PETACTIONBAR_YPOS = PETACTIONBAR_YPOS or 98 \
|
||||
PETACTIONBAR_XPOS = PETACTIONBAR_XPOS or 36",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn row(sender: Option<&str>, subject: &str, money: u32, cod: u32) -> MailInboxRow {
|
||||
MailInboxRow {
|
||||
package_icon: None,
|
||||
stationery_icon: Some("Interface\\Icons\\INV_Misc_Note_01".to_string()),
|
||||
sender: sender.map(str::to_string),
|
||||
subject: subject.to_string(),
|
||||
money,
|
||||
cod,
|
||||
days_left: 29.0,
|
||||
item_count: 0,
|
||||
was_read: false,
|
||||
was_returned: false,
|
||||
text_created: false,
|
||||
can_reply: sender.is_some(),
|
||||
is_gm: false,
|
||||
body: Some("body".into()),
|
||||
stationery_texture: "STATIONERYTEST".into(),
|
||||
is_invoice: false,
|
||||
invoice: None,
|
||||
has_body: true,
|
||||
item_id: 0,
|
||||
item_name: None,
|
||||
item_texture: None,
|
||||
item_quality: None,
|
||||
can_delete: false,
|
||||
item_random_property_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repro() {
|
||||
// A stale openMailID: open row 1, then the inbox re-lists smaller (row purged), keeping the
|
||||
// letter open; MAIL_INBOX_UPDATE re-runs InboxFrame_Update + OpenMail_Update on the stale id.
|
||||
let mut s = UiScript::new().unwrap();
|
||||
load_ui(&s);
|
||||
s.set_mail(Some(MailState {
|
||||
inbox: vec![
|
||||
row(None, "Welcome", 0, 0),
|
||||
row(Some("Jaina"), "second", 0, 0),
|
||||
],
|
||||
}));
|
||||
s.fire_event("MAIL_SHOW", vec![]);
|
||||
s.fire_event("MAIL_INBOX_UPDATE", vec![]);
|
||||
let _ = s.take_errors();
|
||||
s.run("MailItem1Button:Click()").unwrap();
|
||||
let errs = s.take_errors();
|
||||
eprintln!("errors after click: {errs:#?}");
|
||||
|
||||
// The letter stays open while the list empties out (rows purged) — row 1 is now out of range.
|
||||
s.set_mail(Some(MailState { inbox: vec![] }));
|
||||
s.fire_event("MAIL_INBOX_UPDATE", vec![]);
|
||||
let errs = s.take_errors();
|
||||
eprintln!("errors after empty re-list: {errs:#?}");
|
||||
assert!(true);
|
||||
}
|
||||
Reference in New Issue
Block a user