using System.Collections.Generic; using System.Numerics; using DatReaderWriter.Enums; namespace AcDream.Core.Physics; /// /// Indoor walking Phase 2 (2026-05-19). Cached building portal data /// for outdoor→indoor cell entry. One per outdoor landcell that contains /// a building stab. Mirrors retail's BuildingObj.Portals array /// (per the pseudocode doc §"LandCell.find_transit_cells"). /// public sealed class BuildingPhysics { public required Matrix4x4 WorldTransform { get; init; } public required Matrix4x4 InverseWorldTransform { get; init; } public required IReadOnlyList Portals { get; init; } /// /// BR-7 / A6.P4 (2026-06-11): the building's shell part-0 GfxObj id. /// 0x01 BuildingInfo.ModelId values are stored verbatim; 0x02 Setup /// models are resolved to their FIRST part at cache time (the /// CacheBuilding call site reads the dat). Drives the retail building /// collision channel (CBuildingObj::find_building_collisions, /// Ghidra 0x006b5300: one BSP test on part_array->parts[0]). /// 0 = unknown (legacy cache entries / tests) — the channel is inert. /// public uint ModelId { get; init; } } /// /// One building portal: the connection from a SortCell's BuildingObj to /// an interior EnvCell. ExactMatch is decoded from /// bit 0 (PortalFlags.ExactMatch = 0x0001). /// public readonly struct BldPortalInfo { public BldPortalInfo(uint otherCellId, short otherPortalId, ushort flags) { OtherCellId = otherCellId; OtherPortalId = otherPortalId; Flags = flags; } /// Full id of the interior EnvCell this portal connects to. public uint OtherCellId { get; } /// /// The portal id within the destination EnvCell — SIGNED, like retail's /// CBldPortal.other_portal_id (int, acclient.h:32098, /// sign-extended from the dat's 16-bit field). -1 (wire /// 0xFFFF) means "no reciprocal portal"; retail's /// CEnvCell::check_building_transit (Ghidra 0x0052c5d0) rejects /// the whole transit when this is negative — the arg2 >= 0 /// gate is the first instruction. BN's pseudo-C renders the comparison /// unsigned (wrong); the sign-extension is Ghidra-proven /// (wf1-interior-collision.md, BR-7 verified corrections). /// DatReaderWriter parses the field as ushort; construction /// sites reinterpret via unchecked((short)value). /// public short OtherPortalId { get; } public ushort Flags { get; } /// /// Bit 0 of (DatReaderWriter.Enums.PortalFlags.ExactMatch). /// /// /// Reserved per retail's CBldPortal::exact_match. NOT currently /// consumed by — every /// portal overlap is treated as a valid entry trigger. If a future /// regression surfaces (e.g., a building entered by overlapping a /// non-exact-match portal), wire this into the entry test. /// /// public bool ExactMatch => (Flags & (ushort)PortalFlags.ExactMatch) != 0; }