@jetta/cargo-stabilizer 0.1.0-6 → 0.1.0-8

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.
package/CHANGELOG.md CHANGED
@@ -11,11 +11,13 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
11
11
  - Strict English internal I/O contract with bidirectional cargo-movement wire mapper (`map_wire_input_to_contract`, `map_contract_to_wire_output`).
12
12
  - Organization gates: waypoint Z zones, macro delivery order (`preserve_tie_block_organization`), and milkrun wall reservation (`enforce_milkrun_wall_reservation`).
13
13
  - Empty-space candidate discovery for floor and elevated slots via `@jetta/empty-spaces-manager`.
14
+ - Supporter-top candidate slots aligned to each stacked item footprint, included in beam expansion (`top_k ∪ supporter_top`).
14
15
  - Public contract exports: `parse_stabilizer_input`, `parse_stabilizer_output`, `safe_parse_stabilizer_input`, `safe_parse_stabilizer_output`, and related domain types.
15
16
 
16
17
  ### Changed
17
18
 
18
19
  - README documents I/O contract mapping, configuration keys, dependencies, and test/coverage commands.
20
+ - Reorder objective is whole-cargo `transport_score`; loose-item score sum is only a tie-break. Axle, waypoint, and milkrun gates are unchanged.
19
21
 
20
22
  ### Testing
21
23
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @jetta/cargo-stabilizer
2
2
 
3
- TypeScript library that reorders **loose items only** to improve cargo stability while keeping compound blocks pinned. Implements five config-selectable strategies with organization gates (waypoint order, milkrun wall reservation) and empty-space candidate discovery (JET-2262).
3
+ TypeScript library that reorders **loose items only** to improve **whole-cargo transport stability** while keeping compound blocks pinned. The reorder objective is `transport_score` (loose-item score sum is a tie-break). Implements five config-selectable strategies with organization gates (waypoint order, milkrun wall reservation) and empty-space candidate discovery (JET-2262).
4
4
 
5
5
  ## Requirements
6
6
 
