//! The world-map bindings (decision 0203 phase 2) — the Era-shaped map-navigation surface the //! reference `WorldMapFrame.lua` reads, over the questgiver/quest-log seam shape: the app pushes //! a static **catalog** once (continents → zones, from `WorldMapArea`/`AreaTable`/ //! `WorldMapContinent`) and a small per-frame **feed** (the player's resolved zone, projected map //! position, and facing — all projection math stays app-side in `map_proj`); the engine owns the //! **selection** (which map is displayed) so `SetMapZoom` → `GetCurrentMapContinent` reads back //! synchronously in the same click, exactly like the quest-log selection precedent. //! //! `SetMapZoom`/`SetMapToCurrentZone`/`ProcessMapClick` queue `WORLD_MAP_UPDATE` through the //! model's pending-event queue (fired at the next tick — the reference's synchronous fire is a //! repaint trigger, and one tick is invisible at frame rate). //! //! Selection encoding (the Lua-visible one, wow-re-confirmed — the client's internal indices //! `+1`): continent `0` = the world sheet, `1..` = the catalog's continents; zone `0` = the //! whole continent, `1..` = that continent's zone list. The catalog's *order* defines those //! indices everywhere (dropdowns, `SetMapZoom`, the feed) — the app builds it under the //! verified rules (WorldMapArea file order for continents, case-insensitive display-name sort //! for zones; wow-re Q1(d)/Q3(b) verdicts, 2026-07-07). //! //! Continent-level hover/click resolve through the pushed **zone grid** — the 128×128 area //! bitmap (`Interface\WorldMap\.zmp`, remapped app-side to 1-based zone indices; the //! source + cell law are wow-re-verified, `0x4a6ec0` / the Q1 §5 verdict 2026-07-07). The cell //! law itself is transcribed here ([`area_grid_cell`]) rather than in the app's `map_proj` //! because its only callers are these bindings. `UpdateMapHighlight` returns the hovered zone's //! NAME only for now — the highlight-texture halves (fileName + the `0x4a7fa0` POT extents) land //! with phase 3's overlay machinery, which shares them. //! //! Zone maps carry **exploration fog**: the base tiles are the unexplored parchment, and each //! discovered sub-area's overlay art (`GetNumMapOverlays`/`GetMapOverlayInfo`, filtered by the //! pushed `PLAYER_EXPLORED_ZONES` bitset) fills it in — the reference's own overlay pool draws //! the returned pieces. v1 stubs: the landmark (AreaPOI) family (`GetNumMapLandmarks` → 0). use mlua::{Lua, MultiValue, Value}; use super::Model; /// One zone row in a continent's list. Its 1-based position IS the zone index Lua sees. #[derive(Clone, Debug, Default, PartialEq)] pub struct WorldMapZoneView { /// The display name (AreaTable's localized string — "Elwynn Forest"). pub name: String, /// The AreaTable id (the feed's player-zone resolution joins on this; phase 3 overlays too). pub area_id: u32, /// The `Interface\WorldMap\\` art folder (WorldMapArea's internal name — "Elwynn"). pub map_file: String, /// This zone's WorldMapArea loc rect `(left, right, top, bottom)` — the window the continent /// highlight sizes/seats against, and the rect the zone-level `ProcessMapClick` un-lerps /// through (wow-re 15b2a8ea: the zone is a rect window onto the one per-continent bitmap). pub loc_rect: (f32, f32, f32, f32), /// The zone's discovery overlays (WorldMapOverlay rows) — the art that fills the parchment /// as sub-areas are explored (`GetNumMapOverlays`/`GetMapOverlayInfo`). pub overlays: Vec, } /// One discovery overlay: a sub-area's map art, drawn over the base tiles once any of its /// explore bits is set (decision 0203 phase 3). #[derive(Clone, Debug, Default, PartialEq)] pub struct WorldMapOverlayView { /// The full texture path prefix (`Interface\WorldMap\\`) — the reference Lua /// appends the piece number (`..1`, `..2`, …) exactly as the client's C return does. pub texture: String, /// The overlay's art size in map px (the Lua slices it into 256-tiles with POT file sizes). pub width: u32, pub height: u32, /// Placement within the 1002×668 detail frame, px from its TOPLEFT. pub offset_x: u32, pub offset_y: u32, /// The AreaTable `exploreFlag` bit indices that reveal this overlay (any set bit shows it; /// empty = never shown — an overlay whose areas are unknown). pub explore_bits: Vec, } /// One continent. Its 1-based position IS the continent index Lua sees. #[derive(Clone, Debug, Default, PartialEq)] pub struct WorldMapContinentView { /// The display name — Map.dbc's localized MapName ("Eastern Kingdoms", "Kalimdor"), the /// client's own dropdown-label source (`0x4a65a0`; wow-re Q3(a) verdict), never the art /// folder string. pub name: String, /// The `Interface\WorldMap\\` art folder. pub map_file: String, /// This continent's rect on the world sheet, normalized UV `(u0, v0, u1, v1)` — world-level /// `ProcessMapClick` picks the first continent (catalog order) containing the click, exactly /// the client's AABB walk (`0x4a7100`'s world branch over the record trailer floats). The /// app computes it with the verified `0x4a5d00` kernel (`map_proj::continent_sheet_rect`, /// from the WorldMapContinent tile bounds); the 5875 rects are disjoint. pub world_rect: (f32, f32, f32, f32), /// The continent's WorldMapArea world rect (loc left/right/top/bottom) — the rect the /// `0x4a6ec0` cell law un-lerps hover/click UVs through ([`area_grid_cell`]). pub loc_rect: (f32, f32, f32, f32), /// The 128×128 area bitmap, remapped app-side to **1-based zone indices** into /// [`Self::zones`] (0 = no zone; empty = no bitmap shipped — hover/click inert). Row-major, /// indexed by [`area_grid_cell`]. pub zone_grid: Vec, /// The continent's zones, in Lua index order. pub zones: Vec, } /// The `0x4a6ec0` cell law, verbatim: clamp the UV, un-lerp through the continent's WorldMapArea /// rect back to world coords, quantize on the half-ADT world grid (2.9296876e-5 = 1/(64·533.33), /// the binary's 0x806544), range-check, index `col − row·128` (row is truncated from the negated /// axis, so it's ≤ 0). Verified against the shipped Azeroth.zmp: Goldshire's world position /// lands on cell 12735 = Elwynn Forest. fn area_grid_cell(loc: (f32, f32, f32, f32), u: f32, v: f32) -> Option { const K: f32 = 2.929_687_6e-5; let (left, right, top, bottom) = loc; let clamp01 = |p: f32| { if p >= 1.0 { 1.0 } else if p >= 0.0 { p } else { 0.0 } }; let (u, v) = (clamp01(u), clamp01(v)); let span_u = left - right; let span_v = top - bottom; if span_u == 0.0 || span_v == 0.0 { return None; } let f1 = 0.5 - ((1.0 - u) * span_u + right) * K; let f2 = 0.5 - ((1.0 - v) * span_v + bottom) * K; if !(0.0..=1.0).contains(&f1) || !(0.0..=1.0).contains(&f2) { return None; } let col = (f1 * 128.0) as i32; // __ftol: truncate toward zero let row = (f2 * -128.0) as i32; usize::try_from(col - row * 128) .ok() .filter(|&i| i < 128 * 128) } /// The hovered/clicked child (zone OR city) at the current CONTINENT or ZONE selection: the grid /// cell's 1-based index into the continent's `zones`, or `None` off-land / off-grid / at world /// level. The rect that windows the one per-continent 128×128 bitmap is the continent's own loc /// rect at continent level, the CURRENT zone's loc rect at zone level — there is no per-zone /// bitmap, a zone/city is just a different rect window (wow-re 15b2a8ea, FUN_004a6ec0 §1c). This /// is what lets a click on a city's footprint from its neighbouring zone map drill into the city. fn grid_area(state: &WorldMapState, u: f32, v: f32) -> Option { let (c, z) = state.selection; if c == 0 { return None; } let cont = state.continents.get(c as usize - 1)?; if cont.zone_grid.is_empty() { return None; } let rect = if z == 0 { cont.loc_rect } else { cont.zones.get(z as usize - 1)?.loc_rect }; let cell = area_grid_cell(rect, u, v)?; match cont.zone_grid.get(cell).copied().unwrap_or(0) { 0 => None, zi => Some(zi), } } /// Power-of-two round-up (≥ n, ≥ 1) — the client's `worldmap_pot_dim` round-up, used for the /// continent highlight's vertical texcoord crop (`texPercentageY = dim / potdim`). fn next_pow2(n: i64) -> i64 { let mut p = 1i64; while p < n { p <<= 1; } p } /// The engine-side world-map state: the pushed catalog + feed, and the engine-owned selection. #[derive(Clone, Debug, Default, PartialEq)] pub struct WorldMapState { /// The static catalog (pushed once at startup; empty until then — every binding degrades to /// the world level, where `GetMapInfo` returns nil and the reference Lua falls back to /// `"World"`). pub continents: Vec, /// The displayed map: `(continent, zone)`, `(0, 0)` = the world sheet. pub selection: (u32, u32), /// Where `SetMapToCurrentZone` lands — the player's current `(continent, zone)` resolved by /// the app (AreaTable parent walk × the catalog). `None` = unresolvable (instance, loading) → /// the world sheet. pub player_zone: Option<(u32, u32)>, /// `GetPlayerMapPosition("player")` for the CURRENT selection — projected app-side each frame /// (`map_proj`), `None`/(0,0) = off this map (the reference hides the blip). pub player_uv: Option<(f32, f32)>, /// `GetPlayerFacing()` — wow orientation radians (0 = north, counterclockwise-positive). Our /// transcribed frame spins its arrow texture with it (`SetRotation`); the reference rotates /// an engine-rendered arrow model instead (0203 flags the stand-in). pub player_facing: f32, /// `GetCorpseMapPosition()` for the CURRENT selection — the corpse-run marker (decision 0308 /// §5), projected app-side like [`Self::player_uv`]; `None`/(0,0) = no corpse or off this map /// (the reference's WorldMapFrame.lua:443-452 hide law). pub corpse_uv: Option<(f32, f32)>, /// The discovery bitset (`PLAYER_EXPLORED_ZONES_1` — 64 u32 = 2048 bits, bit n = AreaTable /// exploreFlag n), pushed by the app when the descriptor changes. Empty = nothing explored. pub explored: Vec, } /// Is explore-bit `n` set in the pushed bitset? fn explored_bit(explored: &[u32], n: u32) -> bool { explored .get((n / 32) as usize) .is_some_and(|w| w & (1 << (n % 32)) != 0) } /// The current selection's zone view, if a zone map is displayed. fn current_zone(state: &WorldMapState) -> Option<&WorldMapZoneView> { let (c, z) = state.selection; if c == 0 || z == 0 { return None; } state .continents .get(c as usize - 1)? .zones .get(z as usize - 1) } /// The displayed zone's REVEALED overlays, in catalog order (the i-th is `GetMapOverlayInfo(i)`). fn revealed_overlays(state: &WorldMapState) -> Vec<&WorldMapOverlayView> { current_zone(state) .map(|zone| { zone.overlays .iter() .filter(|o| { o.explore_bits .iter() .any(|&b| explored_bit(&state.explored, b)) }) .collect() }) .unwrap_or_default() } impl super::UiScript { /// Push the static catalog (startup, once the DBCs are read). Queues `WORLD_MAP_UPDATE` so an /// already-open frame repaints (startup normally precedes any frame, harmlessly). pub fn set_world_map_catalog(&mut self, continents: Vec) { let mut model = self.model_mut(); model.worldmap.continents = continents; model .pending_events .push(("WORLD_MAP_UPDATE".to_string(), Vec::new())); } /// Push the discovery bitset (the app calls this when `PLAYER_EXPLORED_ZONES` changes — /// including the first stream-in). Queues `WORLD_MAP_UPDATE` so an open zone map fills in /// newly explored art immediately. pub fn set_world_map_explored(&mut self, explored: Vec) { let mut model = self.model_mut(); if model.worldmap.explored != explored { model.worldmap.explored = explored; model .pending_events .push(("WORLD_MAP_UPDATE".to_string(), Vec::new())); } } /// Push the per-frame feed (player zone/projection/facing — see [`WorldMapState`]). pub fn set_world_map_feed( &mut self, player_zone: Option<(u32, u32)>, player_uv: Option<(f32, f32)>, player_facing: f32, corpse_uv: Option<(f32, f32)>, ) { let mut model = self.model_mut(); model.worldmap.player_zone = player_zone; model.worldmap.player_uv = player_uv; model.worldmap.player_facing = player_facing; model.worldmap.corpse_uv = corpse_uv; } /// The engine-owned selection `(continent, zone)` — the app reads it each frame to project /// the feed for the displayed map. pub fn world_map_selection(&self) -> (u32, u32) { self.model_ref().worldmap.selection } /// The **normalized position within the map art** at UI-space `(x, y)`, or `None` when the /// point isn't over it. Reproduces the reference's own click normalization verbatim — /// `WorldMapFrame.xml`'s `WorldMapButton_OnClick`: `u = (x − left)/width`, /// `v = (top − y)/height` over `WorldMapButton`'s rect — so the UV handed back is the same one /// [`ProcessMapClick`](install) reads and the same one the blips are placed in. The Lua's /// effective-scale division cancels here: point and rect are both already in screen units. /// /// A pure query (fires nothing, mutates nothing). Its consumer is the app-side dev map-jump, /// which needs a click's UV *without* going through the faithful click path — that path drills /// into a zone, and the reference's law for it must not grow a second meaning. pub fn world_map_uv_at(&self, x: f32, y: f32) -> Option<(f32, f32)> { let id = self.hit_test(x, y)?; let model = self.model_ref(); let fh = model.id_to_frame.get(&id).copied()?; if model.arena.frame(fh)?.name.as_deref() != Some("WorldMapButton") { return None; } let r = model.resolved.get(&fh)?; let (w, h) = (r.right - r.left, r.top - r.bottom); (w > 0.0 && h > 0.0).then(|| ((x - r.left) / w, (r.top - y) / h)) } } /// Clamp + store a selection and queue the repaint event. The shared tail of /// `SetMapZoom`/`SetMapToCurrentZone`/`ProcessMapClick`. fn select(model: &mut Model, continent: i64, zone: i64) { let c = continent.clamp(0, model.worldmap.continents.len() as i64) as u32; let z = if c == 0 { 0 } else { let n = model.worldmap.continents[c as usize - 1].zones.len() as i64; zone.clamp(0, n) as u32 }; model.worldmap.selection = (c, z); model .pending_events .push(("WORLD_MAP_UPDATE".to_string(), Vec::new())); } /// Register the world-map globals. pub(super) fn install(lua: &Lua) -> mlua::Result<()> { let g = lua.globals(); // GetMapContinents() → name1, name2, … (the dropdown list; index = continent number). g.set( "GetMapContinents", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); let names: Vec = model .worldmap .continents .iter() .map(|c| Ok(Value::String(lua.create_string(&c.name)?))) .collect::>()?; Ok(MultiValue::from_vec(names)) })?, )?; // GetMapZones(continent) → name1, name2, … (that continent's zone list; index = zone number). g.set( "GetMapZones", lua.create_function(|lua, c: i64| { let model = lua.app_data_ref::().expect("model app_data"); let zones = usize::try_from(c) .ok() .and_then(|c| c.checked_sub(1)) .and_then(|c| model.worldmap.continents.get(c)) .map(|cont| cont.zones.as_slice()) .unwrap_or(&[]); let names: Vec = zones .iter() .map(|z| Ok(Value::String(lua.create_string(&z.name)?))) .collect::>()?; Ok(MultiValue::from_vec(names)) })?, )?; // GetCurrentMapContinent() / GetCurrentMapZone() — the selection halves (0 = world / whole // continent). The reference reads them right after SetMapZoom in the same click. g.set( "GetCurrentMapContinent", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); Ok(i64::from(model.worldmap.selection.0)) })?, )?; g.set( "GetCurrentMapZone", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); Ok(i64::from(model.worldmap.selection.1)) })?, )?; // SetMapZoom(continent[, zone]) — the dropdowns' and zoom-out button's navigation verb. g.set( "SetMapZoom", lua.create_function(|lua, (c, z): (i64, Option)| { let mut model = lua.app_data_mut::().expect("model app_data"); select(&mut model, c, z.unwrap_or(0)); Ok(()) })?, )?; // SetMapToCurrentZone() — the OnShow verb: jump to the player's zone (app-resolved feed). g.set( "SetMapToCurrentZone", lua.create_function(|lua, ()| { let mut model = lua.app_data_mut::().expect("model app_data"); let (c, z) = model.worldmap.player_zone.unwrap_or((0, 0)); select(&mut model, i64::from(c), i64::from(z)); Ok(()) })?, )?; // GetMapInfo() → mapFileName (the Interface\WorldMap\\ folder). World level → nil — // the reference Lua's own `"World"` fallback exists because the client returned nil there // (INFERRED, wow-re Q3 pending). g.set( "GetMapInfo", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); let wm = &model.worldmap; let file = match wm.selection { (0, _) => None, (c, z) => wm.continents.get(c as usize - 1).map(|cont| match z { 0 => cont.map_file.clone(), z => cont .zones .get(z as usize - 1) .map(|zone| zone.map_file.clone()) .unwrap_or_else(|| cont.map_file.clone()), }), }; Ok(match file { Some(f) => Value::String(lua.create_string(&f)?), None => Value::Nil, }) })?, )?; // GetCorpseMapPosition() → x, y in [0,1] map UV; (0,0) = no corpse / not on this map — the // reference's hide sentinel (WorldMapFrame.lua:445), same shape as the player position below. g.set( "GetCorpseMapPosition", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); let (x, y) = model.worldmap.corpse_uv.unwrap_or((0.0, 0.0)); Ok((f64::from(x), f64::from(y))) })?, )?; // GetPlayerMapPosition(unit) → x, y in [0,1] map UV; (0,0) = not on this map (hide the // blip). Only "player" resolves — party/raid are the party arc's, not this one's (0203). g.set( "GetPlayerMapPosition", lua.create_function(|lua, unit: String| { let model = lua.app_data_ref::().expect("model app_data"); let (x, y) = match unit.as_str() { "player" => model.worldmap.player_uv.unwrap_or((0.0, 0.0)), _ => (0.0, 0.0), }; Ok((f64::from(x), f64::from(y))) })?, )?; // GetPlayerFacing() → wow orientation radians (counterclockwise-positive, 0 = north). Feeds // the transcribed frame's arrow SetRotation (the reference's engine arrow reads facing // natively; a Lua-visible getter is our seam for the texture stand-in). g.set( "GetPlayerFacing", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); Ok(f64::from(model.worldmap.player_facing)) })?, )?; // ProcessMapClick(x, y) — normalized click within WorldMapButton (wow-re 15b2a8ea, Part 2). // World level: pick the first continent whose sheet-rect contains the click (the 0x4a7100 // AABB walk; the 5875 rects are disjoint). Continent OR zone level: identical instructions — // the 0x4a6ec0 grid cell through the CURRENT selection's rect (the continent's, or the current // zone's) → a continent child index → drill. Because a city is just another child, a click on // its footprint — from the continent map, or from a neighbouring zone map when the footprint // falls in that zone's window — drills into the city's own map. g.set( "ProcessMapClick", lua.create_function(|lua, (x, y): (f32, f32)| { let mut model = lua.app_data_mut::().expect("model app_data"); if model.worldmap.selection.0 == 0 { let hit = model.worldmap.continents.iter().position(|cont| { let (u0, v0, u1, v1) = cont.world_rect; (u0..=u1).contains(&x) && (v0..=v1).contains(&y) }); if let Some(i) = hit { select(&mut model, i as i64 + 1, 0); } } else if let Some(zi) = grid_area(&model.worldmap, x, y) { let c = i64::from(model.worldmap.selection.0); select(&mut model, c, i64::from(zi)); } Ok(()) })?, )?; // UpdateMapHighlight(x, y) → name, fileName, texPctX, texPctY, textureX, textureY, // scrollChildX, scrollChildY (wow-re 15b2a8ea, 0x4a7fa0). Continent level: the hovered zone // lights up — fileName = its art folder (the frame draws `\Highlight`), the six // coords seat/size/crop that texture over the zone's rect within the continent, keeping the // client's f32/f64 asymmetric narrowing. World-level continent highlight and zone-level // overlay-name are deferred: those exits return the nil/zero tail (the frame hides the quad). g.set( "UpdateMapHighlight", lua.create_function(|lua, (x, y): (f32, f32)| { // (name, fileName, texPctY, textureX, textureY, scrollChildX, scrollChildY); texPctX // is the constant 1.0 the continent branch always returns. let hit: Option<(String, String, f64, f64, f64, f64, f64)> = { let model = lua.app_data_ref::().expect("model app_data"); let wm = &model.worldmap; let (c, z) = wm.selection; if c == 0 || z != 0 { None } else { grid_area(wm, x, y) .and_then(|zi| wm.continents.get(c as usize - 1).zip(Some(zi))) .and_then(|(cont, zi)| cont.zones.get(zi as usize - 1).zip(Some(cont))) .and_then(|(zone, cont)| { let (cl, cr, ct, cb) = cont.loc_rect; let (zl, zr, zt, zb) = zone.loc_rect; let (w, h) = (zl - zr, zt - zb); let (cont_w, cont_h) = (cl - cr, ct - cb); if w == 0.0 || h == 0.0 || cont_w == 0.0 || cont_h == 0.0 { return None; } let recip_x = 1.0f32 / cont_w; let recip_y_f32 = 1.0f32 / cont_h; let texture_x = f64::from(recip_x * w); let texture_y = (1.0f64 / f64::from(cont_h)) * f64::from(h); let scroll_x = f64::from(recip_x * (cl - zl)); let scroll_y = f64::from(recip_y_f32 * (ct - zt)); // texPctY = dim/potdim, dim = __ftol((h·128)/w) (rect-form K = 128). let dim = ((h * 128.0) / w) as i64; let potdim = next_pow2(dim); let tex_pct_y = if potdim > 0 { dim as f64 / potdim as f64 } else { 0.0 }; Some(( zone.name.clone(), zone.map_file.clone(), tex_pct_y, texture_x, texture_y, scroll_x, scroll_y, )) }) } }; match hit { Some((name, file, tex_pct_y, tx, ty, sx, sy)) => Ok(( Value::String(lua.create_string(&name)?), Value::String(lua.create_string(&file)?), 1.0f64, tex_pct_y, tx, ty, sx, sy, )), None => Ok(( Value::Nil, Value::Nil, 0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64, )), } })?, )?; // The landmark (AreaPOI) family — a later phase-3 slice. Zero/nil now. g.set("GetNumMapLandmarks", lua.create_function(|_, ()| Ok(0i64))?)?; g.set( "GetMapLandmarkInfo", lua.create_function(|_, _i: i64| Ok(Value::Nil))?, )?; // GetNumMapOverlays() — how many of the displayed zone's overlays are REVEALED by the // explored bitset (the client's C side filters the same way; the reference Lua draws every // returned overlay). 0 at world/continent level. g.set( "GetNumMapOverlays", lua.create_function(|lua, ()| { let model = lua.app_data_ref::().expect("model app_data"); Ok(revealed_overlays(&model.worldmap).len() as i64) })?, )?; // GetMapOverlayInfo(i) → textureName, textureWidth, textureHeight, offsetX, offsetY, // mapPointX, mapPointY — the i-th (1-based) revealed overlay. The texture is the full path // prefix (the Lua appends the piece number); mapPointX/Y are 0 on every 5875 row. g.set( "GetMapOverlayInfo", lua.create_function(|lua, i: i64| { let overlay = { let model = lua.app_data_ref::().expect("model app_data"); usize::try_from(i) .ok() .and_then(|i| i.checked_sub(1)) .and_then(|i| revealed_overlays(&model.worldmap).get(i).cloned().cloned()) }; let Some(o) = overlay else { return Ok(MultiValue::from_vec(vec![Value::Nil])); }; Ok(MultiValue::from_vec(vec![ Value::String(lua.create_string(&o.texture)?), Value::Integer(i64::from(o.width)), Value::Integer(i64::from(o.height)), Value::Integer(i64::from(o.offset_x)), Value::Integer(i64::from(o.offset_y)), Value::Integer(0), Value::Integer(0), ])) })?, )?; Ok(()) }