dredless 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -588,6 +588,367 @@
588
588
  }
589
589
  };
590
590
 
591
+ // src/protocol/outbound/ship-management.js
592
+ var SHIP_MANAGEMENT_TYPE = 4;
593
+ var SET_PRIVACY_ACTION = "set_privacy";
594
+ var STARTER_RECOVERY_ACTION = "starter_recovery";
595
+ var PLAYER_LIST_ACTION = "player_list";
596
+ var INVITE_RESET_ACTION = "invite_reset";
597
+ var SET_RANK_ACTION = "set_rank";
598
+ var KICK_ACTION = "kick";
599
+ var BAN_ACTION = "ban";
600
+ var DEMOTE_SELF_ACTION = "demote_self";
601
+ function requireNonNegativeInteger(value, name) {
602
+ const number = Number(value);
603
+ if (!Number.isInteger(number) || number < 0) throw new RangeError(`${name} must be a non-negative integer`);
604
+ return number;
605
+ }
606
+ function normalizePrivacy(value) {
607
+ if (value === true || value === 1 || value === "private") return 1;
608
+ if (value === false || value === 0 || value === "public") return 0;
609
+ throw new RangeError(`privacy must be 0, 1, true, false, "public", or "private"`);
610
+ }
611
+ function normalizePlayerRef(value) {
612
+ return requireNonNegativeInteger(value, "refId");
613
+ }
614
+ function normalizePlayerRank(value) {
615
+ if (value === 0 || value === "guest") return 0;
616
+ if (value === 1 || value === "crew") return 1;
617
+ if (value === 3 || value === "captain") return 3;
618
+ throw new RangeError(`rank must be 0, 1, 3, "guest", "crew", or "captain"`);
619
+ }
620
+ function buildShipManagementMessage(act, arg = null, extra = null) {
621
+ if (typeof act !== "string" || !act) throw new TypeError(`act must be a non-empty string`);
622
+ const message = { type: SHIP_MANAGEMENT_TYPE, act, arg };
623
+ if (extra && typeof extra === "object") Object.assign(message, extra);
624
+ return message;
625
+ }
626
+ function buildShipPrivacyMessage(privacy) {
627
+ return buildShipManagementMessage(SET_PRIVACY_ACTION, normalizePrivacy(privacy));
628
+ }
629
+ function buildStarterRecoveryMessage(itemId) {
630
+ return buildShipManagementMessage(STARTER_RECOVERY_ACTION, requireNonNegativeInteger(itemId, "itemId"));
631
+ }
632
+ function buildPlayerListMessage() {
633
+ return buildShipManagementMessage(PLAYER_LIST_ACTION);
634
+ }
635
+ function buildInviteResetMessage() {
636
+ return buildShipManagementMessage(INVITE_RESET_ACTION);
637
+ }
638
+ function buildSetPlayerRankMessage(refId, rank) {
639
+ return buildShipManagementMessage(SET_RANK_ACTION, normalizePlayerRef(refId), { rank: normalizePlayerRank(rank) });
640
+ }
641
+ function buildKickPlayerMessage(refId) {
642
+ return buildShipManagementMessage(KICK_ACTION, normalizePlayerRef(refId));
643
+ }
644
+ function buildBanPlayerMessage(refId) {
645
+ return buildShipManagementMessage(BAN_ACTION, normalizePlayerRef(refId));
646
+ }
647
+ function buildDemoteSelfMessage() {
648
+ return buildShipManagementMessage(DEMOTE_SELF_ACTION);
649
+ }
650
+ function normalizeShipConfig(event = {}) {
651
+ const source = event && typeof event === "object" ? event : {};
652
+ const config = source.config && typeof source.config === "object" ? source.config : {};
653
+ return {
654
+ privacy: config.privacy ?? null,
655
+ privacyName: config.privacy === 0 ? "public" : config.privacy === 1 ? "private" : null,
656
+ inviteKey: config.invite_key ?? null,
657
+ teamId: source.team_id ?? null,
658
+ patronPerks: Array.isArray(source.patron_perks) ? source.patron_perks.slice() : []
659
+ };
660
+ }
661
+ function normalizeCaptainSubrank(event = {}) {
662
+ const source = event && typeof event === "object" ? event : {};
663
+ return {
664
+ subrank: source.subrank ?? null,
665
+ enableCheats: Boolean(source.enable_cheats)
666
+ };
667
+ }
668
+ function normalizeShipPlayerList(event = {}, previous = null, currentCaptainSubrank = null) {
669
+ const source = event && typeof event === "object" ? event : {};
670
+ const changes = Array.isArray(source.player_list) ? source.player_list.map(normalizePlayerListChange) : [];
671
+ const removedPlayers = changes.filter((change) => change.remove && Number.isFinite(Number(change.refId))).map((change) => Number(change.refId));
672
+ const changedPlayers = changes.filter((change) => !change.remove).map(({ remove: _remove, ...change }) => change);
673
+ const players = mergePlayerListChanges(previous?.players, changes);
674
+ const ownerCaptainRank = ownerRankForPlayers(players);
675
+ applyPlayerControlState(players, currentCaptainSubrank);
676
+ applyPlayerControlState(changedPlayers, currentCaptainSubrank);
677
+ return {
678
+ ownerCaptainRank,
679
+ shipOwners: players.filter((player) => player.isShipOwner),
680
+ players,
681
+ changes: changedPlayers,
682
+ removedPlayers
683
+ };
684
+ }
685
+ function mergePlayerListChanges(previousPlayers, changes) {
686
+ if (!Array.isArray(previousPlayers) || !previousPlayers.length) return changes.filter((change) => !change.remove).map(({
687
+ remove: _remove,
688
+ ...player
689
+ }) => ({ ...player }));
690
+ const existing = /* @__PURE__ */ new Map();
691
+ for (const player of previousPlayers) {
692
+ if (player?.refId == null) continue;
693
+ existing.set(player.refId, { ...player, isShipOwner: false });
694
+ }
695
+ const changedRefs = /* @__PURE__ */ new Set();
696
+ const merged = [];
697
+ for (const change of changes) {
698
+ if (change.refId == null) {
699
+ if (!change.remove) {
700
+ const { remove: _remove2, ...player2 } = change;
701
+ merged.push({ ...player2, isShipOwner: false });
702
+ }
703
+ continue;
704
+ }
705
+ changedRefs.add(change.refId);
706
+ if (change.remove) {
707
+ existing.delete(change.refId);
708
+ continue;
709
+ }
710
+ const { remove: _remove, ...changePlayer } = change;
711
+ const player = { ...existing.get(change.refId) || {}, ...changePlayer, isShipOwner: false };
712
+ existing.set(change.refId, player);
713
+ merged.push(player);
714
+ }
715
+ for (const player of previousPlayers) {
716
+ if (player?.refId == null || changedRefs.has(player.refId)) continue;
717
+ const retained = existing.get(player.refId);
718
+ if (retained) merged.push(retained);
719
+ }
720
+ return merged;
721
+ }
722
+ function normalizePlayerListChange(entry = {}) {
723
+ const source = entry && typeof entry === "object" ? entry : {};
724
+ if (source._removed) return { refId: source.ref_id ?? null, remove: true };
725
+ return {
726
+ refId: source.ref_id ?? null,
727
+ remove: false,
728
+ discrim: source.discrim ?? null,
729
+ discrimColor: source.discrim_color ?? null,
730
+ teamRank: source.team_rank ?? null,
731
+ captainRank: source.captain_rank ?? null,
732
+ isCaptain: Number(source.captain_rank) > 0,
733
+ isShipOwner: false,
734
+ canBeManaged: false,
735
+ time: source.time ?? null,
736
+ items: Array.isArray(source.items) ? source.items.slice() : [],
737
+ aliasDiscrims: Array.isArray(source.alias_discrims) ? source.alias_discrims.slice() : [],
738
+ extraAliasCount: normalizeNonNegativeNumber(source.extra_aliases),
739
+ onlineCount: source.online_count ?? null
740
+ };
741
+ }
742
+ function normalizeNonNegativeNumber(value) {
743
+ const number = Number(value);
744
+ return Number.isFinite(number) && number > 0 ? number : 0;
745
+ }
746
+ function ownerRankForPlayers(players) {
747
+ const ranks = players.filter((player) => Number(player.captainRank) > 0).map((player) => Number(player.captainRank)).filter(Number.isFinite);
748
+ if (!ranks.length) return null;
749
+ const ownerRank = Math.min(...ranks);
750
+ for (const player of players) player.isShipOwner = Number(player.captainRank) === ownerRank;
751
+ return ownerRank;
752
+ }
753
+ function applyPlayerControlState(players, currentCaptainSubrank) {
754
+ const currentRank = currentCaptainRank(currentCaptainSubrank);
755
+ for (const player of players) player.canBeManaged = playerCanBeManagedBy(player, currentRank);
756
+ }
757
+ function currentCaptainRank(currentCaptainSubrank) {
758
+ const rank = Number(currentCaptainSubrank?.subrank);
759
+ return Number.isFinite(rank) && rank > 0 ? rank : null;
760
+ }
761
+ function playerCanBeManagedBy(player, currentRank) {
762
+ if (!Number.isFinite(currentRank) || !player) return false;
763
+ const targetCaptainRank = Number(player.captainRank);
764
+ if (Number.isFinite(targetCaptainRank) && targetCaptainRank > 0) return currentRank < targetCaptainRank;
765
+ const targetTeamRank = Number(player.teamRank);
766
+ return targetTeamRank === 0 || targetTeamRank === 1;
767
+ }
768
+
769
+ // src/client/domains/tips.js
770
+ function timeoutValue(options) {
771
+ const timeout = options.timeout ?? 5e3;
772
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new RangeError("timeout must be positive milliseconds");
773
+ return timeout;
774
+ }
775
+ function waitFor(client, eventType, read, timeout, label, send = null) {
776
+ return new Promise((resolve, reject) => {
777
+ const cleanup = () => {
778
+ clearTimeout(timer);
779
+ client.off(eventType, check);
780
+ client.off("close", closed);
781
+ };
782
+ const check = (...args) => {
783
+ const value = read(...args);
784
+ if (value) {
785
+ cleanup();
786
+ resolve(value);
787
+ }
788
+ };
789
+ const closed = () => {
790
+ cleanup();
791
+ reject(new Error(`Disconnected while waiting for ${label}`));
792
+ };
793
+ const timer = setTimeout(() => {
794
+ cleanup();
795
+ reject(new Error(`Timed out waiting for ${label}`));
796
+ }, timeout);
797
+ client.on(eventType, check);
798
+ client.on("close", closed);
799
+ if (send) {
800
+ try {
801
+ send();
802
+ } catch (error) {
803
+ cleanup();
804
+ reject(error);
805
+ return;
806
+ }
807
+ }
808
+ check();
809
+ });
810
+ }
811
+ var TipDomain = class {
812
+ #busy = false;
813
+ #target = null;
814
+ #current = null;
815
+ constructor(client) {
816
+ this.client = client;
817
+ client.on("close", () => {
818
+ this.#current = null;
819
+ this.#target = null;
820
+ });
821
+ }
822
+ get current() {
823
+ return this.#current;
824
+ }
825
+ receive(event, world) {
826
+ const options = Array.isArray(event.options) ? event.options.map(String) : [];
827
+ const entity = this.#target?.world === world?.id ? this.#target.entity : null;
828
+ const menu = Object.freeze({
829
+ entity,
830
+ world: world?.id ?? null,
831
+ filter: event.filter ?? null,
832
+ x: event.x ?? 0,
833
+ y: event.y ?? 0,
834
+ options: Object.freeze(options)
835
+ });
836
+ this.#current = options.length ? menu : null;
837
+ this.client.emit("tip-list", menu, world);
838
+ }
839
+ async #exclusive(action) {
840
+ if (this.#busy) throw new Error("A tip menu operation is already in progress");
841
+ this.#busy = true;
842
+ try {
843
+ return await action();
844
+ } catch (error) {
845
+ this.#current = null;
846
+ this.#target = null;
847
+ throw error;
848
+ } finally {
849
+ this.#busy = false;
850
+ }
851
+ }
852
+ async #open(entity, options) {
853
+ if (!this.client.ready || !this.client.connected) throw new Error("Client must be connected and ready to open tips");
854
+ const timeout = timeoutValue(options);
855
+ const handle = typeof entity === "number" ? this.client.currentShip()?.entities.get(entity) : entity;
856
+ if (!handle || handle.client !== this.client || !handle.exists()) throw new Error("Tip target must be an existing entity on this client");
857
+ const world = this.client.worlds.currentWorldId;
858
+ const scopeWorld = handle.scope === "ship" ? this.client.worlds.shipWorld() : handle.scope === "current" ? this.client.worlds.worlds.get(world) : handle.scope === "overworld" ? this.client.worlds.overworld() : this.client.worlds.worlds.get(Number(handle.scope));
859
+ if (scopeWorld?.id !== world) {
860
+ throw new Error("Tip target must be in the current world");
861
+ }
862
+ const position = handle.position;
863
+ if (!position) throw new Error("Tip target has no position");
864
+ this.#current = null;
865
+ this.#target = { entity: handle.id, world, command: {
866
+ ...options.command,
867
+ focus_ent: handle.id,
868
+ mx: position.x,
869
+ my: position.y
870
+ } };
871
+ return waitFor(this.client, "tip-list", () => {
872
+ return this.#current?.entity === handle.id && this.#current.world === world ? this.#current : null;
873
+ }, timeout, "tip menu", () => {
874
+ this.client.send({ ...this.#target.command, act1: false, act2: true, tip_select: null });
875
+ });
876
+ }
877
+ open(entity, options = {}) {
878
+ return this.#exclusive(() => this.#open(entity, options));
879
+ }
880
+ close(options = {}) {
881
+ return this.#exclusive(async () => {
882
+ const timeout = timeoutValue(options);
883
+ const menu = this.#current;
884
+ if (!menu) return this.client;
885
+ if (!this.client.connected || !this.client.ready) throw new Error("Client must be connected and ready to close tips");
886
+ if (menu.world !== this.client.worlds.currentWorldId) throw new Error("Tip menu belongs to another world");
887
+ await waitFor(this.client, "tip-list", (update) => update?.world === menu.world && update.options.length === 0, timeout, "tip menu dismissal", () => {
888
+ this.client.send({
889
+ ...options.command,
890
+ focus_ent: null,
891
+ config_ent: null,
892
+ act1: true,
893
+ act1_held: true,
894
+ act2: false,
895
+ tip_select: null
896
+ });
897
+ });
898
+ this.#target = null;
899
+ return this.client;
900
+ });
901
+ }
902
+ select(option, command = {}) {
903
+ if (this.#busy) throw new Error("A tip menu operation is already in progress");
904
+ return this.#select(option, command);
905
+ }
906
+ #select(option, command = {}) {
907
+ if (!this.client.connected || !this.client.ready) throw new Error("Client must be connected and ready to select tips");
908
+ const menu = this.#current;
909
+ if (!menu || menu.entity == null || !this.#target) throw new Error("Open an entity's tip menu before selecting an option");
910
+ if (menu.world !== this.client.worlds.currentWorldId) throw new Error("Tip menu belongs to another world");
911
+ const index = typeof option === "string" ? menu.options.indexOf(option) : option;
912
+ if (!Number.isInteger(index) || index < 0 || index >= menu.options.length) throw new RangeError("Tip option is not in the current menu");
913
+ this.client.send({
914
+ ...this.#target.command,
915
+ ...command,
916
+ focus_ent: menu.entity,
917
+ act1: false,
918
+ act2: false,
919
+ tip_select: index
920
+ });
921
+ this.#current = null;
922
+ this.#target = null;
923
+ return this.client;
924
+ }
925
+ choose(entity, option, options = {}) {
926
+ return this.#exclusive(async () => {
927
+ await this.#open(entity, options);
928
+ return this.#select(option);
929
+ });
930
+ }
931
+ setRank(handle, rank, options = {}) {
932
+ return this.#exclusive(async () => {
933
+ const value = normalizePlayerRank(rank);
934
+ const timeout = timeoutValue(options);
935
+ if (!handle.exists()) throw new Error("Rank target no longer exists");
936
+ if (handle.rank === value) return this.client;
937
+ const text = { 0: "Restrict to Guests", 1: "Restrict to Crew", 3: "Restrict to Captains" }[value];
938
+ await this.#open(handle, options);
939
+ await waitFor(
940
+ this.client,
941
+ "model",
942
+ () => handle.rank === value,
943
+ timeout,
944
+ "access rank update",
945
+ () => this.#select(text)
946
+ );
947
+ return this.client;
948
+ });
949
+ }
950
+ };
951
+
591
952
  // src/client/core.js
