dredless 0.6.3 → 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.
@@ -1085,6 +1085,13 @@ interface LoaderFullConfig extends LoaderConfig {
1085
1085
 
1086
1086
  State output:
1087
1087
 
1088
+ Loader state uses the official component defaults when configuration fields
1089
+ are omitted: pick `3` (middle-left), place `4` (middle-right), normal priority,
1090
+ stack `16`, cycle `1` second, and both flags disabled. Cycle times are converted
1091
+ from ticks at 20 ticks per second. Flag updates set their absolute boolean
1092
+ values; repeated updates do not toggle them. Raw loader configuration is stored
1093
+ in model table `69` (wire section `162`), separately from navigation state.
1094
+
1088
1095
  ```ts
1089
1096
  interface LoaderSummary {
1090
1097
  entity: number;
@@ -1440,6 +1447,11 @@ interface CargoEjectorHandle extends MachineHandle {
1440
1447
 
1441
1448
  Associated summary shapes:
1442
1449
 
1450
+ Cannon summaries expose `relativeAngleRadians` relative to the mount and
1451
+ `absoluteAngleRadians` in ship coordinates, normalized to `[0, 2π)`, replacing
1452
+ the raw `aim` field. The relative angle is `null` when aim is unavailable; the
1453
+ absolute angle is `null` when either aim or mounting rotation is unavailable.
1454
+
1443
1455
  ```ts
1444
1456
  interface CannonSummary {
1445
1457
  entity: number;
@@ -1448,7 +1460,8 @@ interface CannonSummary {
1448
1460
  ammoItemId: number | null;
1449
1461
  ammoName: string | null;
1450
1462
  ammoCount: number;
1451
- aim: number | null;
1463
+ relativeAngleRadians: number | null; // Barrel angle relative to its mount.
1464
+ absoluteAngleRadians: number | null; // Ship coordinates, normalized to [0, 2π).
1452
1465
  recoil: number | null;
1453
1466
  recoil2: number | null;
1454
1467
  recoils: [number | null, number | null];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dredless",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "A fully-featured and from-scratch headless drednot.io client implementation written in pure js.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/certainhuman/dredless#readme",
package/src/index.d.ts CHANGED
@@ -1832,7 +1832,10 @@ export interface CannonSummary {
1832
1832
  ammoItemId: number | null;
1833
1833
  ammoName: string | null;
1834
1834
  ammoCount: number;
1835
- aim: number | null;
1835
+ /** Barrel angle relative to its mount, in radians. */
1836
+ relativeAngleRadians: number | null;
1837
+ /** Barrel direction in ship coordinates, normalized to [0, 2π); null if aim or mounting rotation is unavailable. */
1838
+ absoluteAngleRadians: number | null;
1836
1839
  recoil: number | null;
1837
1840
  recoil2: number | null;
1838
1841
  recoils: [number | null, number | null];
@@ -2,8 +2,7 @@ import {decoder} from "../../constants.js";
2
2
  import {
3
3
  LOADER_FILTER_MODE_NAMES,
4
4
  LOADER_POSITION_NAMES,
5
- LOADER_PRIORITY_NAMES,
6
- LoaderConfigTracker
5
+ LOADER_PRIORITY_NAMES
7
6
  } from "./loader-config.js";
8
7
  import {navigationDestinationFromEncodedValue, navigationZoneFromBaseId} from "../overworld.js";
9
8
  import {maybeSolveGeneratorMazeSeed} from "../generator-maze.js";
@@ -284,7 +283,11 @@ export const MODEL_TABLE_SPECS = new Map([
284
283
  [63, {name: "numeric_pair", ...TWO_FIELD_SPEC}],
285
284
  [67, {name: "numeric_single", fields: numericFields({1: 20})}],
286
285
  [69, {
287
- name: "rare_snapshot",
286
+ name: "loader_config",
287
+ // The official loader component starts with these values. Wire section
288
+ // 162 sends a packed absolute flag word before the five numeric deltas.
289
+ defaults: {q20: 3, q24: 4, q28: 1, q32: 16, q36: 20, q41: 0, q42: 0},
290
+ packedBeforeValues: true,
288
291
  packedBits: [{bit: 32, offset: 41}, {bit: 64, offset: 42}],
289
292
  fields: numericFields({1: 20, 2: 24, 4: 28, 8: 32, 16: 36})
290
293
  }],
@@ -407,7 +410,7 @@ export const WIRE_TAG_TABLES = new Map([
407
410
  [158, 75],
408
411
  [160, 76],
409
412
  [161, 77],
410
- [162, 78],
413
+ [162, 69], // Loader configuration; distinct from navigation state (168).
411
414
  [163, 72],
412
415
  [164, 73],
413
416
  [165, 74],
@@ -574,32 +577,6 @@ export function firstRecord(records, predicate = null) {
574
577
  return null;
575
578
  }
576
579
 
577
- export const TABLE_78_FIELD_BITS = [
578
- ["q20", 1],
579
- ["q24", 2],
580
- ["q28", 4],
581
- ["q32", 8],
582
- ["q36", 16],
583
- ["q40", 32],
584
- ["q44", 64]
585
- ];
586
-
587
- export function table78DeltaRecord(changed) {
588
- const record = {lastMask: changed.mask};
589
- for (const [field, bit] of TABLE_78_FIELD_BITS) {
590
- if (!(changed.mask & bit)) continue;
591
- if (field === "q44" && ((changed.mask & 32) || changed.record?.q44 == null)) continue;
592
- record[field] = (changed.record?.[field] ?? 0) - (changed.previous?.[field] ?? 0);
593
- }
594
- return record;
595
- }
596
-
597
- export function isSemanticLoaderDelta(changed, full) {
598
- return !full &&
599
- changed.previous != null &&
600
- (changed.mask & 96) === 96;
601
- }
602
-
603
580
  export function decodeText(blob) {
604
581
  if (!(blob instanceof Uint8Array)) return null;
605
582
  try {
@@ -679,8 +656,14 @@ export function summarizeCargoEjector(entity, typeId, record) {
679
656
  };
680
657
  }
681
658
 
682
- export function summarizeCannon(entity, record, typeId = null) {
659
+ export function summarizeCannon(entity, record, typeId = null, transformRecord = null) {
683
660
  if (!record || !CANNON_TYPE_IDS.has(typeId)) return null;
661
+ const relativeAngleRadians = numberOrNull(record.q32, 200);
662
+ const mountingAngleRadians = numberOrNull(transformRecord?.q28, 127.324);
663
+ const tau = 2 * Math.PI;
664
+ const absoluteAngleRadians = relativeAngleRadians == null || mountingAngleRadians == null
665
+ ? null
666
+ : (((mountingAngleRadians + relativeAngleRadians) % tau) + tau) % tau;
684
667
  const ammoItemId = record.q24 == null ? null : CANNON_AMMO_COLOR_ITEM_IDS.get(record.q24) ?? null;
685
668
  const charge = record.q40 ?? null;
686
669
  const recoil = record.q36 ?? null;
@@ -692,7 +675,8 @@ export function summarizeCannon(entity, record, typeId = null) {
692
675
  ammoItemId,
693
676
  ammoName: entityNameFromType(ammoItemId),
694
677
  ammoCount: record.q28 ?? 0,
695
- aim: record.q32 ?? null,
678
+ relativeAngleRadians,
679
+ absoluteAngleRadians,
696
680
  recoil,
697
681
  recoil2,
698
682
  recoils: [recoil, recoil2],
@@ -1142,15 +1126,14 @@ export function summarizeHugeThruster(entity, markerRecord, sizeRecord = null) {
1142
1126
  };
1143
1127
  }
1144
1128
 
1145
- export function summarizeLoader(entity, loaderRecord, loaderFilterRecord = null, filterSlotsRecord = null, tracker = null, includeDefaults = false, itemHolderRecord = null) {
1129
+ export function summarizeLoader(entity, loaderRecord, loaderFilterRecord = null, filterSlotsRecord = null, includeDefaults = false, itemHolderRecord = null) {
1146
1130
  if (!loaderRecord && !loaderFilterRecord && !includeDefaults) return null;
1147
- const config = tracker?.getConfig(null, entity, loaderRecord, loaderFilterRecord, filterSlotsRecord) ?? {};
1148
- const hasLoaderState = Boolean(loaderRecord);
1149
- const hasPositionConfig = Boolean(tracker?.hasPositionConfig(null, entity));
1150
- const pick = hasLoaderState && hasPositionConfig ? config.pick ?? null : null;
1151
- const place = hasLoaderState && hasPositionConfig ? config.place ?? null : null;
1152
- const priority = config.priority ?? 0;
1153
- const filterMode = config.filterMode ?? null;
1131
+ // Match the official component defaults, then apply the already decoded state.
1132
+ const loader = {...MODEL_TABLE_SPECS.get(69).defaults, ...loaderRecord};
1133
+ const pick = loader.q20;
1134
+ const place = loader.q24;
1135
+ const priority = loader.q28 - 1;
1136
+ const filterMode = loaderFilterRecord?.q20 ?? 0;
1154
1137
  const heldItem = itemSummary(itemHolderRecord?.q20, itemHolderRecord?.q24 ?? null);
1155
1138
  return {
1156
1139
  entity,
@@ -1160,13 +1143,15 @@ export function summarizeLoader(entity, loaderRecord, loaderFilterRecord = null,
1160
1143
  placeName: enumValueName(LOADER_POSITION_NAMES, place),
1161
1144
  priority,
1162
1145
  priorityName: enumValueName(LOADER_PRIORITY_NAMES, priority),
1163
- requireOutput: config.requireOutput ?? false,
1164
- waitForStack: config.waitForStack ?? false,
1165
- stack: config.stack ?? 16,
1166
- cycle: Math.max(1, config.cycle ?? 1),
1146
+ requireOutput: Boolean(loader.q41),
1147
+ waitForStack: Boolean(loader.q42),
1148
+ stack: loader.q32,
1149
+ cycle: loader.q36 / 20,
1167
1150
  filterMode,
1168
1151
  filterModeName: enumValueName(LOADER_FILTER_MODE_NAMES, filterMode),
1169
- filterSlots: config.filterSlots ?? null,
1152
+ filterSlots: filterSlotsRecord
1153
+ ? [filterSlotsRecord.q20, filterSlotsRecord.q24, filterSlotsRecord.q28].map(value => value || null)
1154
+ : null,
1170
1155
  heldItemId: heldItem.itemId,
1171
1156
  heldItemName: heldItem.itemName,
1172
1157
  heldCount: heldItem.count,
@@ -1206,7 +1191,7 @@ export function summarizePusher(entity, pusherRecord, filterSlotsRecord = null)
1206
1191
  filteredModeName: enumValueName(PUSHER_MODE_NAMES, filteredMode),
1207
1192
  angle: numberOrNull(pusherRecord.q28, 10),
1208
1193
  speed: pusherRecord.q32 == null ? 20 : (pusherRecord.q32 / 100) + 20,
1209
- length: pusherRecord.q40 == null ? 1000 : ((pusherRecord.q40 + 50) / 10) + 995,
1194
+ length: 1000 + (pusherRecord.q40 ?? 0) / 10,
1210
1195
  filterInventory: pusherRecord.q36 === 1,
1211
1196
  filterSlots: filterSlotsRecord ? [filterSlotsRecord.q20 ?? null, filterSlotsRecord.q24 ?? null, filterSlotsRecord.q28 ?? null] : null,
1212
1197
  state: cloneRecord(pusherRecord),