@siegesailor/terminal-sigma 1.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.
@@ -1,279 +1,213 @@
1
- // The character is a 16x20 pixel canvas. Each terminal cell draws 2 stacked
2
- // pixels with "▀", foreground for the top and background for the bottom.
3
- export const spriteWidth = 16;
4
- export const spriteHeight = 20;
5
- const rows = (count, row) => Array.from({ length: count }, () => row);
6
- const head = {
7
- y: 2,
8
- rows: [
9
- "....kkkkkkkk....",
10
- "...khhhhhhhhk...",
11
- "..khhhhhhhhhhk..",
12
- "..khsssssssshk..",
13
- "..kssessssessk..",
14
- "..ksrssmmssrsk..",
15
- "...kssssssssk...",
16
- "....kkksskkk....",
17
- ],
18
- };
19
- const body = {
20
- y: 10,
21
- rows: [
22
- "...kaaaaaaaak...",
23
- "...kAaaaaaaAk...",
24
- "...kAaaaaaaAk...",
25
- "...knnnnnnnnk...",
26
- "...kppppppppk...",
27
- ],
28
- };
29
- const straps = { y: 10, rows: rows(3, "......P..P......") };
30
- const leftArm = {
31
- y: 10,
32
- rows: ["..kA............", "..kA............", "..ks............"],
33
- };
34
- const rightArm = {
35
- y: 10,
36
- rows: ["............Ak..", "............Ak..", "............sk.."],
37
- };
38
- const swingingArms = {
39
- y: 10,
40
- rows: ["..kA........Ak..", ".ks..........sk."],
41
- };
42
- const raisedArms = {
43
- y: 3,
44
- rows: [
45
- ".s............s.",
46
- ...rows(4, ".A............A."),
47
- ...rows(3, "..A..........A.."),
48
- ],
49
- };
50
- const standingLegs = {
51
- y: 15,
52
- rows: [
53
- "....kpp..ppk....",
54
- "....kpp..ppk....",
55
- "....kbb..bbk....",
56
- "...kbbb..bbbk...",
57
- ],
58
- };
59
- const stridingLegs = {
60
- y: 15,
61
- rows: ["....kpp..ppk....", "...kpp....ppk...", "..kbb......bbk.."],
62
- };
63
- const passingLegs = {
64
- y: 15,
65
- rows: [
66
- ".....kppppk.....",
67
- "......kppk......",
68
- "......kbbk......",
69
- ".....kbbbbk.....",
70
- ],
71
- };
72
- const sweat = { y: 3, rows: ["..............d."] };
73
- const motionLines = {
74
- y: 15,
75
- rows: ["vv..............", "................", "v..............."],
76
- };
77
- const laptop = {
78
- y: 12,
79
- rows: [
80
- "..kggggggggggk..",
81
- "..kggggLLggggk..",
82
- "..kggggLLggggk..",
83
- "..kggggggggggk..",
84
- ".kkkkkkkkkkkkkk.",
85
- ],
86
- };
87
- const typingArms = (hands) => ({
88
- y: 10,
89
- rows: ["..kA........Ak..", hands],
1
+ // The character is a 20x28 pixel canvas, facing right. Each terminal cell draws
2
+ // 2 stacked pixels with "▀", foreground for the top and background for the bottom.
3
+ export const spriteWidth = 20;
4
+ export const spriteHeight = 28;
5
+ const layer = (x, y, ...rows) => ({
6
+ x,
7
+ y,
8
+ rows,
90
9
  });
91
- const cup = {
92
- y: 11,
93
- rows: [".............ccc", ".............chc", ".............ccc"],
94
- };
95
- const steam = (y, row) => ({ y, rows: [row] });
96
- const appleAtMouth = {
97
- y: 6,
98
- rows: [
99
- ".........q......",
100
- ".........ff.....",
101
- ".........ff.....",
102
- "..........s.....",
103
- "..........AAk...",
104
- ],
105
- };
106
- const appleInHand = {
107
- y: 11,
108
- rows: ["...............q", "..............ff", "..............ff"],
109
- };
110
- const chewing = { y: 7, rows: [".......oo......."] };
111
- const dumbbellsUp = {
112
- y: 1,
113
- rows: [
114
- "GG............GG",
115
- "GG............GG",
116
- ".s............s.",
117
- ...rows(6, ".A............A."),
118
- "..A..........A..",
119
- ],
120
- };
121
- const dumbbellsDown = {
122
- y: 7,
123
- rows: [
124
- "GG............GG",
125
- "GG............GG",
126
- ".s............s.",
127
- ".AA..........AA.",
128
- ],
129
- };
130
- const sparkles = (top, y, bottom) => [
131
- { y: 0, rows: [top] },
132
- { y, rows: [bottom] },
10
+ const upper = (x, y, ...rows) => ({
11
+ x,
12
+ y,
13
+ rows,
14
+ upper: true,
15
+ });
16
+ const hood = upper(0, 2, "......kkkk", "....kkHHHHkk", "..kkHHHHHHHhk", "kkHHHHHHHHhffk", "..kHHHHHHhffEfk", "...kHHHHHhfffk", "....khhHHhffk", "....kkkkkkkk");
17
+ const neckScarf = upper(4, 10, "kSSSSSSSk", ".ksSSSsk");
18
+ const torso = upper(4, 12, ".kCCCCCCk", ".kcCCCCCCk", ".kcCCCCCCk", ".kcCCCCCk", ".knnnnNnk", "kcCCkCCCCk", "kcCk.kCCCk", ".kk...kkk");
19
+ const pauldron = upper(8, 11, "kaak", ".aaak", ".AAk");
20
+ const tails = [
21
+ upper(0, 10, "sSSS", "SS..", "s..."),
22
+ upper(0, 9, "s...", ".SSS", "..sS"),
23
+ upper(0, 10, "..SS", ".SSs", "Ss.."),
24
+ upper(0, 10, "SSSS", "ssss"),
133
25
  ];
134
- const weapons = [
135
- { y: 2, rows: ["..............O.", ...rows(15, "..............W.")] },
136
- { y: 4, rows: [".O..............", ...rows(11, ".G..............")] },
137
- {
138
- y: 0,
139
- rows: [
140
- ...rows(11, "..............B."),
141
- ".............yyy",
142
- "..............n.",
143
- "..............n.",
144
- ],
145
- },
26
+ const [tailA, tailB, tailC, tailStream] = tails;
27
+ const hangingArm = upper(10, 14, "cck", "cck", "cck", "mm");
28
+ const standingLegs = layer(5, 19, ".kpk.kpk", ".kPk.kppk", ".kPk.kppk", ".kPk.kppk", ".kPk.kppk", ".kbk.kbbk", "kbbk.kbbbk", "kkkk.kkkkk");
29
+ const stride = (back, front) => layer(1, 19, `.....k${back}${back}${front}${front}k`, `....k${back}k..k${front}${front}k`, `...k${back}k....k${front}${front}k`, `..k${back}k......k${front}k`, ".kbk.......kpk", "kbk........kbbk", "kk.........kkkk");
30
+ const passing = (raised, planted) => layer(4, 19, `..k${raised}${planted}${planted}k`, `.kbk${planted}${planted}k`, `kbbkk${planted}k`, `.kk.k${planted}${planted}k`, `....k${planted}k`, "....kbbk", "....kbbbk", "....kkkkk");
31
+ const runLegs = [
32
+ stride("P", "p"),
33
+ passing("P", "p"),
34
+ stride("p", "P"),
35
+ passing("p", "P"),
36
+ ];
37
+ const armForward = upper(10, 13, "cc..", ".cck", "..mm");
38
+ const armBack = upper(7, 13, "..cc", ".cc.", "mm..");
39
+ const speedLines = [
40
+ layer(0, 14, "vv", "", "", "", "v", "", "", "vv"),
41
+ layer(0, 15, "v", "", "", "vv", "", "", "", "v"),
42
+ ];
43
+ const table = layer(12, 18, "xxxxxxxx", "......x.", "......x.", "......x.", "......x.", "......x.", "......x.", ".....xxx");
44
+ const laptop = layer(13, 12, "....lL", "....lL", "....lL", "....lL", "....lL", "LLLLLL");
45
+ const typing = [
46
+ upper(10, 13, "cc..", ".cc.", "..cm", "...m"),
47
+ upper(10, 13, "cc..", ".cc.", "..cm", "..m."),
48
+ ];
49
+ const codeBits = [
50
+ layer(14, 9, "y", "", "..y", "", ".y"),
51
+ layer(14, 7, ".y", "", "y", "", "..y"),
52
+ layer(15, 5, "y", "", ".y", "", "y"),
53
+ layer(14, 3, "..y", "", "y", "", ".y"),
54
+ ];
55
+ const cupLow = upper(10, 13, "cc..", ".ccm", "..QQ", "..Qo");
56
+ const cupRaised = upper(10, 6, "...QQ", "...Qo", "..cm.", ".cc..", ".cc..", ".cc..");
57
+ const steam = [
58
+ layer(12, 12, ".v", "v.", ".v"),
59
+ layer(12, 11, "v.", ".v", "v."),
60
+ layer(14, 3, "v", ".v"),
146
61
  ];
147
- const helmets = [
148
- [
149
- {
150
- y: 2,
151
- rows: [
152
- "....HHHHHHHH....",
153
- "...HHHHHHHHHH...",
154
- "..HHH......HHH..",
155
- ...rows(3, "..HH........HH.."),
156
- "...H........H...",
157
- ],
158
- },
159
- ],
160
- [
161
- {
162
- y: 1,
163
- rows: ["...........V....", "....MMMMMMMM....", "...kMMMMMMMMk..."],
164
- },
165
- { y: 6, rows: ["..kVVVVVVVVVVk.."] },
166
- ],
167
- [
168
- {
169
- y: 0,
170
- rows: [
171
- ".Y............Y.",
172
- ".YY..........YY.",
173
- "..YYMMMMMMMMYY..",
174
- "...kMMMMMMMMk...",
175
- "..kMMMMMMMMMMk..",
176
- ],
177
- },
178
- ],
62
+ const appleAtMouth = upper(10, 5, "...G.", "...FF", "...FF", "..cm.", ".cc..", ".cc..", ".cc..");
63
+ const appleInHand = upper(10, 13, "cc...", ".cmF.", "..FFG");
64
+ const bitten = upper(10, 13, "cc...", ".cmF.", "..F.G");
65
+ const dumbbellUp = upper(13, 0, "DDD", "DdD", "DDD", ".m.", ".c.", ".c.", ".c.", ".c.", ".c.", ".c.", ".c.", ".c.", "cc.");
66
+ const dumbbellRacked = upper(13, 8, "DDD", "DdD", "DDD", ".m.", ".c.", "cc.");
67
+ const sweat = [layer(16, 6, "u"), layer(16, 8, "u")];
68
+ const reachUp = upper(13, 1, ".m", ".c", ".c", ".c", ".c", ".c", ".c", ".c", ".c", ".c", ".c", "cc");
69
+ const reachForward = upper(10, 13, "ccccc", "....cmm");
70
+ const swordRaised = upper(13, 0, ".t.", ".t.", ".t.", ".t.", "ggg", ".m.", ".c.", ".c.", ".c.", ".c.", ".c.", ".c.", "cc.");
71
+ const sparkles = [
72
+ layer(0, 1, ".y..........y", "", "", "", "", "", "y.................y"),
73
+ layer(0, 3, "y.................y", "", "", "", "", "..y...........y"),
74
+ ];
75
+ const breathing = (legs, front) => [
76
+ { legs, behind: [tailA], front },
77
+ { legs, behind: [tailB], front },
78
+ { legs, behind: [tailC], front, breathe: true },
79
+ { legs, behind: [tailB], front, breathe: true },
80
+ { legs, behind: [tailA], front },
81
+ { legs, behind: [tailB], front, blink: true },
82
+ { legs, behind: [tailC], front, breathe: true },
83
+ { legs, behind: [tailB], front, breathe: true },
179
84
  ];
180
- const arms = [leftArm, rightArm];
181
85
  const animations = {
182
86
  idle: {
183
- interval: 300,
184
- frames: [
185
- ...Array.from({ length: 11 }, () => ({
186
- layers: [...arms, standingLegs],
187
- })),
188
- { layers: [...arms, standingLegs], blink: true },
189
- ],
87
+ interval: 220,
88
+ frames: breathing(standingLegs, [hangingArm]),
190
89
  },
191
90
  focus: {
192
- interval: 250,
91
+ interval: 160,
92
+ frames: codeBits.map((bits, index) => ({
93
+ legs: standingLegs,
94
+ behind: [index % 2 === 0 ? tailA : tailB, table],
95
+ front: [laptop, typing[index % 2] ?? hangingArm, bits],
96
+ breathe: index >= 2,
97
+ })),
98
+ },
99
+ break: {
100
+ interval: 380,
193
101
  frames: [
102
+ { legs: standingLegs, behind: [tailA], front: [cupLow, steam[0]] },
194
103
  {
195
- layers: [
196
- typingArms("...ks......sk..."),
197
- standingLegs,
198
- laptop,
199
- steam(1, "............O..."),
200
- ],
104
+ legs: standingLegs,
105
+ behind: [tailB],
106
+ front: [cupLow, steam[1]],
107
+ breathe: true,
201
108
  },
202
109
  {
203
- layers: [
204
- typingArms("...kss....ssk..."),
205
- standingLegs,
206
- laptop,
207
- steam(0, "..............O."),
208
- ],
110
+ legs: standingLegs,
111
+ behind: [tailC],
112
+ front: [cupLow, steam[0]],
113
+ breathe: true,
209
114
  },
210
- ],
211
- },
212
- break: {
213
- interval: 600,
214
- frames: [
215
115
  {
216
- layers: [...arms, standingLegs, cup, steam(10, "..............v.")],
116
+ legs: standingLegs,
117
+ behind: [tailB],
118
+ front: [cupRaised, steam[2]],
119
+ blink: true,
217
120
  },
218
121
  {
219
- layers: [
220
- ...arms,
221
- standingLegs,
222
- cup,
223
- steam(9, "..............v."),
224
- steam(8, ".............v.."),
225
- ],
122
+ legs: standingLegs,
123
+ behind: [tailA],
124
+ front: [cupRaised, steam[2]],
226
125
  blink: true,
227
126
  },
127
+ { legs: standingLegs, behind: [tailB], front: [cupLow, steam[1]] },
228
128
  ],
229
129
  },
230
130
  eat: {
231
- interval: 400,
131
+ interval: 300,
232
132
  frames: [
233
- { layers: [leftArm, standingLegs, appleAtMouth] },
234
- { layers: [...arms, standingLegs, appleInHand, chewing] },
133
+ { legs: standingLegs, behind: [tailA], front: [appleInHand] },
134
+ { legs: standingLegs, behind: [tailB], front: [appleAtMouth] },
135
+ {
136
+ legs: standingLegs,
137
+ behind: [tailC],
138
+ front: [appleAtMouth],
139
+ breathe: true,
140
+ },
141
+ { legs: standingLegs, behind: [tailB], front: [bitten], breathe: true },
235
142
  ],
236
143
  },
237
144
  run: {
238
- interval: 180,
239
- frames: [
240
- { layers: [swingingArms, stridingLegs, sweat, motionLines] },
241
- { layers: [...arms, passingLegs], dy: -1 },
242
- ],
145
+ interval: 110,
146
+ frames: runLegs.map((legs, index) => ({
147
+ legs,
148
+ behind: [tailStream, speedLines[index % 2] ?? tailStream],
149
+ front: [index % 2 === 0 ? armForward : armBack],
150
+ dy: index % 2 === 0 ? 0 : -1,
151
+ })),
243
152
  },
244
153
  lift: {
245
- interval: 500,
154
+ interval: 420,
246
155
  frames: [
247
- { layers: [dumbbellsUp, standingLegs, sweat] },
248
- { layers: [dumbbellsDown, standingLegs] },
156
+ { legs: standingLegs, behind: [tailA], front: [dumbbellRacked] },
157
+ {
158
+ legs: standingLegs,
159
+ behind: [tailB],
160
+ front: [dumbbellUp, sweat[0]],
161
+ dy: -1,
162
+ },
163
+ {
164
+ legs: standingLegs,
165
+ behind: [tailC],
166
+ front: [dumbbellUp, sweat[1]],
167
+ dy: -1,
168
+ },
169
+ {
170
+ legs: standingLegs,
171
+ behind: [tailB],
172
+ front: [dumbbellRacked],
173
+ breathe: true,
174
+ },
249
175
  ],
250
176
  },
251
177
  stretch: {
252
- interval: 700,
178
+ interval: 520,
253
179
  frames: [
254
- { layers: [raisedArms, standingLegs] },
255
- { layers: [...arms, standingLegs] },
180
+ { legs: standingLegs, behind: [tailA], front: [reachUp], dy: -1 },
181
+ { legs: standingLegs, behind: [tailB], front: [reachUp], dy: -1 },
182
+ {
183
+ legs: standingLegs,
184
+ behind: [tailC],
185
+ front: [reachForward],
186
+ breathe: true,
187
+ },
188
+ {
189
+ legs: standingLegs,
190
+ behind: [tailB],
191
+ front: [reachForward],
192
+ breathe: true,
193
+ },
256
194
  ],
257
195
  },
258
196
  levelUp: {
259
- interval: 250,
260
- frames: [
197
+ interval: 160,
198
+ frames: sparkles.flatMap((burst, index) => [
261
199
  {
262
- layers: [
263
- raisedArms,
264
- standingLegs,
265
- ...sparkles("...y........y...", 13, "y..............y"),
266
- ],
200
+ legs: standingLegs,
201
+ behind: [tailStream],
202
+ front: [swordRaised, burst],
203
+ dy: -1,
267
204
  },
268
205
  {
269
- layers: [
270
- raisedArms,
271
- standingLegs,
272
- ...sparkles("y..............y", 16, "..y..........y.."),
273
- ],
274
- dy: -1,
206
+ legs: standingLegs,
207
+ behind: [index === 0 ? tailA : tailC],
208
+ front: [swordRaised, burst],
275
209
  },
276
- ],
210
+ ]),
277
211
  },
278
212
  };
279
213
  export const animationOf = (activity) => animations[activity];
@@ -281,85 +215,108 @@ export const animationOf = (activity) => animations[activity];
281
215
  // so every level from 2 to 9 changes exactly 1 piece.
282
216
  export const gearOf = (level) => {
283
217
  const tier = (slot) => Math.min(Math.max(Math.floor((level - 2 - slot) / 4) + 1, 0), 2);
284
- return {
285
- weapon: tier(0),
286
- armor: tier(1),
287
- helmet: tier(2),
288
- backpack: tier(3),
289
- };
218
+ return { weapon: tier(0), armor: tier(1), hood: tier(2), relic: tier(3) };
290
219
  };
291
- const fixedColors = [
292
- ["b", "#3a2a24"],
293
- ["B", "#cfe8ff"],
294
- ["c", "#f4f4f4"],
295
- ["d", "#7fd3ff"],
296
- ["e", "#2b2135"],
297
- ["f", "#e5484d"],
298
- ["g", "#9aa3ad"],
299
- ["G", "#6e7681"],
300
- ["h", "#6b4226"],
301
- ["H", "#8e5cc4"],
302
- ["k", "#2b2135"],
303
- ["L", "#D97757"],
304
- ["m", "#b5524f"],
305
- ["M", "#b8c0cc"],
306
- ["n", "#7a4f2a"],
307
- ["o", "#7a2e2e"],
308
- ["O", "#57e3f0"],
309
- ["p", "#3d4f7a"],
310
- ["q", "#4caf6a"],
311
- ["r", "#f08c8c"],
312
- ["s", "#f5c9a0"],
313
- ["v", "#c8c8c8"],
314
- ["V", "#57e3f0"],
315
- ["W", "#9b6b3d"],
316
- ["y", "#ffd84d"],
317
- ["Y", "#f2e6c8"],
220
+ const weapons = [
221
+ upper(2, 13, ".g.", "xxx", ".W.", ".W.", ".W.", ".W.", ".W.", ".w."),
222
+ upper(2, 11, ".g.", ".g.", "xxx", ".w.", ".W.", ".W.", ".W.", ".W.", ".W.", ".W.", ".W.", ".w."),
223
+ upper(1, 10, "..t", "..g", ".xxx", ".tw", ".tw", ".tw", ".tw", ".tw", ".tw", ".tw", ".tw", ".tw", "..t"),
224
+ ];
225
+ const hoods = [
226
+ upper(0, 0, ""),
227
+ upper(4, 4, "SSSSSSS"),
228
+ upper(4, 0, ".Y....Y", ".YY..YY"),
318
229
  ];
230
+ // The relic is a companion orb that bobs beside the character.
231
+ const relics = [
232
+ () => [],
233
+ (bob) => [layer(17, 4 + bob, "t")],
234
+ (bob) => [layer(16, 3 + bob, ".y.", "ytt", ".tt")],
235
+ ];
236
+ const orbBob = [0, 0, 1, 1];
319
237
  const armorColors = [
320
- ["#4caf6a", "#2f7a45"],
321
- ["#d9a440", "#9c7322"],
322
- ["#4fb3e3", "#2a7fb0"],
238
+ ["#6B4A35", "#4A3324"],
239
+ ["#A3ACB8", "#6A7380"],
240
+ ["#D9B45A", "#9C7A2E"],
323
241
  ];
324
- const backpackColors = ["#8a6d3b", "#c08a2e", "#57e3f0"];
325
- export function paletteOf(level) {
242
+ export function paletteOf(level, palette) {
326
243
  const gear = gearOf(level);
327
244
  const [armor, armorShade] = armorColors[gear.armor] ?? armorColors[0];
245
+ const { cloak, cloakShade, scarf, scarfShade, glow } = palette.sprite;
328
246
  return new Map([
329
- ...fixedColors,
330
- ["a", armor],
331
247
  ["A", armorShade],
332
- ["P", backpackColors[gear.backpack] ?? backpackColors[0]],
248
+ ["a", armor],
249
+ ["b", "#3B2D25"],
250
+ ["C", cloak],
251
+ ["c", cloakShade],
252
+ ["D", "#50555F"],
253
+ ["d", "#8A8F99"],
254
+ ["E", glow],
255
+ ["F", "#C8473F"],
256
+ ["f", "#241F2B"],
257
+ ["G", "#5F8F4E"],
258
+ ["g", "#B8913A"],
259
+ ["H", cloak],
260
+ ["h", cloakShade],
261
+ ["k", "#15121A"],
262
+ ["L", "#4A505B"],
263
+ ["l", glow],
264
+ ["m", "#C99272"],
265
+ ["N", armor],
266
+ ["n", "#4A3628"],
267
+ ["o", "#5A3A25"],
268
+ ["P", "#23252F"],
269
+ ["p", "#2E3140"],
270
+ ["Q", "#E6E0D4"],
271
+ ["S", scarf],
272
+ ["s", scarfShade],
273
+ ["t", glow],
274
+ ["u", "#8FC6E8"],
275
+ ["v", "#9AA0A8"],
276
+ ["W", "#8792A0"],
277
+ ["w", "#C9D3DE"],
278
+ ["x", "#3D414B"],
279
+ ["Y", "#E8DCC0"],
280
+ ["y", palette.soft],
333
281
  ]);
334
282
  }
335
- // The pixel keys of 1 frame, row by row, "." where nothing is drawn.
336
- export function composeFrame(activity, frameIndex, level) {
283
+ // The pixel keys of 1 frame, row by row, "." where nothing is drawn. The
284
+ // counter keeps running across frames, so the relic bobs on its own beat.
285
+ export function composeFrame(activity, counter, level) {
337
286
  const { frames } = animations[activity];
338
- const frame = frames[frameIndex % frames.length] ?? { layers: [] };
287
+ const frame = frames[counter % frames.length] ?? frames[0];
339
288
  const gear = gearOf(level);
340
- const canvas = rows(spriteHeight, ".".repeat(spriteWidth)).map((row) => [
341
- ...row,
342
- ]);
343
- const faceLayer = frame.blink
344
- ? { y: head.y, rows: head.rows.map((row) => row.replaceAll("e", "s")) }
345
- : head;
289
+ const canvas = Array.from({ length: spriteHeight }, () => Array.from({ length: spriteWidth }, () => "."));
290
+ if (!frame) {
291
+ return canvas.map((row) => row.join(""));
292
+ }
293
+ const lowerDy = frame.dy ?? 0;
294
+ const upperDy = lowerDy + (frame.breathe ? 1 : 0);
295
+ const head = frame.blink
296
+ ? { ...hood, rows: hood.rows.map((row) => row.replaceAll("E", "f")) }
297
+ : hood;
346
298
  const layers = [
347
299
  weapons[gear.weapon],
348
- faceLayer,
349
- body,
350
- straps,
351
- ...(helmets[gear.helmet] ?? []),
352
- ...frame.layers,
300
+ ...(frame.behind ?? []),
301
+ frame.legs,
302
+ torso,
303
+ neckScarf,
304
+ head,
305
+ hoods[gear.hood],
306
+ pauldron,
307
+ ...(frame.front ?? []),
308
+ ...(relics[gear.relic]?.(orbBob[counter % orbBob.length] ?? 0) ?? []),
353
309
  ];
354
- for (const layer of layers) {
355
- if (!layer) {
310
+ for (const item of layers) {
311
+ if (!item) {
356
312
  continue;
357
313
  }
358
- for (const [offset, row] of layer.rows.entries()) {
359
- const target = canvas[layer.y + offset + (frame.dy ?? 0)];
360
- for (const [x, key] of [...row].entries()) {
361
- if (target && key !== ".") {
362
- target[x] = key;
314
+ const dy = item.upper ? upperDy : lowerDy;
315
+ for (const [offset, row] of item.rows.entries()) {
316
+ const target = canvas[item.y + offset + dy];
317
+ for (const [index, key] of [...row].entries()) {
318
+ if (target && key !== "." && item.x + index < spriteWidth) {
319
+ target[item.x + index] = key;
363
320
  }
364
321
  }
365
322
  }
@@ -374,13 +331,13 @@ export function toSegments(frame, palette) {
374
331
  const bottom = frame[y + 1] ?? "";
375
332
  const line = [];
376
333
  for (let x = 0; x < spriteWidth; x++) {
377
- const upper = palette.get(top[x] ?? ".");
378
- const lower = palette.get(bottom[x] ?? ".");
379
- const cell = upper === undefined
380
- ? lower === undefined
334
+ const upperColor = palette.get(top[x] ?? ".");
335
+ const lowerColor = palette.get(bottom[x] ?? ".");
336
+ const cell = upperColor === undefined
337
+ ? lowerColor === undefined
381
338
  ? { text: " " }
382
- : { text: "▄", color: lower }
383
- : { text: "▀", color: upper, backgroundColor: lower };
339
+ : { text: "▄", color: lowerColor }
340
+ : { text: "▀", color: upperColor, backgroundColor: lowerColor };
384
341
  const previous = line.at(-1);
385
342
  if (previous &&
386
343
  previous.color === cell.color &&