592
953
  init_cookies();
593
954
  init_servers();
@@ -3534,7 +3895,6 @@
3534
3895
  SHIELD_GENERATOR_BOOST_STATE_NAMES: () => SHIELD_GENERATOR_BOOST_STATE_NAMES,
3535
3896
  SIGN_DISPLAY_MODE_NAMES: () => SIGN_DISPLAY_MODE_NAMES,
3536
3897
  SIMPLE_LABEL_STATE_SPEC: () => SIMPLE_LABEL_STATE_SPEC,
3537
- TABLE_78_FIELD_BITS: () => TABLE_78_FIELD_BITS,
3538
3898
  TABLE_78_NUMERIC_FIELDS: () => TABLE_78_NUMERIC_FIELDS,
3539
3899
  TEAM_RANK_NAMES: () => TEAM_RANK_NAMES,
3540
3900
  THRUSTER_FACING_NAMES: () => THRUSTER_FACING_NAMES,
@@ -3567,7 +3927,6 @@
3567
3927
  isOrangeFoolBotState: () => isOrangeFoolBotState,
3568
3928
  isRedSentryBotState: () => isRedSentryBotState,
3569
3929
  isRedSniperBotState: () => isRedSniperBotState,
3570
- isSemanticLoaderDelta: () => isSemanticLoaderDelta2,
3571
3930
  isShieldHelperBotState: () => isShieldHelperBotState,
3572
3931
  isShieldMasterBotState: () => isShieldMasterBotState,
3573
3932
  isYellowHunterBotState: () => isYellowHunterBotState,
@@ -3590,914 +3949,61 @@
3590
3949
  scaledSizeSummary: () => scaledSizeSummary,
3591
3950
  summarizeBlueprintPreview: () => summarizeBlueprintPreview,
3592
3951
  summarizeBot: () => summarizeBot,
