@poe2-toolkit/tree-extractor 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vladislav Rajtmajer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @poe2-toolkit/tree-extractor
2
+
3
+ [![npm](https://img.shields.io/npm/v/@poe2-toolkit/tree-extractor.svg)](https://www.npmjs.com/package/@poe2-toolkit/tree-extractor)
4
+ [![types included](https://img.shields.io/badge/types-included-blue.svg)](#)
5
+ [![ESM only](https://img.shields.io/badge/module-ESM-f7df1e.svg)](#)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
+
8
+ Builds the **Path of Exile 2 passive tree** — the node data and the sprite
9
+ atlases — straight from the official GGPK / patch server, in the shape
10
+ [`@poe2-toolkit/tree-core`](../poe2-tree-core) consumes.
11
+
12
+ It is source-agnostic: it never downloads anything itself. You hand it a
13
+ [`@poe2-toolkit/ggpk`](../poe2-ggpk) source and it returns the data, ready to use or to
14
+ write wherever you publish it.
15
+
16
+ **Code only.** This package ships no game data and no art. Everything it produces
17
+ is read from the patch server at run time and handed back to you; nothing from
18
+ the game is bundled or stored here.
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm install @poe2-toolkit/tree-extractor @poe2-toolkit/ggpk
24
+ ```
25
+
26
+ Node 18+. ESM only. TypeScript types are included.
27
+
28
+ ## The contract
29
+
30
+ The library **returns formatted data** — it performs no I/O of its own beyond
31
+ what the source serves, and it never writes to disk. You decide what to do with
32
+ the result.
33
+
34
+ ```ts
35
+ import { createCdnSource } from '@poe2-toolkit/ggpk';
36
+ import { extractTree } from '@poe2-toolkit/tree-extractor';
37
+
38
+ const source = await createCdnSource({
39
+ patch: '4.5.3.1.7',
40
+ tablesDir: './tables/English',
41
+ cacheDir: './.cache',
42
+ });
43
+
44
+ const { data, graphics, centre } = await extractTree(source);
45
+ ```
46
+
47
+ `extractTree(source)` returns a `TreeBundle`:
48
+
49
+ | Field | Type | What it is |
50
+ | --- | --- | --- |
51
+ | `data` | `TreeExport` | The `data.json` payload — `@poe2-toolkit/tree-core`'s normalize input (nodes, groups, classes, jewel slots, attribute choices). |
52
+ | `graphics` | `GraphicsResult` | The four sprite atlases (`skills`, `skills-disabled`, `frame`, `mastery-effect-active`), each a packed PNG plus its frame-map, with a report of what packed or was skipped. |
53
+ | `centre` | `Record<string, Buffer>` | Centre art keyed by output name (`portrait-ranger`, `ascendancy-deadeye`, `ring-static`, …), each a PNG buffer. |
54
+
55
+ The individual steps are exported too, if you only need one:
56
+ `buildTree(source)`, `buildGraphics(source, data)`, `buildCentre(source)`.
57
+
58
+ ## CLI: write the bundle to disk
59
+
60
+ When you do want files, the bundled CLI writes the whole bundle to a
61
+ **configurable output directory** — nothing is written inside the package:
62
+
63
+ ```sh
64
+ poe2-tree-extract \
65
+ --patch 4.5.3.1.7 \
66
+ --tables ./tables/English \
67
+ --cache ./.cache \
68
+ --out ./out/tree
69
+ ```
70
+
71
+ It writes `data.json`, the four atlases as `assets/<name>.png` + `<name>.json`,
72
+ and the centre art as `centre/<name>.png`. Output is PNG + JSON; converting the
73
+ PNGs to WebP for the web is a separate publish step left to you.
74
+
75
+ ## How it works
76
+
77
+ - **Data** comes from GGPK tables (`PassiveSkills`, `PassiveSkillMasteryGroups`,
78
+ `Characters`, `Ascendancy`, …) joined to the `.psg` passive-graph geometry,
79
+ with stat lines rendered through `@poe2-toolkit/ggpk`'s stat-description engine.
80
+ - **Sprites** are decoded from GGPK DDS art and packed into atlases keyed exactly
81
+ as the renderer expects. A sprite the source cannot serve is skipped and
82
+ reported — never pulled from a vendored asset.
83
+
84
+ Released PoE2 classes and ascendancies only; the GGPK's leftover PoE1 placeholder
85
+ classes are filtered out from the data.
86
+
87
+ ## Attributions and legal
88
+
89
+ This is an unofficial, fan-made project, **not** affiliated with, endorsed by, or
90
+ sponsored by Grinding Gear Games. "Path of Exile 2" is a trademark of Grinding
91
+ Gear Games, and all game content, data, and art are their property. This package
92
+ ships code only and stores nothing derived from the game. Thank you to Grinding
93
+ Gear Games for making Path of Exile 2.
94
+
95
+ GGPK access is provided by [`@poe2-toolkit/ggpk`](../poe2-ggpk), which builds on
96
+ [`pathofexile-dat`](https://github.com/SnosMe/poe-dat-viewer) (MIT, © SnosMe).
97
+ Full attribution is in the repository [NOTICE](../../NOTICE.md).
98
+
99
+ ## License
100
+
101
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shelf bin-packer: composites decoded sprites into one sheet and emits the
3
+ * frame-map the renderer reads (`{ frames: { key: { frame: { x, y, w, h } } } }`).
4
+ */
5
+ /** A decoded sprite to place in an atlas, keyed by its renderer atlas key. */
6
+ export interface AtlasSprite {
7
+ key: string;
8
+ width: number;
9
+ height: number;
10
+ rgba: Uint8Array;
11
+ }
12
+ /** One placed sprite's pixel rect inside the packed sheet. */
13
+ export interface AtlasFrame {
14
+ frame: {
15
+ x: number;
16
+ y: number;
17
+ w: number;
18
+ h: number;
19
+ };
20
+ }
21
+ /** A packed atlas: its PNG bytes and the frame-map keyed by sprite key. */
22
+ export interface PackedAtlas {
23
+ png: Buffer;
24
+ frames: Record<string, AtlasFrame>;
25
+ }
26
+ /** Pack sprites into a single sheet, tallest-first, wrapping at `maxWidth`. */
27
+ export declare function packAtlas(sprites: AtlasSprite[], maxWidth?: number): PackedAtlas;
28
+ /** Grayscale (luminance) copy, alpha preserved — the dimmed/inactive look. */
29
+ export declare function desaturate(rgba: Uint8Array): Uint8Array;
30
+ //# sourceMappingURL=atlas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atlas.d.ts","sourceRoot":"","sources":["../src/atlas.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAKD,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,QAAQ,SAAO,GAAG,WAAW,CAoC9E;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAUvD"}
package/dist/atlas.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shelf bin-packer: composites decoded sprites into one sheet and emits the
3
+ * frame-map the renderer reads (`{ frames: { key: { frame: { x, y, w, h } } } }`).
4
+ */
5
+ import { encodePng } from '@poe2-toolkit/ggpk';
6
+ /** Transparent gutter so neighbours never bleed under bilinear sampling. */
7
+ const PAD = 1;
8
+ /** Pack sprites into a single sheet, tallest-first, wrapping at `maxWidth`. */
9
+ export function packAtlas(sprites, maxWidth = 2048) {
10
+ // Tallest-first shelf packing keeps rows tight and the sheet near-square.
11
+ const sorted = [...sprites].sort((a, b) => b.height - a.height);
12
+ let x = 0, y = 0, shelfHeight = 0, sheetWidth = 0;
13
+ const placed = [];
14
+ for (const sprite of sorted) {
15
+ if (x + sprite.width + PAD > maxWidth && x > 0) {
16
+ x = 0;
17
+ y += shelfHeight + PAD;
18
+ shelfHeight = 0;
19
+ }
20
+ placed.push({ sprite, x, y });
21
+ x += sprite.width + PAD;
22
+ shelfHeight = Math.max(shelfHeight, sprite.height);
23
+ sheetWidth = Math.max(sheetWidth, x);
24
+ }
25
+ const sheetHeight = y + shelfHeight;
26
+ const sheet = new Uint8Array(sheetWidth * sheetHeight * 4);
27
+ const frames = {};
28
+ for (const { sprite, x: sx, y: sy } of placed) {
29
+ for (let row = 0; row < sprite.height; row++) {
30
+ const src = row * sprite.width * 4;
31
+ const dst = ((sy + row) * sheetWidth + sx) * 4;
32
+ sheet.set(sprite.rgba.subarray(src, src + sprite.width * 4), dst);
33
+ }
34
+ frames[sprite.key] = { frame: { x: sx, y: sy, w: sprite.width, h: sprite.height } };
35
+ }
36
+ return { png: encodePng(sheetWidth, sheetHeight, sheet), frames };
37
+ }
38
+ /** Grayscale (luminance) copy, alpha preserved — the dimmed/inactive look. */
39
+ export function desaturate(rgba) {
40
+ const out = new Uint8Array(rgba.length);
41
+ for (let i = 0; i < rgba.length; i += 4) {
42
+ const lum = Math.round(0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2]);
43
+ out[i] = out[i + 1] = out[i + 2] = lum;
44
+ out[i + 3] = rgba[i + 3];
45
+ }
46
+ return out;
47
+ }
48
+ //# sourceMappingURL=atlas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atlas.js","sourceRoot":"","sources":["../src/atlas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Extracts the tree's centre art from GGPK: per-class portraits (real PoE2
3
+ * classes only — those with a released ascendancy), per-ascendancy portraits
4
+ * (released only), and the two hub-ring sprites. All decoded to PNG.
5
+ *
6
+ * Like the rest of the rewrite this is GGPK-only: art the source cannot serve is
7
+ * skipped, never pulled from a vendored GGG sheet.
8
+ */
9
+ import type { GgpkImageSource, GgpkSource } from '@poe2-toolkit/ggpk';
10
+ /** A source able to both read tables and fetch images. */
11
+ export type CentreSource = GgpkSource & GgpkImageSource;
12
+ /**
13
+ * Build the centre art. Returns a map of output name (without extension, e.g.
14
+ * `portrait-ranger`, `ascendancy-deadeye`, `ring-static`) to its PNG bytes.
15
+ */
16
+ export declare function buildCentre(source: CentreSource): Promise<Record<string, Buffer>>;
17
+ //# sourceMappingURL=buildCentre.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildCentre.d.ts","sourceRoot":"","sources":["../src/buildCentre.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,eAAe,EAAE,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAKpE,0DAA0D;AAC1D,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,eAAe,CAAC;AAuBxD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoDvF"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Extracts the tree's centre art from GGPK: per-class portraits (real PoE2
3
+ * classes only — those with a released ascendancy), per-ascendancy portraits
4
+ * (released only), and the two hub-ring sprites. All decoded to PNG.
5
+ *
6
+ * Like the rest of the rewrite this is GGPK-only: art the source cannot serve is
7
+ * skipped, never pulled from a vendored GGG sheet.
8
+ */
9
+ import { encodePng } from '@poe2-toolkit/ggpk';
10
+ /** The hub-ring sprites, by output name and UIImages logical name. */
11
+ const RING = [
12
+ { out: 'ring-static', name: 'Art/2DArt/UIImages/InGame/PassiveTree/PassiveTreeMainCircle' },
13
+ { out: 'ring-active', name: 'Art/2DArt/UIImages/InGame/PassiveTree/PassiveTreeMainCircleActive2' },
14
+ ];
15
+ function isReleased(asc) {
16
+ return Boolean(asc.Name) && !asc.Name.includes('[DNT') && !asc.Disabled;
17
+ }
18
+ /** Decode a DDS path to a PNG buffer, or `null` if the source can't serve it. */
19
+ async function emit(source, ddsPath) {
20
+ if (!ddsPath?.toLowerCase().endsWith('.dds')) {
21
+ return null;
22
+ }
23
+ const img = await source.dds(ddsPath);
24
+ return img ? encodePng(img.width, img.height, img.rgba) : null;
25
+ }
26
+ /**
27
+ * Build the centre art. Returns a map of output name (without extension, e.g.
28
+ * `portrait-ranger`, `ascendancy-deadeye`, `ring-static`) to its PNG bytes.
29
+ */
30
+ export async function buildCentre(source) {
31
+ const Characters = (await source.table('Characters'));
32
+ const Ascendancy = (await source.table('Ascendancy'));
33
+ const out = {};
34
+ // Real PoE2 classes only: the ones with a released ascendancy. GGPK still
35
+ // carries 4 PoE1 placeholder classes whose ascendancies are all [DNT-UNUSED].
36
+ const realClassIndex = new Set();
37
+ for (const asc of Ascendancy) {
38
+ if (isReleased(asc) && asc.Character != null) {
39
+ realClassIndex.add(asc.Character);
40
+ }
41
+ }
42
+ for (const [index, character] of Characters.entries()) {
43
+ if (!realClassIndex.has(index)) {
44
+ continue;
45
+ }
46
+ const png = await emit(source, character.PassiveTreeImage);
47
+ if (png) {
48
+ out[`portrait-${character.Name.toLowerCase()}`] = png;
49
+ }
50
+ }
51
+ for (const asc of Ascendancy) {
52
+ if (!isReleased(asc)) {
53
+ continue;
54
+ }
55
+ const slug = asc.Name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
56
+ const png = await emit(source, asc.PassiveTreeImage);
57
+ if (png) {
58
+ out[`ascendancy-${slug}`] = png;
59
+ }
60
+ }
61
+ // Hub ring: static ornate circle + active-class edge marker.
62
+ for (const { out: name, name: logical } of RING) {
63
+ const ref = await source.resolveSprite(logical);
64
+ const png = ref ? await emit(source, ref.path) : null;
65
+ if (png) {
66
+ out[name] = png;
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ //# sourceMappingURL=buildCentre.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildCentre.js","sourceRoot":"","sources":["../src/buildCentre.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Builds the passive-tree sprite atlases from GGPK art, in the shape the
3
+ * renderer reads: skill icons (`skills` + desaturated `skills-disabled`), node
4
+ * frames (`frame`), and mastery effect patterns (`mastery-effect-active`).
5
+ * Atlas keys mirror the engine's `spriteKeys`. Source: GGPK only.
6
+ *
7
+ * Unlike the legacy script this has no GGG-export fallback: a sprite the source
8
+ * cannot serve is skipped and reported, never pulled from a vendored asset.
9
+ */
10
+ import type { GgpkImageSource, GgpkSource } from '@poe2-toolkit/ggpk';
11
+ import type { PackedAtlas } from './atlas.js';
12
+ import type { TreeExport } from './buildTree.js';
13
+ /** The four atlases this build produces, keyed by atlas name. */
14
+ export interface TreeAtlases {
15
+ skills: PackedAtlas;
16
+ 'skills-disabled': PackedAtlas;
17
+ frame: PackedAtlas;
18
+ 'mastery-effect-active': PackedAtlas;
19
+ }
20
+ /** Per-atlas counts of sprites packed and sources missing, for reporting. */
21
+ export interface GraphicsReport {
22
+ skills: {
23
+ packed: number;
24
+ missing: number;
25
+ };
26
+ frames: {
27
+ packed: number;
28
+ };
29
+ masteryEffects: {
30
+ packed: number;
31
+ missing: number;
32
+ };
33
+ }
34
+ /** The build's atlases plus a report of what was packed or skipped. */
35
+ export interface GraphicsResult {
36
+ atlases: TreeAtlases;
37
+ report: GraphicsReport;
38
+ }
39
+ /** A source able to both read tables and fetch images. */
40
+ export type GraphicsSource = GgpkSource & GgpkImageSource;
41
+ export declare function buildGraphics(source: GraphicsSource, tree: TreeExport): Promise<GraphicsResult>;
42
+ //# sourceMappingURL=buildGraphics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildGraphics.d.ts","sourceRoot":"","sources":["../src/buildGraphics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,KAAK,EAAc,WAAW,EAAC,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,iBAAiB,EAAE,WAAW,CAAC;IAC/B,KAAK,EAAE,WAAW,CAAC;IACnB,uBAAuB,EAAE,WAAW,CAAC;CACtC;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,cAAc,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;CACxB;AAcD,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,eAAe,CAAC;AAmB1D,wBAAsB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CA0HrG"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Builds the passive-tree sprite atlases from GGPK art, in the shape the
3
+ * renderer reads: skill icons (`skills` + desaturated `skills-disabled`), node
4
+ * frames (`frame`), and mastery effect patterns (`mastery-effect-active`).
5
+ * Atlas keys mirror the engine's `spriteKeys`. Source: GGPK only.
6
+ *
7
+ * Unlike the legacy script this has no GGG-export fallback: a sprite the source
8
+ * cannot serve is skipped and reported, never pulled from a vendored asset.
9
+ */
10
+ import { desaturate, packAtlas } from './atlas.js';
11
+ /** Engine variant prefix for a node's skill icon (mirrors `iconKeyFor`). */
12
+ function variantOf(node) {
13
+ if (node.isMastery) {
14
+ return null; // masteries render as their effect pattern
15
+ }
16
+ if (node.isKeystone) {
17
+ return 'keystone';
18
+ }
19
+ if (node.isNotable) {
20
+ return 'notable';
21
+ }
22
+ return 'normal';
23
+ }
24
+ export async function buildGraphics(source, tree) {
25
+ // --- skill icons: skills (active) + skills-disabled (desaturated) ----------
26
+ // icon-path -> variant; first node wins, same icon shares a variant.
27
+ const wanted = new Map();
28
+ for (const node of Object.values(tree.nodes)) {
29
+ const variant = variantOf(node);
30
+ if (variant && node.icon && !wanted.has(`${variant}:${node.icon}`)) {
31
+ wanted.set(`${variant}:${node.icon}`, { variant, icon: node.icon });
32
+ }
33
+ }
34
+ // +attribute choice icons (Str/Dex/Int) — shown on the node once an attribute
35
+ // is picked; not a node's own icon, so add them explicitly.
36
+ for (const override of Object.values(tree.skillOverrides)) {
37
+ const icon = override.icon;
38
+ if (typeof icon === 'string' && icon && !wanted.has(`normal:${icon}`)) {
39
+ wanted.set(`normal:${icon}`, { variant: 'normal', icon });
40
+ }
41
+ }
42
+ const active = [];
43
+ const inactive = [];
44
+ let iconsMissing = 0;
45
+ for (const { variant, icon } of wanted.values()) {
46
+ const img = await source.dds(icon);
47
+ if (!img) {
48
+ iconsMissing += 1;
49
+ continue;
50
+ }
51
+ active.push({ key: `${variant}Active:${icon}`, width: img.width, height: img.height, rgba: img.rgba });
52
+ inactive.push({ key: `${variant}Inactive:${icon}`, width: img.width, height: img.height, rgba: desaturate(img.rgba) });
53
+ }
54
+ // --- node frames: frame atlas ----------------------------------------------
55
+ // The UIArt "Character" row drives the main tree, "Ascendancy" the disc frames.
56
+ // NodeFrameArt Normal/CanAllocate/Active map to the renderer's states.
57
+ const UIArt = (await source.table('PassiveSkillTreeUIArt'));
58
+ const NodeFrameArt = (await source.table('PassiveSkillTreeNodeFrameArt'));
59
+ const mainUi = UIArt.find((r) => r.Id === 'Character');
60
+ const ascUi = UIArt.find((r) => r.Id === 'Ascendancy');
61
+ const frameSprites = [];
62
+ const addFrame = async (key, frameArtIndex, state) => {
63
+ const art = frameArtIndex != null ? NodeFrameArt[frameArtIndex] : undefined;
64
+ const path = art?.[state];
65
+ const img = path ? await source.uiSprite(path) : null;
66
+ if (img) {
67
+ frameSprites.push({ key, width: img.width, height: img.height, rgba: img.rgba });
68
+ }
69
+ };
70
+ // Main-tree frames (engine keys from frameKeyFor).
71
+ await addFrame('KeystoneFrameUnallocated', mainUi?.KeystoneFrame, 'Normal');
72
+ await addFrame('KeystoneFrameCanAllocate', mainUi?.KeystoneFrame, 'CanAllocate');
73
+ await addFrame('KeystoneFrameAllocated', mainUi?.KeystoneFrame, 'Active');
74
+ await addFrame('NotableFrameUnallocated', mainUi?.NotableFrame, 'Normal');
75
+ await addFrame('NotableFrameCanAllocate', mainUi?.NotableFrame, 'CanAllocate');
76
+ await addFrame('NotableFrameAllocated', mainUi?.NotableFrame, 'Active');
77
+ await addFrame('JewelFrameUnallocated', mainUi?.JewelFrame, 'Normal');
78
+ await addFrame('JewelFrameCanAllocate', mainUi?.JewelFrame, 'CanAllocate');
79
+ await addFrame('JewelFrameAllocated', mainUi?.JewelFrame, 'Active');
80
+ await addFrame('PSSkillFrame', mainUi?.PassiveFrame, 'Normal');
81
+ await addFrame('PSSkillFrameHighlighted', mainUi?.PassiveFrame, 'CanAllocate');
82
+ await addFrame('PSSkillFrameActive', mainUi?.PassiveFrame, 'Active');
83
+ // Ascendancy disc frames.
84
+ await addFrame('AscendancyFrameNormalUnallocated', ascUi?.PassiveFrame, 'Normal');
85
+ await addFrame('AscendancyFrameNormalAllocated', ascUi?.PassiveFrame, 'Active');
86
+ await addFrame('AscendancyFrameNotableUnallocated', ascUi?.NotableFrame, 'Normal');
87
+ await addFrame('AscendancyFrameNotableAllocated', ascUi?.NotableFrame, 'Active');
88
+ await addFrame('AscendancyStartNode', ascUi?.AscendancyStart, 'Active');
89
+ // --- mastery effect patterns: mastery-effect-active atlas ------------------
90
+ // Renderer keys them `masteryEffectActive:<ActiveEffectImage>.png`.
91
+ const effectImages = new Set();
92
+ for (const node of Object.values(tree.nodes)) {
93
+ if (node.activeEffectImage) {
94
+ effectImages.add(node.activeEffectImage);
95
+ }
96
+ }
97
+ const masterySprites = [];
98
+ let masteryMissing = 0;
99
+ for (const path of effectImages) {
100
+ const img = await source.uiSprite(path);
101
+ if (!img) {
102
+ masteryMissing += 1;
103
+ continue;
104
+ }
105
+ masterySprites.push({ key: `masteryEffectActive:${path}.png`, width: img.width, height: img.height, rgba: img.rgba });
106
+ }
107
+ // Patterns are large (~775²); a wide sheet keeps both dimensions under webp's
108
+ // 16383px cap.
109
+ const atlases = {
110
+ 'skills': packAtlas(active),
111
+ 'skills-disabled': packAtlas(inactive),
112
+ 'frame': packAtlas(frameSprites),
113
+ 'mastery-effect-active': packAtlas(masterySprites, 6000),
114
+ };
115
+ return {
116
+ atlases,
117
+ report: {
118
+ skills: { packed: active.length, missing: iconsMissing },
119
+ frames: { packed: frameSprites.length },
120
+ masteryEffects: { packed: masterySprites.length, missing: masteryMissing },
121
+ },
122
+ };
123
+ }
124
+ //# sourceMappingURL=buildGraphics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildGraphics.js","sourceRoot":"","sources":["../src/buildGraphics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Builds the passive-tree `data.json` from GGPK tables + the `.psg` graph, in
3
+ * the shape `@poe2-toolkit/tree-core`'s `normalizeGggTree` expects. Source of truth:
4
+ * GGPK only.
5
+ *
6
+ * Linkage (verified): a node's `skill` = PassiveSkillGraphId; the `.psg` keys
7
+ * nodes by the same id; PassiveSkills rows join by PassiveSkillGraphId.
8
+ * Positions are computed from group centre + orbit radius + orbit index (radii
9
+ * derived from the live tree, exact). Stats render via the passive + base
10
+ * stat-description files.
11
+ */
12
+ import type { GgpkSource } from '@poe2-toolkit/ggpk';
13
+ /** A tree node as serialized to `data.json` (graph node, or data-only override). */
14
+ export interface ExportNode {
15
+ skill: number;
16
+ name: string;
17
+ icon: string;
18
+ stats: string[];
19
+ group?: number;
20
+ orbit?: number;
21
+ orbitIndex?: number;
22
+ x?: number;
23
+ y?: number;
24
+ out?: number[];
25
+ in?: number[];
26
+ isNotable?: true;
27
+ isKeystone?: true;
28
+ isJewelSocket?: true;
29
+ isMastery?: true;
30
+ isGenericAttribute?: true;
31
+ isAscendancyStart?: true;
32
+ activeEffectImage?: string;
33
+ ascendancyId?: string;
34
+ classStartIndex?: number[];
35
+ flavourText?: unknown;
36
+ }
37
+ export interface ExportAscendancy {
38
+ id: string;
39
+ name: string;
40
+ offsetX: number;
41
+ offsetY: number;
42
+ }
43
+ export interface ExportClass {
44
+ name: string;
45
+ base_str: number;
46
+ base_dex: number;
47
+ base_int: number;
48
+ ascendancies: ExportAscendancy[];
49
+ overridePairs: Record<number, number>;
50
+ }
51
+ export interface ExportGroup {
52
+ x: number;
53
+ y: number;
54
+ orbits: number[];
55
+ nodes: number[];
56
+ }
57
+ /** The full `data.json` payload. */
58
+ export interface TreeExport {
59
+ tree: string;
60
+ classes: ExportClass[];
61
+ groups: Record<number, ExportGroup>;
62
+ nodes: Record<number, ExportNode>;
63
+ edges: never[];
64
+ skillOverrides: Record<number, Record<string, unknown>>;
65
+ jewelSlots: number[];
66
+ min_x: number;
67
+ min_y: number;
68
+ max_x: number;
69
+ max_y: number;
70
+ }
71
+ /**
72
+ * Build the passive-tree export. All data comes from the supplied
73
+ * {@link GgpkSource}; this function performs no I/O of its own beyond what the
74
+ * source serves.
75
+ */
76
+ export declare function buildTree(source: GgpkSource): Promise<TreeExport>;
77
+ //# sourceMappingURL=buildTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildTree.d.ts","sourceRoot":"","sources":["../src/buildTree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,UAAU,EAAY,MAAM,oBAAoB,CAAC;AA+C9D,oFAAoF;AACpF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,kBAAkB,CAAC,EAAE,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,IAAI,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAEhG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE;AAExF,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAaD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAqUvE"}