@terrafirmagreg-team/recipe 0.1.0 → 1.2.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.
Files changed (240) hide show
  1. package/package.json +16 -16
  2. package/src/components/button-copy.tsx +1 -0
  3. package/src/components/cards/card/index.tsx +5 -3
  4. package/src/components/cards/card/scene.tsx +4 -2
  5. package/src/components/cards/card/strip.tsx +4 -2
  6. package/src/components/cards/card/text.tsx +2 -1
  7. package/src/components/cards/card/tips.tsx +1 -0
  8. package/src/components/cards/default-item.tsx +6 -2
  9. package/src/components/cards/glare-filter.tsx +1 -0
  10. package/src/components/cards/paged-card.tsx +1 -0
  11. package/src/components/cards/slot-hitbox.tsx +4 -2
  12. package/src/components/cards/strip-card/buttons.tsx +7 -2
  13. package/src/components/cards/strip-card/index.tsx +3 -1
  14. package/src/components/cards/strip-card/member-cell.tsx +4 -2
  15. package/src/components/cards/strip-card/sprite.tsx +1 -0
  16. package/src/components/embed-card.tsx +44 -0
  17. package/src/components/mdx/recipe.tsx +34 -16
  18. package/src/components/tool-list.tsx +1 -0
  19. package/src/{lib/tree.ts → components/tree.tsx} +20 -12
  20. package/src/components/views/alloy/index.tsx +14 -7
  21. package/src/components/views/alloy/list.tsx +3 -2
  22. package/src/components/views/alloy/mix/index.tsx +1 -0
  23. package/src/components/views/alloy/mix/ratio.tsx +7 -6
  24. package/src/components/views/alloy/mix/rows.tsx +6 -7
  25. package/src/components/views/alloy/mix/status.tsx +8 -7
  26. package/src/components/views/alloy/mix/total.tsx +13 -12
  27. package/src/components/views/alloy/range-bar.tsx +2 -1
  28. package/src/components/views/alloy/stack-bar.tsx +4 -2
  29. package/src/components/views/browser/axis-switcher.tsx +13 -12
  30. package/src/components/views/browser/card-grid.tsx +2 -1
  31. package/src/components/views/browser/favorite-button.tsx +2 -1
  32. package/src/components/views/browser/frame.tsx +3 -2
  33. package/src/components/views/browser/index.tsx +20 -2
  34. package/src/components/views/browser/item-panel/index.tsx +11 -9
  35. package/src/components/views/browser/item-panel/skeleton.tsx +1 -0
  36. package/src/components/views/browser/item-panel-grid/cell.tsx +5 -3
  37. package/src/components/views/browser/item-panel-grid/favorite-cell.tsx +4 -2
  38. package/src/components/views/browser/item-panel-grid/index.tsx +4 -2
  39. package/src/components/views/browser/item-view/index.tsx +7 -12
  40. package/src/components/views/browser/item-view/tags.tsx +7 -5
  41. package/src/components/views/browser/item-view-header.tsx +10 -9
  42. package/src/components/views/browser/pane-tabs.tsx +1 -0
  43. package/src/components/views/browser/place-header.tsx +4 -3
  44. package/src/components/views/browser/query-controls.tsx +3 -1
  45. package/src/components/views/browser/recipe-item-header.tsx +5 -4
  46. package/src/components/views/browser/settings.tsx +4 -3
  47. package/src/components/views/browser/toolbar.tsx +3 -2
  48. package/src/components/views/browser/type-tabs.tsx +5 -3
  49. package/src/components/views/browser/type-view.tsx +7 -12
  50. package/src/components/views/browser/workstation-rail.tsx +4 -3
  51. package/src/components/views/calc/calculator/actions.tsx +4 -14
  52. package/src/components/views/calc/calculator/help.tsx +69 -28
  53. package/src/components/views/calc/calculator/index.tsx +34 -82
  54. package/src/components/views/calc/dot-overlay.tsx +1 -0
  55. package/src/components/views/calc/edge.tsx +134 -32
  56. package/src/components/views/calc/flow/index.tsx +92 -54
  57. package/src/components/views/calc/flow/picker-dialogs.tsx +84 -6
  58. package/src/components/views/calc/flow/toolbar.tsx +9 -21
  59. package/src/components/views/calc/flow/view-menu.tsx +16 -120
  60. package/src/components/views/calc/member-picker.tsx +8 -6
  61. package/src/components/views/calc/node-card/actions.tsx +28 -106
  62. package/src/components/views/calc/node-card/badges.tsx +10 -28
  63. package/src/components/views/calc/node-card/chips.tsx +8 -40
  64. package/src/components/views/calc/node-card/far.tsx +13 -65
  65. package/src/components/views/calc/node-card/full.tsx +133 -83
  66. package/src/components/views/calc/node-card/index.tsx +1 -0
  67. package/src/components/views/calc/node-card/menu.tsx +93 -22
  68. package/src/components/views/calc/node-card/port-cells.tsx +186 -0
  69. package/src/components/views/calc/node-card/port-rails.tsx +251 -0
  70. package/src/components/views/calc/node-card/ports.tsx +86 -0
  71. package/src/components/views/calc/recipe-picker/body.tsx +17 -14
  72. package/src/components/views/calc/recipe-picker/footer.tsx +5 -3
  73. package/src/components/views/calc/recipe-picker/index.tsx +5 -0
  74. package/src/components/views/calc/sidebar.tsx +19 -10
  75. package/src/components/views/calc/trees/icon-picker.tsx +3 -2
  76. package/src/components/views/calc/trees/index.tsx +5 -4
  77. package/src/components/views/calc/trees/label.tsx +6 -4
  78. package/src/components/views/calc/trees/rows.tsx +6 -5
  79. package/src/components/views/forge/gui.tsx +2 -1
  80. package/src/components/views/forge/index.tsx +14 -7
  81. package/src/components/views/forge/list.tsx +3 -2
  82. package/src/components/views/forge/workbench/controls.tsx +13 -6
  83. package/src/components/views/forge/workbench/index.tsx +3 -2
  84. package/src/components/views/forge/workbench/panel.tsx +1 -0
  85. package/src/components/views/forge/workbench/ruler.tsx +5 -4
  86. package/src/components/views/forge/workbench/status.tsx +6 -5
  87. package/src/components/views/forge/workbench/tape.tsx +5 -4
  88. package/src/hooks/alloy/use-fluid-colors.ts +12 -9
  89. package/src/hooks/alloy/use-mix-state.ts +3 -2
  90. package/src/hooks/calc/flow/use-engage.ts +129 -0
  91. package/src/hooks/calc/flow/use-handlers.ts +192 -0
  92. package/src/hooks/calc/flow/use-layout.ts +105 -0
  93. package/src/hooks/calc/flow/{mount.ts → use-mount.ts} +1 -0
  94. package/src/hooks/calc/flow/{nodes.ts → use-nodes.ts} +46 -37
  95. package/src/hooks/calc/flow/use-pickers.ts +57 -0
  96. package/src/hooks/calc/flow/{viewport.ts → use-viewport.ts} +13 -26
  97. package/src/hooks/calc/flow/{window.ts → use-window.ts} +1 -0
  98. package/src/hooks/calc/use-actions.ts +208 -229
  99. package/src/hooks/calc/use-amounts.ts +23 -60
  100. package/src/hooks/calc/use-data.ts +106 -192
  101. package/src/hooks/calc/use-defaults.ts +4 -2
  102. package/src/hooks/calc/use-far-mode.ts +1 -0
  103. package/src/hooks/calc/use-focus.ts +8 -7
  104. package/src/hooks/calc/use-maps.ts +1 -0
  105. package/src/hooks/calc/use-reset.ts +1 -0
  106. package/src/hooks/calc/use-resolver.ts +32 -196
  107. package/src/hooks/calc/use-session.ts +1 -0
  108. package/src/hooks/calc/use-share.ts +5 -2
  109. package/src/hooks/calc/use-storage.ts +5 -4
  110. package/src/hooks/calc/use-tier.ts +1 -0
  111. package/src/hooks/calc/use-view-mode.ts +1 -0
  112. package/src/hooks/calc/use-worker.ts +56 -121
  113. package/src/hooks/forge/use-anvil-gui.ts +4 -2
  114. package/src/hooks/forge/use-forge-state.ts +1 -0
  115. package/src/hooks/forge/use-ruler.ts +1 -0
  116. package/src/hooks/use-add-to-calc.ts +23 -19
  117. package/src/hooks/use-categories-by-id.ts +18 -0
  118. package/src/hooks/use-favorite-hotkey.ts +1 -0
  119. package/src/hooks/use-favorites.ts +1 -0
  120. package/src/hooks/use-group-outlines.ts +1 -0
  121. package/src/hooks/use-hide-unused.ts +2 -1
  122. package/src/hooks/use-index.ts +6 -2
  123. package/src/hooks/use-item-feeds.ts +3 -1
  124. package/src/hooks/use-item-pane.ts +6 -4
  125. package/src/hooks/use-item-press.ts +27 -23
  126. package/src/hooks/use-item-tag-groups.ts +4 -2
  127. package/src/hooks/use-item-tooltips.ts +3 -1
  128. package/src/hooks/use-left-sidebar-tree.ts +11 -3
  129. package/src/hooks/use-mod-names.ts +4 -2
  130. package/src/hooks/use-mount-limit.ts +1 -0
  131. package/src/hooks/use-panel-cells.ts +3 -1
  132. package/src/hooks/use-panel-page.ts +9 -6
  133. package/src/hooks/use-panel-tabs.ts +2 -1
  134. package/src/hooks/use-query.ts +8 -6
  135. package/src/hooks/use-session.ts +6 -2
  136. package/src/hooks/use-sidebar-group.ts +3 -7
  137. package/src/hooks/use-tag-members.ts +3 -1
  138. package/src/hooks/use-tool-recipes.ts +1 -0
  139. package/src/hooks/use-type-filter.ts +1 -0
  140. package/src/lib/alloy/mix.ts +1 -0
  141. package/src/lib/alloy/parse.ts +1 -0
  142. package/src/lib/calc/defaults.ts +6 -4
  143. package/src/lib/calc/flow.ts +143 -107
  144. package/src/lib/calc/focus.ts +1 -0
  145. package/src/lib/calc/meta.ts +3 -1
  146. package/src/lib/calc/pack.ts +47 -154
  147. package/src/lib/calc/ports.ts +43 -0
  148. package/src/lib/calc/resolve.ts +6 -38
  149. package/src/lib/calc/route/buckets.ts +1 -0
  150. package/src/lib/calc/route/geometry.ts +13 -24
  151. package/src/lib/calc/route/index.ts +1 -0
  152. package/src/lib/calc/route/polyline.ts +3 -3
  153. package/src/lib/calc/route/router.ts +181 -57
  154. package/src/lib/calc/scheme.ts +226 -0
  155. package/src/lib/calc/storage/entry.ts +27 -19
  156. package/src/lib/calc/storage/index.ts +2 -0
  157. package/src/lib/calc/storage/keys.ts +4 -8
  158. package/src/lib/calc/storage/store.ts +11 -36
  159. package/src/lib/calc/tree/index.ts +2 -16
  160. package/src/lib/calc/tree/materials.ts +1 -0
  161. package/src/lib/calc/tree/recipe.ts +1 -0
  162. package/src/lib/calc/tree/share.ts +86 -149
  163. package/src/lib/calc/worker/api.ts +3 -11
  164. package/src/lib/calc/worker/fetch.ts +1 -0
  165. package/src/lib/calc/worker/flush.ts +7 -20
  166. package/src/lib/calc/worker/index.ts +1 -1
  167. package/src/lib/calc/worker/protocol.ts +16 -13
  168. package/src/lib/calc/worker/resolve.ts +32 -84
  169. package/src/lib/calc/worker/state.ts +5 -6
  170. package/src/{components/cards/card/model.ts → lib/card-model.ts} +1 -0
  171. package/src/lib/client/recipe-categories.ts +11 -93
  172. package/src/lib/client/recipe.ts +13 -11
  173. package/src/lib/client-index/catalog.ts +33 -27
  174. package/src/lib/client-index/chrome.ts +30 -30
  175. package/src/lib/client-index/index.ts +1 -0
  176. package/src/lib/client-index/registry.ts +70 -59
  177. package/src/lib/client-index/rows.ts +63 -57
  178. package/src/lib/client-index/tooltips.ts +28 -34
  179. package/src/lib/config.ts +2 -1
  180. package/src/lib/data/category/dto.ts +4 -3
  181. package/src/lib/data/category/load.ts +9 -8
  182. package/src/lib/data/category/parse.ts +4 -3
  183. package/src/lib/data/dto.ts +4 -3
  184. package/src/lib/data/grid.ts +2 -1
  185. package/src/lib/data/parse/entry.ts +3 -2
  186. package/src/lib/data/parse/index.ts +1 -0
  187. package/src/lib/data/parse/layout.ts +5 -2
  188. package/src/lib/data/parse/slots.ts +3 -2
  189. package/src/lib/data/parse/widgets.ts +3 -2
  190. package/src/lib/data/scene.ts +1 -1
  191. package/src/lib/data/slot-io.ts +1 -0
  192. package/src/lib/dump.ts +4 -3
  193. package/src/lib/embed.ts +22 -0
  194. package/src/lib/favorite-hover.ts +1 -0
  195. package/src/lib/fluid-outputs.ts +10 -12
  196. package/src/lib/forge/actions.ts +2 -1
  197. package/src/lib/forge/data.ts +2 -1
  198. package/src/lib/forge/solve.ts +1 -0
  199. package/src/lib/group.ts +1 -0
  200. package/src/lib/index-store.ts +15 -9
  201. package/src/lib/links.ts +15 -9
  202. package/src/lib/lookup.ts +10 -8
  203. package/src/lib/panel-outline.ts +1 -0
  204. package/src/lib/panel.ts +3 -2
  205. package/src/lib/preview.ts +12 -8
  206. package/src/lib/recipe-slots.ts +1 -0
  207. package/src/lib/search.ts +1 -0
  208. package/src/lib/sidebar.ts +1 -0
  209. package/src/lib/used-items.ts +1 -0
  210. package/src/locales/en_us.json +170 -226
  211. package/src/locales/ru_ru.json +170 -226
  212. package/src/locales.ts +1 -0
  213. package/src/types/alloy.ts +1 -0
  214. package/src/{hooks/calc/flow/props.ts → types/calc-flow-props.ts} +18 -14
  215. package/src/types/calc.ts +41 -44
  216. package/src/types/forge.ts +1 -0
  217. package/src/types/index.ts +4 -2
  218. package/styles/calc.css +308 -47
  219. package/src/components/cards/fit-card.tsx +0 -26
  220. package/src/components/views/calc/target.tsx +0 -271
  221. package/src/hooks/calc/flow/engage.ts +0 -89
  222. package/src/hooks/calc/flow/handlers.ts +0 -115
  223. package/src/hooks/calc/flow/layout.ts +0 -149
  224. package/src/hooks/calc/flow/pickers.ts +0 -50
  225. package/src/hooks/calc/use-calc-fill.ts +0 -91
  226. package/src/hooks/calc/use-density.ts +0 -28
  227. package/src/hooks/calc/use-derived.ts +0 -258
  228. package/src/hooks/calc/use-direction.ts +0 -23
  229. package/src/hooks/calc/use-edge-style.ts +0 -40
  230. package/src/hooks/calc/use-fill.ts +0 -213
  231. package/src/lib/alloy/alloy.test.ts +0 -186
  232. package/src/lib/calc/tree/build.ts +0 -196
  233. package/src/lib/calc/tree/context.ts +0 -42
  234. package/src/lib/calc/tree/goal.ts +0 -64
  235. package/src/lib/calc/tree/ideal.ts +0 -36
  236. package/src/lib/calc/tree/pick.ts +0 -68
  237. package/src/lib/calc/tree/tree.test.ts +0 -169
  238. package/src/lib/calc/worker/fill.ts +0 -123
  239. package/src/lib/client-index/cache.ts +0 -13
  240. package/src/lib/forge/forge.test.ts +0 -123