3593
- summarizeCannon: () => summarizeCannon,
3594
- summarizeCargoEjector: () => summarizeCargoEjector,
3595
- summarizeCargoHatch: () => summarizeCargoHatch,
3596
- summarizeCommsStation: () => summarizeCommsStation,
3597
- summarizeDockingSpring: () => summarizeDockingSpring,
3598
- summarizeDoor: () => summarizeDoor,
3599
- summarizeExpandoBox: () => summarizeExpandoBox,
3600
- summarizeFabricator: () => summarizeFabricator,
3601
- summarizeHealth: () => summarizeHealth,
3602
- summarizeHelm: () => summarizeHelm,
3603
- summarizeHoverOutline: () => summarizeHoverOutline,
3604
- summarizeHugeThruster: () => summarizeHugeThruster,
3605
- summarizeItemCrate: () => summarizeItemCrate,
3606
- summarizeItemHolder: () => summarizeItemHolder,
3607
- summarizeItemLauncher: () => summarizeItemLauncher,
3608
- summarizeLoader: () => summarizeLoader,
3609
- summarizeMapMarker: () => summarizeMapMarker,
3610
- summarizeNavigationUnit: () => summarizeNavigationUnit,
3611
- summarizePlayer: () => summarizePlayer,
3612
- summarizePlayerPreview: () => summarizePlayerPreview,
3613
- summarizePusher: () => summarizePusher,
3614
- summarizePusherBeam: () => summarizePusherBeam,
3615
- summarizeShieldGenerator: () => summarizeShieldGenerator,
3616
- summarizeShieldProjector: () => summarizeShieldProjector,
3617
- summarizeShipControl: () => summarizeShipControl,
3618
- summarizeShipSize: () => summarizeShipSize,
3619
- summarizeShipWorldMetadata: () => summarizeShipWorldMetadata,
3620
- summarizeSign: () => summarizeSign,
3621
- summarizeSpawnPoint: () => summarizeSpawnPoint,
3622
- summarizeThruster: () => summarizeThruster,
3623
- table78DeltaRecord: () => table78DeltaRecord
3624
- });
3625
- init_constants();
3626
-
3627
- // src/state/model/loader-config.js
3628
- var LOADER_POSITION_NAMES = /* @__PURE__ */ new Map([
3629
- [0, "top-left"],
3630
- [1, "top-middle"],
3631
- [2, "top-right"],
3632
- [3, "middle-left"],
3633
- [4, "middle-right"],
3634
- [5, "bottom-left"],
3635
- [6, "bottom-middle"],
3636
- [7, "bottom-right"]
3637
- ]);
3638
- var LOADER_PRIORITY_NAMES = /* @__PURE__ */ new Map([
3639
- [-1, "low"],
3640
- [0, "normal"],
3641
- [1, "high"]
3642
- ]);
3643
- var LOADER_FILTER_MODE_NAMES = /* @__PURE__ */ new Map([
3644
- [0, "allow-all"],
3645
- [1, "block-filter"],
3646
- [2, "allow-filter"],
3647
- [3, "block-all"]
3648
- ]);
3649
- var DEFAULT_LOADER_PRIORITY = 0;
3650
- var DEFAULT_LOADER_FILTER_MODE = 0;
3651
- var DEFAULT_LOADER_STACK = 16;
3652
- var DEFAULT_LOADER_CYCLE = 1;
3653
- var DEFAULT_LOADER_PICK = 3;
3654
- var DEFAULT_LOADER_PLACE = 4;
3655
- var LOADER_CONFIG_NUMERIC_BITS = [1, 2, 4, 8, 16];
3656
- var LOADER_CONFIG_NUMERIC_FIELDS = ["q20", "q24", "q28", "q32", "q36"];
3657
- function wrapLoaderPosition(value) {
3658
- return value == null ? null : (value % 8 + 8) % 8;
3659
- }
3660
- function decodeIndexedLoaderSnapshotRecord(loader, mask = 0, cumulative = null, reference = null) {
3661
- const config = {
3662
- pick: 3,
3663
- place: 4,
3664
- priority: DEFAULT_LOADER_PRIORITY,
3665
- requireOutput: false,
3666
- waitForStack: false,
3667
- stack: DEFAULT_LOADER_STACK,
3668
- cycle: DEFAULT_LOADER_CYCLE
3669
- };
3670
- if (mask & 32) config.requireOutput = true;
3671
- if (mask & 64) config.waitForStack = true;
3672
- switch (mask) {
3673
- case 0:
3674
- break;
3675
- case 1:
3676
- config.pick = wrapLoaderPosition((loader.q20 ?? 0) + 3);
3677
- if (cumulative?.q40 != null) config.requireOutput = true;
3678
- break;
3679
- case 2:
3680
- if (reference && cumulative?.q40 != null && loader.q24 != null) {
3681
- if (loader.q24 <= -8) {
3682
- config.pick = wrapLoaderPosition((reference.pick ?? 3) + 1);
3683
- config.place = wrapLoaderPosition((reference.place ?? 4) + 3);
3684
- config.requireOutput = true;
3685
- } else {
3686
- config.pick = wrapLoaderPosition((reference.pick ?? 3) + 2);
3687
- config.place = wrapLoaderPosition((reference.place ?? 4) + 4);
3688
- }
3689
- } else {
3690
- config.place = wrapLoaderPosition((loader.q24 ?? 0) + 4);
3691
- }
3692
- break;
3693
- case 3:
3694
- config.pick = reference && loader.q24 <= -8 ? wrapLoaderPosition((reference.pick ?? 3) + (loader.q20 ?? 0) + 3) : wrapLoaderPosition((loader.q20 ?? 0) + 3);
3695
- config.place = wrapLoaderPosition((loader.q24 ?? 0) + 4);
3696
- if (cumulative?.q44 != null && cumulative?.q36 == null && cumulative?.q40 == null) {
3697
- config.requireOutput = true;
3698
- config.waitForStack = true;
3699
- }
3700
- break;
3701
- case 5:
3702
- config.pick = wrapLoaderPosition((loader.q28 ?? 0) + 1);
3703
- config.place = wrapLoaderPosition((loader.q28 ?? 0) + 3);
3704
- break;
3705
- case 6:
3706
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 5);
3707
- config.place = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3708
- config.requireOutput = true;
3709
- break;
3710
- case 7:
3711
- if (reference && (cumulative?.q40 != null || loader.q24 <= -8 || loader.q28 >= 2)) {
3712
- if (loader.q24 <= -8) {
3713
- config.pick = wrapLoaderPosition((reference.pick ?? 3) + (loader.q20 ?? 0) + 5);
3714
- config.place = wrapLoaderPosition(reference.place ?? 4);
3715
- config.requireOutput = true;
3716
- config.waitForStack = true;
3717
- if (loader.q28 != null) config.priority = loader.q28;
3718
- } else if (loader.q28 >= 2) {
3719
- config.pick = wrapLoaderPosition((reference.pick ?? 3) + loader.q28);
3720
- config.place = wrapLoaderPosition((reference.place ?? 4) + (loader.q20 ?? 0));
3721
- config.priority = loader.q28 - 4;
3722
- } else if ((loader.q20 ?? 0) >= 3) {
3723
- config.pick = wrapLoaderPosition((reference.pick ?? 3) + (loader.q20 ?? 0));
3724
- config.place = wrapLoaderPosition(reference.place ?? 4);
3725
- config.priority = (loader.q28 ?? 0) + 1;
3726
- config.requireOutput = Boolean(reference.requireOutput);
3727
- config.waitForStack = Boolean(reference.waitForStack);
3728
- } else {
3729
- config.pick = wrapLoaderPosition((loader.q20 ?? 0) + 2);
3730
- config.place = wrapLoaderPosition((reference.place ?? 4) + (loader.q24 ?? 0));
3731
- config.priority = (loader.q28 ?? 0) + 1;
3732
- }
3733
- } else {
3734
- config.pick = wrapLoaderPosition((loader.q20 ?? 0) + 3);
3735
- config.place = wrapLoaderPosition((loader.q24 ?? 0) + 4);
3736
- if (loader.q28 != null) config.priority = loader.q28;
3737
- }
3738
- break;
3739
- case 8:
3740
- config.pick = wrapLoaderPosition((cumulative?.q40 ?? 0) + 4);
3741
- config.place = wrapLoaderPosition((cumulative?.q28 ?? 0) + 2);
3742
- config.requireOutput = true;
3743
- break;
3744
- case 9:
3745
- config.pick = wrapLoaderPosition((loader.q20 ?? 0) + 3);
3746
- config.place = wrapLoaderPosition((cumulative?.q40 ?? 0) + 3);
3747
- break;
3748
- case 18:
3749
- config.pick = wrapLoaderPosition(loader.q24 ?? 0);
3750
- config.place = wrapLoaderPosition((loader.q36 ?? 0) + 2);
3751
- if (loader.q36 != null) config.cycle = loader.q36 / 20 + 1;
3752
- break;
3753
- case 32:
3754
- break;
3755
- case 65:
3756
- config.pick = wrapLoaderPosition((loader.q44 ?? 0) + 3);
3757
- break;
3758
- case 33:
3759
- config.pick = wrapLoaderPosition((loader.q40 ?? 0) + 3);
3760
- config.place = wrapLoaderPosition((loader.q20 ?? 0) + 4);
3761
- break;
3762
- case 34:
3763
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3764
- config.place = wrapLoaderPosition((loader.q40 ?? 0) + 4);
3765
- break;
3766
- case 35:
3767
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3768
- config.place = wrapLoaderPosition((loader.q40 ?? 0) + 4);
3769
- break;
3770
- case 67:
3771
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3772
- config.place = wrapLoaderPosition((loader.q44 ?? 0) + 4);
3773
- break;
3774
- case 95:
3775
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3776
- config.place = wrapLoaderPosition((loader.q28 ?? 0) + 4);
3777
- if (loader.q32 != null) config.priority = loader.q32;
3778
- if (loader.q36 != null) config.stack = DEFAULT_LOADER_STACK + loader.q36;
3779
- if (loader.q44 != null) config.cycle = loader.q44 / 20 + 1;
3780
- break;
3781
- case 98:
3782
- config.place = wrapLoaderPosition((loader.q40 ?? 0) + 4);
3783
- break;
3784
- case 99:
3785
- config.pick = wrapLoaderPosition((loader.q24 ?? 0) + 3);
3786
- config.place = wrapLoaderPosition((loader.q40 ?? 0) + 4);
3787
- break;
3788
- case 126:
3789
- config.pick = wrapLoaderPosition(loader.q28 ?? 0);
3790
- config.place = wrapLoaderPosition(loader.q24 ?? 0);
3791
- if (loader.q32 != null) config.priority = loader.q32;
3792
- if (loader.q36 != null) config.stack = DEFAULT_LOADER_STACK + loader.q36;
3793
- if (loader.q40 != null) config.cycle = loader.q40 / 20 + 1;
3794
- break;
3795
- default:
3796
- return null;
3797
- }
3798
- return config;
3799
- }
3800
- function decodeLoaderSnapshotRecord(loader, mask = loader?.lastMask ?? 0) {
3801
- if (!loader) return null;
3802
- const hasRequireOutput = Boolean(mask & 32);
3803
- const hasWaitForStack = Boolean(mask & 64);
3804
- const hasFlag = hasRequireOutput || hasWaitForStack;
3805
- const slots = [];
3806
- for (const [index, bit] of LOADER_CONFIG_NUMERIC_BITS.entries()) {
3807
- if (!(mask & bit)) continue;
3808
- const value = loader[LOADER_CONFIG_NUMERIC_FIELDS[index]];
3809
- if (value == null) return null;
3810
- slots.push(value);
3811
- }
3812
- if (hasRequireOutput) {
3813
- if (loader.q40 == null) return null;
3814
- slots.push(loader.q40);
3815
- } else if (hasWaitForStack) {
3816
- if (loader.q44 == null) return null;
3817
- slots.push(loader.q44);
3818
- }
3819
- if (hasFlag && slots.length) slots.shift();
3820
- const config = {
3821
- pick: 3,
3822
- place: 4,
3823
- priority: DEFAULT_LOADER_PRIORITY,
3824
- requireOutput: hasRequireOutput,
3825
- waitForStack: hasWaitForStack,
3826
- stack: DEFAULT_LOADER_STACK,
3827
- cycle: DEFAULT_LOADER_CYCLE
3828
- };
3829
- if (mask & 1) {
3830
- if (!slots.length) return null;
3831
- config.pick = wrapLoaderPosition(slots.shift() + 3);
3832
- }
3833
- if (mask & 2) {
3834
- if (!slots.length) return null;
3835
- config.place = wrapLoaderPosition(slots.shift() + 4);
3836
- }
3837
- if (mask & 4) {
3838
- if (!slots.length) return null;
3839
- config.priority = slots.shift();
3840
- }
3841
- if (mask & 8) {
3842
- if (!slots.length) return null;
3843
- config.stack = DEFAULT_LOADER_STACK + slots.shift();
3844
- }
3845
- if (mask & 16) {
3846
- if (!slots.length) return null;
3847
- config.cycle = slots.shift() / 20 + 1;
3848
- }
3849
- return config;
3850
- }
3851
- function decodeLoaderSemanticDeltaRecord(loader, mask = loader?.lastMask ?? 0) {
3852
- if (!loader) return null;
3853
- const hasRequireOutput = Boolean(mask & 32);
3854
- const hasWaitForStack = Boolean(mask & 64);
3855
- if (!hasRequireOutput && !hasWaitForStack) return null;
3856
- const slots = [];
3857
- for (const [index, bit] of LOADER_CONFIG_NUMERIC_BITS.entries()) {
3858
- if (!(mask & bit)) continue;
3859
- slots.push(loader[LOADER_CONFIG_NUMERIC_FIELDS[index]] ?? 0);
3860
- }
3861
- if (hasRequireOutput) slots.push(loader.q40 ?? 0);
3862
- else if (hasWaitForStack) slots.push(loader.q44 ?? 0);
3863
- if (slots.length) slots.shift();
3864
- const config = {};
3865
- if (mask & 1) config.pick = slots.shift() ?? 0;
3866
- if (mask & 2) config.place = slots.shift() ?? 0;
3867
- if (mask & 4) config.priority = slots.shift() ?? 0;
3868
- if (mask & 8) config.stack = slots.shift() ?? 0;
3869
- if (mask & 16) config.cycleTicks = slots.shift() ?? 0;
3870
- config.requireOutput = hasRequireOutput;
3871
- config.waitForStack = hasWaitForStack;
3872
- return config;
3873
- }
3874
- function loaderDeltaRecord(loader, mask, previous) {
3875
- const record = { lastMask: mask };
3876
- for (const [field, bit] of [
3877
- ["q20", 1],
3878
- ["q24", 2],
3879
- ["q28", 4],
3880
- ["q32", 8],
3881
- ["q36", 16],
3882
- ["q40", 32],
3883
- ["q44", 64]
3884
- ]) {
3885
- if (!(mask & bit)) continue;
3886
- if (field === "q44" && (mask & 32 || loader?.q44 == null)) continue;
3887
- record[field] = (loader?.[field] ?? 0) - (previous?.[field] ?? 0);
3888
- }
3889
- return record;
3890
- }
3891
- function isSemanticLoaderDelta(changed, full) {
3892
- return !full && changed.previous != null && (changed.mask & 96) === 96;
3893
- }
3894
- function filterSlotValue(value) {
3895
- return value == null || value === 0 ? null : value;
3896
- }
3897
- var LoaderConfigTracker = class {
3898
- constructor() {
3899
- this.pickBases = /* @__PURE__ */ new Map();
3900
- this.pickDeltaBases = /* @__PURE__ */ new Map();
3901
- this.placeBases = /* @__PURE__ */ new Map();
3902
- this.placeDeltaBases = /* @__PURE__ */ new Map();
3903
- this.currentPicks = /* @__PURE__ */ new Map();
3904
- this.currentPlaces = /* @__PURE__ */ new Map();
3905
- this.sparsePositionBaselines = /* @__PURE__ */ new Set();
3906
- this.priorities = /* @__PURE__ */ new Map();
3907
- this.priorityOffsets = /* @__PURE__ */ new Map();
3908
- this.filterModes = /* @__PURE__ */ new Map();
3909
- this.filterSources = /* @__PURE__ */ new Map();
3910
- this.requireOutputs = /* @__PURE__ */ new Map();
3911
- this.waitForStacks = /* @__PURE__ */ new Map();
3912
- this.stackBases = /* @__PURE__ */ new Map();
3913
- this.stackSources = /* @__PURE__ */ new Map();
3914
- this.cycles = /* @__PURE__ */ new Map();
3915
- this.cycleModes = /* @__PURE__ */ new Map();
3916
- this.cycleDeltaBases = /* @__PURE__ */ new Map();
3917
- this.directCycleValues = /* @__PURE__ */ new Map();
3918
- this.directCycleFields = /* @__PURE__ */ new Map();
3919
- }
3920
- updateFromModel(update) {
3921
- const world = update?.world;
3922
- const model = update?.result?.model;
3923
- if (!world || !model) return;
3924
- const seenEntities = /* @__PURE__ */ new Set();
3925
- for (const section of model.sections || []) {
3926
- if (section.table !== 78) continue;
3927
- for (const changed of section.records || []) {
3928
- if (changed.indexedLoaderConfig) {
3929
- this.updateIndexedSnapshotRecord(world.id, changed.entity, changed.record, changed.mask, changed.cumulativeRecord, changed.configEntity);
3930
- continue;
3931
- }
3932
- const loader = changed.record ?? world.model.record(78, changed.entity);
3933
- this.updateRecord(world.id, changed.entity, loader, changed.mask, changed.previous, {
3934
- repeatedInUpdate: seenEntities.has(changed.entity),
3935
- repeatedInSection: changed.repeatedInSection,
3936
- semanticSnapshot: Boolean(model.full),
3937
- semanticDelta: isSemanticLoaderDelta(changed, model.full),
3938
- deltaRecord: loaderDeltaRecord(loader, changed.mask, changed.previous)
3939
- });
3940
- seenEntities.add(changed.entity);
3941
- }
3942
- }
3943
- }
3944
- updateRecord(worldId, entityId, loader, mask = 0, previous = null, options = {}) {
3945
- if (!loader) return;
3946
- const key = this.#key(worldId, entityId);
3947
- const hasTrackedConfig = this.#hasTrackedConfig(key);
3948
- if (hasTrackedConfig && (options.repeatedInSection || options.repeatedInUpdate) && this.sparsePositionBaselines.has(key) && this.hasPositionConfig(worldId, entityId)) {
3949
- return;
3950
- }
3951
- if (!hasTrackedConfig && this.#isDirectPositionCycleBaseline(loader, mask)) {
3952
- this.pickBases.set(key, 3);
3953
- this.pickDeltaBases.set(key, 0);
3954
- this.placeBases.set(key, 4);
3955
- this.placeDeltaBases.set(key, 0);
3956
- this.#setCurrentPosition(key, loader);
3957
- if (mask & 4 && loader.q28 >= -1 && loader.q28 <= 1) this.priorities.set(key, loader.q28);
3958
- this.requireOutputs.set(key, Boolean(mask & 32));
3959
- this.waitForStacks.set(key, Boolean(mask & 64));
3960
- this.stackBases.set(key, 16);
3961
- this.stackSources.delete(key);
3962
- this.cycleModes.set(key, "direct");
3963
- this.cycleDeltaBases.set(key, 0);
3964
- this.directCycleValues.set(key, loader.q44 ?? loader.q36);
3965
- this.directCycleFields.set(key, loader.q44 == null ? "q36" : "q44");
3966
- this.cycles.set(key, (loader.q44 ?? loader.q36) / 20 + 1);
3967
- return;
3968
- }
3969
- if (!hasTrackedConfig && options.allowSparseBaseline && this.#isSparsePositionBaseline(loader, mask)) {
3970
- this.#applySparsePositionBaseline(key, loader, mask);
3971
- return;
3972
- }
3973
- const baseline = hasTrackedConfig ? null : this.#loaderBaseline(loader, mask, options.semanticSnapshot);
3974
- if (hasTrackedConfig && options.semanticDelta && this.#applySemanticDelta(worldId, entityId, loader, mask, previous, options.deltaRecord)) {
3975
- return;
3976
- }
3977
- const hasInitialConfigFields = baseline || mask & 31 && (mask & 32 || mask & 64 && mask & 4);
3978
- if (!hasTrackedConfig && hasInitialConfigFields && mask !== 32 && mask !== 64) {
3979
- if (baseline) this.#applyLoaderBaseline(key, loader, baseline);
3980
- else {
3981
- const usedQ28AsPositionBase = this.#initializePositionBaseline(key, loader, mask);
3982
- if (mask & 4 && loader.q28 != null) {
3983
- if (usedQ28AsPositionBase) this.#initializePriorityOffset(key, loader);
3984
- else this.priorities.set(key, loader.q28);
3985
- }
3986
- }
3987
- this.requireOutputs.set(key, Boolean(mask & 32));
3988
- this.waitForStacks.set(key, Boolean(mask & 64));
3989
- if (!this.stackBases.has(key)) this.#initializeStackBaseline(key, loader);
3990
- if (!this.cycles.has(key)) this.#initializeCycleBaseline(key, loader);
3991
- return;
3992
- }
3993
- const q44CycleChanged = mask & 64 && this.#usesQ44CycleBase(key) && loader.q44 != null && (this.#fieldChanged(previous, loader, "q44") || this.directCycleValues.has(key) && loader.q44 !== this.directCycleValues.get(key));
3994
- const q40DirectCycleChanged = mask & 32 && this.cycleModes.get(key) === "q40-direct" && loader.q40 != null && (this.#fieldChanged(previous, loader, "q40") || this.directCycleValues.has(key) && loader.q40 !== this.directCycleValues.get(key));
3995
- if (q44CycleChanged) {
3996
- this.directCycleValues.set(key, loader.q44);
3997
- this.#updateCycle(key, loader);
3998
- } else if (q40DirectCycleChanged) {
3999
- this.directCycleValues.set(key, loader.q40);
4000
- this.#updateCycle(key, loader);
4001
- } else if (mask & 32 && mask === 32) {
4002
- this.requireOutputs.set(key, !this.requireOutputs.get(key));
4003
- } else if (mask & 32 && !this.requireOutputs.has(key)) {
4004
- this.requireOutputs.set(key, true);
4005
- }
4006
- if (!q44CycleChanged) {
4007
- if (mask & 64 && mask === 64) this.waitForStacks.set(key, !this.waitForStacks.get(key));
4008
- else if (mask & 64 && !this.waitForStacks.has(key)) this.waitForStacks.set(key, true);
4009
- }
4010
- if (mask & 4 && loader.q28 != null) this.priorities.set(key, this.#priority(key, loader.q28));
4011
- if (mask & 8 && this.filterSources.get(key) === "q32" && loader.q32 != null) this.filterModes.set(key, loader.q32);
4012
- const q32FilterOnlyUpdate = this.filterSources.get(key) === "q32" && mask === 8;
4013
- if (mask & 8 && !options.semanticDelta && !q32FilterOnlyUpdate && loader.q32 != null && this.stackBases.has(key)) {
4014
- const previousConfig = this.getConfig(worldId, entityId, previous);
4015
- this.stackBases.set(key, previousConfig.stack - (previous?.q32 ?? 0));
4016
- this.stackSources.set(key, "q32");
4017
- }
4018
- if (mask & 16) this.#updateCycle(key, loader);
4019
- if (mask & 3 && this.hasPositionConfig(worldId, entityId)) this.#setCurrentPosition(key, loader);
4020
- }
4021
- updateIndexedSnapshotRecord(worldId, entityId, loader, mask = 0, cumulative = null, configEntity = null) {
4022
- if (!loader) return false;
4023
- const reference = configEntity == null || Number(configEntity) === Number(entityId) ? null : this.#trackedConfig(worldId, configEntity);
4024
- const config = decodeIndexedLoaderSnapshotRecord(loader, mask, cumulative, reference);
4025
- if (!config) return false;
4026
- const key = this.#key(worldId, entityId);
4027
- this.pickBases.set(key, config.pick);
4028
- this.pickDeltaBases.set(key, loader.q20 ?? 0);
4029
- this.placeBases.set(key, config.place);
4030
- this.placeDeltaBases.set(key, loader.q24 ?? 0);
4031
- this.currentPicks.set(key, config.pick);
4032
- this.currentPlaces.set(key, config.place);
4033
- this.sparsePositionBaselines.delete(key);
4034
- this.priorities.set(key, config.priority);
4035
- if (mask === 95 && loader.q28 != null) this.priorityOffsets.set(key, config.priority - loader.q28);
4036
- else this.priorityOffsets.delete(key);
4037
- this.filterModes.delete(key);
4038
- this.filterSources.delete(key);
4039
- this.requireOutputs.set(key, config.requireOutput);
4040
- this.waitForStacks.set(key, config.waitForStack);
4041
- this.stackBases.set(key, config.stack - (loader.q32 ?? 0));
4042
- this.stackSources.delete(key);
4043
- this.cycles.set(key, config.cycle);
4044
- if (mask === 95) {
4045
- this.cycleModes.set(key, "q44-q36-delta");
4046
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4047
- this.directCycleValues.set(key, loader.q44);
4048
- this.directCycleFields.set(key, "q44");
4049
- } else if (mask === 126) {
4050
- this.cycleModes.set(key, "offset");
4051
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4052
- this.directCycleValues.delete(key);
4053
- this.directCycleFields.delete(key);
4054
- } else {
4055
- this.cycleModes.set(key, "direct");
4056
- this.cycleDeltaBases.set(key, 0);
4057
- }
4058
- if (loader.q36 != null && mask === 18) {
4059
- this.directCycleValues.set(key, loader.q36);
4060
- this.directCycleFields.set(key, "q36");
4061
- } else if (mask !== 95 && mask !== 126) {
4062
- this.directCycleValues.delete(key);
4063
- this.directCycleFields.delete(key);
4064
- }
4065
- return true;
4066
- }
4067
- delete(worldId, entityId) {
4068
- const key = this.#key(worldId, entityId);
4069
- this.pickBases.delete(key);
4070
- this.pickDeltaBases.delete(key);
4071
- this.placeBases.delete(key);
4072
- this.placeDeltaBases.delete(key);
4073
- this.currentPicks.delete(key);
4074
- this.currentPlaces.delete(key);
4075
- this.sparsePositionBaselines.delete(key);
4076
- this.priorities.delete(key);
4077
- this.priorityOffsets.delete(key);
4078
- this.filterModes.delete(key);
4079
- this.filterSources.delete(key);
4080
- this.requireOutputs.delete(key);
4081
- this.waitForStacks.delete(key);
4082
- this.stackBases.delete(key);
4083
- this.stackSources.delete(key);
4084
- this.cycles.delete(key);
4085
- this.cycleModes.delete(key);
4086
- this.cycleDeltaBases.delete(key);
4087
- this.directCycleValues.delete(key);
4088
- this.directCycleFields.delete(key);
4089
- }
4090
- getConfig(worldId, entityId, loader, loaderFilter = null, filterSlots = null) {
4091
- const key = this.#key(worldId, entityId);
4092
- const pick = this.currentPicks.has(key) ? this.currentPicks.get(key) : this.#pick(key, loader);
4093
- const place = this.currentPlaces.has(key) ? this.currentPlaces.get(key) : this.#place(key, loader);
4094
- const stackBase = this.stackBases.get(key) ?? 16;
4095
- const stackSource = this.stackSources.get(key) ?? "q32";
4096
- const stackField = stackSource === "fixed" ? null : loader?.[stackSource];
4097
- const stack = stackField == null ? this.stackBases.has(key) || this.hasPositionConfig(worldId, entityId) ? stackBase : null : stackBase + stackField;
4098
- const cycle = this.cycles.has(key) ? this.cycles.get(key) : loader?.q40 == null || loader.q36 == null ? null : (loader.q40 + loader.q36) / 20 + 1;
4099
- return {
4100
- pick,
4101
- place,
4102
- priority: this.priorities.has(key) ? this.priorities.get(key) : DEFAULT_LOADER_PRIORITY,
4103
- requireOutput: this.requireOutputs.has(key) ? this.requireOutputs.get(key) : false,
4104
- waitForStack: this.waitForStacks.has(key) ? this.waitForStacks.get(key) : false,
4105
- stack: stack ?? DEFAULT_LOADER_STACK,
4106
- cycle: cycle ?? DEFAULT_LOADER_CYCLE,
4107
- filterMode: loaderFilter ? loaderFilter.q20 ?? DEFAULT_LOADER_FILTER_MODE : this.#filterMode(key, loader) ?? DEFAULT_LOADER_FILTER_MODE,
4108
- filterSlots: filterSlots ? [filterSlotValue(filterSlots.q20), filterSlotValue(filterSlots.q24), filterSlotValue(filterSlots.q28)] : null
4109
- };
4110
- }
4111
- hasPositionConfig(worldId, entityId) {
4112
- const key = this.#key(worldId, entityId);
4113
- return this.pickBases.has(key) && this.placeBases.has(key);
4114
- }
4115
- #hasTrackedConfig(key) {
4116
- return this.pickBases.has(key) || this.placeBases.has(key) || this.priorities.has(key) || this.priorityOffsets.has(key) || this.filterModes.has(key) || this.requireOutputs.has(key) || this.waitForStacks.has(key) || this.stackBases.has(key) || this.cycles.has(key);
4117
- }
4118
- #fieldChanged(previous, loader, field) {
4119
- return previous != null && loader != null && previous[field] !== loader[field];
4120
- }
4121
- #usesQ44CycleBase(key) {
4122
- const mode = this.cycleModes.get(key);
4123
- return mode === "q44-q36-delta";
4124
- }
4125
- #initializePositionBaseline(key, loader, mask) {
4126
- const hasQ28PositionBase = loader.q28 != null && mask & 4;
4127
- if (loader.q24 != null) {
4128
- this.pickBases.set(key, wrapLoaderPosition(loader.q24 + 3));
4129
- this.pickDeltaBases.set(key, loader.q20 ?? 0);
4130
- this.placeDeltaBases.set(key, loader.q24);
4131
- const initialPlace = hasQ28PositionBase ? loader.q28 + 4 : loader.q36 == null ? null : loader.q36 + 4;
4132
- if (initialPlace != null) this.placeBases.set(key, wrapLoaderPosition(initialPlace));
4133
- return hasQ28PositionBase;
4134
- }
4135
- if (hasQ28PositionBase) {
4136
- this.pickBases.set(key, wrapLoaderPosition(loader.q28 + 3));
4137
- this.pickDeltaBases.set(key, 0);
4138
- this.placeBases.set(key, 4);
4139
- this.placeDeltaBases.set(key, 0);
4140
- return true;
4141
- }
4142
- if (loader.q20 != null && loader.q36 != null) {
4143
- this.pickBases.set(key, wrapLoaderPosition(loader.q36 + 3));
4144
- this.pickDeltaBases.set(key, 0);
4145
- this.placeBases.set(key, 4);
4146
- this.placeDeltaBases.set(key, 0);
4147
- }
4148
- return false;
4149
- }
4150
- #initializeCycleBaseline(key, loader) {
4151
- if (loader?.q44 != null && (loader.q36 == null || loader.q40 == null)) {
4152
- this.cycleModes.set(key, "q44-q36-delta");
4153
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4154
- this.directCycleValues.set(key, loader.q44);
4155
- this.directCycleFields.set(key, "q44");
4156
- this.cycles.set(key, loader.q44 / 20 + 1);
4157
- return;
4158
- }
4159
- if (loader?.q40 == null || loader.q36 == null) return;
4160
- this.cycleModes.set(key, "offset");
4161
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4162
- this.directCycleValues.delete(key);
4163
- this.directCycleFields.delete(key);
4164
- this.cycles.set(key, loader.q40 / 20 + 1);
4165
- }
4166
- #initializeStackBaseline(key, loader) {
4167
- if (this.stackBases.has(key)) return;
4168
- if (loader?.q32 == null) return;
4169
- const baseline = this.#loaderBaseline(loader, loader?.lastMask);
4170
- if (baseline) {
4171
- this.stackBases.set(key, baseline.stack - loader.q32);
4172
- return;
4173
- }
4174
- if (loader.q20 == null && loader.q24 == null && loader.q28 == null && loader.q36 != null) {
4175
- this.stackBases.set(key, 17 + loader.q36);
4176
- return;
4177
- }
4178
- this.stackBases.set(key, 16);
4179
- }
4180
- #initializePriorityOffset(key, loader) {
4181
- if (loader?.q28 == null || loader?.q36 == null) return;
4182
- const offset = loader.q36 + 2;
4183
- const priority = loader.q28 + offset;
4184
- if (priority < -1 || priority > 1) return;
4185
- this.priorityOffsets.set(key, offset);
4186
- this.priorities.set(key, priority);
4187
- }
4188
- #priority(key, q28) {
4189
- return q28 + (this.priorityOffsets.get(key) ?? 0);
4190
- }
4191
- #updateCycle(key, loader) {
4192
- const mode = this.cycleModes.get(key);
4193
- if (mode === "direct") {
4194
- if (loader?.q36 != null) {
4195
- this.directCycleValues.set(key, loader.q36);
4196
- this.directCycleFields.set(key, "q36");
4197
- this.cycles.set(key, loader.q36 / 20 + 1);
4198
- } else if (loader?.q44 != null) {
4199
- this.directCycleValues.set(key, loader.q44);
4200
- this.directCycleFields.set(key, "q44");
4201
- this.cycles.set(key, loader.q44 / 20 + 1);
4202
- }
4203
- return;
4204
- }
4205
- if (mode === "q44-q36-delta") {
4206
- const base = loader?.q44 ?? this.directCycleValues.get(key);
4207
- if (base != null) {
4208
- this.directCycleValues.set(key, base);
4209
- this.directCycleFields.set(key, "q44");
4210
- const q36 = loader?.q36 ?? 0;
4211
- this.cycles.set(key, base < 0 && loader?.q36 != null ? q36 / 20 + 1 : (base + q36 - (this.cycleDeltaBases.get(key) ?? 0)) / 20 + 1);
4212
- }
4213
- return;
4214
- }
4215
- if (mode === "q40-direct") {
4216
- if (loader?.q40 != null) {
4217
- this.directCycleValues.set(key, loader.q40);
4218
- this.directCycleFields.set(key, "q40");
4219
- this.cycles.set(key, loader.q40 / 20 + 1);
4220
- }
4221
- return;
4222
- }
4223
- if (loader?.q40 == null) {
4224
- if (loader?.q44 != null || loader?.q36 != null) this.cycles.set(key, (loader.q44 ?? loader.q36) / 20 + 1);
4225
- return;
4226
- }
4227
- const deltaBase = this.cycleDeltaBases.get(key) ?? 0;
4228
- this.cycles.set(key, (loader.q40 + (loader.q36 ?? 0) - deltaBase) / 20 + 1);
4229
- }
4230
- #isDirectPositionCycleBaseline(loader, mask) {
4231
- return (mask & 19) === 19 && loader.q20 != null && loader.q24 != null && loader.q36 != null && loader.q40 == null && loader.q44 == null;
4232
- }
4233
- #trackedConfig(worldId, entityId) {
4234
- const key = this.#key(worldId, entityId);
4235
- if (!this.#hasTrackedConfig(key)) return null;
4236
- return {
4237
- pick: this.currentPicks.has(key) ? this.currentPicks.get(key) : this.pickBases.get(key),
4238
- place: this.currentPlaces.has(key) ? this.currentPlaces.get(key) : this.placeBases.get(key),
4239
- priority: this.priorities.get(key) ?? DEFAULT_LOADER_PRIORITY,
4240
- requireOutput: this.requireOutputs.get(key) ?? false,
4241
- waitForStack: this.waitForStacks.get(key) ?? false,
4242
- stack: this.stackBases.get(key) ?? DEFAULT_LOADER_STACK,
4243
- cycle: this.cycles.get(key) ?? DEFAULT_LOADER_CYCLE
4244
- };
4245
- }
4246
- #isSparsePositionBaseline(loader, mask) {
4247
- return (mask & 3) === 3 && loader.q20 != null && loader.q24 != null && loader.q28 == null && loader.q32 == null && loader.q36 == null && loader.q44 == null;
4248
- }
4249
- #applySparsePositionBaseline(key, loader, mask) {
4250
- const pickField = loader.q40 == null ? loader.q20 : loader.q24;
4251
- const placeField = loader.q40 == null ? loader.q24 : loader.q40;
4252
- const pick = wrapLoaderPosition(pickField + 3);
4253
- const place = wrapLoaderPosition(placeField + 4);
4254
- this.pickBases.set(key, pick);
4255
- this.pickDeltaBases.set(key, loader.q20 ?? 0);
4256
- this.placeBases.set(key, place);
4257
- this.placeDeltaBases.set(key, loader.q24 ?? 0);
4258
- this.currentPicks.set(key, pick);
4259
- this.currentPlaces.set(key, place);
4260
- this.sparsePositionBaselines.add(key);
4261
- this.priorities.set(key, DEFAULT_LOADER_PRIORITY);
4262
- this.requireOutputs.set(key, Boolean(mask & 32));
4263
- this.waitForStacks.set(key, Boolean(mask & 64));
4264
- this.stackBases.set(key, DEFAULT_LOADER_STACK);
4265
- this.cycles.set(key, DEFAULT_LOADER_CYCLE);
4266
- }
4267
- #filterMode(key, loader) {
4268
- if (this.filterSources.get(key) === "q32" && loader?.q32 != null) {
4269
- return this.#validFilterMode(loader.q32);
4270
- }
4271
- if (this.filterModes.has(key)) return this.#validFilterMode(this.filterModes.get(key));
4272
- const baseline = this.#loaderBaseline(loader, loader?.lastMask);
4273
- if (!baseline) return null;
4274
- return this.#validFilterMode(baseline.filterMode);
4275
- }
4276
- #validFilterMode(mode) {
4277
- return mode >= 0 && mode <= 3 ? mode : null;
4278
- }
4279
- #semanticFilterMode(loader, mask) {
4280
- const q28IsDirectPriority = loader.q28 != null && loader.q28 >= -1 && loader.q28 <= 1;
4281
- const q24FilterMode = loader.q24 != null && loader.q32 != null && loader.q40 != null ? loader.q24 + 2 : null;
4282
- const q28FilterMode = loader.q28 != null && loader.q32 != null ? loader.q28 + 1 : null;
4283
- const q32FilterMode = !q28IsDirectPriority && loader.q28 != null && loader.q32 != null && loader.q40 != null ? loader.q32 + 1 : null;
4284
- const q32DirectMode = loader.q28 == null && loader.q32 != null && mask & 32 ? loader.q32 : null;
4285
- if (q28IsDirectPriority && this.#validFilterMode(q24FilterMode) != null) return q24FilterMode;
4286
- if (this.#validFilterMode(q28FilterMode) != null) return q28FilterMode;
4287
- if (this.#validFilterMode(q32FilterMode) != null) return q32FilterMode;
4288
- if (this.#validFilterMode(q32DirectMode) != null) return q32DirectMode;
4289
- if (this.#validFilterMode(q24FilterMode) != null) return q24FilterMode;
4290
- return null;
4291
- }
4292
- #semanticFilterField(loader, mask) {
4293
- const q32DirectMode = loader.q28 == null && loader.q32 != null && mask & 32 ? loader.q32 : null;
4294
- return this.#validFilterMode(q32DirectMode) != null ? "q32" : null;
4295
- }
4296
- #loaderBaseline(loader, mask = loader?.lastMask, semanticSnapshot = false) {
4297
- const snapshot = semanticSnapshot ? decodeLoaderSnapshotRecord(loader, mask) : null;
4298
- if (snapshot) {
4299
- return {
4300
- pickBase: snapshot.pick,
4301
- pickDeltaBase: loader.q20 ?? 0,
4302
- placeBase: snapshot.place,
4303
- placeDeltaBase: loader.q24 ?? 0,
4304
- priority: snapshot.priority,
4305
- stack: snapshot.stack,
4306
- stackField: "fixed",
4307
- cycle: snapshot.cycle,
4308
- cycleMode: loader.q44 != null && loader.q40 == null ? "q44-q36-delta" : "offset",
4309
- filterMode: this.#semanticFilterMode(loader, mask),
4310
- filterField: this.#semanticFilterField(loader, mask),
4311
- q32: loader.q32 ?? 0
4312
- };
4313
- }
4314
- if (!loader || loader.q36 == null) return null;
4315
- if (loader.q40 == null && loader.q44 == null) {
4316
- if (loader.q20 != null || loader.q24 == null || loader.q28 == null || loader.q32 == null) return null;
4317
- return {
4318
- pickBase: wrapLoaderPosition(loader.q28 + 2),
4319
- pickDeltaBase: 0,
4320
- placeBase: wrapLoaderPosition(loader.q24 + 4),
4321
- placeDeltaBase: loader.q24,
4322
- priority: loader.q28,
4323
- stack: 16 + loader.q32,
4324
- cycle: loader.q36 / 20 + 1,
4325
- cycleMode: "direct",
4326
- filterMode: loader.q28 + 1,
4327
- q32: loader.q32
4328
- };
4329
- }
4330
- if (loader.q24 == null && loader.q28 == null && loader.q20 != null && loader.q32 != null && loader.q40 != null && loader.q44 != null) {
4331
- return {
4332
- pickBase: wrapLoaderPosition(loader.q20 + 7),
4333
- pickDeltaBase: loader.q20,
4334
- placeBase: 4,
4335
- placeDeltaBase: 0,
4336
- priority: 0,
4337
- stack: 16 + loader.q36,
4338
- stackField: "q36",
4339
- cycle: loader.q40 / 20 + 1,
4340
- cycleMode: "q40-direct",
4341
- filterMode: loader.q32,
4342
- filterField: "q32",
4343
- q32: loader.q32
4344
- };
4345
- }
4346
- const q20 = loader.q20 ?? 0;
4347
- const q32 = loader.q32 ?? 0;
4348
- let pickBase = null;
4349
- let placeBase = null;
4350
- if (loader.q44 != null && loader.q40 == null && loader.q28 == null && loader.q24 != null && loader.q32 != null) {
4351
- pickBase = wrapLoaderPosition(loader.q24 + 3);
4352
- placeBase = wrapLoaderPosition(loader.q32 + 4);
4353
- } else if (loader.q24 != null) {
4354
- pickBase = wrapLoaderPosition(loader.q24 + 3);
4355
- placeBase = loader.q28 != null ? wrapLoaderPosition(loader.q28 + 4) : wrapLoaderPosition(loader.q36 + 4);
4356
- } else if (loader.q28 != null) {
4357
- pickBase = wrapLoaderPosition(loader.q28 + 3);
4358
- placeBase = 4;
4359
- } else if (loader.q20 != null) {
4360
- pickBase = wrapLoaderPosition(loader.q36 + 3);
4361
- placeBase = 4;
4362
- }
4363
- if (pickBase == null || placeBase == null) return null;
4364
- const q28IsDirectPriority = loader.q28 != null && loader.q28 >= -1 && loader.q28 <= 1;
4365
- const priority = loader.q28 != null && loader.q32 != null && loader.q44 != null && loader.q40 != null ? loader.q32 : loader.q28 != null && loader.q32 != null && loader.q44 == null && loader.q40 == null ? loader.q28 : loader.q28 != null && loader.q32 != null && loader.q44 == null && loader.q40 != null ? loader.q32 : loader.q32 != null && loader.q44 != null && loader.q40 == null ? loader.q32 : null;
4366
- const q28FilterMode = loader.q28 != null && loader.q32 != null && loader.q44 != null ? loader.q28 + 1 : null;
4367
- const q24FilterMode = loader.q24 != null && loader.q32 != null && loader.q44 != null && loader.q40 != null ? loader.q24 + 2 : null;
4368
- const q32FilterMode = !q28IsDirectPriority && loader.q28 != null && loader.q32 != null && loader.q44 != null && loader.q40 != null ? loader.q32 + 1 : null;
4369
- const filterMode = q28IsDirectPriority && q24FilterMode != null && q24FilterMode >= 0 && q24FilterMode <= 3 ? q24FilterMode : q28FilterMode != null && q28FilterMode >= 0 && q28FilterMode <= 3 ? q28FilterMode : q32FilterMode != null && q32FilterMode >= 0 && q32FilterMode <= 3 ? q32FilterMode : q24FilterMode;
4370
- return {
4371
- pickBase,
4372
- pickDeltaBase: q20,
4373
- placeBase,
4374
- placeDeltaBase: loader.q24 ?? 0,
4375
- priority,
4376
- stack: loader.q32 == null ? null : 16 + loader.q36,
4377
- cycle: (loader.q40 ?? loader.q44) / 20 + 1,
4378
- cycleMode: loader.q44 != null && loader.q40 == null ? "q44-q36-delta" : "offset",
4379
- filterMode,
4380
- q32
4381
- };
4382
- }
4383
- #applyLoaderBaseline(key, loader, baseline) {
4384
- this.pickBases.set(key, baseline.pickBase);
4385
- this.pickDeltaBases.set(key, baseline.pickDeltaBase);
4386
- this.placeBases.set(key, baseline.placeBase);
4387
- this.placeDeltaBases.set(key, baseline.placeDeltaBase);
4388
- this.sparsePositionBaselines.delete(key);
4389
- this.#setCurrentPosition(key, loader);
4390
- if (baseline.filterMode != null) {
4391
- this.filterModes.set(key, baseline.filterMode);
4392
- if (baseline.filterField) this.filterSources.set(key, baseline.filterField);
4393
- else this.filterSources.delete(key);
4394
- }
4395
- if (baseline.stack != null) {
4396
- const stackField = baseline.stackField === "fixed" ? 0 : baseline.stackField ? loader[baseline.stackField] ?? 0 : baseline.q32;
4397
- this.stackBases.set(key, baseline.stack - stackField);
4398
- if (baseline.stackField) this.stackSources.set(key, baseline.stackField);
4399
- else this.stackSources.delete(key);
4400
- }
4401
- this.cycleModes.set(key, baseline.cycleMode);
4402
- if (baseline.cycleMode === "direct") {
4403
- this.cycleDeltaBases.set(key, 0);
4404
- this.directCycleValues.set(key, loader.q44 ?? loader.q36);
4405
- this.directCycleFields.set(key, loader.q44 == null ? "q36" : "q44");
4406
- } else if (baseline.cycleMode === "q44-q36-delta") {
4407
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4408
- this.directCycleValues.set(key, loader.q44);
4409
- this.directCycleFields.set(key, "q44");
4410
- } else if (baseline.cycleMode === "q40-direct") {
4411
- this.cycleDeltaBases.set(key, 0);
4412
- this.directCycleValues.set(key, loader.q40);
4413
- this.directCycleFields.set(key, "q40");
4414
- } else {
4415
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4416
- this.directCycleValues.delete(key);
4417
- this.directCycleFields.delete(key);
4418
- }
4419
- this.cycles.set(key, baseline.cycle);
4420
- if (baseline.priority != null && baseline.priority >= -1 && baseline.priority <= 1) {
4421
- if (loader.q28 != null) this.priorityOffsets.set(key, baseline.priority - loader.q28);
4422
- this.priorities.set(key, baseline.priority);
4423
- } else if (loader.q28 != null && loader.q36 != null) this.#initializePriorityOffset(key, loader);
4424
- }
4425
- #applySemanticDelta(worldId, entityId, loader, mask, previous, deltaRecord) {
4426
- const key = this.#key(worldId, entityId);
4427
- const delta = decodeLoaderSemanticDeltaRecord(deltaRecord ?? loaderDeltaRecord(loader, mask, previous), mask);
4428
- if (!delta) return false;
4429
- const previousConfig = this.getConfig(worldId, entityId, previous);
4430
- const pick = delta.pick == null ? previousConfig.pick : wrapLoaderPosition((previousConfig.pick ?? DEFAULT_LOADER_PICK) + delta.pick);
4431
- const place = delta.place == null ? previousConfig.place : wrapLoaderPosition((previousConfig.place ?? DEFAULT_LOADER_PLACE) + delta.place);
4432
- if (pick != null) {
4433
- this.pickBases.set(key, pick);
4434
- this.pickDeltaBases.set(key, loader?.q20 ?? 0);
4435
- this.currentPicks.set(key, pick);
4436
- }
4437
- if (place != null) {
4438
- this.placeBases.set(key, place);
4439
- this.placeDeltaBases.set(key, loader?.q24 ?? 0);
4440
- this.currentPlaces.set(key, place);
4441
- }
4442
- if (delta.priority != null) {
4443
- const priority = previousConfig.priority + delta.priority;
4444
- this.priorities.set(key, priority);
4445
- if (loader?.q28 != null) this.priorityOffsets.set(key, priority - loader.q28);
4446
- else this.priorityOffsets.delete(key);
4447
- }
4448
- if (delta.stack != null) {
4449
- this.stackBases.set(key, previousConfig.stack + delta.stack);
4450
- this.stackSources.set(key, "fixed");
4451
- }
4452
- if (delta.cycleTicks != null) {
4453
- this.cycles.set(key, previousConfig.cycle + delta.cycleTicks / 20);
4454
- if (loader?.q44 != null && loader.q40 == null) {
4455
- this.cycleModes.set(key, "q44-q36-delta");
4456
- this.cycleDeltaBases.set(key, loader.q36 ?? 0);
4457
- this.directCycleValues.set(key, loader.q44);
4458
- this.directCycleFields.set(key, "q44");
4459
- } else {
4460
- this.cycleModes.set(key, "offset");
4461
- this.cycleDeltaBases.set(key, loader?.q36 ?? 0);
4462
- this.directCycleValues.delete(key);
4463
- this.directCycleFields.delete(key);
4464
- }
4465
- }
4466
- if (delta.requireOutput) this.requireOutputs.set(key, !previousConfig.requireOutput);
4467
- if (delta.waitForStack) this.waitForStacks.set(key, !previousConfig.waitForStack);
4468
- this.sparsePositionBaselines.delete(key);
4469
- return true;
4470
- }
4471
- #setCurrentPosition(key, loader) {
4472
- const pick = this.#pick(key, loader);
4473
- const place = this.#place(key, loader);
4474
- if (pick != null) this.currentPicks.set(key, pick);
4475
- if (place != null) this.currentPlaces.set(key, place);
4476
- }
4477
- #pick(key, loader) {
4478
- if (!loader) return null;
4479
- if (this.pickBases.has(key)) return wrapLoaderPosition(this.pickBases.get(key) + ((loader.q20 ?? 0) - (this.pickDeltaBases.get(key) ?? 0)));
4480
- if (loader.q24 != null) return wrapLoaderPosition(loader.q24 + 3);
4481
- if (loader.q36 != null) return wrapLoaderPosition(loader.q36 + 3);
4482
- if (loader.q20 != null) return wrapLoaderPosition(loader.q20);
4483
- return null;
4484
- }
4485
- #place(key, loader) {
4486
- if (!loader) return null;
4487
- if (this.placeBases.has(key)) {
4488
- const base = this.placeBases.get(key);
4489
- if (loader.q24 != null && this.placeDeltaBases.has(key)) {
4490
- return wrapLoaderPosition(base + (loader.q24 - this.placeDeltaBases.get(key)));
4491
- }
4492
- return wrapLoaderPosition(base);
4493
- }
4494
- if (loader.q36 != null) return loader.q24 == null ? 4 : wrapLoaderPosition(loader.q36 + 4);
4495
- return null;
4496
- }
4497
- #key(worldId, entityId) {
4498
- return `${worldId ?? "-"}:${entityId}`;
4499
- }
4500
- };
3952
+ summarizeCannon: () => summarizeCannon,
3953
+ summarizeCargoEjector: () => summarizeCargoEjector,
3954
+ summarizeCargoHatch: () => summarizeCargoHatch,
3955
+ summarizeCommsStation: () => summarizeCommsStation,
3956
+ summarizeDockingSpring: () => summarizeDockingSpring,
3957
+ summarizeDoor: () => summarizeDoor,
3958
+ summarizeExpandoBox: () => summarizeExpandoBox,
3959
+ summarizeFabricator: () => summarizeFabricator,
3960
+ summarizeHealth: () => summarizeHealth,
3961
+ summarizeHelm: () => summarizeHelm,
3962
+ summarizeHoverOutline: () => summarizeHoverOutline,
3963
+ summarizeHugeThruster: () => summarizeHugeThruster,
3964
+ summarizeItemCrate: () => summarizeItemCrate,
3965
+ summarizeItemHolder: () => summarizeItemHolder,
3966
+ summarizeItemLauncher: () => summarizeItemLauncher,
3967
+ summarizeLoader: () => summarizeLoader,
3968
+ summarizeMapMarker: () => summarizeMapMarker,
3969
+ summarizeNavigationUnit: () => summarizeNavigationUnit,
3970
+ summarizePlayer: () => summarizePlayer,
3971
+ summarizePlayerPreview: () => summarizePlayerPreview,
3972
+ summarizePusher: () => summarizePusher,
3973
+ summarizePusherBeam: () => summarizePusherBeam,
3974
+ summarizeShieldGenerator: () => summarizeShieldGenerator,
3975
+ summarizeShieldProjector: () => summarizeShieldProjector,
3976
+ summarizeShipControl: () => summarizeShipControl,
3977
+ summarizeShipSize: () => summarizeShipSize,
3978
+ summarizeShipWorldMetadata: () => summarizeShipWorldMetadata,
3979
+ summarizeSign: () => summarizeSign,
3980
+ summarizeSpawnPoint: () => summarizeSpawnPoint,
3981
+ summarizeThruster: () => summarizeThruster
3982
+ });
3983
+ init_constants();
3984
+
3985
+ // src/state/model/loader-config.js
3986
+ var LOADER_POSITION_NAMES = /* @__PURE__ */ new Map([
3987
+ [0, "top-left"],
3988
+ [1, "top-middle"],
3989
+ [2, "top-right"],
3990
+ [3, "middle-left"],
3991
+ [4, "middle-right"],
3992
+ [5, "bottom-left"],
3993
+ [6, "bottom-middle"],
3994
+ [7, "bottom-right"]
3995
+ ]);
3996
+ var LOADER_PRIORITY_NAMES = /* @__PURE__ */ new Map([
3997
+ [-1, "low"],
3998
+ [0, "normal"],
3999
+ [1, "high"]
4000
+ ]);
4001
+ var LOADER_FILTER_MODE_NAMES = /* @__PURE__ */ new Map([
4002
+ [0, "allow-all"],
4003
+ [1, "block-filter"],
4004
+ [2, "allow-filter"],
4005
+ [3, "block-all"]
4006
+ ]);
4501
4007
 