@@ -70,7 +70,7 @@ Exported parsers: `parse_stabilizer_input`, `parse_stabilizer_output`, `safe_par
70
70
  | Key | Default | Description |
71
71
  |-----|---------|-------------|
72
72
  | `cargo_stabilizer.reorder_strategy` | `gravity` | `gravity`, `greedy`, `beam_search`, `simulated_annealing`, `genetic` |
73
- | `cargo_stabilizer.transport_stability_floor` | `0` | Hard minimum transport stability score |
73
+ | `cargo_stabilizer.transport_stability_floor` | `0` | Hard minimum transport stability score (gate, not the maximize objective) |
74
74
  | `cargo_stabilizer.max_iteration` | `10` | Greedy / SA iteration budget |
75
75
  | `cargo_stabilizer.preserve_tie_block_organization` | `true` | When true, enforce waypoint Z zones and macro delivery order |
76
76
  | `cargo_stabilizer.enforce_milkrun_wall_reservation` | `false` | When true, reserve rear compound-wall Z bands using profile key below |
@@ -1,7 +1,7 @@
1
1
  import type { MoveCandidate } from '../move/apply_move.js';
2
2
  import type { CandidateGenerationContext } from './candidate_context.js';
3
3
  /**
4
- * Generates ranked candidate slots from empty-space regions for a loose item.
4
+ * Generates ranked candidate slots from empty-space regions and supporter tops.
5
5
  * @param context - Candidate generation context.
6
6
  * @returns Candidate moves sorted floor-first, same container preferred.
7
7
  */
@@ -1,8 +1,8 @@
1
- import { enumerate_empty_space_slots } from './enumerate_empty_space_slots.js';
1
+ import { collect_container_slots } from './collect_container_slots.js';
2
2
  import { is_candidate_valid } from './candidate_validation.js';
3
3
  import { rank_candidates } from './rank_candidates.js';
4
4
  /**
5
- * Generates ranked candidate slots from empty-space regions for a loose item.
5
+ * Generates ranked candidate slots from empty-space regions and supporter tops.
6
6
  * @param context - Candidate generation context.
7
7
  * @returns Candidate moves sorted floor-first, same container preferred.
8
8
  */
@@ -11,18 +11,32 @@ export function generate_candidates(context) {
11
11
  return rank_candidates(candidates, context.loose.container_index);
12
12
  }
13
13
  function build_container_candidates(context, container, target_index) {
14
- const slots = enumerate_empty_space_slots(container, context.loose.item, context.loose.item_id);
15
14
  const candidates = [];
16
- for (const position of slots) {
17
- if (!is_candidate_valid(context, target_index, position)) {
15
+ for (const tagged of collect_container_slots(container, context.loose.item, context.loose.item_id)) {
16
+ if (!is_candidate_valid(context, target_index, tagged.position)) {
18
17
  continue;
19
18
  }
20
19
  candidates.push({
21
20
  item_id: context.loose.item_id,
22
21
  source_container_index: context.loose.container_index,
23
22
  target_container_index: target_index,
24
- new_position: position
23
+ new_position: tagged.position,
24
+ slot_kind: tagged.slot_kind
25
25
  });
26
26
  }
27
- return candidates;
27
+ return unique_kind_positions(candidates);
28
+ }
29
+ function unique_kind_positions(candidates) {
30
+ const seen = new Set();
31
+ const unique = [];
32
+ for (const candidate of candidates) {
33
+ const position = candidate.new_position;
34
+ const key = `${position.x}:${position.y}:${position.z}:${candidate.target_container_index}`;
35
+ if (seen.has(key)) {
36
+ continue;
37
+ }
38
+ seen.add(key);
39
+ unique.push(candidate);
40
+ }
41
+ return unique;
28
42
  }
@@ -0,0 +1,16 @@
1
+ import type { StabilizerContainer } from '../validation/contract/stabilizer_domain_schema.js';
2
+ import type { CandidateSlotKind } from '../move/apply_move.js';
3
+ import type { EmptySpaceSlot } from './enumerate_empty_space_slots.js';
4
+ export interface TaggedSlot {
5
+ position: EmptySpaceSlot;
6
+ slot_kind: CandidateSlotKind;
7
+ }
8
+ /**
9
+ * Collects empty-space origins and per-supporter top origins, supporter first.
10
+ * @param container - Target container layout.
11
+ * @param item - Loose item to place.
12
+ * @param exclude_item_id - Loose item id omitted from obstacles and supporters.
13
+ * @returns Tagged slots with supporter-top entries before empty-space entries.
14
+ */
15
+ export declare function collect_container_slots(container: StabilizerContainer, item: StabilizerContainer['items'][number], exclude_item_id: string): TaggedSlot[];
16
+ //# sourceMappingURL=collect_container_slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect_container_slots.d.ts","sourceRoot":"","sources":["../../../src/core/candidates/collect_container_slots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAC7F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAItE,MAAM,WAAW,UAAU;IAEvB,QAAQ,EAAE,cAAc,CAAA;IACxB,SAAS,EAAE,iBAAiB,CAAA;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACnC,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,EAC1C,eAAe,EAAE,MAAM,GACxB,UAAU,EAAE,CAQd"}
@@ -0,0 +1,20 @@
1
+ import { enumerate_empty_space_slots } from './enumerate_empty_space_slots.js';
2
+ import { enumerate_supporter_top_slots } from './enumerate_supporter_top_slots.js';
3
+ /**
4
+ * Collects empty-space origins and per-supporter top origins, supporter first.
5
+ * @param container - Target container layout.
6
+ * @param item - Loose item to place.
7
+ * @param exclude_item_id - Loose item id omitted from obstacles and supporters.
8
+ * @returns Tagged slots with supporter-top entries before empty-space entries.
9
+ */
10
+ export function collect_container_slots(container, item, exclude_item_id) {
11
+ const supporter = enumerate_supporter_top_slots(container, item, exclude_item_id);
12
+ const empty = enumerate_empty_space_slots(container, item, exclude_item_id);
13
+ return [
14
+ ...tag_slots(supporter, 'supporter_top'),
15
+ ...tag_slots(empty, 'empty_space')
16
+ ];
17
+ }
18
+ function tag_slots(slots, slot_kind) {
19
+ return slots.map(position => ({ position, slot_kind }));
20
+ }
@@ -0,0 +1,11 @@
1
+ import type { StabilizerContainer, StabilizerItem } from '../validation/contract/stabilizer_domain_schema.js';
2
+ import type { EmptySpaceSlot } from './enumerate_empty_space_slots.js';
3
+ /**
4
+ * Emits one candidate origin per supporter footprint (x, supporter top y, z).
5
+ * @param container - Target container layout.
6
+ * @param item - Loose item dimensions.
7
+ * @param exclude_item_id - Loose item id omitted as a supporter.
8
+ * @returns Slot positions aligned to each supporter origin.
9
+ */
10
+ export declare function enumerate_supporter_top_slots(container: StabilizerContainer, item: StabilizerItem, exclude_item_id: string): EmptySpaceSlot[];
11
+ //# sourceMappingURL=enumerate_supporter_top_slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enumerate_supporter_top_slots.d.ts","sourceRoot":"","sources":["../../../src/core/candidates/enumerate_supporter_top_slots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AAC7G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAItE;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CACzC,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE,cAAc,EACpB,eAAe,EAAE,MAAM,GACxB,cAAc,EAAE,CAgBlB"}
@@ -0,0 +1,36 @@
1
+ const CONTAINER_EPSILON = 1e-6;
2
+ /**
3
+ * Emits one candidate origin per supporter footprint (x, supporter top y, z).
4
+ * @param container - Target container layout.
5
+ * @param item - Loose item dimensions.
6
+ * @param exclude_item_id - Loose item id omitted as a supporter.
7
+ * @returns Slot positions aligned to each supporter origin.
8
+ */
9
+ export function enumerate_supporter_top_slots(container, item, exclude_item_id) {
10
+ const slots = [];
11
+ for (const supporter of container.items) {
12
+ if (supporter.item_id === exclude_item_id) {
13
+ continue;
14
+ }
15
+ const slot = supporter_top_slot(supporter);
16
+ if (slot_fits_in_container(container, item, slot)) {
17
+ slots.push(slot);
18
+ }
19
+ }
20
+ return slots;
21
+ }
22
+ function supporter_top_slot(supporter) {
23
+ return {
24
+ x: supporter.x,
25
+ y: supporter.y + supporter.height,
26
+ z: supporter.z
27
+ };
28
+ }
29
+ function slot_fits_in_container(container, item, slot) {
30
+ return slot.x >= -CONTAINER_EPSILON &&
31
+ slot.y >= -CONTAINER_EPSILON &&
32
+ slot.z >= -CONTAINER_EPSILON &&
33
+ slot.x + item.width <= container.width + CONTAINER_EPSILON &&
34
+ slot.y + item.height <= container.height + CONTAINER_EPSILON &&
35
+ slot.z + item.depth <= container.length + CONTAINER_EPSILON;
36
+ }
@@ -0,0 +1,13 @@
1
+ import { type StabilityEvaluation } from '../adapters/stability_adapter.js';
2
+ import type { StabilizerContainer } from '../validation/contract/stabilizer_domain_schema.js';
3
+ import type { LooseItemRef } from '../filters/loose_item_filter.js';
4
+ export declare const LOOSE_SUM_TIE_BREAK_SCALE = 0.000001;
5
+ /**
6
+ * Computes the reorder objective: maximize transport stability, then loose-item sum.
7
+ * @param stability - Transport stability evaluation.
8
+ * @param containers - Container layout used for loose-item index lookup.
9
+ * @param loose_items - Loose items included in the tie-break term.
10
+ * @returns Scalar objective; higher is better.
11
+ */
12
+ export declare function compute_reorder_objective(stability: StabilityEvaluation, containers: StabilizerContainer[], loose_items: LooseItemRef[]): number;
13
+ //# sourceMappingURL=compute_reorder_objective.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute_reorder_objective.d.ts","sourceRoot":"","sources":["../../../src/core/evaluation/compute_reorder_objective.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAEnE,eAAO,MAAM,yBAAyB,WAAO,CAAA;AAE7C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,mBAAmB,EAC9B,UAAU,EAAE,mBAAmB,EAAE,EACjC,WAAW,EAAE,YAAY,EAAE,GAC5B,MAAM,CAIR"}
@@ -0,0 +1,13 @@
1
+ import { sum_loose_item_scores } from '../adapters/stability_adapter.js';
2
+ export const LOOSE_SUM_TIE_BREAK_SCALE = 1e-6;
3
+ /**
4
+ * Computes the reorder objective: maximize transport stability, then loose-item sum.
5
+ * @param stability - Transport stability evaluation.
6
+ * @param containers - Container layout used for loose-item index lookup.
7
+ * @param loose_items - Loose items included in the tie-break term.
8
+ * @returns Scalar objective; higher is better.
9
+ */
10
+ export function compute_reorder_objective(stability, containers, loose_items) {
11
+ const loose_sum = sum_loose_item_scores(stability, containers, loose_items);
12
+ return stability.transport_score + loose_sum * LOOSE_SUM_TIE_BREAK_SCALE;
13
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"state_evaluator.d.ts","sourceRoot":"","sources":["../../../src/core/evaluation/state_evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAC7F,OAAO,EAGH,KAAK,mBAAmB,EAC3B,MAAM,kCAAkC,CAAA;AAKzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAA;AAE9E,MAAM,WAAW,gBAAgB;IAE7B,SAAS,EAAE,mBAAmB,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,qBAAqB,EAAE,OAAO,CAAA;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,UAAU,EAAE,mBAAmB,EAAE,EACjC,OAAO,EAAE,sBAAsB,GAChC,gBAAgB,CAUlB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAChC,gBAAgB,CAGlB;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,cAAc,GAAG,sBAAsB,CAU/F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAGvF;AAwBD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACvC,UAAU,EAAE,mBAAmB,EAAE,EACjC,OAAO,EAAE,sBAAsB,GAChC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAMrD"}
1
+ {"version":3,"file":"state_evaluator.d.ts","sourceRoot":"","sources":["../../../src/core/evaluation/state_evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAC7F,OAAO,EAEH,KAAK,mBAAmB,EAC3B,MAAM,kCAAkC,CAAA;AAMzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAA;AAE9E,MAAM,WAAW,gBAAgB;IAE7B,SAAS,EAAE,mBAAmB,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,qBAAqB,EAAE,OAAO,CAAA;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,UAAU,EAAE,mBAAmB,EAAE,EACjC,OAAO,EAAE,sBAAsB,GAChC,gBAAgB,CAUlB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,EAAE,sBAAsB,GAChC,gBAAgB,CAGlB;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,cAAc,GAAG,sBAAsB,CAU/F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAGvF;AAwBD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACvC,UAAU,EAAE,mBAAmB,EAAE,EACjC,OAAO,EAAE,sBAAsB,GAChC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAMrD"}
@@ -1,4 +1,5 @@
1
- import { try_evaluate_transport_stability, sum_loose_item_scores } from '../adapters/stability_adapter.js';
1
+ import { try_evaluate_transport_stability } from '../adapters/stability_adapter.js';
2
+ import { compute_reorder_objective } from './compute_reorder_objective.js';
2
3
  import { is_axle_load_valid } from '../adapters/axle_adapter.js';
3
4
  import { preserves_waypoint_order } from '../waypoint/preserves_waypoint_order.js';
4
5
  import { is_layout_milkrun_valid } from '../waypoint/milkrun_wall_reservation.js';
@@ -50,7 +51,7 @@ export function clone_baseline(containers) {
50
51
  return clone_containers(containers);
51
52
  }
52
53
  function evaluate_gates(containers, context, stability) {
53
- const objective = sum_loose_item_scores(stability, containers, context.loose_items);
54
+ const objective = compute_reorder_objective(stability, containers, context.loose_items);
54
55
  const transport_floor_valid = stability.transport_score >= context.config.transport_stability_floor;
55
56
  const axle_valid = is_axle_load_valid({ ...context.env, containers }, context.config.axle_load_margin_percent);
56
57
  const waypoint_valid = is_waypoint_order_valid(containers, context);
@@ -1,4 +1,5 @@
1
1
  import type { StabilizerContainer } from '../validation/contract/stabilizer_domain_schema.js';
2
+ export type CandidateSlotKind = 'empty_space' | 'supporter_top';
2
3
  export interface MoveCandidate {
3
4
  item_id: string;
4
5
  source_container_index: number;
@@ -8,6 +9,7 @@ export interface MoveCandidate {
8
9
  y: number;
9
10
  z: number;
10
11
  };
12
+ slot_kind?: CandidateSlotKind;
11
13
  }
12
14
  /**
13
15
  * Applies a move candidate to containers (mutates arrays in place).
@@ -1 +1 @@
1
- {"version":3,"file":"apply_move.d.ts","sourceRoot":"","sources":["../../../src/core/move/apply_move.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAE7F,MAAM,WAAW,aAAa;IAE1B,OAAO,EAAE,MAAM,CAAA;IACf,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACtB,UAAU,EAAE,mBAAmB,EAAE,EACjC,SAAS,EAAE,aAAa,GACzB,IAAI,CAgBN;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAGzF"}
1
+ {"version":3,"file":"apply_move.d.ts","sourceRoot":"","sources":["../../../src/core/move/apply_move.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAA;AAE7F,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,eAAe,CAAA;AAE/D,MAAM,WAAW,aAAa;IAE1B,OAAO,EAAE,MAAM,CAAA;IACf,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,SAAS,CAAC,EAAE,iBAAiB,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACtB,UAAU,EAAE,mBAAmB,EAAE,EACjC,SAAS,EAAE,aAAa,GACzB,IAAI,CAgBN;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAGzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"BeamSearchReorder.d.ts","sourceRoot":"","sources":["../../../../src/core/strategies/reorder/BeamSearchReorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAA;AAYhG;;GAEG;AACH,qBAAa,iBAAkB,YAAW,eAAe;IAErD,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,EAAE;CAkBtD"}
1
+ {"version":3,"file":"BeamSearchReorder.d.ts","sourceRoot":"","sources":["../../../../src/core/strategies/reorder/BeamSearchReorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAA;AAahG;;GAEG;AACH,qBAAa,iBAAkB,YAAW,eAAe;IAErD,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,EAAE;CAkBtD"}
@@ -1,5 +1,6 @@
1
1
  import { generate_candidates } from '../../candidates/candidate_generator.js';
2
2
  import { candidate_context_for_loose } from '../../candidates/build_candidate_context.js';
3
+ import { select_beam_expansion_candidates } from './select_beam_expansion_candidates.js';
3
4
  import { apply_move, clone_containers } from '../../move/apply_move.js';
4
5
  import { evaluate_trial_layout, evaluation_context_from_reorder } from '../../evaluation/state_evaluator.js';
5
6
  import { read_item_score } from '../../adapters/stability_adapter.js';
@@ -39,7 +40,7 @@ function expand_beam(request) {
39
40
  function append_beam_branches(beam_context) {
40
41
  const { reorder, evaluation, expanded, state, loose } = beam_context;
41
42
  const loose_ref = { ...loose, container_index: find_container_index(state.containers, loose.item_id) };
42
- const candidates = generate_candidates(candidate_context_for_loose(reorder, state.containers, loose_ref)).slice(0, reorder.config.beam_top_k);
43
+ const candidates = select_beam_expansion_candidates(generate_candidates(candidate_context_for_loose(reorder, state.containers, loose_ref)), reorder.config.beam_top_k);
43
44
  for (const candidate of candidates) {
44
45
  const trial = clone_containers(state.containers);
45
46
  apply_move(trial, candidate);
@@ -0,0 +1,9 @@
1
+ import type { MoveCandidate } from '../../move/apply_move.js';
2
+ /**
3
+ * Selects beam expansions: ranked top_k plus every supporter-top slot.
4
+ * @param ranked - Candidates already ranked floor-first.
5
+ * @param top_k - Beam top-k from config.
6
+ * @returns Candidates for trial evaluation.
7
+ */
8
+ export declare function select_beam_expansion_candidates(ranked: MoveCandidate[], top_k: number): MoveCandidate[];
9
+ //# sourceMappingURL=select_beam_expansion_candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select_beam_expansion_candidates.d.ts","sourceRoot":"","sources":["../../../../src/core/strategies/reorder/select_beam_expansion_candidates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE7D;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC5C,MAAM,EAAE,aAAa,EAAE,EACvB,KAAK,EAAE,MAAM,GACd,aAAa,EAAE,CAKjB"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Selects beam expansions: ranked top_k plus every supporter-top slot.
3
+ * @param ranked - Candidates already ranked floor-first.
4
+ * @param top_k - Beam top-k from config.
5
+ * @returns Candidates for trial evaluation.
6
+ */
7
+ export function select_beam_expansion_candidates(ranked, top_k) {
8
+ const leading = ranked.slice(0, top_k);
9
+ const supporter_top = ranked.filter(candidate => candidate.slot_kind === 'supporter_top');
10
+ return unique_move_positions([...leading, ...supporter_top]);
11
+ }
12
+ function unique_move_positions(candidates) {
13
+ const seen = new Set();
14
+ const unique = [];
15
+ for (const candidate of candidates) {
16
+ const key = position_key(candidate);
17
+ if (seen.has(key)) {
18
+ continue;
19
+ }
20
+ seen.add(key);
21
+ unique.push(candidate);
22
+ }
23
+ return unique;
24
+ }
25
+ function position_key(candidate) {
26
+ const position = candidate.new_position;
27
+ return `${position.x}:${position.y}:${position.z}:${candidate.target_container_index}`;
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetta/cargo-stabilizer",
3
- "version": "0.1.0-6",
3
+ "version": "0.1.0-8",
4
4
  "description": "Stable loose-item reorder library for cargo-movement (JET-2262)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",