@@ -1,8 +1,8 @@
1
1
  import { deflateSync, inflateSync, strFromU8, strToU8 } from 'fflate';
2
2
  import type {
3
- CalcMaterial,
3
+ CalcCard,
4
4
  CalculatorState,
5
- CalculatorTarget,
5
+ CalcWire,
6
6
  } from '@terrafirmagreg-team/recipe/types/calc';
7
7
 
8
8
  function encodeUrlSafe(base64: string): string {
@@ -25,48 +25,6 @@ function u8ToBinary(u8: Uint8Array): string {
25
25
  }
26
26
 
27
27
  export function encodeCalcState(state: CalculatorState): string {
28
- return encodeCalcShare({
29
- state,
30
- autoRoots: state.auto ?? [],
31
- pins: {},
32
- derivablePick: () => undefined,
33
- });
34
- }
35
-
36
- type SharePayload = {
37
- v: 1;
38
-
39
- d: string[];
40
-
41
- t?: [number, number, number] | [number, number, number][];
42
-
43
- i?: [number, number, number][];
44
-
45
- a: string[];
46
-
47
- s: Record<string, number>;
48
-
49
- g: Record<string, number>;
50
-
51
- c: string[];
52
- };
53
-
54
- export function encodeCalcShare({
55
- state,
56
- derivablePick,
57
- pins = {},
58
- autoRoots,
59
- livePaths,
60
- }: {
61
- state: CalculatorState;
62
-
63
- derivablePick: (path: string) => string | undefined;
64
-
65
- pins?: Record<string, string>;
66
- autoRoots: readonly string[];
67
-
68
- livePaths?: ReadonlySet<string>;
69
- }): string {
70
28
  const dict = new Map<string, number>();
71
29
  const ref = (id: string): number => {
72
30
  const hit = dict.get(id);
@@ -75,124 +33,103 @@ export function encodeCalcShare({
75
33
  dict.set(id, idx);
76
34
  return idx;
77
35
  };
78
- const target = state.target
79
- ? ([
80
- ref(state.target.id),
81
- state.target.kind === 'fluid' ? 1 : 0,
82
- state.target.amount,
83
- ] satisfies [number, number, number])
84
- : undefined;
85
- const inventory = (state.inventory ?? []).map(
86
- (row): [number, number, number] => [
87
- ref(row.id),
88
- row.kind === 'fluid' ? 1 : 0,
89
- row.amount,
90
- ],
91
- );
92
- const selections: Record<string, number> = {};
93
- for (const [key, value] of Object.entries(pins)) {
94
- if (value) selections[`#${ref(key)}`] = ref(value);
95
- }
96
- for (const [path, recipeId] of Object.entries(state.selections)) {
97
- if (!recipeId) continue;
98
- if (livePaths && /^g\d/.test(path) && !livePaths.has(path)) continue;
99
- if (derivablePick(path) === recipeId) continue;
100
- selections[path] = ref(recipeId);
101
- }
36
+ const indexOf = new Map<string, number>();
37
+ const cards = state.cards.map((card, index) => {
38
+ indexOf.set(card.id, index);
39
+ const row = [
40
+ ref(card.materialId),
41
+ card.kind === 'fluid' ? 1 : 0,
42
+ card.recipeId ? ref(card.recipeId) : -1,
43
+ card.crafts,
44
+ Math.round(card.pos?.x ?? 0),
45
+ Math.round(card.pos?.y ?? 0),
46
+ card.pos ? 1 : 0,
47
+ ];
48
+ return row;
49
+ });
50
+ const wires = state.wires.flatMap((wire) => {
51
+ const from = indexOf.get(wire.from);
52
+ const to = indexOf.get(wire.to);
53
+ return from === undefined || to === undefined
54
+ ? []
55
+ : [[from, to, wire.input, wire.out ?? 0, wire.amount ?? 0]];
56
+ });
102
57
  const tagSelections: Record<string, number> = {};
103
58
  for (const [tagId, memberId] of Object.entries(state.tagSelections)) {
104
- if (!memberId) continue;
105
- tagSelections[String(ref(tagId))] = ref(memberId);
59
+ if (memberId) tagSelections[String(ref(tagId))] = ref(memberId);
106
60
  }
107
- const payload: SharePayload = {
108
- v: 1,
61
+ const payload = {
62
+ v: 2,
109
63
  d: [...dict.keys()],
110
- ...(target ? { t: target } : {}),
111
- ...(inventory.length ? { i: inventory } : {}),
112
- a: [...autoRoots],
113
- s: selections,
64
+ c: cards,
65
+ w: wires,
114
66
  g: tagSelections,
115
- c: [...(state.collapsed ?? [])],
116
67
  };
117
68
  const packed = deflateSync(strToU8(JSON.stringify(payload)));
118
69
  return `x${encodeUrlSafe(btoa(u8ToBinary(packed)))}`;
119
70
  }
120
71
 
121
- function decodeCalcSharePayload(text: string): CalculatorState | null {
122
- const bin = atob(decodeUrlSafe(text.slice(1)));
123
- const packed = Uint8Array.from(bin, (c) => c.charCodeAt(0));
124
- const payload = JSON.parse(strFromU8(inflateSync(packed))) as SharePayload;
125
- if (payload.v !== 1 || !Array.isArray(payload.d)) return null;
126
- const dict = payload.d;
127
- const id = (idx: unknown): string | null =>
128
- typeof idx === 'number' && idx >= 0 && idx < dict.length
129
- ? dict[idx]!
130
- : null;
131
- const material = (row: unknown): CalcMaterial | null => {
132
- if (!Array.isArray(row)) return null;
133
- const rowId = id(row[0]);
134
- if (!rowId) return null;
135
- return {
136
- id: rowId,
137
- kind: row[1] === 1 ? ('fluid' as const) : ('item' as const),
138
- amount: Number(row[2]) || 1,
139
- };
140
- };
141
-
142
- const targetRow = Array.isArray(payload.t?.[0])
143
- ? payload.t[0]
144
- : payload.t;
145
- const targetMat = material(targetRow);
146
- const target: CalculatorTarget | null = targetMat
147
- ? { id: targetMat.id, kind: targetMat.kind, amount: targetMat.amount }
148
- : null;
149
- if (!target) return null;
150
- const inventory = Array.isArray(payload.i)
151
- ? payload.i.flatMap((row): CalcMaterial[] => {
152
- const mat = material(row);
153
- return mat ? [mat] : [];
154
- })
155
- : [];
156
- const selections: Record<string, string> = {};
157
- for (const [key, value] of Object.entries(payload.s ?? {})) {
158
- const recipeId = id(value);
159
- if (!recipeId) continue;
160
- if (key.startsWith('#')) {
161
- const materialId = id(Number(key.slice(1)));
162
- if (materialId) selections[materialId] = recipeId;
163
- } else {
164
- selections[key] = recipeId;
165
- }
166
- }
167
- const tagSelections: Record<string, string> = {};
168
- for (const [tagIdx, memberIdx] of Object.entries(payload.g ?? {})) {
169
- const tagId = id(Number(tagIdx));
170
- const memberId = id(memberIdx);
171
- if (tagId && memberId) tagSelections[tagId] = memberId;
172
- }
173
- const collapsed = Array.isArray(payload.c)
174
- ? payload.c.filter((p): p is string => typeof p === 'string')
175
- : [];
176
- const auto = Array.isArray(payload.a)
177
- ? payload.a.filter((p): p is string => typeof p === 'string')
178
- : [];
179
- return {
180
- target,
181
- ...(inventory.length ? { inventory } : {}),
182
- selections,
183
- tagSelections,
184
- ...(collapsed.length ? { collapsed } : {}),
185
- ...(auto.length ? { auto } : {}),
186
- };
187
- }
188
-
189
72
  export function decodeCalcState(encoded: string): CalculatorState | null {
190
- if (!encoded.trim()) return null;
73
+ const text = encoded.trim();
74
+ if (!text.startsWith('x')) return null;
191
75
  try {
192
- const text = encoded.trim();
193
- if (text.startsWith('x')) return decodeCalcSharePayload(text);
194
- return null;
76
+ const bin = atob(decodeUrlSafe(text.slice(1)));
77
+ const packed = Uint8Array.from(bin, (c) => c.charCodeAt(0));
78
+ const payload = JSON.parse(strFromU8(inflateSync(packed))) as {
79
+ v?: number;
80
+ d?: string[];
81
+ c?: number[][];
82
+ w?: number[][];
83
+ g?: Record<string, number>;
84
+ };
85
+ if (payload.v !== 2 || !Array.isArray(payload.d)) return null;
86
+ const dict = payload.d;
87
+ const id = (idx: unknown): string | null =>
88
+ typeof idx === 'number' && idx >= 0 && idx < dict.length
89
+ ? dict[idx]!
90
+ : null;
91
+ const cards: CalcCard[] = [];
92
+ for (const [index, row] of (payload.c ?? []).entries()) {
93
+ if (!Array.isArray(row)) continue;
94
+ const materialId = id(row[0]);
95
+ if (!materialId) continue;
96
+ const kind = row[1] === 1 ? 'fluid' : 'item';
97
+ const recipeId = row[2] === -1 ? null : id(row[2]);
98
+ cards.push({
99
+ id: `c${index}`,
100
+ materialId,
101
+ kind,
102
+ recipeId: recipeId ?? null,
103
+ crafts: Number(row[3]) || 1,
104
+ pos:
105
+ row[6] === 1
106
+ ? { x: Number(row[4]) || 0, y: Number(row[5]) || 0 }
107
+ : undefined,
108
+ });
109
+ }
110
+ const wires: CalcWire[] = [];
111
+ for (const row of payload.w ?? []) {
112
+ if (!Array.isArray(row)) continue;
113
+ const from = cards[row[0]!];
114
+ const to = cards[row[1]!];
115
+ if (!from || !to) continue;
116
+ wires.push({
117
+ from: from.id,
118
+ to: to.id,
119
+ input: Number(row[2]) || 0,
120
+ out: Number(row[3]) || undefined,
121
+ amount: Number(row[4]) || undefined,
122
+ });
123
+ }
124
+ const tagSelections: Record<string, string> = {};
125
+ for (const [tagIdx, memberIdx] of Object.entries(payload.g ?? {})) {
126
+ const tagId = id(Number(tagIdx));
127
+ const memberId = id(memberIdx);
128
+ if (tagId && memberId) tagSelections[tagId] = memberId;
129
+ }
130
+ return { cards, wires, tagSelections };
195
131
  } catch {
196
132
  return null;
197
133
  }
198
134
  }
135
+
@@ -1,12 +1,12 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
1
2
  import type { RecipeCompactIndex } from '@terrafirmagreg-team/recipe/types';
2
- import { runFill } from './fill';
3
3
  import type { FlushHandler, ResolveInput } from './protocol';
4
4
  import { runResolve } from './resolve';
5
5
  import { resetWorkerState, state } from './state';
6
6
 
7
7
  export const api = {
8
8
  async init(
9
- nextDataset: string,
9
+ nextDataset: DatasetRef,
10
10
  nextCatalog: RecipeCompactIndex,
11
11
  flushHandler: FlushHandler,
12
12
  ) {
@@ -14,20 +14,12 @@ export const api = {
14
14
  state.onFlush = flushHandler;
15
15
  },
16
16
  async resolve(input: ResolveInput) {
17
-
18
17
  state.resolveGen += 1;
19
- state.fillGen += 1;
20
18
  await runResolve(input);
21
19
  },
22
- async fill(input: ResolveInput & { roots: string[] }) {
23
- state.fillGen += 1;
24
- state.resolveGen += 1;
25
- return runFill(input);
26
- },
27
-
28
20
  async invalidate() {
29
21
  state.resolveGen += 1;
30
- state.fillGen += 1;
31
22
  state.epoch += 1;
32
23
  },
33
24
  };
25
+
@@ -63,3 +63,4 @@ export async function fetchWanted(
63
63
  }
64
64
  return progressed;
65
65
  }
66
+
@@ -1,7 +1,9 @@
1
1
  import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
2
2
  import type { RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
3
- import type { CalcMaterial, CalcRecipe, TagResolution } from '@terrafirmagreg-team/recipe/types/calc';
4
- import { flattenTree, mergeAmounts, type CalcGoal } from '@terrafirmagreg-team/recipe/lib/calc/tree';
3
+ import type {
4
+ CalcRecipe,
5
+ TagResolution,
6
+ } from '@terrafirmagreg-team/recipe/types/calc';
5
7
  import { state } from './state';
6
8
 
7
9
  export function createFlush() {
@@ -17,22 +19,8 @@ export function createFlush() {
17
19
 
18
20
  export type Pending = ReturnType<typeof createFlush>;
19
21
 
20
- function deriveSummary(goals: CalcGoal[]): {
21
- raw: CalcMaterial[];
22
- catalysts: CalcMaterial[];
23
- } {
24
- const raw: CalcMaterial[] = [];
25
- const catalysts: CalcMaterial[] = [];
26
- for (const goal of goals) {
27
- const flat = flattenTree(goal.node);
28
- raw.push(...flat.cost);
29
- catalysts.push(...flat.catalysts);
30
- }
31
- return { raw: mergeAmounts(raw), catalysts: mergeAmounts(catalysts) };
32
- }
33
-
34
- export function postFlush(pending: Pending, goals?: CalcGoal[]) {
35
- if (!pending.dirty && !goals) return;
22
+ export function postFlush(pending: Pending) {
23
+ if (!pending.dirty) return;
36
24
  pending.dirty = false;
37
25
  state.onFlush({
38
26
  epoch: state.epoch,
@@ -41,8 +29,6 @@ export function postFlush(pending: Pending, goals?: CalcGoal[]) {
41
29
  recipes: [...pending.recipes],
42
30
  bodies: [...pending.bodies],
43
31
  dead: [...pending.dead],
44
- goals,
45
- ...(goals ? { summary: deriveSummary(goals) } : {}),
46
32
  });
47
33
  pending.out.clear();
48
34
  pending.tag.clear();
@@ -50,3 +36,4 @@ export function postFlush(pending: Pending, goals?: CalcGoal[]) {
50
36
  pending.bodies.clear();
51
37
  pending.dead.clear();
52
38
  }
39
+
@@ -1,4 +1,3 @@
1
-
2
1
  import { expose } from 'comlink';
3
2
  import { api } from './api';
4
3
 
@@ -6,3 +5,4 @@ export type CalcWorkerApi = typeof api;
6
5
  export type { CalcWorkerFlush } from './protocol';
7
6
 
8
7
  expose(api);
8
+
@@ -1,32 +1,35 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
1
2
  import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
2
3
  import type { RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
3
4
  import type {
4
- CalcMaterial,
5
5
  CalcRecipe,
6
- CalculatorTarget,
7
6
  TagResolution,
8
7
  } from '@terrafirmagreg-team/recipe/types/calc';
9
- import type { CalcGoal } from '@terrafirmagreg-team/recipe/lib/calc/tree';
10
8
 
11
9
  export type ResolveInput = {
12
- target: CalculatorTarget | null;
13
- inventory: CalcMaterial[];
14
- selections: Record<string, string>;
15
- tagSelections: Record<string, string>;
16
- collapsed: string[];
17
- auto: string[];
10
+ materials?: string[];
11
+ recipes?: string[];
12
+ tags?: string[];
18
13
  };
19
14
 
20
15
  export type CalcWorkerFlush = {
21
-
22
16
  epoch: number;
23
17
  out: [string, RecipeCompactRow[]][];
24
18
  tag: [string, TagResolution][];
25
19
  recipes: [string, CalcRecipe][];
26
20
  bodies: [string, Recipe][];
27
21
  dead: string[];
28
- goals?: CalcGoal[];
29
- summary?: { raw: CalcMaterial[]; catalysts: CalcMaterial[] };
30
22
  };
31
23
 
32
- export type FlushHandler = (flush: CalcWorkerFlush) => void;
24
+ export type FlushHandler = (msg: CalcWorkerFlush) => void;
25
+
26
+ export type CalcWorkerApi = {
27
+ init(
28
+ dataset: DatasetRef,
29
+ catalog: unknown,
30
+ flushHandler: FlushHandler,
31
+ ): Promise<void>;
32
+ resolve(input: ResolveInput): Promise<void>;
33
+ invalidate(): Promise<void>;
34
+ };
35
+
@@ -1,101 +1,49 @@
1
- import type { CalcNode } from '@terrafirmagreg-team/recipe/types/calc';
2
- import { pickAutoRecipe } from '@terrafirmagreg-team/recipe/lib/calc/resolve';
3
- import {
4
- AUTO_NODE_BUDGET,
5
- buildGoal,
6
- type BuildContext,
7
- type CalcGoal,
8
- } from '@terrafirmagreg-team/recipe/lib/calc/tree';
9
- import { createWorkerSummaries, fetchWanted } from './fetch';
1
+ import { fetchWanted } from './fetch';
10
2
  import { createFlush, postFlush } from './flush';
11
3
  import type { ResolveInput } from './protocol';
12
4
  import { state } from './state';
13
5
 
14
6
  export async function runResolve(input: ResolveInput) {
15
7
  const gen = state.resolveGen;
16
- const summaries = createWorkerSummaries(state.recipeMap);
17
8
  const pending = createFlush();
18
- const collapsed = new Set(input.collapsed);
19
- const ctx = (
20
- autoRoots: readonly string[],
21
- ): Omit<BuildContext, 'pool' | 'used' | 'usedMaterials'> => ({
22
- recipeMap: state.recipeMap,
23
- getRecipesForItem: summaries.get,
24
- selections: input.selections,
25
- tagSelections: input.tagSelections,
26
- tagMembers: (id) => state.tagMap.get(id),
27
- collapsed,
28
- autoRoots,
29
- autoBudget: { left: AUTO_NODE_BUDGET },
30
- recipesResolved: (id) => state.outMap.has(id),
31
- deadIds: state.deadIds,
32
- });
33
- const localRecipes = state.recipeMap;
34
- let latestGoals: CalcGoal[] | undefined;
35
- for (let iter = 0; iter < 200; iter += 1) {
9
+ const wantedRecipes = new Set(
10
+ (input.recipes ?? []).filter(
11
+ (id) => id && !state.recipeMap.has(id) && !state.deadIds.has(id),
12
+ ),
13
+ );
14
+ const wantedOut = new Set(
15
+ (input.materials ?? []).filter((id) => id && !state.outMap.has(id)),
16
+ );
17
+ const wantedTag = new Set(
18
+ (input.tags ?? []).filter((id) => id && !state.tagMap.has(id)),
19
+ );
20
+ for (let iter = 0; iter < 50; iter += 1) {
36
21
  if (gen !== state.resolveGen) return;
37
- const wanted = new Set<string>();
38
- const wantedOut = new Set<string>();
39
- const wantedTag = new Set<string>();
40
- const built: CalcGoal[] = [];
41
- if (input.target) {
42
- const goalCtx = ctx(input.auto);
43
- const goal = buildGoal(input.target, goalCtx, 'g0', input.inventory);
44
- built.push(goal);
45
- const walk = (n: CalcNode, ancestors: Set<string>): void => {
46
- const next =
47
- n.recipe && n.need > 0
48
- ? new Set(ancestors).add(`${n.kind}:${n.materialId}`)
49
- : ancestors;
50
- if (n.tagId && !state.tagMap.has(n.tagId)) wantedTag.add(n.tagId);
51
- if (!state.outMap.has(n.materialId)) wantedOut.add(n.materialId);
52
- if (n.recipe) {
53
- for (const entry of n.recipe.inputs) {
54
- if (entry.tagId && !state.tagMap.has(entry.tagId)) {
55
- wantedTag.add(entry.tagId);
56
- }
57
- }
58
- } else if (n.auto) {
59
- const autoPick = pickAutoRecipe(
60
- n,
61
- input.selections,
62
- ancestors,
63
- goalCtx,
64
- );
65
- if (
66
- autoPick.pick &&
67
- !localRecipes.has(autoPick.pick) &&
68
- !state.deadIds.has(autoPick.pick)
69
- ) {
70
- wanted.add(autoPick.pick);
71
- }
72
- for (const id of autoPick.wantRecipes) {
73
- if (!localRecipes.has(id) && !state.deadIds.has(id)) wanted.add(id);
74
- }
75
- for (const id of autoPick.wantOut) {
76
- if (!state.outMap.has(id)) wantedOut.add(id);
77
- }
78
- }
79
- for (const child of n.children) walk(child, next);
80
- };
81
- walk(goal.node, new Set());
82
- }
83
- for (const id of Object.values(input.selections)) {
84
- if (id && !localRecipes.has(id) && !state.deadIds.has(id)) wanted.add(id);
85
- }
86
- latestGoals = built;
87
- if (!wanted.size && !wantedOut.size && !wantedTag.size) break;
22
+ if (!wantedRecipes.size && !wantedOut.size && !wantedTag.size) break;
88
23
  const progressed = await fetchWanted(
89
24
  pending,
90
- wanted,
25
+ wantedRecipes,
91
26
  wantedOut,
92
27
  wantedTag,
93
- localRecipes,
28
+ state.recipeMap,
94
29
  );
95
30
  if (gen !== state.resolveGen) return;
96
31
  if (!progressed) break;
97
- summaries.clear();
98
- if (iter % 5 === 4) postFlush(pending, latestGoals);
32
+ for (const id of input.tags ?? []) {
33
+ if (id && !state.tagMap.has(id)) wantedTag.add(id);
34
+ }
35
+ wantedRecipes.forEach((id) => {
36
+ if (state.recipeMap.has(id) || state.deadIds.has(id)) {
37
+ wantedRecipes.delete(id);
38
+ }
39
+ });
40
+ wantedOut.forEach((id) => {
41
+ if (state.outMap.has(id)) wantedOut.delete(id);
42
+ });
43
+ wantedTag.forEach((id) => {
44
+ if (state.tagMap.has(id)) wantedTag.delete(id);
45
+ });
99
46
  }
100
- postFlush(pending, latestGoals);
47
+ postFlush(pending);
101
48
  }
49
+
@@ -1,10 +1,11 @@
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
1
2
  import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
2
3
  import type { RecipeCompactIndex, RecipeCompactRow } from '@terrafirmagreg-team/recipe/types';
3
4
  import type { CalcRecipe, TagResolution } from '@terrafirmagreg-team/recipe/types/calc';
4
5
  import type { FlushHandler } from './protocol';
5
6
 
6
7
  export type CalcWorkerState = {
7
- dataset: string;
8
+ dataset: DatasetRef;
8
9
  catalog: RecipeCompactIndex | null;
9
10
  onFlush: FlushHandler;
10
11
  outMap: Map<string, RecipeCompactRow[]>;
@@ -15,11 +16,10 @@ export type CalcWorkerState = {
15
16
 
16
17
  epoch: number;
17
18
  resolveGen: number;
18
- fillGen: number;
19
19
  };
20
20
 
21
21
  export const state: CalcWorkerState = {
22
- dataset: '',
22
+ dataset: { id: '' },
23
23
  catalog: null,
24
24
  onFlush: () => {},
25
25
  outMap: new Map(),
@@ -29,17 +29,15 @@ export const state: CalcWorkerState = {
29
29
  deadIds: new Set(),
30
30
  epoch: 0,
31
31
  resolveGen: 0,
32
- fillGen: 0,
33
32
  };
34
33
 
35
34
  export function resetWorkerState(
36
- nextDataset: string,
35
+ nextDataset: DatasetRef,
37
36
  nextCatalog: RecipeCompactIndex,
38
37
  ) {
39
38
  state.dataset = nextDataset;
40
39
  state.catalog = nextCatalog;
41
40
  state.resolveGen += 1;
42
- state.fillGen += 1;
43
41
  state.epoch += 1;
44
42
  state.outMap.clear();
45
43
  state.tagMap.clear();
@@ -47,3 +45,4 @@ export function resetWorkerState(
47
45
  state.bodyMap.clear();
48
46
  state.deadIds.clear();
49
47
  }
48
+
@@ -62,3 +62,4 @@ export function textInStrip(
62
62
  widget.x < geo.right
63
63
  );
64
64
  }
65
+