4502
4008
  // src/state/overworld.js
4503
4009
  var OVERWORLD_ZONES = Object.freeze([
@@ -5184,7 +4690,11 @@
5184
4690
  [63, { name: "numeric_pair", ...TWO_FIELD_SPEC }],
5185
4691
  [67, { name: "numeric_single", fields: numericFields({ 1: 20 }) }],
5186
4692
  [69, {
5187
- name: "rare_snapshot",
4693
+ name: "loader_config",
4694
+ // The official loader component starts with these values. Wire section
4695
+ // 162 sends a packed absolute flag word before the five numeric deltas.
4696
+ defaults: { q20: 3, q24: 4, q28: 1, q32: 16, q36: 20, q41: 0, q42: 0 },
4697
+ packedBeforeValues: true,
5188
4698
  packedBits: [{ bit: 32, offset: 41 }, { bit: 64, offset: 42 }],
5189
4699
  fields: numericFields({ 1: 20, 2: 24, 4: 28, 8: 32, 16: 36 })
5190
4700
  }],
@@ -5300,7 +4810,8 @@
5300
4810
  [158, 75],
5301
4811
  [160, 76],
5302
4812
  [161, 77],
5303
- [162, 78],
4813
+ [162, 69],
4814
+ // Loader configuration; distinct from navigation state (168).
5304
4815
  [163, 72],
5305
4816
  [164, 73],
5306
4817
  [165, 74],
@@ -5474,27 +4985,6 @@
5474
4985
  }
5475
4986
  return null;
5476
4987
  }
