Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ModernIBarSkin.cpp
Go to the documentation of this file.
1#include "ModernIBarSkin.hpp"
3#include <algorithm>
4#include <array>
5#include <sstream>
6#include <cmath>
7
8namespace monopoly::ibar
9{
10 namespace
11 {
12 constexpr std::array<std::string_view, 28> French{
13 "Enchères", "Acheter", "Vue", "Terminer", "Vendre", "Taxe fixe",
14 "Pourcentage", "Construire", "Accepter", "Contre-offre", "Faillite",
15 "Refuser", "Hypothéquer", "Plateau", "Options", "Payer",
16 "Nouvelle partie", "Lancer", "Bilan", "Échanger", "Lever hyp.",
17 "Quitter", "Rejouer", "Utiliser carte", "Enchère maison",
18 "Enchère hôtel", "Placer maison", "Placer hôtel"};
19 constexpr std::array<std::string_view, 28> English{
20 "Auction", "Buy", "View", "Done", "Sell", "Flat tax", "Percentage",
21 "Build", "Accept", "Counteroffer", "Bankrupt", "Reject", "Mortgage",
22 "Board", "Options", "Pay", "New game", "Roll dice", "Status", "Trade",
23 "Unmortgage", "Exit", "Play again", "Use card", "Auction house",
24 "Auction hotel", "Place house", "Place hotel"};
25 struct Button { unsigned index{}, state{}; bool grey{}; };
26 std::optional<Button> button(data::DataId root)
27 {
29 return {};
30 const auto tag = data::dataTag(root);
31 for (const auto base : {ButtonBaseTag, AIButtonBaseTag})
32 if (tag >= base && tag < base + 28 * ButtonAnimationsPerSet)
33 return Button{unsigned(tag - base) / 4, unsigned(tag - base) % 4,
35 return {};
36 }
37 struct NativeActionShape { std::uint16_t root,leaf,width,height; std::int16_t x,y; };
38 // Actual dat_lm01 CNK descriptions: Buy/Done/FlatTax/Percentage/Mortgage/Roll,
39 // enabled and grey, all four authored states. Native geometry only.
40 // Full production description/tick dump SHA256: 4883f86c5f70a4a19a4d40159f9f8720c04b2d15a5d56c94a04649c13a87fa03
41 constexpr std::array<NativeActionShape,430> NativeActionShapes{{
42 {0x8e,0x128d,92,13,271,470},
43 {0x8e,0x128e,95,22,269,461},
44 {0x8e,0x128f,96,28,269,455},
45 {0x8e,0x1290,93,30,269,453},
46 {0x8e,0x1291,96,30,266,453},
47 {0x8e,0x1292,96,30,266,453},
48 {0x8e,0x1293,96,30,266,453},
49 {0x8e,0x1294,97,30,264,453},
50 {0x8e,0x1295,99,30,261,453},
51 {0x8e,0x1296,116,33,248,453},
52 {0x8e,0x1297,120,84,240,453},
53 {0x8f,0x1297,120,84,240,453},
54 {0x8f,0x1298,120,84,240,453},
55 {0x8f,0x1299,120,84,240,453},
56 {0x8f,0x129a,120,84,240,453},
57 {0x8f,0x129b,120,84,240,453},
58 {0x8f,0x129c,120,84,240,453},
59 {0x8f,0x129d,120,84,240,453},
60 {0x8f,0x129e,120,84,240,453},
61 {0x90,0x12a4,120,84,240,453},
62 {0x90,0x12a5,120,84,240,453},
63 {0x90,0x12a6,120,94,238,458},
64 {0x90,0x12a7,145,93,210,462},
65 {0x90,0x12a8,139,73,205,466},
66 {0x90,0x12a9,144,75,154,467},
67 {0x90,0x12aa,145,73,103,466},
68 {0x90,0x12ab,142,73,126,466},
69 {0x90,0x12ac,137,73,246,466},
70 {0x90,0x12ad,141,74,433,465},
71 {0x90,0x12ae,117,75,683,465},
72 {0x91,0x129f,120,84,240,453},
73 {0x91,0x12a0,120,85,240,452},
74 {0x91,0x12a1,120,86,240,452},
75 {0x91,0x12a2,120,85,240,452},
76 {0x91,0x12a3,120,84,240,453},
77 {0x91,0x12a4,120,84,240,453},
78 {0x91,0x12a5,120,84,240,453},
79 {0x91,0x12a6,120,94,238,458},
80 {0x91,0x12a7,145,93,210,462},
81 {0x91,0x12a8,139,73,205,466},
82 {0x91,0x12a9,144,75,154,467},
83 {0x91,0x12aa,145,73,103,466},
84 {0x91,0x12ab,142,73,126,466},
85 {0x91,0x12ac,137,73,246,466},
86 {0x91,0x12ad,141,74,433,465},
87 {0x91,0x12ae,117,75,683,465},
88 {0x96,0x12c1,34,63,0,450},
89 {0x96,0x12c2,141,101,41,451},
90 {0x96,0x12c3,124,96,205,451},
91 {0x96,0x12c4,119,42,352,453},
92 {0x97,0x12c5,119,42,352,453},
93 {0x97,0x12c6,119,42,352,453},
94 {0x97,0x12c7,119,42,352,453},
95 {0x97,0x12c8,119,42,352,453},
96 {0x97,0x12c9,119,42,352,453},
97 {0x97,0x12ca,119,42,352,453},
98 {0x97,0x12cb,119,42,352,453},
99 {0x98,0x12d1,119,42,352,453},
100 {0x98,0x12d2,118,42,353,453},
101 {0x98,0x12d3,120,85,451,453},
102 {0x98,0x12d4,120,85,549,453},
103 {0x98,0x12d5,120,95,645,453},
104 {0x98,0x12d6,50,23,750,490},
105 {0x99,0x12cc,118,42,352,453},
106 {0x99,0x12cd,118,43,353,452},
107 {0x99,0x12ce,118,43,352,452},
108 {0x99,0x12cf,118,43,353,452},
109 {0x99,0x12d0,118,42,353,453},
110 {0x99,0x12d1,119,42,352,453},
111 {0x99,0x12d2,118,42,353,453},
112 {0x99,0x12d3,120,85,451,453},
113 {0x99,0x12d4,120,85,549,453},
114 {0x99,0x12d5,120,95,645,453},
115 {0x99,0x12d6,50,23,750,490},
116 {0x9e,0x12ee,89,12,273,471},
117 {0x9e,0x12ef,94,22,270,461},
118 {0x9e,0x12f0,95,29,269,454},
119 {0x9e,0x12f1,95,30,269,453},
120 {0x9e,0x12f2,97,30,267,453},
121 {0x9e,0x12f3,94,30,267,453},
122 {0x9e,0x12f4,96,31,265,452},
123 {0x9e,0x12f5,109,32,256,452},
124 {0x9e,0x12f6,117,33,248,453},
125 {0x9e,0x12f7,122,84,243,453},
126 {0x9f,0x12f7,122,84,243,453},
127 {0xa0,0x12fd,122,84,243,453},
128 {0xa0,0x12fe,122,84,243,453},
129 {0xa0,0x12ff,117,33,250,453},
130 {0xa0,0x1300,97,32,264,451},
131 {0xa0,0x1301,95,31,266,452},
132 {0xa0,0x1302,97,31,267,452},
133 {0xa0,0x1303,95,30,269,453},
134 {0xa0,0x1304,96,30,269,453},
135 {0xa0,0x1305,96,30,269,453},
136 {0xa0,0x1306,92,21,272,462},
137 {0xa1,0x12f8,122,84,243,453},
138 {0xa1,0x12f9,122,85,243,452},
139 {0xa1,0x12fa,122,85,243,452},
140 {0xa1,0x12fb,122,85,243,452},
141 {0xa1,0x12fc,122,84,243,453},
142 {0xa1,0x12fd,122,84,243,453},
143 {0xa1,0x12fe,122,84,243,453},
144 {0xa1,0x12ff,117,33,250,453},
145 {0xa1,0x1300,97,32,264,451},
146 {0xa1,0x1301,95,31,266,452},
147 {0xa1,0x1302,97,31,267,452},
148 {0xa1,0x1303,95,30,269,453},
149 {0xa1,0x1304,96,30,269,453},
150 {0xa1,0x1305,96,30,269,453},
151 {0xa1,0x1306,92,21,272,462},
152 {0xa2,0x1307,89,10,454,473},
153 {0xa2,0x1308,95,26,452,457},
154 {0xa2,0x1309,95,30,452,453},
155 {0xa2,0x130a,95,30,454,453},
156 {0xa2,0x130b,94,30,455,453},
157 {0xa2,0x130c,94,30,456,453},
158 {0xa2,0x130d,97,31,456,452},
159 {0xa2,0x130e,108,32,457,452},
160 {0xa2,0x130f,119,33,454,453},
161 {0xa2,0x1310,120,85,459,453},
162 {0xa3,0x1310,120,85,459,453},
163 {0xa4,0x1316,120,85,459,453},
164 {0xa4,0x1317,120,85,459,453},
165 {0xa4,0x1318,119,73,459,453},
166 {0xa4,0x1319,120,65,456,453},
167 {0xa4,0x131a,120,33,454,453},
168 {0xa4,0x131b,116,32,455,453},
169 {0xa4,0x131c,115,32,455,453},
170 {0xa4,0x131d,113,32,293,453},
171 {0xa4,0x131e,93,32,154,453},
172 {0xa4,0x131f,18,7,0,489},
173 {0xa5,0x1311,120,85,459,453},
174 {0xa5,0x1312,121,86,458,452},
175 {0xa5,0x1313,120,86,459,452},
176 {0xa5,0x1314,120,86,459,452},
177 {0xa5,0x1315,120,85,459,453},
178 {0xa5,0x1316,120,85,459,453},
179 {0xa5,0x1317,120,85,459,453},
180 {0xa5,0x1318,119,73,459,453},
181 {0xa5,0x1319,120,65,456,453},
182 {0xa5,0x131a,120,33,454,453},
183 {0xa5,0x131b,116,32,455,453},
184 {0xa5,0x131c,115,32,455,453},
185 {0xa5,0x131d,113,32,293,453},
186 {0xa5,0x131e,93,32,154,453},
187 {0xa5,0x131f,18,7,0,489},
188 {0xba,0x1353,175,84,510,474},
189 {0xba,0x1354,152,74,493,467},
190 {0xba,0x1355,150,93,481,462},
191 {0xba,0x1356,136,95,471,457},
192 {0xba,0x1357,120,85,459,453},
193 {0xbb,0x1357,120,85,459,453},
194 {0xbc,0x135d,121,85,458,453},
195 {0xbc,0x135e,119,33,454,453},
196 {0xbc,0x135f,110,31,453,453},
197 {0xbc,0x1360,95,31,456,452},
198 {0xbc,0x1361,95,30,455,453},
199 {0xbc,0x1362,96,30,453,453},
200 {0xbc,0x1363,97,30,452,453},
201 {0xbc,0x1364,95,30,452,453},
202 {0xbc,0x1365,95,30,452,453},
203 {0xbc,0x1366,94,29,452,454},
204 {0xbd,0x1358,120,85,459,453},
205 {0xbd,0x1359,120,86,459,452},
206 {0xbd,0x135a,120,86,459,452},
207 {0xbd,0x135b,120,86,459,452},
208 {0xbd,0x135c,120,85,459,453},
209 {0xbd,0x135d,121,85,458,453},
210 {0xbd,0x135e,119,33,454,453},
211 {0xbd,0x135f,110,31,453,453},
212 {0xbd,0x1360,95,31,456,452},
213 {0xbd,0x1361,95,30,455,453},
214 {0xbd,0x1362,96,30,453,453},
215 {0xbd,0x1363,97,30,452,453},
216 {0xbd,0x1364,95,30,452,453},
217 {0xbd,0x1365,95,30,452,453},
218 {0xbd,0x1366,94,29,452,454},
219 {0xce,0x13f1,136,84,664,474},
220 {0xce,0x13f2,162,84,337,474},
221 {0xce,0x13f3,141,68,345,467},
222 {0xce,0x13f4,131,93,348,462},
223 {0xce,0x13f5,115,94,353,457},
224 {0xce,0x13f6,121,42,349,453},
225 {0xcf,0xae7,121,42,349,453},
226 {0xcf,0xae8,121,42,349,453},
227 {0xcf,0xae9,121,42,349,453},
228 {0xcf,0xaea,121,42,349,453},
229 {0xcf,0xaeb,121,42,349,453},
230 {0xcf,0xaec,121,42,349,453},
231 {0xcf,0xaed,121,42,349,453},
232 {0xd0,0x13fc,121,42,350,453},
233 {0xd0,0x13fd,115,33,352,453},
234 {0xd0,0x13fe,109,31,355,453},
235 {0xd0,0x13ff,93,31,362,452},
236 {0xd0,0x1400,94,30,361,453},
237 {0xd0,0x1401,94,30,361,453},
238 {0xd0,0x1402,94,30,361,453},
239 {0xd0,0x1403,95,30,361,453},
240 {0xd0,0x1404,95,28,361,455},
241 {0xd0,0x1405,95,28,361,455},
242 {0xd1,0x13f7,121,42,350,453},
243 {0xd1,0x13f8,121,43,350,452},
244 {0xd1,0x13f9,122,43,349,452},
245 {0xd1,0x13fa,121,43,350,452},
246 {0xd1,0x13fb,120,42,350,453},
247 {0xd1,0x13fc,121,42,350,453},
248 {0xd1,0x13fd,115,33,352,453},
249 {0xd1,0x13fe,109,31,355,453},
250 {0xd1,0x13ff,93,31,362,452},
251 {0xd1,0x1400,94,30,361,453},
252 {0xd1,0x1401,94,30,361,453},
253 {0xd1,0x1402,94,30,361,453},
254 {0xd1,0x1403,95,30,361,453},
255 {0xd1,0x1404,95,28,361,455},
256 {0xd1,0x1405,95,28,361,455},
257 {0x10a,0x14ce,92,13,271,470},
258 {0x10a,0x14cf,95,22,269,461},
259 {0x10a,0x14d0,96,28,269,455},
260 {0x10a,0x14d1,93,30,269,453},
261 {0x10a,0x14d2,96,30,266,453},
262 {0x10a,0x14d3,96,30,266,453},
263 {0x10a,0x14d4,96,30,266,453},
264 {0x10a,0x14d5,97,30,264,453},
265 {0x10a,0x14d6,99,30,261,453},
266 {0x10a,0x14d7,116,33,248,453},
267 {0x10a,0x14d8,120,84,240,453},
268 {0x10b,0x14d8,120,84,240,453},
269 {0x10b,0x14d9,120,84,240,453},
270 {0x10b,0x14da,120,84,240,453},
271 {0x10b,0x14db,120,84,240,453},
272 {0x10b,0x14dc,120,84,240,453},
273 {0x10b,0x14dd,120,84,240,453},
274 {0x10b,0x14de,120,84,240,453},
275 {0x10b,0x14df,120,84,240,453},
276 {0x10c,0x14e5,120,84,240,453},
277 {0x10c,0x14e6,120,84,240,453},
278 {0x10c,0x14e7,120,94,238,458},
279 {0x10c,0x14e8,145,93,210,462},
280 {0x10c,0x14e9,139,73,205,466},
281 {0x10c,0x14ea,144,75,154,467},
282 {0x10c,0x14eb,145,73,103,466},
283 {0x10c,0x14ec,142,73,126,466},
284 {0x10c,0x14ed,137,73,246,466},
285 {0x10c,0x14ee,141,74,433,465},
286 {0x10c,0x14ef,117,75,683,465},
287 {0x10d,0x14e0,120,84,240,453},
288 {0x10d,0x14e1,120,85,240,452},
289 {0x10d,0x14e2,120,86,240,452},
290 {0x10d,0x14e3,120,85,240,452},
291 {0x10d,0x14e4,120,84,240,453},
292 {0x10d,0x14e5,120,84,240,453},
293 {0x10d,0x14e6,120,84,240,453},
294 {0x10d,0x14e7,120,94,238,458},
295 {0x10d,0x14e8,145,93,210,462},
296 {0x10d,0x14e9,139,73,205,466},
297 {0x10d,0x14ea,144,75,154,467},
298 {0x10d,0x14eb,145,73,103,466},
299 {0x10d,0x14ec,142,73,126,466},
300 {0x10d,0x14ed,137,73,246,466},
301 {0x10d,0x14ee,141,74,433,465},
302 {0x10d,0x14ef,117,75,683,465},
303 {0x112,0x14f8,34,63,0,450},
304 {0x112,0x14f9,141,101,41,451},
305 {0x112,0x14fa,124,96,205,451},
306 {0x112,0x14fb,119,42,352,453},
307 {0x113,0x14fc,119,42,352,453},
308 {0x113,0x14fd,119,42,352,453},
309 {0x113,0x14fe,119,42,352,453},
310 {0x113,0x14ff,119,42,352,453},
311 {0x113,0x1500,119,42,352,453},
312 {0x113,0x1501,119,42,352,453},
313 {0x113,0x1502,119,42,352,453},
314 {0x114,0x1508,119,42,352,453},
315 {0x114,0x1509,118,42,353,453},
316 {0x114,0x150a,120,85,451,453},
317 {0x114,0x150b,120,85,549,453},
318 {0x114,0x150c,120,97,645,451},
319 {0x114,0x150d,61,59,739,454},
320 {0x115,0x1503,118,42,352,453},
321 {0x115,0x1504,118,43,353,452},
322 {0x115,0x1505,118,43,352,452},
323 {0x115,0x1506,118,43,353,452},
324 {0x115,0x1507,118,42,353,453},
325 {0x115,0x1508,119,42,352,453},
326 {0x115,0x1509,118,42,353,453},
327 {0x115,0x150a,120,85,451,453},
328 {0x115,0x150b,120,85,549,453},
329 {0x115,0x150c,120,97,645,451},
330 {0x115,0x150d,61,59,739,454},
331 {0x11a,0x1525,89,12,273,471},
332 {0x11a,0x1526,94,22,270,461},
333 {0x11a,0x1527,95,29,269,454},
334 {0x11a,0x1528,95,30,269,453},
335 {0x11a,0x1529,97,30,267,453},
336 {0x11a,0x152a,94,30,267,453},
337 {0x11a,0x152b,96,31,265,452},
338 {0x11a,0x152c,109,32,256,452},
339 {0x11a,0x152d,117,33,248,453},
340 {0x11a,0x152e,122,84,243,453},
341 {0x11b,0x152e,122,84,243,453},
342 {0x11c,0x1534,122,84,243,453},
343 {0x11c,0x1535,122,84,243,453},
344 {0x11c,0x1536,117,33,250,453},
345 {0x11c,0x1537,97,32,264,451},
346 {0x11c,0x1538,95,31,266,452},
347 {0x11c,0x1539,97,31,267,452},
348 {0x11c,0x153a,95,30,269,453},
349 {0x11c,0x153b,96,30,269,453},
350 {0x11c,0x153c,96,30,269,453},
351 {0x11c,0x153d,92,21,272,462},
352 {0x11d,0x152f,122,84,243,453},
353 {0x11d,0x1530,122,85,243,452},
354 {0x11d,0x1531,122,85,243,452},
355 {0x11d,0x1532,122,85,243,452},
356 {0x11d,0x1533,122,84,243,453},
357 {0x11d,0x1534,122,84,243,453},
358 {0x11d,0x1535,122,84,243,453},
359 {0x11d,0x1536,117,33,250,453},
360 {0x11d,0x1537,97,32,264,451},
361 {0x11d,0x1538,95,31,266,452},
362 {0x11d,0x1539,97,31,267,452},
363 {0x11d,0x153a,95,30,269,453},
364 {0x11d,0x153b,96,30,269,453},
365 {0x11d,0x153c,96,30,269,453},
366 {0x11d,0x153d,92,21,272,462},
367 {0x11e,0x153e,89,10,454,473},
368 {0x11e,0x153f,95,26,452,457},
369 {0x11e,0x1540,95,30,452,453},
370 {0x11e,0x1541,95,30,454,453},
371 {0x11e,0x1542,94,30,455,453},
372 {0x11e,0x1543,94,30,456,453},
373 {0x11e,0x1544,97,31,456,452},
374 {0x11e,0x1545,108,32,457,452},
375 {0x11e,0x1546,119,33,454,453},
376 {0x11e,0x1547,120,85,459,453},
377 {0x11f,0x1547,120,85,459,453},
378 {0x120,0x154d,120,85,459,453},
379 {0x120,0x154e,120,85,459,453},
380 {0x120,0x154f,119,73,459,453},
381 {0x120,0x1550,120,65,456,453},
382 {0x120,0x1551,120,33,454,453},
383 {0x120,0x1552,116,32,455,453},
384 {0x120,0x1553,115,32,455,453},
385 {0x120,0x1554,113,32,293,453},
386 {0x120,0x1555,93,32,154,453},
387 {0x120,0x1556,18,7,0,489},
388 {0x121,0x1548,120,85,459,453},
389 {0x121,0x1549,121,86,458,452},
390 {0x121,0x154a,120,86,459,452},
391 {0x121,0x154b,120,86,459,452},
392 {0x121,0x154c,120,85,459,453},
393 {0x121,0x154d,120,85,459,453},
394 {0x121,0x154e,120,85,459,453},
395 {0x121,0x154f,119,73,459,453},
396 {0x121,0x1550,120,65,456,453},
397 {0x121,0x1551,120,33,454,453},
398 {0x121,0x1552,116,32,455,453},
399 {0x121,0x1553,115,32,455,453},
400 {0x121,0x1554,113,32,293,453},
401 {0x121,0x1555,93,32,154,453},
402 {0x121,0x1556,18,7,0,489},
403 {0x136,0x158a,175,84,510,474},
404 {0x136,0x158b,152,74,493,467},
405 {0x136,0x158c,150,93,481,462},
406 {0x136,0x158d,136,95,471,457},
407 {0x136,0x158e,120,85,459,453},
408 {0x137,0x158e,120,85,459,453},
409 {0x138,0x1594,121,85,458,453},
410 {0x138,0x1595,119,33,454,453},
411 {0x138,0x1596,110,31,453,453},
412 {0x138,0x1597,95,31,456,452},
413 {0x138,0x1598,95,30,455,453},
414 {0x138,0x1599,96,30,453,453},
415 {0x138,0x159a,97,30,452,453},
416 {0x138,0x159b,95,30,452,453},
417 {0x138,0x159c,95,30,452,453},
418 {0x138,0x159d,94,29,452,454},
419 {0x139,0x158f,120,85,459,453},
420 {0x139,0x1590,120,86,459,452},
421 {0x139,0x1591,120,86,459,452},
422 {0x139,0x1592,120,86,459,452},
423 {0x139,0x1593,120,85,459,453},
424 {0x139,0x1594,121,85,458,453},
425 {0x139,0x1595,119,33,454,453},
426 {0x139,0x1596,110,31,453,453},
427 {0x139,0x1597,95,31,456,452},
428 {0x139,0x1598,95,30,455,453},
429 {0x139,0x1599,96,30,453,453},
430 {0x139,0x159a,97,30,452,453},
431 {0x139,0x159b,95,30,452,453},
432 {0x139,0x159c,95,30,452,453},
433 {0x139,0x159d,94,29,452,454},
434 {0x14a,0x160b,136,84,664,474},
435 {0x14a,0x160c,162,84,337,474},
436 {0x14a,0x160d,141,68,345,467},
437 {0x14a,0x160e,131,93,348,462},
438 {0x14a,0x160f,115,94,353,457},
439 {0x14a,0x1610,121,42,349,453},
440 {0x14b,0x962,121,42,349,453},
441 {0x14b,0x963,121,42,349,453},
442 {0x14b,0x964,121,42,349,453},
443 {0x14b,0x965,121,42,349,453},
444 {0x14b,0x966,121,42,349,453},
445 {0x14b,0x967,121,42,349,453},
446 {0x14b,0x968,121,42,349,453},
447 {0x14c,0x1616,121,42,350,453},
448 {0x14c,0x1617,115,33,352,453},
449 {0x14c,0x1618,109,31,355,453},
450 {0x14c,0x1619,93,31,362,452},
451 {0x14c,0x161a,94,30,361,453},
452 {0x14c,0x161b,94,30,361,453},
453 {0x14c,0x161c,94,30,361,453},
454 {0x14c,0x161d,95,30,361,453},
455 {0x14c,0x161e,95,28,361,455},
456 {0x14c,0x161f,95,28,361,455},
457 {0x14d,0x1611,121,42,350,453},
458 {0x14d,0x1612,121,43,350,452},
459 {0x14d,0x1613,122,43,349,452},
460 {0x14d,0x1614,121,43,350,452},
461 {0x14d,0x1615,120,42,350,453},
462 {0x14d,0x1616,121,42,350,453},
463 {0x14d,0x1617,115,33,352,453},
464 {0x14d,0x1618,109,31,355,453},
465 {0x14d,0x1619,93,31,362,452},
466 {0x14d,0x161a,94,30,361,453},
467 {0x14d,0x161b,94,30,361,453},
468 {0x14d,0x161c,94,30,361,453},
469 {0x14d,0x161d,95,30,361,453},
470 {0x14d,0x161e,95,28,361,455},
471 {0x14d,0x161f,95,28,361,455},
472 }};
473 bool nativeActionShape(data::DataId root,const data::BitmapRuntimeAsset& asset)
474 {
477 asset.sourceType!=data::LegacyDataType::Uap || !asset.source) return false;
478 const auto key=std::pair{data::dataTag(root),data::dataTag(asset.dataId)};
479 const auto found=std::lower_bound(NativeActionShapes.begin(),NativeActionShapes.end(),key,
480 [](const auto& shape,const auto& wanted){return std::pair{shape.root,shape.leaf}<wanted;});
481 if(found==NativeActionShapes.end() || std::pair{found->root,found->leaf}!=key ||
482 asset.image.width!=found->width || asset.image.height!=found->height) return false;
483 const auto metadata=data::inspectLegacyUap(*asset.source);
484 return metadata && metadata->width==found->width && metadata->height==found->height &&
485 metadata->originX==found->x && metadata->originY==found->y;
486 }
487 std::optional<layout::ActionButtonSlot> buttonSlot(unsigned index)
488 {
490 // Same semantic roles as IBar::dispatchDirectRuleAction/handleLocalRuleAction.
491 switch (index)
492 {
493 case 0: case 6: case 11: case 12: case 23: return S::General3;
494 case 1: case 3: case 5: case 8: case 10: case 17: case 21: case 24: case 25: return S::Main;
495 case 2: return S::Camera;
496 case 4: case 9: case 15: case 16: return S::General2;
497 case 7: return S::General1;
498 case 13: case 18: return S::Status;
499 case 14: return S::Options;
500 case 19: return S::Trade;
501 case 20: return S::General4;
502 default: return {}; // Presentation-only/unknown roles retain retail pixels.
503 }
504 }
505 std::optional<unsigned> scoreColour(data::DataId root)
506 {
508 const auto tag = data::dataTag(root);
509 if (tag >= 0x01CB && tag <= 0x01D6) return unsigned(tag - 0x01CB) % 6;
510 return {};
511 }
512 // Same six identities as the existing BoardLightingController palette.
513 constexpr std::array<std::array<int, 3>, 6> PlayerColours{{
514 {255, 0, 0}, {0, 0, 255}, {60, 150, 60},
515 {255, 255, 0}, {255, 0, 255}, {255, 128, 0}}};
516 struct Property { unsigned index{}, style{}; bool miniature{}; };
517 std::optional<Property> property(data::DataId root)
518 {
520 {
521 const auto tag=data::dataTag(root);
522 if(tag>=0x05E6 && tag<=0x0601) return Property{unsigned(tag-0x05E6),0,true};
523 if(tag>=0x05CA && tag<=0x05E5) return Property{unsigned(tag-0x05CA),2,true};
524 return {};
525 }
527 const auto tag = data::dataTag(root);
528 if (tag < 0x0163 || tag > 0x01B6) return {};
529 return Property{unsigned(tag - 0x0163) % 28, unsigned(tag - 0x0163) / 28};
530 }
531 // Authored display-language group colours from EuropeanDeed::propertyColor.
532 constexpr std::array<std::array<int,3>,10> OrdinaryGroups{{
533 {134,103,86}, {35,158,206}, {221,3,109}, {253,128,61},
534 {223,0,0}, {255,255,0}, {37,154,57}, {45,65,144},
535 {188,157,94}, {22,69,70}}};
536 constexpr std::array<std::array<int,3>,10> FrenchGroups{{
537 {217,91,187}, {35,158,206}, {134,0,157}, {237,181,0},
538 {223,0,0}, {255,255,0}, {0,128,52}, {45,65,144},
539 {188,157,94}, {22,69,70}}};
540 bool valid(const data::LegacyBitmapRGBA8& image)
541 { return image.width && image.height && image.pixels.size() == std::size_t(image.width) * image.height * 4; }
542 void caption(data::LegacyBitmapRGBA8& destination, const data::LegacyBitmapRGBA8& source,
543 unsigned top, unsigned height, unsigned ink)
544 {
545 const float scale = std::min({1.0F, float(destination.width - 12) / source.width,
546 float(height) / source.height});
547 const unsigned w = std::max(1U, unsigned(source.width * scale));
548 const unsigned h = std::max(1U, unsigned(source.height * scale));
549 const unsigned ox = (destination.width - w) / 2;
550 for (unsigned y = 0; y < h; ++y)
551 for (unsigned x = 0; x < w; ++x)
552 {
553 const auto src = (std::size_t(y * source.height / h) * source.width + x * source.width / w) * 4;
554 const auto dst = (std::size_t(y + top) * destination.width + x + ox) * 4;
555 const unsigned alpha = source.pixels[src + 3];
556 for (unsigned c = 0; c < 3; ++c)
557 destination.pixels[dst+c] = std::uint8_t((ink * alpha + destination.pixels[dst+c] * (255-alpha) + 127) / 255);
558 }
559 }
560 std::optional<unsigned> currentPlayerToken(data::DataId root)
561 {
563 data::dataTag(root)>=0x005F && data::dataTag(root)<=0x0069)
564 return unsigned(data::dataTag(root)-0x005F);
565 return {};
566 }
567 struct CurrentPlayerShape { std::uint16_t root,leaf,width,height; std::int16_t x,y; };
568 // Actual dat_main CNK_indstra..k: one bitmap per authored phase.
569 // Position includes UDIBar StartXY(0,-4); preview255 is deliberately static.
570 // Decoded eleven CNK payloads SHA256: defa68bcc66567106d6ebf675a54f42e808a1df17f2d6eb2aec9128888cc102a.
571 constexpr std::array<CurrentPlayerShape,328> CurrentPlayerShapes{{
572 {0x5f,0x01d7,43,23,351,501},
573 {0x5f,0x01d8,43,23,350,502},
574 {0x5f,0x01d9,42,23,350,502},
575 {0x5f,0x01da,39,25,351,501},
576 {0x5f,0x01db,34,25,353,501},
577 {0x5f,0x01dc,28,25,356,501},
578 {0x5f,0x01dd,23,24,359,501},
579 {0x5f,0x01de,17,24,363,501},
580 {0x5f,0x01df,18,24,364,501},
581 {0x5f,0x01e0,24,24,362,501},
582 {0x5f,0x01e1,30,24,359,501},
583 {0x5f,0x01e2,36,24,356,501},
584 {0x5f,0x01e3,40,24,354,501},
585 {0x5f,0x01e4,43,24,352,501},
586 {0x5f,0x01e5,44,22,351,502},
587 {0x5f,0x01e6,43,24,351,500},
588 {0x5f,0x01e7,43,26,352,499},
589 {0x5f,0x01e8,42,30,353,497},
590 {0x5f,0x01e9,39,33,355,495},
591 {0x5f,0x01ea,35,36,358,494},
592 {0x5f,0x01eb,28,37,362,493},
593 {0x5f,0x01ec,23,38,364,493},
594 {0x5f,0x01ed,18,38,365,492},
595 {0x5f,0x01ee,19,38,363,492},
596 {0x5f,0x01ef,24,38,359,493},
597 {0x5f,0x01f0,30,37,356,494},
598 {0x5f,0x01f1,36,35,353,495},
599 {0x5f,0x01f2,39,33,352,496},
600 {0x5f,0x01f3,42,30,351,498},
601 {0x5f,0x01f4,43,27,351,499},
602 {0x60,0x01f5,39,17,350,506},
603 {0x60,0x01f6,39,18,350,506},
604 {0x60,0x01f7,39,19,350,506},
605 {0x60,0x01f8,38,21,350,505},
606 {0x60,0x01f9,34,23,352,504},
607 {0x60,0x01fa,30,23,354,504},
608 {0x60,0x01fb,24,24,357,503},
609 {0x60,0x01fc,17,23,361,503},
610 {0x60,0x01fd,19,23,359,503},
611 {0x60,0x01fe,25,23,356,503},
612 {0x60,0x01ff,31,23,353,503},
613 {0x60,0x0200,34,22,352,504},
614 {0x60,0x0201,38,21,350,504},
615 {0x60,0x0202,39,19,350,505},
616 {0x60,0x0203,39,18,350,505},
617 {0x60,0x0204,38,17,351,505},
618 {0x60,0x0205,40,19,350,504},
619 {0x60,0x0206,39,21,351,503},
620 {0x60,0x0207,37,22,352,503},
621 {0x60,0x0208,34,24,354,502},
622 {0x60,0x0209,29,26,356,501},
623 {0x60,0x020a,24,26,359,501},
624 {0x60,0x020b,17,26,362,501},
625 {0x60,0x020c,20,26,361,501},
626 {0x60,0x020d,26,26,358,501},
627 {0x60,0x020e,31,25,355,502},
628 {0x60,0x020f,35,24,353,503},
629 {0x60,0x0210,38,23,351,503},
630 {0x60,0x0211,40,21,350,504},
631 {0x60,0x0212,40,19,350,505},
632 {0x61,0x0302,40,29,352,497},
633 {0x61,0x0303,40,30,352,496},
634 {0x61,0x0304,38,32,352,495},
635 {0x61,0x0305,34,35,354,494},
636 {0x61,0x0306,31,36,355,494},
637 {0x61,0x0307,25,37,357,493},
638 {0x61,0x0308,20,37,359,493},
639 {0x61,0x0309,15,37,360,493},
640 {0x61,0x030a,17,38,358,492},
641 {0x61,0x030b,22,37,355,492},
642 {0x61,0x030c,27,38,352,491},
643 {0x61,0x030d,31,36,350,491},
644 {0x61,0x030e,35,34,349,492},
645 {0x61,0x030f,38,33,348,492},
646 {0x61,0x0310,39,30,349,493},
647 {0x61,0x0311,39,29,350,494},
648 {0x61,0x0312,39,29,350,495},
649 {0x61,0x0313,37,28,352,496},
650 {0x61,0x0314,34,30,354,495},
651 {0x61,0x0315,30,32,356,494},
652 {0x61,0x0316,24,32,360,494},
653 {0x61,0x0317,19,34,363,493},
654 {0x61,0x0318,15,34,366,493},
655 {0x61,0x0319,16,33,367,494},
656 {0x61,0x031a,21,33,365,494},
657 {0x61,0x031b,27,33,362,495},
658 {0x61,0x031c,31,32,360,496},
659 {0x61,0x031d,36,31,357,497},
660 {0x61,0x031e,38,28,355,499},
661 {0x61,0x031f,40,29,353,498},
662 {0x62,0x0213,34,19,350,505},
663 {0x62,0x0214,33,20,350,505},
664 {0x62,0x0215,33,21,350,505},
665 {0x62,0x0216,31,22,351,505},
666 {0x62,0x0217,30,22,351,505},
667 {0x62,0x0218,28,23,352,505},
668 {0x62,0x0219,28,24,352,504},
669 {0x62,0x021a,28,24,352,504},
670 {0x62,0x021b,28,24,352,504},
671 {0x62,0x021c,28,24,352,504},
672 {0x62,0x021d,29,24,351,504},
673 {0x62,0x021e,30,23,351,504},
674 {0x62,0x021f,32,22,350,504},
675 {0x62,0x0220,33,20,350,505},
676 {0x62,0x0221,33,19,350,505},
677 {0x62,0x0222,34,18,350,505},
678 {0x62,0x0223,34,19,350,505},
679 {0x62,0x0224,32,21,351,504},
680 {0x62,0x0225,31,22,352,504},
681 {0x62,0x0226,29,23,353,504},
682 {0x62,0x0227,29,24,353,504},
683 {0x62,0x0228,28,24,354,504},
684 {0x62,0x0229,27,24,354,504},
685 {0x62,0x022a,28,24,354,504},
686 {0x62,0x022b,28,24,354,504},
687 {0x62,0x022c,29,24,353,504},
688 {0x62,0x022d,31,22,352,505},
689 {0x62,0x022e,31,22,352,505},
690 {0x62,0x022f,32,21,351,505},
691 {0x62,0x0230,34,20,350,505},
692 {0x63,0x0231,37,22,349,501},
693 {0x63,0x0232,37,24,349,500},
694 {0x63,0x0233,38,25,349,500},
695 {0x63,0x0234,36,26,350,499},
696 {0x63,0x0235,33,27,352,499},
697 {0x63,0x0236,30,28,354,498},
698 {0x63,0x0237,26,28,355,498},
699 {0x63,0x0238,24,28,355,497},
700 {0x63,0x0239,24,28,352,497},
701 {0x63,0x023a,25,28,350,497},
702 {0x63,0x023b,28,28,348,497},
703 {0x63,0x023c,32,27,347,497},
704 {0x63,0x023d,34,26,347,498},
705 {0x63,0x023e,37,25,347,498},
706 {0x63,0x023f,36,23,349,499},
707 {0x63,0x0240,38,22,349,499},
708 {0x63,0x0241,38,22,349,498},
709 {0x63,0x0242,38,22,349,498},
710 {0x63,0x0243,35,24,350,497},
711 {0x63,0x0244,33,25,351,497},
712 {0x63,0x0245,30,27,352,496},
713 {0x63,0x0246,26,28,354,496},
714 {0x63,0x0247,24,29,357,496},
715 {0x63,0x0248,23,29,360,496},
716 {0x63,0x0249,24,28,361,497},
717 {0x63,0x024a,28,28,359,497},
718 {0x63,0x024b,32,26,356,498},
719 {0x63,0x024c,35,24,354,499},
720 {0x63,0x024d,36,23,352,499},
721 {0x63,0x024e,37,22,350,500},
722 {0x64,0x0250,36,44,349,483},
723 {0x64,0x0251,34,44,350,483},
724 {0x64,0x0252,32,44,351,483},
725 {0x64,0x0253,29,44,352,483},
726 {0x64,0x0254,26,45,353,483},
727 {0x64,0x0255,22,45,353,483},
728 {0x64,0x0256,21,45,353,482},
729 {0x64,0x0257,21,45,353,482},
730 {0x64,0x0258,23,45,351,482},
731 {0x64,0x0259,26,44,349,482},
732 {0x64,0x025a,29,44,347,482},
733 {0x64,0x025b,32,44,346,481},
734 {0x64,0x025c,35,44,345,481},
735 {0x64,0x025d,35,44,346,481},
736 {0x64,0x025e,36,45,346,480},
737 {0x64,0x025f,36,46,346,479},
738 {0x64,0x0260,35,47,347,478},
739 {0x64,0x0261,32,49,349,477},
740 {0x64,0x0262,29,50,351,476},
741 {0x64,0x0263,26,50,353,476},
742 {0x64,0x0264,23,52,356,475},
743 {0x64,0x0265,21,52,358,475},
744 {0x64,0x0266,21,52,358,475},
745 {0x64,0x0267,22,52,358,476},
746 {0x64,0x0268,25,51,357,477},
747 {0x64,0x0269,28,50,356,478},
748 {0x64,0x026a,32,48,353,479},
749 {0x64,0x026b,34,46,352,481},
750 {0x64,0x026c,36,46,350,481},
751 {0x65,0x026d,49,27,347,497},
752 {0x65,0x026e,47,27,348,497},
753 {0x65,0x026f,45,28,349,498},
754 {0x65,0x0270,39,30,352,498},
755 {0x65,0x0271,32,31,355,498},
756 {0x65,0x0272,25,33,358,498},
757 {0x65,0x0273,17,33,362,498},
758 {0x65,0x0274,13,34,364,498},
759 {0x65,0x0275,13,34,364,498},
760 {0x65,0x0276,19,34,361,497},
761 {0x65,0x0277,26,33,357,497},
762 {0x65,0x0278,34,31,353,497},
763 {0x65,0x0279,40,30,351,497},
764 {0x65,0x027a,46,29,348,496},
765 {0x65,0x027b,48,27,347,496},
766 {0x65,0x027c,48,27,348,496},
767 {0x65,0x027d,47,27,349,496},
768 {0x65,0x027e,44,30,351,495},
769 {0x65,0x027f,38,32,354,495},
770 {0x65,0x0280,32,33,357,495},
771 {0x65,0x0281,25,35,361,495},
772 {0x65,0x0282,17,36,365,495},
773 {0x65,0x0283,13,37,367,495},
774 {0x65,0x0284,14,37,366,495},
775 {0x65,0x0285,18,36,364,496},
776 {0x65,0x0286,26,35,360,496},
777 {0x65,0x0287,34,33,356,496},
778 {0x65,0x0288,41,32,352,496},
779 {0x65,0x0289,45,29,350,497},
780 {0x65,0x028a,48,28,348,497},
781 {0x66,0x028b,41,25,351,499},
782 {0x66,0x028c,41,24,351,501},
783 {0x66,0x028d,41,23,351,503},
784 {0x66,0x028e,38,23,352,504},
785 {0x66,0x028f,34,23,354,504},
786 {0x66,0x0290,29,24,356,504},
787 {0x66,0x0291,22,23,359,505},
788 {0x66,0x0292,16,23,361,505},
789 {0x66,0x0293,13,23,363,505},
790 {0x66,0x0294,17,22,361,505},
791 {0x66,0x0295,22,22,359,505},
792 {0x66,0x0296,26,22,358,504},
793 {0x66,0x0297,32,21,355,504},
794 {0x66,0x0298,36,20,353,504},
795 {0x66,0x0299,40,21,351,502},
796 {0x66,0x029a,41,23,351,500},
797 {0x66,0x029b,42,24,351,499},
798 {0x66,0x029c,41,28,352,497},
799 {0x66,0x029d,39,31,353,495},
800 {0x66,0x029e,34,33,356,494},
801 {0x66,0x029f,29,35,359,493},
802 {0x66,0x02a0,23,37,362,492},
803 {0x66,0x02a1,17,38,366,492},
804 {0x66,0x02a2,13,38,368,492},
805 {0x66,0x02a3,16,38,366,492},
806 {0x66,0x02a4,21,38,363,492},
807 {0x66,0x02a5,26,36,360,493},
808 {0x66,0x02a6,31,34,357,494},
809 {0x66,0x02a7,36,31,354,496},
810 {0x66,0x02a8,39,28,352,497},
811 {0x67,0x02a9,28,31,359,496},
812 {0x67,0x02aa,26,31,360,496},
813 {0x67,0x02ab,26,30,360,496},
814 {0x67,0x02ac,26,30,360,496},
815 {0x67,0x02ad,24,30,361,496},
816 {0x67,0x02ae,24,29,361,496},
817 {0x67,0x02af,23,28,361,496},
818 {0x67,0x02b0,23,27,360,496},
819 {0x67,0x02b1,23,27,359,496},
820 {0x67,0x02b2,22,28,358,496},
821 {0x67,0x02b3,23,29,357,496},
822 {0x67,0x02b4,25,30,356,496},
823 {0x67,0x02b5,25,30,356,496},
824 {0x67,0x02b6,27,30,355,496},
825 {0x67,0x02b7,27,31,355,496},
826 {0x67,0x02b8,27,31,355,496},
827 {0x67,0x02b9,27,31,355,496},
828 {0x67,0x02ba,26,31,355,496},
829 {0x67,0x02bb,25,30,356,497},
830 {0x67,0x02bc,25,30,356,497},
831 {0x67,0x02bd,23,29,357,497},
832 {0x67,0x02be,23,29,358,497},
833 {0x67,0x02bf,23,29,359,497},
834 {0x67,0x02c0,23,29,360,497},
835 {0x67,0x02c1,23,29,361,497},
836 {0x67,0x02c2,24,29,361,497},
837 {0x67,0x02c3,25,30,360,497},
838 {0x67,0x02c4,26,30,360,497},
839 {0x67,0x02c5,26,31,360,496},
840 {0x67,0x02c6,27,31,359,496},
841 {0x68,0x02c7,49,20,345,500},
842 {0x68,0x02c8,50,23,343,499},
843 {0x68,0x02c9,48,25,343,499},
844 {0x68,0x02ca,44,27,344,498},
845 {0x68,0x02cb,38,28,346,498},
846 {0x68,0x02cc,31,30,349,497},
847 {0x68,0x02cd,25,31,352,497},
848 {0x68,0x02ce,17,31,357,497},
849 {0x68,0x02cf,19,31,359,497},
850 {0x68,0x02d0,26,30,357,497},
851 {0x68,0x02d1,33,30,354,496},
852 {0x68,0x02d2,40,29,350,496},
853 {0x68,0x02d3,45,27,348,496},
854 {0x68,0x02d4,48,24,346,497},
855 {0x68,0x02d5,50,23,345,497},
856 {0x68,0x02d6,48,21,346,497},
857 {0x68,0x02d7,49,19,347,498},
858 {0x68,0x02d8,47,21,349,497},
859 {0x68,0x02d9,44,22,352,497},
860 {0x68,0x02da,38,23,356,497},
861 {0x68,0x02db,31,25,360,496},
862 {0x68,0x02dc,25,25,363,496},
863 {0x68,0x02dd,18,25,365,496},
864 {0x68,0x02de,20,26,361,496},
865 {0x68,0x02df,26,25,356,497},
866 {0x68,0x02e0,33,25,352,497},
867 {0x68,0x02e1,40,23,349,498},
868 {0x68,0x02e2,45,22,346,499},
869 {0x68,0x02e3,48,21,345,499},
870 {0x68,0x02e4,49,19,345,500},
871 {0x69,0x02e5,39,45,356,484},
872 {0x69,0x02e6,39,45,356,484},
873 {0x69,0x02e7,39,45,356,484},
874 {0x69,0x02e8,39,44,356,484},
875 {0x69,0x02e9,41,44,355,484},
876 {0x69,0x02ea,41,44,355,484},
877 {0x69,0x02eb,41,44,355,484},
878 {0x69,0x02ec,41,45,355,483},
879 {0x69,0x02ed,43,46,354,483},
880 {0x69,0x02ee,42,46,355,483},
881 {0x69,0x02ef,39,46,357,483},
882 {0x69,0x02f0,40,46,356,483},
883 {0x69,0x02f1,40,46,356,483},
884 {0x69,0x02f2,40,46,356,483},
885 {0x69,0x02f3,39,46,356,483},
886 {0x69,0x02f4,39,46,356,483},
887 {0x69,0x02f5,39,46,356,483},
888 {0x69,0x02f6,39,46,356,483},
889 {0x69,0x02f7,40,46,356,483},
890 {0x69,0x02f8,40,45,356,484},
891 {0x69,0x02f9,41,45,355,484},
892 {0x69,0x02fa,42,45,355,484},
893 {0x69,0x02fb,41,45,355,484},
894 {0x69,0x02fc,43,45,354,484},
895 {0x69,0x02fd,43,44,354,484},
896 {0x69,0x02fe,39,44,356,484},
897 {0x69,0x02ff,39,45,356,484},
898 {0x69,0x0300,39,45,356,484},
899 {0x69,0x0301,39,45,356,484},
900 }};
901 // Fixed static-thumbnail envelope, derived from every authentic phase.
902 // World Y already includes root StartXY(0,-4). Roll ends at483 and
903 // name starts at530: keep the rectangle within484..529 and retain
904 // one logical pixel of transparent edge padding.
905 constexpr auto CurrentPlayerWorldRects=[]
906 {
907 std::array<std::array<int,4>,11> boxes{};
908 for(auto& box:boxes) box={1600,600,0,0};
909 for(const auto& shape:CurrentPlayerShapes)
910 {
911 auto& box=boxes[shape.root-0x005F];
912 box[0]=std::min(box[0],int(shape.x));
913 box[1]=std::min(box[1],int(shape.y));
914 box[2]=std::max(box[2],int(shape.x)+shape.width);
915 box[3]=std::max(box[3],int(shape.y)+shape.height);
916 }
917 for(auto& box:boxes)
918 {
919 box[1]=std::max(box[1],484);
920 box[3]=std::min(box[3],529);
921 }
922 return boxes;
923 }();
924 constexpr std::array<std::uint16_t,11> AuctionTokenRoots{4,5,6,10,8,7,3,12,9,13,11};
925 std::optional<unsigned> auctionToken(data::DataId root)
926 {
928 const auto found=std::find(AuctionTokenRoots.begin(),AuctionTokenRoots.end(),data::dataTag(root));
929 return found==AuctionTokenRoots.end() ? std::optional<unsigned>{} :
930 std::optional{unsigned(found-AuctionTokenRoots.begin())};
931 }
932 struct AuctionTokenShape { std::uint16_t root,leaf,width,height; std::int16_t x,y; };
933 // Actual dat_pat aa00n..aa10n bitmap descriptions, including repeated
934 // UAPs at different positions. Static thumbnail255 has no spin mapping.
935 // Eleven decoded CNKs SHA256: 8b329f40d2531137c74257f9c6ab0040773d3478260dab709bfc684444e6b2c3.
936 constexpr std::array<AuctionTokenShape,196> AuctionTokenShapes{{
937 {0x04,0x0b0,42,22,-19,-19},
938 {0x04,0x0b5,42,21,-19,-21},
939 {0x04,0x0b6,43,20,-20,-22},
940 {0x04,0x0b7,44,21,-21,-22},
941 {0x04,0x0b8,46,20,-22,-22},
942 {0x04,0x0ba,49,18,-24,-18},
943 {0x04,0x0bb,55,16,-29,-12},
944 {0x04,0x0bc,53,17,-28,-13},
945 {0x04,0x0bd,30,30,-10,-28},
946 {0x04,0x0be,20,35,-3,-35},
947 {0x04,0x0bf,21,33,-4,-36},
948 {0x04,0x0c0,26,31,-6,-35},
949 {0x04,0x0c1,27,30,-8,-32},
950 {0x04,0x0c2,32,28,-11,-30},
951 {0x04,0x0c3,36,24,-15,-26},
952 {0x04,0x0c4,42,22,-19,-19},
953 {0x04,0x0c5,45,20,-21,-16},
954 {0x04,0x0c6,47,19,-23,-15},
955 {0x04,0x0c7,40,22,-18,-18},
956 {0x05,0x0c8,38,15,-18,-13},
957 {0x05,0x0c9,41,15,-19,-13},
958 {0x05,0x0ca,38,17,-15,-18},
959 {0x05,0x0cb,39,17,-12,-15},
960 {0x05,0x0cc,41,16,-13,-13},
961 {0x05,0x0cd,42,17,-14,-13},
962 {0x05,0x0ce,39,17,-13,-16},
963 {0x05,0x0cf,36,17,-11,-19},
964 {0x05,0x0d0,36,17,-10,-19},
965 {0x05,0x0d1,38,17,-10,-17},
966 {0x05,0x0d2,39,17,-9,-15},
967 {0x05,0x0d3,41,17,-9,-14},
968 {0x05,0x0d4,42,17,-10,-14},
969 {0x05,0x0d5,38,17,-10,-18},
970 {0x05,0x0d6,35,18,-12,-21},
971 {0x05,0x0d7,34,18,-18,-17},
972 {0x05,0x0d8,41,15,-19,-13},
973 {0x05,0x0d9,38,15,-18,-13},
974 {0x06,0x0da,29,21,-13,-18},
975 {0x06,0x0db,26,24,-13,-21},
976 {0x06,0x0dc,26,24,-13,-21},
977 {0x06,0x0dd,26,23,-13,-20},
978 {0x06,0x0de,29,20,-13,-17},
979 {0x06,0x0df,33,24,-13,-21},
980 {0x06,0x0e0,32,23,-13,-20},
981 {0x06,0x0e1,33,24,-13,-21},
982 {0x06,0x0e2,32,23,-13,-20},
983 {0x06,0x0e3,31,21,-13,-18},
984 {0x06,0x0e4,30,21,-13,-18},
985 {0x06,0x0e5,33,24,-13,-21},
986 {0x06,0x0e6,30,22,-13,-19},
987 {0x06,0x0e7,27,20,-13,-17},
988 {0x06,0x0e8,29,21,-13,-18},
989 {0x0a,0x12c,31,13,-15,-11},
990 {0x0a,0x12d,35,11,-17,-8},
991 {0x0a,0x12e,36,11,-18,-7},
992 {0x0a,0x12f,29,15,-14,-19},
993 {0x0a,0x130,20,18,-9,-33},
994 {0x0a,0x131,16,19,-7,-40},
995 {0x0a,0x132,18,18,-8,-44},
996 {0x0a,0x133,21,16,-10,-45},
997 {0x0a,0x134,28,15,-13,-44},
998 {0x0a,0x135,28,12,-13,-40},
999 {0x0a,0x136,29,12,-13,-36},
1000 {0x0a,0x137,28,14,-12,-29},
1001 {0x0a,0x138,20,17,-9,-21},
1002 {0x0a,0x139,19,17,-8,-15},
1003 {0x0a,0x13a,29,13,-13,-12},
1004 {0x0a,0x13b,36,11,-18,-8},
1005 {0x0a,0x13c,31,13,-15,-11},
1006 {0x08,0x0fa,28,14,-13,-11},
1007 {0x08,0x0fb,32,13,-15,-10},
1008 {0x08,0x0fc,35,11,-17,-9},
1009 {0x08,0x0fd,34,11,-16,-9},
1010 {0x08,0x0fe,30,13,-14,-10},
1011 {0x08,0x0ff,28,14,-13,-11},
1012 {0x08,0x100,28,14,-13,-11},
1013 {0x08,0x101,30,13,-14,-10},
1014 {0x08,0x102,40,10,-19,-8},
1015 {0x08,0x103,34,12,-16,-10},
1016 {0x08,0x104,30,13,-14,-16},
1017 {0x08,0x105,30,13,-14,-23},
1018 {0x08,0x106,30,13,-14,-28},
1019 {0x08,0x106,30,13,-14,-29},
1020 {0x08,0x106,30,13,-14,-28},
1021 {0x08,0x107,30,13,-14,-25},
1022 {0x08,0x108,32,13,-15,-17},
1023 {0x08,0x109,30,14,-14,-12},
1024 {0x08,0x10a,36,11,-17,-9},
1025 {0x08,0x10b,28,14,-13,-11},
1026 {0x07,0x0e9,20,24,-9,-23},
1027 {0x07,0x0ea,20,24,-9,-23},
1028 {0x07,0x0eb,24,21,-11,-19},
1029 {0x07,0x0ec,17,27,-7,-25},
1030 {0x07,0x0ed,16,29,-7,-28},
1031 {0x07,0x0ee,18,25,-8,-28},
1032 {0x07,0x0ef,23,22,-11,-28},
1033 {0x07,0x0f0,24,20,-11,-29},
1034 {0x07,0x0f1,22,22,-10,-33},
1035 {0x07,0x0f2,20,25,-9,-36},
1036 {0x07,0x0f3,18,26,-8,-35},
1037 {0x07,0x0f4,18,27,-8,-32},
1038 {0x07,0x0f5,18,27,-8,-28},
1039 {0x07,0x0f6,20,24,-9,-23},
1040 {0x07,0x0f7,24,20,-11,-18},
1041 {0x07,0x0f8,20,24,-9,-23},
1042 {0x07,0x0f9,20,24,-9,-23},
1043 {0x03,0x0a0,34,19,-16,-18},
1044 {0x03,0x0a1,37,17,-17,-17},
1045 {0x03,0x0a2,30,22,-14,-21},
1046 {0x03,0x0a3,24,28,-11,-26},
1047 {0x03,0x0a4,23,30,-10,-29},
1048 {0x03,0x0a5,44,15,-21,-15},
1049 {0x03,0x0a6,34,19,-16,-20},
1050 {0x03,0x0a7,23,29,-10,-29},
1051 {0x03,0x0a8,24,32,-10,-32},
1052 {0x03,0x0a9,24,33,-10,-37},
1053 {0x03,0x0a9,24,33,-10,-38},
1054 {0x03,0x0aa,26,29,-11,-31},
1055 {0x03,0x0ab,30,22,-14,-26},
1056 {0x03,0x0ac,34,19,-16,-21},
1057 {0x03,0x0ad,34,19,-16,-21},
1058 {0x03,0x0ae,34,19,-16,-21},
1059 {0x0c,0x14c,27,16,-13,-15},
1060 {0x0c,0x14d,21,20,-7,-18},
1061 {0x0c,0x14e,18,22,-4,-19},
1062 {0x0c,0x14f,20,24,-6,-23},
1063 {0x0c,0x150,21,26,-7,-29},
1064 {0x0c,0x151,22,20,-11,-40},
1065 {0x0c,0x152,23,17,-12,-38},
1066 {0x0c,0x153,25,16,-14,-37},
1067 {0x0c,0x154,27,16,-15,-36},
1068 {0x0c,0x157,21,17,-12,-33},
1069 {0x0c,0x158,22,19,-14,-20},
1070 {0x0c,0x159,24,20,-13,-20},
1071 {0x0c,0x15a,29,11,-16,-9},
1072 {0x0c,0x15b,24,14,-11,-13},
1073 {0x0c,0x15c,26,15,-12,-14},
1074 {0x09,0x113,17,19,-8,-18},
1075 {0x09,0x114,16,20,-7,-20},
1076 {0x09,0x115,16,22,-7,-22},
1077 {0x09,0x116,17,22,-8,-23},
1078 {0x09,0x117,18,23,-9,-25},
1079 {0x09,0x118,16,22,-7,-24},
1080 {0x09,0x119,15,23,-7,-24},
1081 {0x09,0x11a,17,22,-8,-22},
1082 {0x09,0x11b,21,22,-9,-21},
1083 {0x09,0x11c,21,22,-9,-21},
1084 {0x09,0x11d,22,23,-10,-21},
1085 {0x09,0x11e,19,23,-10,-21},
1086 {0x09,0x11f,17,22,-10,-20},
1087 {0x09,0x120,16,22,-10,-20},
1088 {0x09,0x121,16,22,-10,-19},
1089 {0x09,0x122,20,22,-14,-19},
1090 {0x09,0x123,21,22,-15,-20},
1091 {0x09,0x124,21,22,-14,-20},
1092 {0x09,0x125,20,22,-13,-20},
1093 {0x09,0x126,17,22,-10,-19},
1094 {0x09,0x127,17,22,-9,-20},
1095 {0x09,0x128,17,21,-8,-19},
1096 {0x09,0x129,17,19,-8,-18},
1097 {0x09,0x12a,17,19,-8,-18},
1098 {0x09,0x12b,17,19,-8,-18},
1099 {0x0d,0x15d,41,15,-24,-12},
1100 {0x0d,0x15e,58,11,-32,-9},
1101 {0x0d,0x15f,43,41,-19,-36},
1102 {0x0d,0x160,19,55,4,-51},
1103 {0x0d,0x161,20,57,4,-53},
1104 {0x0d,0x162,14,57,10,-53},
1105 {0x0d,0x163,15,57,9,-53},
1106 {0x0d,0x164,15,57,8,-53},
1107 {0x0d,0x165,20,57,3,-53},
1108 {0x0d,0x166,21,56,2,-52},
1109 {0x0d,0x167,21,56,3,-52},
1110 {0x0d,0x168,20,56,4,-52},
1111 {0x0d,0x169,19,56,4,-51},
1112 {0x0d,0x160,19,55,4,-51},
1113 {0x0d,0x15f,43,41,-19,-36},
1114 {0x0d,0x15e,58,11,-32,-9},
1115 {0x0d,0x15d,41,15,-24,-12},
1116 {0x0b,0x13d,20,19,-9,-17},
1117 {0x0b,0x13e,28,14,-13,-11},
1118 {0x0b,0x13f,16,25,-7,-23},
1119 {0x0b,0x140,18,21,-8,-28},
1120 {0x0b,0x141,20,17,-9,-31},
1121 {0x0b,0x13f,16,25,-7,-32},
1122 {0x0b,0x143,18,18,-8,-33},
1123 {0x0b,0x144,19,17,-8,-34},
1124 {0x0b,0x144,19,17,-8,-35},
1125 {0x0b,0x144,19,17,-8,-36},
1126 {0x0b,0x145,18,20,-8,-36},
1127 {0x0b,0x146,18,22,-8,-35},
1128 {0x0b,0x147,17,25,-7,-34},
1129 {0x0b,0x148,28,14,-12,-12},
1130 {0x0b,0x149,19,21,-8,-18},
1131 {0x0b,0x14a,20,19,-9,-17},
1132 {0x0b,0x14b,20,19,-9,-17},
1133 }};
1134 std::optional<unsigned> scoreToken(data::DataId root)
1135 {
1137 data::dataTag(root) >= 0x01C0 && data::dataTag(root) <= 0x01CA)
1138 return unsigned(data::dataTag(root) - 0x01C0);
1139 return {};
1140 }
1141 constexpr std::array<std::array<unsigned,2>,11> ScoreTokenSizes{{
1142 {53,29},{52,22},{39,29},{39,26},{39,26},{22,29},
1143 {54,32},{47,28},{22,29},{49,20},{24,31}}};
1144 std::optional<std::array<unsigned,4>> deedArtworkRect(data::DataId root,
1146 {
1148 const auto tag=data::dataTag(root);
1150 (tag==0x0CD2 || tag==0x0CDA || tag==0x0CE1 || tag==0x0CE9)) return std::array<unsigned,4>{68,18,125,61};
1151 if(kind==ModernIBarSkin::DeedArtwork::Electric && tag==0x0CD7) return std::array<unsigned,4>{74,16,114,63};
1152 if(kind==ModernIBarSkin::DeedArtwork::Water && tag==0x0CE4) return std::array<unsigned,4>{73,18,127,63};
1153 return {};
1154 }
1155 constexpr std::array<unsigned,16> ChanceFaceBackgrounds{
1156 0x814,0x815,0x816,0x849,0x819,0x81B,0x81C,0x81D,
1157 0x81E,0x81F,0x820,0x821,0x812,0x823,0x824,0x813};
1158 constexpr std::array<unsigned,16> ChestFaceBackgrounds{
1159 0x8CB,0x8CC,0x8CD,0x8CE,0x8C8,0x8C9,0x8D1,0x8D2,
1160 0x8D3,0x8D4,0x8D5,0x8D6,0x8CA,0x8D8,0x86C,0x8DA};
1161 struct NativeIdleCardSource { data::DataId leaf; unsigned height; };
1163 {
1164 //31 measured Out UAPs are byte-identical to their idle artwork.
1165 // Poor Tax's outgoing leaf50845 differs; keep that complete root retail.
1166 if((root>=0x50048 && root<=0x50057 && root!=0x50054) ||
1167 (root>=0x50079 && root<=0x50088)) return root-0x20;
1168 return root;
1169 }
1170 std::string_view readableIllustratedCardBody(data::DataId idleRoot) noexcept
1171 {
1172 // Exact baked-occlusion owners. Their measured Out faces are raw
1173 // byte-identical; the existing owner/leaf guards qualify each phase.
1174 switch(idleRoot)
1175 {
1176 case 0x5002B:return "Your building and loan matures. Collect $150.";
1177 case 0x5005D:return "Get out of jail, free. This card may be kept until needed or sold.";
1178 case 0x5005F:return "Bank error in your favour. Collect $200.";
1179 case 0x50060:return "Income tax refund. Collect $20.";
1180 case 0x50062:return "Go to jail. Go directly to jail. Do not pass GO. Do not collect $200.";
1181 default:return {};
1182 }
1183 }
1184 std::optional<NativeIdleCardSource> nativeIdleCardSource(data::DataId root)
1185 {
1186 // All32 licensed USA idle faces have a single measured UAP leaf.
1187 // St.Charles is239px high; do not expand its authored footprint.
1188 if(root>=0x00050028 && root<=0x00050037)
1189 return NativeIdleCardSource{0x00050982+root-0x00050028,root==0x00050029?239U:240U};
1190 if(root>=0x00050059 && root<=0x00050068)
1191 return NativeIdleCardSource{0x00050972+root-0x00050059,240U};
1192 if(root>=0x50048 && root<=0x50057 && root!=0x50054)
1193 return NativeIdleCardSource{0x50839+root-0x50048,root==0x50049?239U:240U};
1194 if(root>=0x50079 && root<=0x50088)
1195 return NativeIdleCardSource{0x508EB+root-0x50079,240U};
1196 return {};
1197 }
1198 std::optional<data::DataId> faceIdleRoot(data::DataId root)
1199 {
1200 if(root>=0x50018 && root<=0x50027) return root+0x10;
1201 if(root>=0x50008 && root<=0x50017) return root+0x51;
1202 return {};
1203 }
1205 {
1207 const auto tag=data::dataTag(leaf);
1208 if(root>=0x50018 && root<=0x50027)
1209 return tag==ChanceFaceBackgrounds[root-0x50018] || (root==0x5001C && tag==0x81A);
1210 return root>=0x50008 && root<=0x50017 && tag==ChestFaceBackgrounds[root-0x50008];
1211 }
1212 struct FaceSprite
1213 {
1215 std::int16_t x,y;
1216 };
1217 constexpr std::array<FaceSprite,1151> FaceSprites{{
1218#include "ModernCardFaceInSprites.inc"
1219 }};
1220 bool faceSprite(data::DataId root,const data::BitmapRuntimeAsset& asset)
1221 {
1222 if(data::dataGroup(asset.dataId)!=data::legacyGroupValue(data::LegacyGroupId::LanguageGraphics) || !asset.source) return false;
1223 const auto key=std::pair{data::dataTag(root),data::dataTag(asset.dataId)};
1224 const auto found=std::lower_bound(FaceSprites.begin(),FaceSprites.end(),key,
1225 [](const FaceSprite& item,const auto& wanted){return std::pair{item.rootTag,item.leafTag}<wanted;});
1226 if(found==FaceSprites.end() || std::pair{found->rootTag,found->leafTag}!=key ||
1227 asset.image.width!=found->width || asset.image.height!=found->height) return false;
1228 const auto metadata=data::inspectLegacyUap(*asset.source);
1229 return metadata && metadata->width==found->width && metadata->height==found->height &&
1230 metadata->originX==found->x && metadata->originY==found->y;
1231 }
1232
1233 bool playerCashPlacement(const sequence::Matrix2D& world)
1234 {
1235 bool column=false;
1236 for(int width:{130,198})for(int index=0;index<(width==130?6:4);++index)
1237 for(int shown:{0,index})
1238 column=column || std::abs(world.values[6]-(index*width+8+shown*3))<=.0001F;
1239 const std::array<float,9> expected{1,0,0,0,1,0,world.values[6],254,1};
1240 for(unsigned i=0;i<9;++i)
1241 if(!std::isfinite(world.values[i]) || std::abs(world.values[i]-expected[i])>.0001F)return false;
1242 return column;
1243 }
1244 bool cashCoinPlacement(const sequence::Matrix2D& world,std::uint16_t priority)
1245 {
1246 if(priority==501)return playerCashPlacement(world);
1247 if(priority<100 || priority>103)return false;
1248 // UDTrade StartXY TAB_snpscsh1: exact four owner/priority positions.
1249 constexpr std::array<std::array<float,2>,4> positions{{{9,395},{609,395},{209,358},{409,358}}};
1250 const auto& position=positions[priority-100];
1251 const auto expected=sequence::translate2D(position[0],position[1]);
1252 for(unsigned i=0;i<9;++i)
1253 if(!std::isfinite(world.values[i]) || std::abs(world.values[i]-expected.values[i])>.0001F)return false;
1254 return true;
1255 }
1256 bool originalPlayerCash(const data::BitmapRuntimeAsset& asset)
1257 {
1258 if(asset.dataId!=0x00020355 || asset.sourceType!=data::LegacyDataType::Uap || !asset.source ||
1259 asset.source->size()!=4404 || asset.image.width!=52 || asset.image.height!=45 ||
1260 asset.image.pixels.size()!=52*45*4)return false;
1261 const auto header=data::inspectLegacyUap(*asset.source);
1262 if(!header || header->originX || header->originY || header->width!=52 || header->height!=45 ||
1263 header->flags!=6 || header->colourCount!=256 || header->alphaCount!=17)return false;
1264 // Exact retail bytes and decoded RGBA, not merely an arbitrary52x45 icon.
1265 // Payload SHA2566727a50b4bb0a4aca60e08539585c907311f44896765d0086d5cbed0698e3718.
1266 std::uint64_t source=14695981039346656037ULL,pixels=source;
1267 for(const auto value:*asset.source)source=(source^std::to_integer<std::uint8_t>(value))*1099511628211ULL;
1268 for(const auto value:asset.image.pixels)pixels=(pixels^value)*1099511628211ULL;
1269 return source==0x9cf37537e7999931ULL && pixels==0x7a62522977daf2e7ULL;
1270 }
1271 data::LegacyBitmapRGBA8 playerCashCoin(const data::LegacyBitmapRGBA8& native)
1272 {
1273 data::LegacyBitmapRGBA8 image{156,135,std::vector<std::uint8_t>(156*135*4)};
1274 const auto segment=[](float x,float y,float ax,float ay,float bx,float by)
1275 {
1276 const auto dx=bx-ax,dy=by-ay;
1277 const float t=std::clamp(((x-ax)*dx+(y-ay)*dy)/(dx*dx+dy*dy),0.0F,1.0F);
1278 return std::hypot(x-ax-t*dx,y-ay-t*dy);
1279 };
1280 const auto curve=[](float t,const std::array<float,8>& p)
1281 {
1282 const float u=1-t;
1283 return std::array<float,2>{u*u*u*p[0]+3*u*u*t*p[2]+3*u*t*t*p[4]+t*t*t*p[6],
1284 u*u*u*p[1]+3*u*u*t*p[3]+3*u*t*t*p[5]+t*t*t*p[7]};
1285 };
1286 constexpr std::array<std::array<float,8>,2> strokes{{
1287 {30,9,20,5,19,15,26,15},{26,15,34,15,33,25,22,21}}};
1288 for(unsigned y=0;y<135;++y)for(unsigned x=0;x<156;++x)
1289 {
1290 const auto at=(std::size_t(y)*156+x)*4,old=(std::size_t(y/3)*52+x/3)*4;
1291 std::copy_n(native.pixels.begin()+old,4,image.pixels.begin()+at);
1292 if(y>=93)continue; // Socle31..44 retains every straight RGBA byte exactly.
1293 const float px=(x+.5F)/3,py=(y+.5F)/3;
1294 const float radius=std::hypot(px-26,py-15);
1295 std::array<float,3> color{22,55,56};
1296 const auto mix=[&](std::array<float,3> ink,float coverage)
1297 {for(unsigned c=0;c<3;++c)color[c]+=std::clamp(coverage,0.0F,1.0F)*(ink[c]-color[c]);};
1298 mix({192,158,90},(14.1F-radius)*3+.5F);
1299 mix({242,231,197},(11.8F-radius)*3+.5F);
1300 float distance=segment(px,py,26,5.5F,26,25);
1301 for(const auto& stroke:strokes)
1302 {
1303 auto previous=curve(0,stroke);
1304 for(unsigned step=1;step<=24;++step)
1305 {
1306 const auto next=curve(float(step)/24,stroke);
1307 distance=std::min(distance,segment(px,py,previous[0],previous[1],next[0],next[1]));
1308 previous=next;
1309 }
1310 }
1311 mix({22,69,70},(.8F-distance)*3+.5F);
1312 for(unsigned c=0;c<3;++c)image.pixels[at+c]=image.pixels[at+3]?std::uint8_t(std::lround(color[c])):native.pixels[old+c];
1313 // Preserve the native transparent/partial-alpha mask exactly at3x.
1314 }
1315 return image;
1316 }
1317 bool backdrop(data::DataId root)
1318 {
1319 return data::dataGroup(root) == data::legacyGroupValue(data::LegacyGroupId::Main) &&
1320 (data::dataTag(root) >= 0x015B && data::dataTag(root) <= 0x0160 ||
1321 data::dataTag(root) == 0x0162);
1322 }
1323 }
1324
1325 bool ModernIBarSkin::supportsCardFaceIn(data::DataId root) const noexcept
1326 {
1327 const auto idle=faceIdleRoot(root);
1328 return language_==data::LanguageId::EnglishUs && presentationContext_ && idle && drawCards_.contains(*idle);
1329 }
1330
1331 bool ModernIBarSkin::supportsAuctionToken(data::DataId root) const noexcept
1332 {
1333 return auctionToken(root) && language_==data::LanguageId::EnglishUs && tokenImages_ && presentationContext_;
1334 }
1335
1336 bool ModernIBarSkin::supports(data::DataId root) const noexcept
1337 {
1338 if(supportsCardFaceIn(root) || supportsAuctionToken(root)) return true;
1339 if(root==0x00020355)return language_==data::LanguageId::EnglishUs && bool(presentationContext_);
1340 if(const auto p=property(root);p && p->miniature)
1341 return language_==data::LanguageId::EnglishUs && properties_ && propertyText_ && presentationContext_;
1342 const bool language = language_ == data::LanguageId::French ||
1343 language_ == data::LanguageId::EnglishUs || language_ == data::LanguageId::EnglishUk;
1344 if (scoreToken(root) || currentPlayerToken(root)) return language_ == data::LanguageId::EnglishUs && tokenImages_ && presentationContext_;
1345 if (deedText_ && deeds_.contains(root)) return true;
1346 if (drawText_ && drawCards_.contains(illustratedCardIdleOwner(root))) return true;
1347 return language && text_ && (backdrop(root) || (button(root).has_value() && layout_) || scoreColour(root).has_value() ||
1348 (property(root).has_value() && properties_ && propertyText_));
1349 }
1350
1351 bool ModernIBarSkin::supportsIdleCardPresentation(data::DataId root) const noexcept
1352 {
1353 return language_==data::LanguageId::EnglishUs && drawCards_.contains(root) &&
1354 ((root>=0x50028 && root<=0x50037) || (root>=0x50059 && root<=0x50068));
1355 }
1356
1357 std::shared_ptr<const data::BitmapRuntimeAsset> ModernIBarSkin::substitute(
1358 data::DataId root, std::shared_ptr<const data::BitmapRuntimeAsset> original, bool principal,
1359 std::optional<sequence::Matrix2D> rasterToWorld, std::optional<std::uint16_t> priority,
1360 std::optional<sequence::Matrix2D> sequenceToWorld)
1361 {
1362 if (!original || !supports(root)) return original;
1363 const auto w = original->image.width, h = original->image.height;
1364 if (!w || !h || w > 1600 || h > 600) return original;
1365 if(root==0x00020355)
1366 {
1367 if(!principal || !presentationContext_ || !presentationContext_() || !priority ||
1368 !rasterToWorld || !cashCoinPlacement(*rasterToWorld,*priority) || !originalPlayerCash(*original))return original;
1369 const Key key{root,w,h,true,{},501,{},original.get()};
1370 if(const auto found=cache_.find(key);found!=cache_.end())return found->second.replacement;
1371 auto result=std::make_shared<data::BitmapRuntimeAsset>();result->dataId=original->dataId;
1372 result->sourceType=data::LegacyDataType::Native;result->source=original->source;
1373 result->image=playerCashCoin(original->image);result->preferLinearFiltering=true;
1374 result->presentationRect=std::array<float,4>{0,0,52,45};
1375 if(cache_.size()>=128)cache_.clear();cache_.emplace(key,CachedArtwork{result,original});return result;
1376 }
1377 if(faceIdleRoot(root))
1378 {
1379 if(!supportsCardFaceIn(root) || !presentationContext_() || original->sourceType!=data::LegacyDataType::Uap || !valid(original->image)) return original;
1380 const bool background=faceBackground(root,original->dataId) && w==400 && h==240;
1381 if(!background && !faceSprite(root,*original)) return original;
1382 const Key key{root,w,h,false,std::array<float,9>{},0,{},original.get()};
1383 if(const auto found=cache_.find(key);found!=cache_.end()) return found->second.replacement;
1384 bool warm=false,ink=false;
1385 const auto& pixels=original->image.pixels;
1386 // Actual USA backgrounds/sprites contain warm fill plus dark/grayscale ink.
1387 // Unexpected coloured artwork fails closed rather than losing its colour.
1388 for(std::size_t i=0;i<pixels.size();i+=4)
1389 {
1390 const unsigned r=pixels[i],g=pixels[i+1],b=pixels[i+2];
1391 const auto low=std::min({r,g,b}),high=std::max({r,g,b});
1392 if(pixels[i+3]==0) continue; // Preserve even the transparent magenta colour key.
1393 if(high<=40 || high-low<=8) { ink|=high<64; continue; }
1394 if(r+8<g || g+8<b) return original;
1395 warm=true;
1396 }
1397 if(!warm || (background && !ink)) return original;
1398 auto result=std::make_shared<data::BitmapRuntimeAsset>(*original);
1399 result->sourceType=data::LegacyDataType::Native;
1400 constexpr std::array<unsigned,3> cream{237,232,215};
1401 for(std::size_t i=0;i<pixels.size();i+=4)
1402 {
1403 const unsigned r=pixels[i],g=pixels[i+1],b=pixels[i+2];
1404 if(pixels[i+3]==0 || std::max({r,g,b})<=40 || std::max({r,g,b})-std::min({r,g,b})<=8) continue;
1405 for(unsigned c=0;c<3;++c) result->image.pixels[i+c]=std::uint8_t((r*cream[c]+127)/255);
1406 }
1407 if(cache_.size()>=128) cache_.clear();
1408 cache_.emplace(key,CachedArtwork{result,original});
1409 return result;
1410 }
1411 const auto miniature=property(root);
1412 if(miniature && miniature->miniature &&
1413 (!principal || original->dataId!=root || original->sourceType!=data::LegacyDataType::Uap ||
1414 w!=36 || h!=42 || original->image.pixels.size()!=std::size_t(w)*h*4 ||
1415 !presentationContext_ || !presentationContext_())) return original;
1416 const auto currentToken=currentPlayerToken(root);
1417 const auto auctionOwner=auctionToken(root);
1418 if (const auto token = currentToken ? currentToken : auctionOwner ? auctionOwner : scoreToken(root))
1419 {
1420 std::optional<std::array<float,4>> currentRect;
1421 if (!principal || !presentationContext_ || !presentationContext_() ||
1422 original->image.pixels.size()!=std::size_t(w)*h*4) return original;
1423 if (currentToken)
1424 {
1425 if (!rasterToWorld || original->sourceType!=data::LegacyDataType::Uap ||
1426 (priority && *priority!=258) ||
1427 data::dataGroup(original->dataId)!=data::legacyGroupValue(data::LegacyGroupId::Main)) return original;
1428 const auto shape=std::find_if(CurrentPlayerShapes.begin(),CurrentPlayerShapes.end(),
1429 [&](const auto& value) { return value.root==data::dataTag(root) &&
1430 value.leaf==data::dataTag(original->dataId) && value.width==w && value.height==h; });
1431 if (shape==CurrentPlayerShapes.end()) return original;
1432 // Exact phase guards stay authored, but static thumbnail255 must
1433 // retain one size and anchor across the old rotating UAP phases.
1434 // Convert the measured world union back to local coordinates;
1435 // the renderer applies unchanged root StartXY(0,-4) once.
1436 const auto& box=CurrentPlayerWorldRects[*currentToken];
1437 currentRect=std::array<float,4>{float(box[0]),float(box[1]+4),
1438 float(box[2]),float(box[3]+4)};
1439 const std::array<float,9> expected{1,0,0,0,1,0,float(shape->x),float(shape->y),1};
1440 for(unsigned i=0;i<9;++i)
1441 if(!std::isfinite(rasterToWorld->values[i]) ||
1442 std::abs(rasterToWorld->values[i]-expected[i])>0.0001F) return original;
1443 }
1444 else if(auctionOwner)
1445 {
1447 original->sourceType!=data::LegacyDataType::Uap ||
1448 data::dataGroup(original->dataId)!=data::legacyGroupValue(data::LegacyGroupId::Patterns))return original;
1449 const unsigned player=*priority-316;
1450 bool column=false;
1451 for(unsigned count=player+1;count<=6;++count)
1452 {
1453 const int width=count>4 ? 134 : 201;
1454 const int spacing=(800-int(count)*width)/int(count+1);
1455 const int center=spacing+int(player)*(width+spacing)+width/2;
1456 column=column || std::abs(sequenceToWorld->values[6]-center)<=0.0001F;
1457 }
1458 if(!column)return original;
1459 const std::array<float,9> expectedSequence{1,0,0,0,1,0,sequenceToWorld->values[6],560,1};
1460 for(unsigned i=0;i<9;++i)
1461 if(!std::isfinite(sequenceToWorld->values[i]) ||
1462 std::abs(sequenceToWorld->values[i]-expectedSequence[i])>0.0001F)return original;
1463 const auto shape=std::find_if(AuctionTokenShapes.begin(),AuctionTokenShapes.end(),[&](const auto& value)
1464 {
1465 if(value.root!=data::dataTag(root) || value.leaf!=data::dataTag(original->dataId) || value.width!=w || value.height!=h)return false;
1466 const std::array<float,9> expected{1,0,0,0,1,0,
1467 sequenceToWorld->values[6]+value.x,560.0F+value.y,1};
1468 for(unsigned i=0;i<9;++i)
1469 if(!std::isfinite(rasterToWorld->values[i]) ||
1470 std::abs(rasterToWorld->values[i]-expected[i])>0.0001F)return false;
1471 return true;
1472 });
1473 if(shape==AuctionTokenShapes.end())return original;
1474 // Complete name surface ends550; cash surface starts565.
1475 // Fixed54x15 logical canvas leaves both untouched for every phase.
1476 currentRect=std::array<float,4>{-27,-10,27,5};
1477 }
1478 else if(original->dataId!=root || w!=ScoreTokenSizes[*token][0] || h!=ScoreTokenSizes[*token][1]) return original;
1479 const bool retainSource=currentToken.has_value() || auctionOwner.has_value();
1480 const Key key{root,w,h,true,currentToken ? rasterToWorld->values :
1481 auctionOwner ? sequenceToWorld->values : std::array<float,9>{},
1482 auctionOwner ? int(*priority) : 0,{},retainSource ? original.get() : nullptr};
1483 if (const auto found=cache_.find(key);found!=cache_.end())return found->second.replacement;
1484 const auto source=tokenImages_(std::uint8_t(*token));
1485 if (!source || source->width!=768 || source->height!=640 || source->pixels.size()!=std::size_t(768)*640*4)
1486 return original;
1487 unsigned left=768,top=640,right=0,bottom=0;
1488 bool transparent=false;
1489 for(unsigned y=0;y<640;++y)for(unsigned x=0;x<768;++x)
1490 {
1491 if(source->pixels[(std::size_t(y)*768+x)*4+3])
1492 {left=std::min(left,x);top=std::min(top,y);right=std::max(right,x+1);bottom=std::max(bottom,y+1);}
1493 else transparent=true;
1494 }
1495 if(!transparent || right<=left || bottom<=top)return original;
1496 auto result=std::make_shared<data::BitmapRuntimeAsset>();
1497 result->dataId=original->dataId;result->sourceType=data::LegacyDataType::Native;
1498 if(retainSource) result->source=original->source;
1499 result->preferLinearFiltering=true;
1500 result->presentationRect=currentRect ? currentRect : std::optional{std::array<float,4>{0,0,float(w),float(h)}};
1501 auto& image=result->image;
1502 const unsigned canvasWidth=currentRect ? unsigned((*currentRect)[2]-(*currentRect)[0]) : w;
1503 const unsigned canvasHeight=currentRect ? unsigned((*currentRect)[3]-(*currentRect)[1]) : h;
1505 std::vector<std::uint8_t>(std::size_t(canvasWidth)*canvasHeight*36,0)};
1506 const double fit=std::min(double(image.width-6)/(right-left),double(image.height-6)/(bottom-top));
1507 const unsigned iw=std::max(1U,unsigned((right-left)*fit)),ih=std::max(1U,unsigned((bottom-top)*fit));
1508 const unsigned ox=(image.width-iw)/2,oy=(image.height-ih)/2;
1509 for(unsigned y=0;y<ih;++y)for(unsigned x=0;x<iw;++x)
1510 {
1511 const auto src=(std::size_t(top+y*(bottom-top)/ih)*768+left+x*(right-left)/iw)*4;
1512 const auto dst=(std::size_t(y+oy)*image.width+x+ox)*4;
1513 std::copy_n(source->pixels.data()+src,4,image.pixels.data()+dst);
1514 }
1515 if(cache_.size()>=128)cache_.clear();
1516 cache_.emplace(key,CachedArtwork{result,retainSource ? original : nullptr});return result;
1517 }
1518 const auto deed = deeds_.find(root);
1520 const auto draw = drawCards_.find(idleOwner);
1521 if(const auto native=nativeIdleCardSource(root);native && draw!=drawCards_.end())
1522 {
1523 // Each production idle UAP contains both its Morris illustration
1524 // and printed caption. Artwork often overlaps the caption.
1525 // Preserve that authored ink rather than covering it with a new
1526 // title/body; no generic crop can safely separate these layers.
1527 if(!principal || original->dataId!=native->leaf || w!=400 || h!=native->height ||
1528 original->sourceType!=data::LegacyDataType::Uap || !valid(original->image) ||
1529 language_!=data::LanguageId::EnglishUs || !presentationContext_ ||
1530 !presentationContext_()) return original;
1531 if(idleOwner!=root)
1532 {
1533 // Out keeps its original moving CNK and supplementary1x1 leaf.
1534 // Only the real zero-origin opaque UAP face is qualified here.
1535 if(!original->source)return original;
1536 const auto metadata=data::inspectLegacyUap(*original->source);
1537 if(!metadata || metadata->width!=400 || metadata->height!=native->height ||
1538 metadata->originX!=0 || metadata->originY!=0 || metadata->flags!=5) return original;
1539 }
1540 std::optional<IdleCardPresentation> presentation;
1541 if(supportsIdleCardPresentation(root) && priority==1005 && sequenceToWorld && idleCardPresentation_)
1542 {
1543 const auto& m=sequenceToWorld->values;
1544 // Only the two native root placements qualify, without rotations/scales.
1545 if(m[0]!=1 || m[1]!=0 || m[2]!=0 || m[3]!=0 || m[4]!=1 || m[5]!=0 ||
1546 m[6]!=0 || (m[7]!=0 && m[7]!=136) || m[8]!=1)return original;
1547 presentation=idleCardPresentation_(root,*priority,*sequenceToWorld);
1548 if(presentation)
1549 {
1550 const auto& box=presentation->localRect;
1551 if(!std::ranges::all_of(box,[](float value){return std::isfinite(value);}) ||
1552 box[0]!=600 || box[2]!=800 || box[1]+m[7]!=(225-h/2.0F)/2.0F ||
1553 box[3]+m[7]!=(225+h/2.0F)/2.0F)return original;
1554 }
1555 }
1556 const auto placement=presentation ? std::optional{presentation->localRect} : std::nullopt;
1557 const int occlusionMode=presentation && presentation->occluded ? 1 : 0;
1558 const Key key{root,w,h,true,std::array<float,9>{},occlusionMode,placement,original.get()};
1559 if(const auto found=cache_.find(key);found!=cache_.end()) return found->second.replacement;
1560 bool warm=false,ink=false;
1561 const auto& pixels=original->image.pixels;
1562 for(std::size_t i=0;i<pixels.size();i+=4)
1563 {
1564 if(pixels[i+3]==0) continue;
1565 const unsigned r=pixels[i],g=pixels[i+1],b=pixels[i+2];
1566 const auto low=std::min({r,g,b}),high=std::max({r,g,b});
1567 if(high<=40 || high-low<=8) { ink|=high<64; continue; }
1568 if(r+8<g || g+8<b) return original;
1569 warm=true;
1570 }
1571 if(!warm || !ink) return original;
1572 auto result=std::make_shared<data::BitmapRuntimeAsset>(*original);
1573 result->sourceType=data::LegacyDataType::Native;
1574 constexpr std::array<unsigned,3> cream{237,232,215};
1575 for(std::size_t i=0;i<pixels.size();i+=4)
1576 {
1577 const unsigned r=pixels[i],g=pixels[i+1],b=pixels[i+2];
1578 if(pixels[i+3]==0 || std::max({r,g,b})<=40 ||
1579 std::max({r,g,b})-std::min({r,g,b})<=8) continue;
1580 for(unsigned c=0;c<3;++c) result->image.pixels[i+c]=std::uint8_t((r*cream[c]+127)/255);
1581 }
1582 // Five measured baked-occlusion cards: keep the entire licensed image,
1583 // including baked title/caption/art, and repeat its authentic body
1584 // legibly below. No guessed illustration crop or restored glyphs.
1585 if(const auto body=readableIllustratedCardBody(idleOwner); !body.empty() && draw->second.body==body)
1586 {
1587 if(!original->source || !drawText_)return original;
1588 const auto header=data::inspectLegacyUap(*original->source);
1589 if(!header || header->width!=400 || header->height!=240 || header->originX!=0 ||
1590 header->originY!=0 || header->flags!=5)return original;
1591 for(std::size_t at=3;at<pixels.size();at+=4)if(pixels[at]!=255)return original;
1592 std::vector<data::LegacyBitmapRGBA8> lines;
1593 std::istringstream words(draw->second.body);std::string word,line;
1594 std::optional<data::LegacyBitmapRGBA8> accepted;
1595 while(words>>word)
1596 {
1597 const auto candidate=line.empty()?word:line+" "+word;
1598 auto measured=drawText_(candidate,48,false,false);
1599 if(!measured || !valid(*measured))return original;
1600 if(measured->width>368*3)
1601 {
1602 if(line.empty() || lines.size()>=1)return original;
1603 lines.push_back(std::move(*accepted));line=word;
1604 auto single=drawText_(word,48,false,false);
1605 if(!single || !valid(*single) || single->width>368*3)return original;
1606 accepted=std::move(*single);
1607 }
1608 else {line=candidate;accepted=std::move(*measured);}
1609 }
1610 if(!accepted)return original;
1611 lines.push_back(std::move(*accepted));
1612 unsigned textHeight=0;for(const auto& glyphs:lines)textHeight+=glyphs.height;
1613 if(lines.size()>2 || textHeight>52*3)return original;
1614 auto artwork=std::move(result->image);
1615 auto& composed=result->image;
1616 composed={1200,720,std::vector<std::uint8_t>(1200*720*4,255)};
1617 for(std::size_t at=0;at<composed.pixels.size();at+=4)
1618 for(unsigned c=0;c<3;++c)composed.pixels[at+c]=std::uint8_t(cream[c]);
1619 // Full400x240 input maps to logical300x180 at(50,0), aspect5:3.
1620 for(unsigned y=0;y<540;++y)for(unsigned x=0;x<900;++x)
1621 {
1622 const auto src=(std::size_t(y*240/540)*400+x*400/900)*4;
1623 const auto dst=(std::size_t(y)*1200+x+150)*4;
1624 std::copy_n(artwork.pixels.data()+src,4,composed.pixels.data()+dst);
1625 }
1626 unsigned y=184*3+(52*3-textHeight)/2;
1627 for(const auto& glyphs:lines)
1628 {
1629 const unsigned x=(1200-glyphs.width)/2;
1630 for(unsigned py=0;py<glyphs.height;++py)for(unsigned px=0;px<glyphs.width;++px)
1631 {
1632 const auto src=(std::size_t(py)*glyphs.width+px)*4;
1633 const auto dst=(std::size_t(y+py)*1200+x+px)*4;
1634 const unsigned alpha=glyphs.pixels[src+3];
1635 for(unsigned c=0;c<3;++c)composed.pixels[dst+c]=std::uint8_t(
1636 (25*alpha+composed.pixels[dst+c]*(255-alpha)+127)/255);
1637 }
1638 y+=glyphs.height;
1639 }
1640 result->preferLinearFiltering=true;
1641 }
1642 if(presentation)
1643 {
1644 result->presentationRect=presentation->localRect;
1645 result->preferLinearFiltering=true;
1646 if(presentation->occluded)
1647 for(std::size_t i=3;i<result->image.pixels.size();i+=4)result->image.pixels[i]=0;
1648 }
1649 if(cache_.size()>=128) cache_.clear();
1650 cache_.emplace(key,CachedArtwork{result,original});return result;
1651 }
1652 const bool fullCard = deed != deeds_.end() || draw != drawCards_.end();
1653 if ((fullCard || property(root).has_value()) && presentationContext_ && !presentationContext_()) return original;
1654 if (fullCard && presentationContext_ && !presentationContext_()) return original;
1655 if (deed != deeds_.end() && principal && (w != 199 || h != 227)) return original;
1656 std::optional<std::array<unsigned,4>> artwork;
1657 if(deed!=deeds_.end() && deed->second.artwork)
1658 {
1659 artwork=deedArtworkRect(root,*deed->second.artwork);
1660 if(!artwork || language_!=data::LanguageId::EnglishUs) return original;
1661 if(principal)
1662 {
1663 if(original->dataId!=root || !valid(original->image)) return original;
1664 bool ink=false;
1665 for(unsigned y=(*artwork)[1];y<(*artwork)[3];++y)
1666 for(unsigned x=(*artwork)[0];x<(*artwork)[2];++x)
1667 {
1668 const auto i=(std::size_t(y)*w+x)*4;const auto& pixels=original->image.pixels;
1669 if(pixels[i]!=pixels[i+1] || pixels[i]!=pixels[i+2] || pixels[i+3]!=255) return original;
1670 ink|=pixels[i]<250;
1671 }
1672 if(!ink) return original;
1673 }
1674 }
1675 if (draw != drawCards_.end() && principal &&
1676 (w != draw->second.nativeWidth || h != draw->second.nativeHeight || w < 199 || h < 150 || w > 600 || h > 400 ||
1677 draw->second.title.empty() || draw->second.title.size()>128 || draw->second.body.empty())) return original;
1678 std::optional<std::array<float,4>> deedPlacement;
1679 if (deed != deeds_.end() && principal && priority == 1002 && rasterToWorld && deedPlacement_)
1680 {
1681 if (!std::ranges::all_of(rasterToWorld->values, [](float value){return std::isfinite(value);})) return original;
1682 deedPlacement = deedPlacement_(root,*priority,*rasterToWorld);
1683 if (deedPlacement)
1684 {
1685 const auto& rect = *deedPlacement;
1686 if (!std::ranges::all_of(rect, [](float value){return std::isfinite(value);}) ||
1687 rect[2] <= rect[0] || rect[3] <= rect[1] || rect[2]-rect[0] > 800 || rect[3]-rect[1] > 600)
1688 return original;
1689 }
1690 }
1691 const auto b = !fullCard ? button(root) : std::optional<Button>{};
1692 if (b && presentationContext_ && !presentationContext_()) return original;
1693 layout::Rect band{0,0,int(w),int(h)};
1694 std::optional<layout::Rect> hit;
1695 const int activeLayout = b && layout_ ? int(layout_()) : 0;
1696 // Only the measured native transition fallback changes. Settled highres
1697 // artwork still follows the original whole-band qualification below.
1698 const auto nativeTransition = [&]() -> std::shared_ptr<const data::BitmapRuntimeAsset>
1699 {
1700 if(!b || !principal || language_!=data::LanguageId::EnglishUs ||
1701 !presentationContext_ || !presentationContext_() || !rasterToWorld ||
1702 !valid(original->image) || !nativeActionShape(root,*original)) return original;
1703 for(const float value:rasterToWorld->values) if(!std::isfinite(value)) return original;
1704 const auto& transform=rasterToWorld->values;
1705 const float determinant=transform[0]*transform[4]-transform[1]*transform[3];
1706 if(!std::isfinite(determinant) || std::abs(determinant)<1e-8F)return original;
1707 const Key nativeKey{root,w,h,false,std::array<float,9>{},-1,{},original.get()};
1708 if(const auto found=cache_.find(nativeKey);found!=cache_.end())return found->second.replacement;
1709 auto result=std::make_shared<data::BitmapRuntimeAsset>(*original);
1710 // Retain the actual printed caption, transparent colour key, alpha,
1711 // highlights and dark ink; tint only the measured neutral chrome.
1712 const auto& pixels=original->image.pixels;
1713 bool chrome=false;
1714 for(std::size_t i=0;i<pixels.size();i+=4)
1715 {
1716 const unsigned low=std::min({pixels[i],pixels[i+1],pixels[i+2]});
1717 const unsigned high=std::max({pixels[i],pixels[i+1],pixels[i+2]});
1718 if(!pixels[i+3] || high<=48 || low>=240)continue;
1719 if(high-low>24) continue; // Authored coloured caption/glow pixels stay exact.
1720 chrome=true;
1721 result->image.pixels[i]=std::uint8_t((unsigned(pixels[i])*70+50)/100);
1722 result->image.pixels[i+1]=std::uint8_t((unsigned(pixels[i+1])*92+50)/100);
1723 result->image.pixels[i+2]=std::uint8_t((unsigned(pixels[i+2])*88+50)/100);
1724 }
1725 if(!chrome)return original;
1726 result->sourceType=data::LegacyDataType::Native;
1727 if(cache_.size()>=128)cache_.clear();
1728 cache_.emplace(nativeKey,CachedArtwork{result,original});return result;
1729 };
1730 if (b && principal)
1731 {
1732 const auto slot = buttonSlot(b->index);
1733 if (!slot || !rasterToWorld) return nativeTransition();
1734 hit = layout::actionButtonRect(*slot, layout::ActionButtonLayout(activeLayout));
1735 const auto& m = rasterToWorld->values;
1736 const float det = m[0]*m[4] - m[1]*m[3];
1737 if (!std::isfinite(det) || std::abs(det) < 1e-8F) return nativeTransition();
1738 auto local = [&](float x, float y)
1739 {
1740 x -= m[6]; y -= m[7];
1741 return std::array<float,2>{(x*m[4]-y*m[3])/det, (y*m[0]-x*m[1])/det};
1742 };
1743 const auto center = local((hit->left+hit->right)*.5F,(hit->top+hit->bottom)*.5F);
1744 if (center[0] < 0 || center[0] >= w || center[1] < 0 || center[1] >= h) return nativeTransition();
1745 const std::array corners{local(float(hit->left),float(hit->top)),
1746 local(float(hit->right),float(hit->top)), local(float(hit->left),float(hit->bottom)),
1747 local(float(hit->right),float(hit->bottom))};
1748 float left = float(w), top = float(h), right = 0, bottom = 0;
1749 for (const auto& c : corners)
1750 { left=std::min(left,c[0]); top=std::min(top,c[1]); right=std::max(right,c[0]); bottom=std::max(bottom,c[1]); }
1751 // A moving CNK that cannot carry its whole interactive band retains
1752 // its actual retail transition; idle artwork never centres in a glow.
1753 // Actual USA Trade idle UAP is60x32 at70,454, while its legacy
1754 // interactive rectangle ends at132: two clickable pixels are outside
1755 // the authored bitmap ending at130. Clip only this proven settled
1756 // payload; moving/pressed frames keep their original transition.
1757 const bool settledTrade = b->index == 19 && b->state < 3 && w == 60 && h == 32 &&
1758 original->dataId == data::packDataId(data::LegacyGroupId::LanguageGraphics,
1759 b->grey ? 0x1628 : 0x140E) &&
1760 m == std::array<float,9>{1,0,0,0,1,0,70,454,1} &&
1761 left == 0 && top == 1 && right == 62 && bottom == 29;
1762 if (!settledTrade && (left < -.01F || top < -.01F || right > w+.01F || bottom > h+.01F)) return nativeTransition();
1763 band = {std::clamp(int(std::floor(left)),0,int(w)),std::clamp(int(std::floor(top)),0,int(h)),
1764 std::clamp(int(std::ceil(right)),0,int(w)),std::clamp(int(std::ceil(bottom)),0,int(h))};
1765 if (band.right-band.left < 8 || band.bottom-band.top < 8) return nativeTransition();
1766 }
1767 const bool modernAction=b && actionText_ &&
1768 (b->index==0 || b->index==1 || b->index==3 || b->index==12 || b->index==17 ||
1769 b->index==13 || b->index==14 || b->index==18 || b->index==19);
1770 if(modernAction && principal && !valid(original->image))return original;
1771 const Key key{root, w, h, principal, b && rasterToWorld ? rasterToWorld->values : std::array<float,9>{},
1772 activeLayout, deedPlacement, (miniature && miniature->miniature) || modernAction ? original.get() : nullptr};
1773 if (const auto it = cache_.find(key); it != cache_.end()) return it->second.replacement;
1774 const auto p = property(root);
1775 std::optional<PropertyDescriptor> descriptor;
1776 if (p && principal)
1777 {
1778 descriptor = properties_(p->index);
1779 if (!descriptor || descriptor->name.empty() || descriptor->name.size() > 512 ||
1780 descriptor->purchaseText.size() > 128 || descriptor->group >= 10 ||
1781 w < 16 || h < 20 || w > 128 || h > 128) return original;
1782 }
1783 auto result = std::make_shared<data::BitmapRuntimeAsset>();
1784 result->dataId = original->dataId;
1785 result->sourceType = data::LegacyDataType::Native;
1786 result->preferLinearFiltering = true;
1787 result->presentationRect = deedPlacement;
1788 auto& image = result->image;
1789 image = {w, h, std::vector<std::uint8_t>(std::size_t(w) * h * 4)};
1790 if(modernAction)
1791 {
1792 // Intrinsic UAP origin remains authoritative in the production slot.
1793 image={w*3,h*3,std::vector<std::uint8_t>(std::size_t(w)*h*36,0)};
1794 if(principal)
1795 {
1796 const auto label=actionText_((language_==data::LanguageId::French?French:English)[b->index]);
1797 if(!label || !valid(*label))return original;
1798 const bool pressed=b->state==3;
1799 const std::array<unsigned,3> fill=b->grey?std::array<unsigned,3>{35,55,57}:
1800 pressed?std::array<unsigned,3>{13,47,48}:std::array<unsigned,3>{22,69,70};
1801 const std::array<unsigned,3> trim=b->grey?std::array<unsigned,3>{110,116,104}:std::array<unsigned,3>{188,157,94};
1802 for(unsigned y=unsigned(band.top)*3;y<unsigned(band.bottom)*3;++y)
1803 for(unsigned x=unsigned(band.left)*3;x<unsigned(band.right)*3;++x)
1804 {
1805 const auto i=(std::size_t(y)*image.width+x)*4;
1806 const auto native=(std::size_t(y/3)*w+x/3)*4;
1807 const bool edge=x<unsigned(band.left)*3+2 || y<unsigned(band.top)*3+2 ||
1808 x+2>=unsigned(band.right)*3 || y+2>=unsigned(band.bottom)*3;
1809 for(unsigned c=0;c<3;++c)image.pixels[i+c]=std::uint8_t(edge?trim[c]:fill[c]);
1810 image.pixels[i+3]=original->image.pixels[native+3];
1811 }
1812 const float fit=std::min({1.0F,float((band.right-band.left-8)*3)/label->width,float((band.bottom-band.top-6)*3)/label->height});
1813 const unsigned tw=std::max(1U,unsigned(label->width*fit)),th=std::max(1U,unsigned(label->height*fit));
1814 const unsigned ox=unsigned(band.left)*3+(unsigned(band.right-band.left)*3-tw)/2;
1815 const unsigned oy=unsigned(band.top)*3+(unsigned(band.bottom-band.top)*3-th)/2;
1816 for(unsigned y=0;y<th;++y)for(unsigned x=0;x<tw;++x)
1817 {
1818 const auto src=(std::size_t(y*label->height/th)*label->width+x*label->width/tw)*4;
1819 const auto dst=(std::size_t(y+oy)*image.width+x+ox)*4;
1820 const unsigned alpha=label->pixels[src+3];
1821 for(unsigned c=0;c<3;++c)
1822 {
1823 const unsigned ink=b->grey?180:(c==2?214:239);
1824 image.pixels[dst+c]=std::uint8_t((ink*alpha+image.pixels[dst+c]*(255-alpha)+127)/255);
1825 }
1826 }
1827 // Hit clipping uses subpixel centres but sourcealpha remains
1828 // exactly authored; no opacity invented for moving leaves.
1829 const auto& m=rasterToWorld->values;
1830 for(unsigned y=0;y<image.height;++y)for(unsigned x=0;x<image.width;++x)
1831 {
1832 const float lx=(x+.5F)/3,ly=(y+.5F)/3;
1833 const float wx=lx*m[0]+ly*m[3]+m[6],wy=lx*m[1]+ly*m[4]+m[7];
1834 if(wx<hit->left || wx>=hit->right || wy<hit->top || wy>=hit->bottom)
1835 image.pixels[(std::size_t(y)*image.width+x)*4+3]=0;
1836 }
1837 }
1838 if(cache_.size()>=128)cache_.clear();
1839 cache_.emplace(key,CachedArtwork{result,original});return result;
1840 }
1841 if (fullCard)
1842 {
1843 DeedDescriptor drawPlan;
1844 const DeedDescriptor* content = deed != deeds_.end() ? &deed->second : &drawPlan;
1845 const auto& rasterizer = deed != deeds_.end() ? deedText_ : drawText_;
1846 if (draw != drawCards_.end())
1847 {
1849 data::dataTag(root)>=0x0059;
1850 drawPlan.fills={{14,10,int(w)-28,48,community ? (22U|(69U<<8)|(70U<<16)) : (162U|(98U<<8)|(33U<<16))}};
1851 drawPlan.text={{draw->second.title,17,32,1,2,22,0x00EFEFEF,true,false,true},
1852 {draw->second.body,70,int(h)-91,1,0,16,0x00191919,false,false,false}};
1853 }
1854 image = {w*3,h*3,std::vector<std::uint8_t>(std::size_t(w)*h*36)};
1855 if (principal)
1856 {
1857 if (content->text.empty() || content->text.size() > 64 || content->fills.size() > 32) return original;
1858 for (unsigned y=0; y<image.height; ++y)
1859 for (unsigned x=0; x<image.width; ++x)
1860 {
1861 const bool border = x<6 || y<6 || x+6>=image.width || y+6>=image.height;
1862 const bool brass = !border && (x==8 || y==8 || x+9==image.width || y+9==image.height);
1863 const std::array<unsigned,3> color = border ? std::array<unsigned,3>{22,69,70} :
1864 brass ? std::array<unsigned,3>{188,157,94} : std::array<unsigned,3>{237,232,215};
1865 const auto i=(std::size_t(y)*image.width+x)*4;
1866 for (unsigned c=0;c<3;++c) image.pixels[i+c]=std::uint8_t(color[c]);
1867 image.pixels[i+3]=255;
1868 }
1869 for (const auto& fill : content->fills)
1870 {
1871 if (fill.x<0 || fill.y<0 || fill.width<0 || fill.height<0 ||
1872 fill.x>w || fill.y>h || fill.width>int(w)-fill.x || fill.height>int(h)-fill.y) return original;
1873 for (int y=fill.y*3; y<(fill.y+fill.height)*3; ++y)
1874 for (int x=fill.x*3; x<(fill.x+fill.width)*3; ++x)
1875 {
1876 const auto i=(std::size_t(y)*image.width+unsigned(x))*4;
1877 for (unsigned c=0;c<3;++c) image.pixels[i+c]=std::uint8_t(fill.color>>(c*8));
1878 }
1879 }
1880 if(artwork)
1881 for(unsigned y=(*artwork)[1]*3;y<(*artwork)[3]*3;++y)
1882 for(unsigned x=(*artwork)[0]*3;x<(*artwork)[2]*3;++x)
1883 {
1884 const auto source=(std::size_t(y/3)*w+x/3)*4;
1885 const auto destination=(std::size_t(y)*image.width+x)*4;
1886 // Original opaque white-backed grayscale yields
1887 // exact ink coverage255-gray; no generated icon.
1888 const unsigned gray=original->image.pixels[source];
1889 for(unsigned c=0;c<3;++c)
1890 image.pixels[destination+c]=std::uint8_t((image.pixels[destination+c]*gray+127)/255);
1891 }
1892 for(std::size_t textIndex=0;textIndex<content->text.size();++textIndex)
1893 {
1894 auto region=content->text[textIndex];
1895 const bool artTitle=artwork && textIndex==0;
1896 if(artTitle)
1897 {region.y=70;region.height=20;region.fontSize=16;region.bold=true;region.verticalLeeway=0;region.verticalCenter=true;}
1898 const unsigned textInset=artTitle?12U:21U;
1899 if (region.text.empty()) continue;
1900 if (region.text.size()>2048 || region.y<0 || region.y>=int(h) || region.height<=0 ||
1901 region.height>int(h)-region.y || region.verticalLeeway<0 || region.verticalLeeway>12 ||
1902 region.fontSize<=0 || region.fontSize>32 || region.justification<0 || region.justification>2) return original;
1903 bool fitted=false;
1904 for (int size=region.fontSize; size>=1 && !fitted; --size)
1905 {
1906 std::vector<data::LegacyBitmapRGBA8> lines;
1907 std::istringstream words(region.text);
1908 std::string word,line;
1909 std::optional<data::LegacyBitmapRGBA8> accepted;
1910 bool tooWide=false;
1911 while (words>>word)
1912 {
1913 const auto candidate=line.empty()?word:line+" "+word;
1914 auto measure=rasterizer(candidate,size*3,region.bold,region.italic);
1915 if (!measure || !valid(*measure)) return original;
1916 if (measure->width>(w-textInset*2)*3)
1917 {
1918 if (line.empty()) { tooWide=true; break; }
1919 // The last fitting candidate already contains this exact line.
1920 // Reusing it avoids another font resize/render/restore cycle.
1921 lines.push_back(std::move(*accepted)); line=word;
1922 auto one=rasterizer(word,size*3,region.bold,region.italic);
1923 if (!one || !valid(*one)) return original;
1924 if (one->width>(w-textInset*2)*3) { tooWide=true; break; }
1925 accepted=std::move(*one);
1926 }
1927 else { line=candidate; accepted=std::move(*measure); }
1928 }
1929 if (tooWide) continue;
1930 if (line.empty()) return original;
1931 lines.push_back(std::move(*accepted));
1932 unsigned total=0;
1933 for (const auto& rendered:lines) total+=rendered.height;
1934 if (total>unsigned(region.height+region.verticalLeeway)*3) continue;
1935 int y=region.y*3;
1936 const int overflow=std::max(0,int(total)-region.height*3);
1937 if (overflow<=region.verticalLeeway*3) y+=overflow;
1938 if (region.verticalCenter && lines.size()==1) y+=(region.height*3-int(total))/2;
1939 for (const auto& rendered:lines)
1940 {
1941 const int x=region.justification==0?int(textInset*3):region.justification==1?
1943 if (x<0 || y<0 || x+int(rendered.width)>int(image.width) || y+int(rendered.height)>int(image.height)) return original;
1944 for (unsigned py=0;py<rendered.height;++py)
1945 for (unsigned px=0;px<rendered.width;++px)
1946 {
1947 const auto src=(std::size_t(py)*rendered.width+px)*4;
1948 const auto dst=(std::size_t(y+int(py))*image.width+x+px)*4;
1949 const unsigned alpha=rendered.pixels[src+3];
1950 std::uint32_t inkColor=region.color;
1951 // Modern title contrast changes ink only; the exact
1952 // title/canonical group colour remain unchanged.
1953 if (region.bold && region.verticalCenter)
1954 for (const auto& fill:content->fills)
1955 if (region.y>=fill.y && region.y<fill.y+fill.height &&
1956 0.2126*(fill.color&255)+0.7152*((fill.color>>8)&255)+0.0722*((fill.color>>16)&255)<125)
1957 inkColor=0x00EFEFEF;
1958 for (unsigned c=0;c<3;++c)
1959 {
1960 const unsigned ink=(inkColor>>(c*8))&255;
1961 image.pixels[dst+c]=std::uint8_t((ink*alpha+image.pixels[dst+c]*(255-alpha)+127)/255);
1962 }
1963 }
1964 y+=int(rendered.height);
1965 }
1966 fitted=true;
1967 }
1968 if (!fitted) return original;
1969 }
1970 }
1971 if (cache_.size()>=128) cache_.clear();
1972 cache_.emplace(key,CachedArtwork{result,{}});
1973 return result;
1974 }
1975 if (p)
1976 {
1977 image = {w * 3, h * 3, std::vector<std::uint8_t>(std::size_t(w) * h * 36)};
1978 if (principal)
1979 {
1980 const auto group = (language_ == data::LanguageId::French ? FrenchGroups : OrdinaryGroups)[descriptor->group];
1981 for (unsigned y = 0; y < image.height; ++y)
1982 for (unsigned x = 0; x < image.width; ++x)
1983 {
1984 const auto i = (std::size_t(y) * image.width + x) * 4;
1985 const bool border = x < 3 || x + 3 >= image.width || y < 3 || y + 3 >= image.height;
1986 const bool groupBand = !border && y < 22;
1987 const bool mortgage = p->style == 2;
1988 for (unsigned c = 0; c < 3; ++c)
1989 {
1990 int colour = border ? (mortgage ? std::array<int,3>{155,57,54}[c] : std::array<int,3>{22,69,70}[c]) :
1991 groupBand ? group[c] : (mortgage ? std::array<int,3>{242,222,216}[c] : std::array<int,3>{237,232,215}[c]);
1992 if (p->style == 1 && groupBand) colour = (colour + 2 * 155) / 3;
1993 // A red diagonal across the header marks the mortgaged
1994 // state without replacing or inventing translated text.
1995 if (mortgage && groupBand && (x + y) % image.width < 4) colour = std::array<int,3>{155,57,54}[c];
1996 image.pixels[i+c] = std::uint8_t(colour);
1997 }
1998 image.pixels[i+3] = 255;
1999 }
2000 std::vector<data::LegacyBitmapRGBA8> lines;
2001 std::istringstream words(descriptor->name);
2002 std::string word, line;
2003 while (words >> word)
2004 {
2005 const auto candidate = line.empty() ? word : line + " " + word;
2006 auto measured = propertyText_(candidate);
2007 if (!measured || !valid(*measured)) return original;
2008 if (!line.empty() && measured->width > image.width - 12)
2009 {
2010 auto previous = propertyText_(line);
2011 if (!previous || !valid(*previous)) return original;
2012 lines.push_back(std::move(*previous));
2013 line = word;
2014 }
2015 else line = candidate;
2016 }
2017 if (line.empty()) return original;
2018 auto last = propertyText_(line);
2019 if (!last || !valid(*last)) return original;
2020 lines.push_back(std::move(*last));
2021 const bool mortgageBadge = p->miniature && p->style == 2;
2022 const auto readableCaption = [&](const data::LegacyBitmapRGBA8& text, unsigned height,
2023 unsigned minimumInkHeight)
2024 {
2025 const float scale = std::min({1.0F, float(image.width - 12) / float(text.width),
2026 float(height) / float(text.height)});
2027 unsigned first = text.height, last = 0;
2028 for (unsigned y = 0; y < text.height; ++y)
2029 for (unsigned x = 0; x < text.width; ++x)
2030 if (text.pixels[(std::size_t(y) * text.width + x) * 4 + 3] > 16)
2031 {
2032 first = std::min(first, y);
2033 last = std::max(last, y);
2034 }
2035 if (first == text.height) return false;
2036 const unsigned inkHeight = last - first + 1;
2037 // Runtime uses 96 DPI: a point-size18 surface is27px high.
2038 // Judge fitted visible ink, not its transparent font padding.
2039 return float(inkHeight) * scale >= float(std::min(minimumInkHeight, inkHeight));
2040 };
2041 if (mortgageBadge)
2042 {
2043 // Authentic US TRANS label: EuropeanDeedText.inc / retail UDPENNY.
2044 // Style 1 is the unselected face, not a mortgage indicator.
2045 auto label = propertyText_("Mortgaged");
2046 if (!label || !valid(*label) || !readableCaption(*label, 18, 14)) return original;
2047 for (unsigned y = 23; y < 42; ++y)
2048 for (unsigned x = 3; x + 3 < image.width; ++x)
2049 {
2050 const auto i = (std::size_t(y) * image.width + x) * 4;
2051 image.pixels[i] = 155;
2052 image.pixels[i + 1] = 57;
2053 image.pixels[i + 2] = 54;
2054 }
2055 caption(image, *label, 24, 18, 241);
2056 }
2057 const unsigned textHeight = mortgageBadge ? image.height - 72 : image.height - 54;
2058 if (lines.size() > textHeight) return original;
2059 const unsigned lineHeight = std::max(1U, textHeight / unsigned(lines.size()));
2060 if (mortgageBadge)
2061 for (const auto& name : lines)
2062 if (!readableCaption(name, lineHeight, 10)) return original;
2063 unsigned y = mortgageBadge ? 46 : 28;
2064 for (const auto& name : lines)
2065 {
2066 caption(image, name, y, lineHeight, p->style == 1 ? 115 : 25);
2067 y += lineHeight;
2068 }
2069 if (!descriptor->purchaseText.empty())
2070 {
2071 auto price = propertyText_(descriptor->purchaseText);
2072 if (!price || !valid(*price) ||
2073 (mortgageBadge && !readableCaption(*price, 17, 12))) return original;
2074 caption(image, *price, image.height - 23, 17, p->style == 1 ? 115 : 25);
2075 }
2076 }
2077 if(p->miniature)
2078 for(unsigned y=0;y<image.height;++y)for(unsigned x=0;x<image.width;++x)
2079 image.pixels[(std::size_t(y)*image.width+x)*4+3]=original->image.pixels[(std::size_t(y/3)*w+x/3)*4+3];
2080 if (cache_.size() >= 128) cache_.clear();
2081 cache_.emplace(key, CachedArtwork{result,p->miniature?original:nullptr});
2082 return result;
2083 }
2084 const auto playerColour = scoreColour(root);
2085 if (principal)
2086 {
2087 for (unsigned y = unsigned(band.top); y < unsigned(band.bottom); ++y)
2088 for (unsigned x = unsigned(band.left); x < unsigned(band.right); ++x)
2089 {
2090 const auto i = (std::size_t(y) * w + x) * 4;
2091 const bool edge = x == unsigned(band.left) || y == unsigned(band.top) ||
2092 x + 1 == unsigned(band.right) || y + 1 == unsigned(band.bottom);
2093 const bool corner = b && ((x < unsigned(band.left+3) || x+3 >= unsigned(band.right)) &&
2094 (y < unsigned(band.top+3) || y+3 >= unsigned(band.bottom)));
2095 if (corner) continue;
2096 const int gradient = int(8 * (h - y) / h);
2097 const bool pressed = b && b->state == 3;
2098 const bool grey = b && b->grey;
2099 const std::array<int, 3> fill = playerColour ? std::array<int, 3>{237, 232, 215} : grey ? std::array<int, 3>{57, 64, 65} :
2100 pressed ? std::array<int, 3>{13, 47, 48} : std::array<int, 3>{22, 69, 70};
2101 const std::array<int, 3> trim = playerColour ? std::array<int, 3>{22, 69, 70} : grey ? std::array<int, 3>{104, 111, 111} :
2102 std::array<int, 3>{188, 157, 94};
2103 for (unsigned c = 0; c < 3; ++c)
2104 {
2105 // Black name/cash overlays require a light interior. The
2106 // left player stripe does not enter their authored x>=57 region.
2107 const bool stripe = playerColour && x >= 2 && x < 5 && y >= 2 && y + 2 < h;
2108 const bool brass = playerColour && !edge && y == 1;
2109 image.pixels[i + c] = std::uint8_t(stripe ? PlayerColours[*playerColour][c] :
2110 brass ? std::array<int, 3>{188, 157, 94}[c] :
2111 edge ? trim[c] : fill[c] + (playerColour ? 0 : gradient));
2112 }
2113 image.pixels[i + 3] = b || playerColour ? 255 : 244;
2114 }
2115 if (b)
2116 {
2117 auto label = text_((language_ == data::LanguageId::French ? French : English)[b->index]);
2118 if (!label || !label->width || !label->height ||
2119 label->pixels.size() != std::size_t(label->width) * label->height * 4 || w < 8 || h < 8)
2120 return original;
2121 const float scale = std::min({1.0F, float(band.right-band.left-8) / label->width, float(band.bottom-band.top-6) / label->height});
2122 const unsigned tw = std::max(1U, unsigned(label->width * scale));
2123 const unsigned th = std::max(1U, unsigned(label->height * scale));
2124 const unsigned ox = unsigned(band.left) + (unsigned(band.right-band.left) - tw) / 2,
2125 oy = unsigned(band.top) + (unsigned(band.bottom-band.top) - th) / 2;
2126 for (unsigned y = 0; y < th; ++y)
2127 for (unsigned x = 0; x < tw; ++x)
2128 {
2129 const auto src = (std::size_t(y * label->height / th) * label->width + x * label->width / tw) * 4;
2130 const auto dst = (std::size_t(y + oy) * w + x + ox) * 4;
2131 const unsigned a = label->pixels[src + 3];
2132 for (unsigned c = 0; c < 3; ++c)
2133 {
2134 const unsigned ink = b->grey ? 180 : (c == 2 ? 214 : 239);
2135 image.pixels[dst + c] = std::uint8_t((ink * a + image.pixels[dst + c] * (255 - a) + 127) / 255);
2136 }
2137 }
2138 }
2139 }
2140 if (b && principal && hit && rasterToWorld)
2141 {
2142 const auto& m = rasterToWorld->values;
2143 for (unsigned y = 0; y < h; ++y)
2144 for (unsigned x = 0; x < w; ++x)
2145 {
2146 const float wx = (x+.5F)*m[0] + (y+.5F)*m[3] + m[6];
2147 const float wy = (x+.5F)*m[1] + (y+.5F)*m[4] + m[7];
2148 if (wx < hit->left || wx >= hit->right || wy < hit->top || wy >= hit->bottom)
2149 image.pixels[(std::size_t(y)*w+x)*4+3] = 0;
2150 }
2151 }
2152 if (cache_.size() >= 128) cache_.clear();
2153 cache_.emplace(key, CachedArtwork{result,{}});
2154 return result;
2155 }
2156}
std::size_t count
const char * name
std::size_t root
std::uint16_t group
const char * header
std::string_view text
std::atomic< float > left
std::atomic< float > right
LegacyBitmapMetadata metadata
LegacyBitmapRGBA8 image
int bottom
int top
std::uint16_t priority
PlayerNumber slot
std::shared_ptr< const data::HmdTextureImage > source
std::uint64_t key
std::uint32_t height
std::uint32_t width
std::int16_t x
unsigned state
std::uint16_t width
std::uint16_t rootTag
unsigned index
unsigned style
std::uint16_t leafTag
std::uint16_t leaf
std::uint16_t root
bool grey
bool miniature
std::uint16_t height
std::int16_t y
std::string_view label
data::DataTag first
int token
int colour
Kind kind
Rect rect
Button button
bool valid
std::size_t at
std::uint16_t value
Definition Timers.cpp:16
data::DataId idle
CardTag native
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataTag dataTag(DataId id) noexcept
Definition DataBanks.hpp:64
constexpr std::uint16_t legacyGroupValue(LegacyGroupId group) noexcept
Definition DataBanks.hpp:30
std::expected< LegacyUapMetadata, BitmapError > inspectLegacyUap(std::span< const std::byte > bytes)
constexpr std::uint16_t dataGroup(DataId id) noexcept
Definition DataBanks.hpp:57
constexpr data::DataTag AIButtonBaseTag
constexpr std::uint8_t ButtonAnimationsPerSet
constexpr data::DataTag ButtonBaseTag
std::size_t size()