5477
- var TABLE_78_FIELD_BITS = [
5478
- ["q20", 1],
5479
- ["q24", 2],
5480
- ["q28", 4],
5481
- ["q32", 8],
5482
- ["q36", 16],
5483
- ["q40", 32],
5484
- ["q44", 64]
5485
- ];
5486
- function table78DeltaRecord(changed) {
5487
- const record = { lastMask: changed.mask };
5488
- for (const [field, bit] of TABLE_78_FIELD_BITS) {
5489
- if (!(changed.mask & bit)) continue;
5490
- if (field === "q44" && (changed.mask & 32 || changed.record?.q44 == null)) continue;
5491
- record[field] = (changed.record?.[field] ?? 0) - (changed.previous?.[field] ?? 0);
5492
- }
5493
- return record;
5494
- }
5495
- function isSemanticLoaderDelta2(changed, full) {
5496
- return !full && changed.previous != null && (changed.mask & 96) === 96;
5497
- }
5498
4988
  function decodeText(blob) {
5499
4989
  if (!(blob instanceof Uint8Array)) return null;
5500
4990
  try {
@@ -5566,8 +5056,12 @@
5566
5056
  active: record?.q33 == null ? null : Boolean(record.q33)
5567
5057
  };
5568
5058
  }
5569
- function summarizeCannon(entity, record, typeId = null) {
5059
+ function summarizeCannon(entity, record, typeId = null, transformRecord = null) {
5570
5060
  if (!record || !CANNON_TYPE_IDS.has(typeId)) return null;
5061
+ const relativeAngleRadians = numberOrNull(record.q32, 200);
5062
+ const mountingAngleRadians = numberOrNull(transformRecord?.q28, 127.324);
5063
+ const tau = 2 * Math.PI;
5064
+ const absoluteAngleRadians = relativeAngleRadians == null || mountingAngleRadians == null ? null : ((mountingAngleRadians + relativeAngleRadians) % tau + tau) % tau;
5571
5065
  const ammoItemId = record.q24 == null ? null : CANNON_AMMO_COLOR_ITEM_IDS.get(record.q24) ?? null;
5572
5066
  const charge = record.q40 ?? null;
5573
5067
  const recoil = record.q36 ?? null;
@@ -5579,7 +5073,8 @@
5579
5073
  ammoItemId,
5580
5074
  ammoName: entityNameFromType(ammoItemId),
5581
5075
  ammoCount: record.q28 ?? 0,
5582
- aim: record.q32 ?? null,
5076
+ relativeAngleRadians,
5077
+ absoluteAngleRadians,
5583
5078
  recoil,
5584
5079
  recoil2,
5585
5080
  recoils: [recoil, recoil2],
@@ -5955,15 +5450,13 @@
5955
5450
  state: cloneRecord(markerRecord)
5956
5451
  };
5957
5452
  }
5958
- function summarizeLoader(entity, loaderRecord, loaderFilterRecord = null, filterSlotsRecord = null, tracker = null, includeDefaults = false, itemHolderRecord = null) {
5453
+ function summarizeLoader(entity, loaderRecord, loaderFilterRecord = null, filterSlotsRecord = null, includeDefaults = false, itemHolderRecord = null) {
5959
5454
  if (!loaderRecord && !loaderFilterRecord && !includeDefaults) return null;
5960
- const config = tracker?.getConfig(null, entity, loaderRecord, loaderFilterRecord, filterSlotsRecord) ?? {};
5961
- const hasLoaderState = Boolean(loaderRecord);
5962
- const hasPositionConfig = Boolean(tracker?.hasPositionConfig(null, entity));
5963
- const pick = hasLoaderState && hasPositionConfig ? config.pick ?? null : null;
5964
- const place = hasLoaderState && hasPositionConfig ? config.place ?? null : null;
5965
- const priority = config.priority ?? 0;
5966
- const filterMode = config.filterMode ?? null;
5455
+ const loader = { ...MODEL_TABLE_SPECS.get(69).defaults, ...loaderRecord };
5456
+ const pick = loader.q20;
5457
+ const place = loader.q24;
5458
+ const priority = loader.q28 - 1;
5459
+ const filterMode = loaderFilterRecord?.q20 ?? 0;
5967
5460
  const heldItem = itemSummary(itemHolderRecord?.q20, itemHolderRecord?.q24 ?? null);
5968
5461
  return {
5969
5462
  entity,
@@ -5973,13 +5466,13 @@
5973
5466
  placeName: enumValueName(LOADER_POSITION_NAMES, place),
5974
5467
  priority,
5975
5468
  priorityName: enumValueName(LOADER_PRIORITY_NAMES, priority),
5976
- requireOutput: config.requireOutput ?? false,
5977
- waitForStack: config.waitForStack ?? false,
5978
- stack: config.stack ?? 16,
5979
- cycle: Math.max(1, config.cycle ?? 1),
5469
+ requireOutput: Boolean(loader.q41),
5470
+ waitForStack: Boolean(loader.q42),
5471
+ stack: loader.q32,
5472
+ cycle: loader.q36 / 20,
5980
5473
  filterMode,
5981
5474
  filterModeName: enumValueName(LOADER_FILTER_MODE_NAMES, filterMode),
5982
- filterSlots: config.filterSlots ?? null,
5475
+ filterSlots: filterSlotsRecord ? [filterSlotsRecord.q20, filterSlotsRecord.q24, filterSlotsRecord.q28].map((value) => value || null) : null,
5983
5476
  heldItemId: heldItem.itemId,
5984
5477
  heldItemName: heldItem.itemName,
5985
5478
  heldCount: heldItem.count,
@@ -6017,7 +5510,7 @@
6017
5510
  filteredModeName: enumValueName(PUSHER_MODE_NAMES, filteredMode),
6018
5511
  angle: numberOrNull(pusherRecord.q28, 10),
6019
5512
  speed: pusherRecord.q32 == null ? 20 : pusherRecord.q32 / 100 + 20,
6020
- length: pusherRecord.q40 == null ? 1e3 : (pusherRecord.q40 + 50) / 10 + 995,
5513
+ length: 1e3 + (pusherRecord.q40 ?? 0) / 10,
6021
5514
  filterInventory: pusherRecord.q36 === 1,
6022
5515
  filterSlots: filterSlotsRecord ? [filterSlotsRecord.q20 ?? null, filterSlotsRecord.q24 ?? null, filterSlotsRecord.q28 ?? null] : null,
6023
5516
  state: cloneRecord(pusherRecord),
@@ -6370,9 +5863,6 @@
6370
5863
  blobKey: blobKey2,
6371
5864
  cloneRecord: cloneRecord2,
6372
5865
  firstRecord: firstRecord2,
6373
- TABLE_78_FIELD_BITS: TABLE_78_FIELD_BITS2,
6374
- table78DeltaRecord: table78DeltaRecord2,
6375
- isSemanticLoaderDelta: isSemanticLoaderDelta3,
6376
5866
  decodeText: decodeText2,
6377
5867
  entityNameFromType: entityNameFromType2,
6378
5868
  markerTypeIdForTables: markerTypeIdForTables2,
@@ -6448,7 +5938,6 @@
6448
5938
  defineLazyProperty: defineLazyProperty2
6449
5939
  } = decoder_exports;
6450
5940
  var ModelState = class {
6451
- #loaderConfig = new LoaderConfigTracker();
6452
5941
  #blueprintItems = null;
6453
5942
  #helmOccupied = /* @__PURE__ */ new Map();
6454
5943
  #commsStationOccupied = /* @__PURE__ */ new Map();
@@ -6537,8 +6026,6 @@
6537
6026
  this.totalErrorCount += 1;
6538
6027
  pushCapped2(this.errors, { message: error.message, generation: update.generation }, MAX_RETAINED_ERRORS2);
6539
6028
  }
6540
- this.#remapIndexedLoaderConfig(update);
6541
- this.#updateLoaderConfig(update);
6542
6029
  this.#updateNavigationUnitAutoWarp(update);
6543
6030
  this.#updateHelmOccupancy(update);
6544
6031
  this.#updateCommsStationOccupancy(update);
@@ -6640,53 +6127,6 @@
6640
6127
  this._derived.summariesDirty = true;
6641
6128
  this._derived.tableSummariesDirty = true;
6642
6129
  }
6643
- #updateLoaderConfig(update) {
6644
- const seenEntities = /* @__PURE__ */ new Set();
6645
- for (const section of update.sections || []) {
6646
- if (section.table !== 78) continue;
6647
- for (const changed of section.records || []) {
6648
- if (changed.indexedLoaderConfig) {
6649
- this.#loaderConfig.updateIndexedSnapshotRecord(null, changed.entity, changed.record, changed.mask, changed.cumulativeRecord, changed.configEntity);
6650
- continue;
6651
- }
6652
- const typeId = entityTypeIdFromRecord2(this.record(7, changed.entity));
6653
- this.#loaderConfig.updateRecord(null, changed.entity, changed.record ?? this.record(78, changed.entity), changed.mask, changed.previous, {
6654
- repeatedInUpdate: seenEntities.has(changed.entity),
6655
- allowSparseBaseline: typeId === LOADER_TYPE_ID2,
6656
- repeatedInSection: changed.repeatedInSection,
6657
- semanticSnapshot: Boolean(update.full),
6658
- semanticDelta: isSemanticLoaderDelta3(changed, update.full),
6659
- deltaRecord: table78DeltaRecord2(changed)
6660
- });
6661
- seenEntities.add(changed.entity);
6662
- }
6663
- }
6664
- }
6665
- #remapIndexedLoaderConfig(update) {
6666
- if (!update.full || update.error) return;
6667
- const rows = [];
6668
- for (const section of update.sections || []) {
6669
- if (section.table !== 78) continue;
6670
- for (const changed of section.records || []) rows.push(changed);
6671
- }
6672
- if (!rows.length) return;
6673
- const loaderIds = [...this.table(7).entries()].filter(([, record]) => entityTypeIdFromRecord2(record) === LOADER_TYPE_ID2).map(([entity]) => entity).sort((a, b) => a - b);
6674
- if (loaderIds.length !== rows.length) return;
6675
- if (rows.every((row, index) => row.entity === loaderIds[index])) return;
6676
- const remappedTable = /* @__PURE__ */ new Map();
6677
- for (const [index, changed] of rows.entries()) {
6678
- const rawRecord = table78DeltaRecord2(changed);
6679
- changed.configEntity = changed.entity;
6680
- changed.entity = loaderIds[index];
6681
- changed.cumulativeRecord = cloneRecord2(changed.record || {});
6682
- changed.record = cloneRecord2(rawRecord);
6683
- delete changed.previous;
6684
- changed.indexedLoaderConfig = true;
6685
- remappedTable.set(changed.entity, cloneRecord2(rawRecord));
6686
- update.changedEntities.add(changed.entity);
6687
- }
6688
- this.tables.set(78, remappedTable);
6689
- }
6690
6130
  #updateNavigationUnitAutoWarp(update) {
6691
6131
  const trackedBeforeUpdate = new Set(this.#navigationUnitAutoWarp.keys());
6692
6132
  const initializedThisUpdate = /* @__PURE__ */ new Set();
@@ -7053,7 +6493,8 @@
7053
6493
  const pusherRecord = rec(72);
7054
6494
  const pusherBeamRecord = rec(42);
7055
6495
  const launcherRecord = rec(44);
7056
- const loaderRecord = rec(78);
6496
+ const loaderRecord = rec(69);
6497
+ const navigationRecord = rec(78);
7057
6498
  const loaderFilterRecord = rec(76);
7058
6499
  const loaderFilterSlotsRecord = rec(77);
7059
6500
  const commsStationRecord = rec(39);
@@ -7088,14 +6529,14 @@
7088
6529
  const health = summarizeHealth2(entityId, healthRecord);
7089
6530
  const fabricator = summarizeFabricator2(entityId, fabricatorRecord, itemHolderRecord, typeRecord);
7090
6531
  const cargoEjector = summarizeCargoEjector2(entityId, typeId, cargoEjectorRecord);
7091
- const cannon = summarizeCannon2(entityId, cannonRecord, typeId);
6532
+ const cannon = summarizeCannon2(entityId, cannonRecord, typeId, transformRecord);
7092
6533
  const thruster = summarizeThruster2(entityId, typeId, thrusterRecord);
7093
6534
  const pusher = summarizePusher2(entityId, pusherRecord, loaderFilterSlotsRecord);
7094
6535
  const pusherBeam = summarizePusherBeam2(entityId, pusherBeamRecord);
7095
6536
  const launcher = summarizeItemLauncher2(entityId, typeId, launcherRecord);
7096
- const loader = typeId === LOADER_TYPE_ID2 || typeId == null && (loaderRecord || loaderFilterRecord || loaderFilterSlotsRecord) ? summarizeLoader2(entityId, loaderRecord, loaderFilterRecord, loaderFilterSlotsRecord, this.#loaderConfig, typeId === LOADER_TYPE_ID2, itemHolderRecord) : null;
6537
+ const loader = typeId === LOADER_TYPE_ID2 || typeId == null && (loaderRecord || loaderFilterRecord || loaderFilterSlotsRecord) ? summarizeLoader2(entityId, loaderRecord, loaderFilterRecord, loaderFilterSlotsRecord, typeId === LOADER_TYPE_ID2, itemHolderRecord) : null;
7097
6538
  const cargoHatch = summarizeCargoHatch2(entityId, typeId, loaderFilterRecord, loaderFilterSlotsRecord, rec(45));
7098
- const navigationUnit = summarizeNavigationUnit2(entityId, typeId, loaderRecord, this.#navigationUnitAutoWarp.get(entityId));
6539
+ const navigationUnit = summarizeNavigationUnit2(entityId, typeId, navigationRecord, this.#navigationUnitAutoWarp.get(entityId));
7099
6540
  const fluidTank = fluidTankRecord ? {
7100
6541
  entity: entityId,
7101
6542
  amount: fluidTankRecord.q24 ?? null,
@@ -7342,7 +6783,6 @@
7342
6783
  } else {
7343
6784
  for (const records of this.tables.values()) records.delete(entity);
7344
6785
  }
7345
- this.#loaderConfig.delete(null, entity);
7346
6786
  this.#helmOccupied.delete(entity);
7347
6787
  this.#commsStationOccupied.delete(entity);
7348
6788
  this.#navigationUnitAutoWarp.delete(entity);
@@ -7358,7 +6798,6 @@
7358
6798
  #readSectionRecords(reader, tag, tableId, spec) {
7359
6799
  const section = { tag, table: tableId, name: spec?.name || null, records: [] };
7360
6800
  let entity = 0;
7361
- const seenEntities = tableId === 78 ? /* @__PURE__ */ new Set() : null;
7362
6801
  while (reader.remaining > 0) {
7363
6802
  const recordOffset = reader.offset;
7364
6803
  let delta;
@@ -7376,7 +6815,6 @@
7376
6815
  throw new Error(`model table ${tableId} tag ${tag} entity ${entity} mask offset ${reader.offset}: ${error.message}`);
7377
6816
  }
7378
6817
  const record = this.#getRecord(tableId, entity);
7379
- const previous = tableId === 78 ? cloneRecord2(record) : null;
7380
6818
  record.lastMask = mask;
7381
6819
  try {
7382
6820
  if (spec) this.#applyRecordSpec(reader, record, mask, spec);
@@ -7385,10 +6823,6 @@
7385
6823
  throw new Error(`model table ${tableId} tag ${tag} entity ${entity} mask ${mask} offset ${reader.offset}: ${error.message}`);
7386
6824
  }
7387
6825
  const changed = { entity, mask };
7388
- if (previous) changed.previous = previous;
7389
- if (seenEntities?.has(entity)) changed.repeatedInSection = true;
7390
- seenEntities?.add(entity);
7391
- if (tableId === 78) changed.record = cloneRecord2(record);
7392
6826
  section.records.push(changed);
7393
6827
  }
7394
6828
  return section;
@@ -7396,7 +6830,7 @@
7396
6830
  #getRecord(tableId, entity) {
7397
6831
  if (!this.tables.has(tableId)) this.tables.set(tableId, /* @__PURE__ */ new Map());
7398
6832
  const records = this.tables.get(tableId);
7399
- if (!records.has(entity)) records.set(entity, {});
6833
+ if (!records.has(entity)) records.set(entity, { ...MODEL_TABLE_SPECS2.get(tableId)?.defaults });
7400
6834
  return records.get(entity);
7401
6835
  }
7402
6836
  #applyRecordSpec(reader, record, mask, spec) {
@@ -8433,184 +7867,6 @@
8433
7867
  return number === 0 && count === 0 ? null : number;
8434
7868
  }
8435
7869
 
8436
- // src/protocol/outbound/ship-management.js
8437
- var SHIP_MANAGEMENT_TYPE = 4;
8438
- var SET_PRIVACY_ACTION = "set_privacy";
8439
- var STARTER_RECOVERY_ACTION = "starter_recovery";
8440
- var PLAYER_LIST_ACTION = "player_list";
8441
- var INVITE_RESET_ACTION = "invite_reset";
8442
- var SET_RANK_ACTION = "set_rank";
8443
- var KICK_ACTION = "kick";
8444
- var BAN_ACTION = "ban";
8445
- var DEMOTE_SELF_ACTION = "demote_self";
8446
- function requireNonNegativeInteger(value, name) {
8447
- const number = Number(value);
8448
- if (!Number.isInteger(number) || number < 0) throw new RangeError(`${name} must be a non-negative integer`);
8449
- return number;
8450
- }
8451
- function normalizePrivacy(value) {
8452
- if (value === true || value === 1 || value === "private") return 1;
8453
- if (value === false || value === 0 || value === "public") return 0;
8454
- throw new RangeError(`privacy must be 0, 1, true, false, "public", or "private"`);
8455
- }
8456
- function normalizePlayerRef(value) {
8457
- return requireNonNegativeInteger(value, "refId");
8458
- }
8459
- function normalizePlayerRank(value) {
8460
- if (value === 0 || value === "guest") return 0;
8461
- if (value === 1 || value === "crew") return 1;
8462
- if (value === 3 || value === "captain") return 3;
8463
- throw new RangeError(`rank must be 0, 1, 3, "guest", "crew", or "captain"`);
8464
- }
8465
- function buildShipManagementMessage(act, arg = null, extra = null) {
8466
- if (typeof act !== "string" || !act) throw new TypeError(`act must be a non-empty string`);
8467
- const message = { type: SHIP_MANAGEMENT_TYPE, act, arg };
8468
- if (extra && typeof extra === "object") Object.assign(message, extra);
8469
- return message;
8470
- }
8471
- function buildShipPrivacyMessage(privacy) {
8472
- return buildShipManagementMessage(SET_PRIVACY_ACTION, normalizePrivacy(privacy));
8473
- }
8474
- function buildStarterRecoveryMessage(itemId) {
8475
- return buildShipManagementMessage(STARTER_RECOVERY_ACTION, requireNonNegativeInteger(itemId, "itemId"));
8476
- }
8477
- function buildPlayerListMessage() {
8478
- return buildShipManagementMessage(PLAYER_LIST_ACTION);
8479
- }
8480
- function buildInviteResetMessage() {
8481
- return buildShipManagementMessage(INVITE_RESET_ACTION);
8482
- }
8483
- function buildSetPlayerRankMessage(refId, rank) {
8484
- return buildShipManagementMessage(SET_RANK_ACTION, normalizePlayerRef(refId), { rank: normalizePlayerRank(rank) });
8485
- }
8486
- function buildKickPlayerMessage(refId) {
8487
- return buildShipManagementMessage(KICK_ACTION, normalizePlayerRef(refId));
8488
- }
8489
- function buildBanPlayerMessage(refId) {
8490
- return buildShipManagementMessage(BAN_ACTION, normalizePlayerRef(refId));
8491
- }
8492
- function buildDemoteSelfMessage() {
8493
- return buildShipManagementMessage(DEMOTE_SELF_ACTION);
8494
- }
8495
- function normalizeShipConfig(event = {}) {
8496
- const source = event && typeof event === "object" ? event : {};
8497
- const config = source.config && typeof source.config === "object" ? source.config : {};
8498
- return {
8499
- privacy: config.privacy ?? null,
8500
- privacyName: config.privacy === 0 ? "public" : config.privacy === 1 ? "private" : null,
8501
- inviteKey: config.invite_key ?? null,
8502
- teamId: source.team_id ?? null,
8503
- patronPerks: Array.isArray(source.patron_perks) ? source.patron_perks.slice() : []
8504
- };
8505
- }
8506
- function normalizeCaptainSubrank(event = {}) {
8507
- const source = event && typeof event === "object" ? event : {};
8508
- return {
8509
- subrank: source.subrank ?? null,
8510
- enableCheats: Boolean(source.enable_cheats)
8511
- };
8512
- }
8513
- function normalizeShipPlayerList(event = {}, previous = null, currentCaptainSubrank = null) {
8514
- const source = event && typeof event === "object" ? event : {};
8515
- const changes = Array.isArray(source.player_list) ? source.player_list.map(normalizePlayerListChange) : [];
8516
- const removedPlayers = changes.filter((change) => change.remove && Number.isFinite(Number(change.refId))).map((change) => Number(change.refId));
8517
- const changedPlayers = changes.filter((change) => !change.remove).map(({ remove: _remove, ...change }) => change);
8518
- const players = mergePlayerListChanges(previous?.players, changes);
8519
- const ownerCaptainRank = ownerRankForPlayers(players);
8520
- applyPlayerControlState(players, currentCaptainSubrank);
8521
- applyPlayerControlState(changedPlayers, currentCaptainSubrank);
8522
- return {
8523
- ownerCaptainRank,
8524
- shipOwners: players.filter((player) => player.isShipOwner),
8525
- players,
8526
- changes: changedPlayers,
8527
- removedPlayers
8528
- };
8529
- }
8530
- function mergePlayerListChanges(previousPlayers, changes) {
8531
- if (!Array.isArray(previousPlayers) || !previousPlayers.length) return changes.filter((change) => !change.remove).map(({
8532
- remove: _remove,
8533
- ...player
8534
- }) => ({ ...player }));
8535
- const existing = /* @__PURE__ */ new Map();
8536
- for (const player of previousPlayers) {
8537
- if (player?.refId == null) continue;
8538
- existing.set(player.refId, { ...player, isShipOwner: false });
8539
- }
8540
- const changedRefs = /* @__PURE__ */ new Set();
8541
- const merged = [];
8542
- for (const change of changes) {
8543
- if (change.refId == null) {
8544
- if (!change.remove) {
8545
- const { remove: _remove2, ...player2 } = change;
8546
- merged.push({ ...player2, isShipOwner: false });
8547
- }
8548
- continue;
8549
- }
8550
- changedRefs.add(change.refId);
8551
- if (change.remove) {
8552
- existing.delete(change.refId);
8553
- continue;
8554
- }
8555
- const { remove: _remove, ...changePlayer } = change;
8556
- const player = { ...existing.get(change.refId) || {}, ...changePlayer, isShipOwner: false };
8557
- existing.set(change.refId, player);
8558
- merged.push(player);
8559
- }
8560
- for (const player of previousPlayers) {
8561
- if (player?.refId == null || changedRefs.has(player.refId)) continue;
8562
- const retained = existing.get(player.refId);
8563
- if (retained) merged.push(retained);
8564
- }
8565
- return merged;
8566
- }
8567
- function normalizePlayerListChange(entry = {}) {
8568
- const source = entry && typeof entry === "object" ? entry : {};
8569
- if (source._removed) return { refId: source.ref_id ?? null, remove: true };
8570
- return {
8571
- refId: source.ref_id ?? null,
8572
- remove: false,
8573
- discrim: source.discrim ?? null,
8574
- discrimColor: source.discrim_color ?? null,
8575
- teamRank: source.team_rank ?? null,
8576
- captainRank: source.captain_rank ?? null,
8577
- isCaptain: Number(source.captain_rank) > 0,
8578
- isShipOwner: false,
8579
- canBeManaged: false,
8580
- time: source.time ?? null,
8581
- items: Array.isArray(source.items) ? source.items.slice() : [],
8582
- aliasDiscrims: Array.isArray(source.alias_discrims) ? source.alias_discrims.slice() : [],
8583
- extraAliasCount: normalizeNonNegativeNumber(source.extra_aliases),
8584
- onlineCount: source.online_count ?? null
8585
- };
8586
- }
8587
- function normalizeNonNegativeNumber(value) {
8588
- const number = Number(value);
8589
- return Number.isFinite(number) && number > 0 ? number : 0;
8590
- }
8591
- function ownerRankForPlayers(players) {
8592
- const ranks = players.filter((player) => Number(player.captainRank) > 0).map((player) => Number(player.captainRank)).filter(Number.isFinite);
8593
- if (!ranks.length) return null;
8594
- const ownerRank = Math.min(...ranks);
8595
- for (const player of players) player.isShipOwner = Number(player.captainRank) === ownerRank;
8596
- return ownerRank;
8597
- }
8598
- function applyPlayerControlState(players, currentCaptainSubrank) {
8599
- const currentRank = currentCaptainRank(currentCaptainSubrank);
8600
- for (const player of players) player.canBeManaged = playerCanBeManagedBy(player, currentRank);
8601
- }
8602
- function currentCaptainRank(currentCaptainSubrank) {
8603
- const rank = Number(currentCaptainSubrank?.subrank);
8604
- return Number.isFinite(rank) && rank > 0 ? rank : null;
8605
- }
8606
- function playerCanBeManagedBy(player, currentRank) {
8607
- if (!Number.isFinite(currentRank) || !player) return false;
8608
- const targetCaptainRank = Number(player.captainRank);
8609
- if (Number.isFinite(targetCaptainRank) && targetCaptainRank > 0) return currentRank < targetCaptainRank;
8610
- const targetTeamRank = Number(player.teamRank);
8611
- return targetTeamRank === 0 || targetTeamRank === 1;
8612
- }
8613
-
8614
7870
  // src/protocol/outbound/sign.js
8615
7871
  var SIGN_TEXT_COMMAND = "sign_text";
8616
7872
  var SignDisplayMode = Object.freeze({
@@ -9800,6 +9056,9 @@
9800
9056
  use(options = {}, command = {}) {
9801
9057
  return this.client.player.useEntity(this.id, options, command);
9802
9058
  }
9059
+ openTips(options = {}) {
9060
+ return this.client.tips.open(this, options);
9061
+ }
9803
9062
  };
9804
9063
  var MachineHandle = class extends EntityHandle {
9805
9064
  open(options = {}, command = {}) {
@@ -10102,6 +9361,9 @@
10102
9361
  }
10103
9362
  };
10104
9363
  var DoorHandle = class extends MachineHandle {
9364
+ setRank(rank, options = {}) {
9365
+ return this.client.tips.setRank(this, rank, options);
9366
+ }
10105
9367
  get state() {
10106
9368
  return summaryForEntity(this.client, this.scope, this.id)?.contents?.door || null;
10107
9369
  }
@@ -10113,6 +9375,9 @@
10113
9375
  }
10114
9376
  };
10115
9377
  var SpawnPointHandle = class extends MachineHandle {
9378
+ setRank(rank, options = {}) {
9379
+ return this.client.tips.setRank(this, rank, options);
9380
+ }
10116
9381
  get state() {
10117
9382
  return summaryForEntity(this.client, this.scope, this.id)?.contents?.spawnPoint || null;
10118
9383
  }
@@ -10657,6 +9922,7 @@
10657
9922
  this.debug = new ClientDebugDomain(this);
10658
9923
  this.player = new PlayerDomain(this);
10659
9924
  this.inventory = new InventoryDomain(this);
9925
+ this.tips = new TipDomain(this);
10660
9926
  this.management = new ShipManagementDomain(this);
10661
9927
  this.cpuLoad = null;
10662
9928
  this.inventoryState = normalizeInventoryEvent({ type: "inventory", general_slots: 5 });
@@ -11410,6 +10676,7 @@
11410
10676
  }
11411
10677
  #handleSideEvent(event, world) {
11412
10678
  if (!event || typeof event !== "object") return;
10679
+ if (event.type === "tip_list") this.tips.receive(event, world);
11413
10680
  if (event.type === "inventory") {
11414
10681
  this.inventoryState = normalizeInventoryEvent(event);
11415
10682
  this.emit("inventory", this.inventoryState, world);