@underpostnet/cyberia 3.3.76 → 3.3.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/coverall.cyberia.ci.yml +5 -0
  2. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  3. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  4. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  5. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  6. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  7. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  8. package/.github/workflows/engine-cyberia.ci.yml +1 -1
  9. package/CHANGELOG.md +35 -122
  10. package/CLI-HELP.md +2 -2
  11. package/Dockerfile +1 -1
  12. package/Dockerfile.dev +1 -1
  13. package/Dockerfile.test +1 -1
  14. package/README.md +1 -1
  15. package/bin/build.js +65 -10
  16. package/bin/cyberia.js +4 -4
  17. package/bin/index.js +4 -4
  18. package/conf.js +0 -1
  19. package/deploy/lib/config.sh +1 -1
  20. package/deploy/lib/host.sh +1 -1
  21. package/deployment.yaml +1 -1
  22. package/docker-compose.yml +7 -7
  23. package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
  24. package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
  25. package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
  26. package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
  27. package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
  28. package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
  29. package/docs/coverage/cyberia/base.css +362 -0
  30. package/docs/coverage/cyberia/block-navigation.js +82 -0
  31. package/docs/coverage/cyberia/favicon.png +0 -0
  32. package/docs/coverage/cyberia/index.html +161 -0
  33. package/docs/coverage/cyberia/prettify.css +101 -0
  34. package/docs/coverage/cyberia/prettify.js +937 -0
  35. package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
  36. package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
  37. package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
  38. package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
  39. package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
  40. package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
  41. package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
  42. package/docs/coverage/cyberia/sorter.js +205 -0
  43. package/hardhat/package-lock.json +2 -2
  44. package/hardhat/package.json +1 -1
  45. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  46. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  47. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  48. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  49. package/package.json +2 -2
  50. package/scripts/test-monitor.sh +1 -1
  51. package/src/cli/cluster.js +73 -18
  52. package/src/cli/deploy.js +211 -30
  53. package/src/cli/docker-compose.js +1 -1
  54. package/src/cli/release.js +3 -1
  55. package/src/cli/repository.js +7 -9
  56. package/src/cli/run.js +10 -3
  57. package/src/client/components/core/Badge.js +0 -1
  58. package/src/client/components/core/CssCore.js +8 -1
  59. package/src/client/components/core/Modal.js +29 -30
  60. package/src/client/components/core/ToolTip.js +15 -0
  61. package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
  62. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
  63. package/src/client/components/default/AppShellDefault.js +1 -4
  64. package/src/client/components/itemledger/AppShellItemledger.js +0 -2
  65. package/src/client/components/underpost/AppShellUnderpost.js +1 -1
  66. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  67. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  68. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  69. package/src/client/ssr/views/Cyberia404.js +3 -3
  70. package/src/index.js +1 -1
  71. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  72. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  73. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  74. package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
  75. package/src/server/network/underpost-gateway.js +113 -14
  76. package/src/server/runtime/conf.js +36 -7
  77. package/src/server/runtime/start.js +11 -2
  78. package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
  79. package/test/unit/catalog.test.js +13 -2
  80. package/test/unit/conf-resolution.test.js +41 -7
  81. package/test/unit/cyberia/instance-data.test.js +314 -0
  82. package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
  83. package/test/unit/gateway-pod-fetch.test.js +95 -0
  84. package/test/unit/letsencrypt-issuer.test.js +101 -0
  85. package/test/unit/release-bump.test.js +3 -1
  86. package/test/unit/start-options.test.js +12 -0
@@ -0,0 +1,1315 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for projects/cyberia/atlas-sprite-sheet-generator.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../../prettify.css" />
9
+ <link rel="stylesheet" href="../../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../../index.html">All files</a> / <a href="index.html">projects/cyberia</a> atlas-sprite-sheet-generator.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">91.91% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>125/136</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">84.81% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>67/79</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>12/12</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">94.16% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>113/120</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a>
366
+ <a name='L301'></a><a href='#L301'>301</a>
367
+ <a name='L302'></a><a href='#L302'>302</a>
368
+ <a name='L303'></a><a href='#L303'>303</a>
369
+ <a name='L304'></a><a href='#L304'>304</a>
370
+ <a name='L305'></a><a href='#L305'>305</a>
371
+ <a name='L306'></a><a href='#L306'>306</a>
372
+ <a name='L307'></a><a href='#L307'>307</a>
373
+ <a name='L308'></a><a href='#L308'>308</a>
374
+ <a name='L309'></a><a href='#L309'>309</a>
375
+ <a name='L310'></a><a href='#L310'>310</a>
376
+ <a name='L311'></a><a href='#L311'>311</a>
377
+ <a name='L312'></a><a href='#L312'>312</a>
378
+ <a name='L313'></a><a href='#L313'>313</a>
379
+ <a name='L314'></a><a href='#L314'>314</a>
380
+ <a name='L315'></a><a href='#L315'>315</a>
381
+ <a name='L316'></a><a href='#L316'>316</a>
382
+ <a name='L317'></a><a href='#L317'>317</a>
383
+ <a name='L318'></a><a href='#L318'>318</a>
384
+ <a name='L319'></a><a href='#L319'>319</a>
385
+ <a name='L320'></a><a href='#L320'>320</a>
386
+ <a name='L321'></a><a href='#L321'>321</a>
387
+ <a name='L322'></a><a href='#L322'>322</a>
388
+ <a name='L323'></a><a href='#L323'>323</a>
389
+ <a name='L324'></a><a href='#L324'>324</a>
390
+ <a name='L325'></a><a href='#L325'>325</a>
391
+ <a name='L326'></a><a href='#L326'>326</a>
392
+ <a name='L327'></a><a href='#L327'>327</a>
393
+ <a name='L328'></a><a href='#L328'>328</a>
394
+ <a name='L329'></a><a href='#L329'>329</a>
395
+ <a name='L330'></a><a href='#L330'>330</a>
396
+ <a name='L331'></a><a href='#L331'>331</a>
397
+ <a name='L332'></a><a href='#L332'>332</a>
398
+ <a name='L333'></a><a href='#L333'>333</a>
399
+ <a name='L334'></a><a href='#L334'>334</a>
400
+ <a name='L335'></a><a href='#L335'>335</a>
401
+ <a name='L336'></a><a href='#L336'>336</a>
402
+ <a name='L337'></a><a href='#L337'>337</a>
403
+ <a name='L338'></a><a href='#L338'>338</a>
404
+ <a name='L339'></a><a href='#L339'>339</a>
405
+ <a name='L340'></a><a href='#L340'>340</a>
406
+ <a name='L341'></a><a href='#L341'>341</a>
407
+ <a name='L342'></a><a href='#L342'>342</a>
408
+ <a name='L343'></a><a href='#L343'>343</a>
409
+ <a name='L344'></a><a href='#L344'>344</a>
410
+ <a name='L345'></a><a href='#L345'>345</a>
411
+ <a name='L346'></a><a href='#L346'>346</a>
412
+ <a name='L347'></a><a href='#L347'>347</a>
413
+ <a name='L348'></a><a href='#L348'>348</a>
414
+ <a name='L349'></a><a href='#L349'>349</a>
415
+ <a name='L350'></a><a href='#L350'>350</a>
416
+ <a name='L351'></a><a href='#L351'>351</a>
417
+ <a name='L352'></a><a href='#L352'>352</a>
418
+ <a name='L353'></a><a href='#L353'>353</a>
419
+ <a name='L354'></a><a href='#L354'>354</a>
420
+ <a name='L355'></a><a href='#L355'>355</a>
421
+ <a name='L356'></a><a href='#L356'>356</a>
422
+ <a name='L357'></a><a href='#L357'>357</a>
423
+ <a name='L358'></a><a href='#L358'>358</a>
424
+ <a name='L359'></a><a href='#L359'>359</a>
425
+ <a name='L360'></a><a href='#L360'>360</a>
426
+ <a name='L361'></a><a href='#L361'>361</a>
427
+ <a name='L362'></a><a href='#L362'>362</a>
428
+ <a name='L363'></a><a href='#L363'>363</a>
429
+ <a name='L364'></a><a href='#L364'>364</a>
430
+ <a name='L365'></a><a href='#L365'>365</a>
431
+ <a name='L366'></a><a href='#L366'>366</a>
432
+ <a name='L367'></a><a href='#L367'>367</a>
433
+ <a name='L368'></a><a href='#L368'>368</a>
434
+ <a name='L369'></a><a href='#L369'>369</a>
435
+ <a name='L370'></a><a href='#L370'>370</a>
436
+ <a name='L371'></a><a href='#L371'>371</a>
437
+ <a name='L372'></a><a href='#L372'>372</a>
438
+ <a name='L373'></a><a href='#L373'>373</a>
439
+ <a name='L374'></a><a href='#L374'>374</a>
440
+ <a name='L375'></a><a href='#L375'>375</a>
441
+ <a name='L376'></a><a href='#L376'>376</a>
442
+ <a name='L377'></a><a href='#L377'>377</a>
443
+ <a name='L378'></a><a href='#L378'>378</a>
444
+ <a name='L379'></a><a href='#L379'>379</a>
445
+ <a name='L380'></a><a href='#L380'>380</a>
446
+ <a name='L381'></a><a href='#L381'>381</a>
447
+ <a name='L382'></a><a href='#L382'>382</a>
448
+ <a name='L383'></a><a href='#L383'>383</a>
449
+ <a name='L384'></a><a href='#L384'>384</a>
450
+ <a name='L385'></a><a href='#L385'>385</a>
451
+ <a name='L386'></a><a href='#L386'>386</a>
452
+ <a name='L387'></a><a href='#L387'>387</a>
453
+ <a name='L388'></a><a href='#L388'>388</a>
454
+ <a name='L389'></a><a href='#L389'>389</a>
455
+ <a name='L390'></a><a href='#L390'>390</a>
456
+ <a name='L391'></a><a href='#L391'>391</a>
457
+ <a name='L392'></a><a href='#L392'>392</a>
458
+ <a name='L393'></a><a href='#L393'>393</a>
459
+ <a name='L394'></a><a href='#L394'>394</a>
460
+ <a name='L395'></a><a href='#L395'>395</a>
461
+ <a name='L396'></a><a href='#L396'>396</a>
462
+ <a name='L397'></a><a href='#L397'>397</a>
463
+ <a name='L398'></a><a href='#L398'>398</a>
464
+ <a name='L399'></a><a href='#L399'>399</a>
465
+ <a name='L400'></a><a href='#L400'>400</a>
466
+ <a name='L401'></a><a href='#L401'>401</a>
467
+ <a name='L402'></a><a href='#L402'>402</a>
468
+ <a name='L403'></a><a href='#L403'>403</a>
469
+ <a name='L404'></a><a href='#L404'>404</a>
470
+ <a name='L405'></a><a href='#L405'>405</a>
471
+ <a name='L406'></a><a href='#L406'>406</a>
472
+ <a name='L407'></a><a href='#L407'>407</a>
473
+ <a name='L408'></a><a href='#L408'>408</a>
474
+ <a name='L409'></a><a href='#L409'>409</a>
475
+ <a name='L410'></a><a href='#L410'>410</a>
476
+ <a name='L411'></a><a href='#L411'>411</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
477
+ <span class="cline-any cline-neutral">&nbsp;</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-neutral">&nbsp;</span>
481
+ <span class="cline-any cline-neutral">&nbsp;</span>
482
+ <span class="cline-any cline-neutral">&nbsp;</span>
483
+ <span class="cline-any cline-neutral">&nbsp;</span>
484
+ <span class="cline-any cline-neutral">&nbsp;</span>
485
+ <span class="cline-any cline-neutral">&nbsp;</span>
486
+ <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-yes">4x</span>
488
+ <span class="cline-any cline-neutral">&nbsp;</span>
489
+ <span class="cline-any cline-neutral">&nbsp;</span>
490
+ <span class="cline-any cline-yes">4x</span>
491
+ <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-neutral">&nbsp;</span>
494
+ <span class="cline-any cline-neutral">&nbsp;</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-yes">4x</span>
497
+ <span class="cline-any cline-neutral">&nbsp;</span>
498
+ <span class="cline-any cline-neutral">&nbsp;</span>
499
+ <span class="cline-any cline-neutral">&nbsp;</span>
500
+ <span class="cline-any cline-neutral">&nbsp;</span>
501
+ <span class="cline-any cline-neutral">&nbsp;</span>
502
+ <span class="cline-any cline-neutral">&nbsp;</span>
503
+ <span class="cline-any cline-yes">4x</span>
504
+ <span class="cline-any cline-neutral">&nbsp;</span>
505
+ <span class="cline-any cline-neutral">&nbsp;</span>
506
+ <span class="cline-any cline-neutral">&nbsp;</span>
507
+ <span class="cline-any cline-neutral">&nbsp;</span>
508
+ <span class="cline-any cline-neutral">&nbsp;</span>
509
+ <span class="cline-any cline-neutral">&nbsp;</span>
510
+ <span class="cline-any cline-neutral">&nbsp;</span>
511
+ <span class="cline-any cline-neutral">&nbsp;</span>
512
+ <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-neutral">&nbsp;</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-neutral">&nbsp;</span>
516
+ <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-neutral">&nbsp;</span>
518
+ <span class="cline-any cline-neutral">&nbsp;</span>
519
+ <span class="cline-any cline-neutral">&nbsp;</span>
520
+ <span class="cline-any cline-neutral">&nbsp;</span>
521
+ <span class="cline-any cline-neutral">&nbsp;</span>
522
+ <span class="cline-any cline-neutral">&nbsp;</span>
523
+ <span class="cline-any cline-neutral">&nbsp;</span>
524
+ <span class="cline-any cline-neutral">&nbsp;</span>
525
+ <span class="cline-any cline-neutral">&nbsp;</span>
526
+ <span class="cline-any cline-neutral">&nbsp;</span>
527
+ <span class="cline-any cline-neutral">&nbsp;</span>
528
+ <span class="cline-any cline-neutral">&nbsp;</span>
529
+ <span class="cline-any cline-neutral">&nbsp;</span>
530
+ <span class="cline-any cline-neutral">&nbsp;</span>
531
+ <span class="cline-any cline-yes">91x</span>
532
+ <span class="cline-any cline-no">&nbsp;</span>
533
+ <span class="cline-any cline-neutral">&nbsp;</span>
534
+ <span class="cline-any cline-neutral">&nbsp;</span>
535
+ <span class="cline-any cline-yes">91x</span>
536
+ <span class="cline-any cline-yes">91x</span>
537
+ <span class="cline-any cline-neutral">&nbsp;</span>
538
+ <span class="cline-any cline-yes">91x</span>
539
+ <span class="cline-any cline-neutral">&nbsp;</span>
540
+ <span class="cline-any cline-yes">91x</span>
541
+ <span class="cline-any cline-yes">780x</span>
542
+ <span class="cline-any cline-yes">16510x</span>
543
+ <span class="cline-any cline-yes">16510x</span>
544
+ <span class="cline-any cline-neutral">&nbsp;</span>
545
+ <span class="cline-any cline-yes">16445x</span>
546
+ <span class="cline-any cline-yes">16445x</span>
547
+ <span class="cline-any cline-neutral">&nbsp;</span>
548
+ <span class="cline-any cline-yes">16445x</span>
549
+ <span class="cline-any cline-neutral">&nbsp;</span>
550
+ <span class="cline-any cline-yes">16510x</span>
551
+ <span class="cline-any cline-yes">17771x</span>
552
+ <span class="cline-any cline-yes">43295x</span>
553
+ <span class="cline-any cline-yes">43295x</span>
554
+ <span class="cline-any cline-yes">43295x</span>
555
+ <span class="cline-any cline-neutral">&nbsp;</span>
556
+ <span class="cline-any cline-neutral">&nbsp;</span>
557
+ <span class="cline-any cline-neutral">&nbsp;</span>
558
+ <span class="cline-any cline-neutral">&nbsp;</span>
559
+ <span class="cline-any cline-neutral">&nbsp;</span>
560
+ <span class="cline-any cline-yes">91x</span>
561
+ <span class="cline-any cline-neutral">&nbsp;</span>
562
+ <span class="cline-any cline-neutral">&nbsp;</span>
563
+ <span class="cline-any cline-neutral">&nbsp;</span>
564
+ <span class="cline-any cline-neutral">&nbsp;</span>
565
+ <span class="cline-any cline-neutral">&nbsp;</span>
566
+ <span class="cline-any cline-neutral">&nbsp;</span>
567
+ <span class="cline-any cline-neutral">&nbsp;</span>
568
+ <span class="cline-any cline-neutral">&nbsp;</span>
569
+ <span class="cline-any cline-neutral">&nbsp;</span>
570
+ <span class="cline-any cline-neutral">&nbsp;</span>
571
+ <span class="cline-any cline-yes">18x</span>
572
+ <span class="cline-any cline-neutral">&nbsp;</span>
573
+ <span class="cline-any cline-neutral">&nbsp;</span>
574
+ <span class="cline-any cline-yes">62x</span>
575
+ <span class="cline-any cline-neutral">&nbsp;</span>
576
+ <span class="cline-any cline-neutral">&nbsp;</span>
577
+ <span class="cline-any cline-no">&nbsp;</span>
578
+ <span class="cline-any cline-neutral">&nbsp;</span>
579
+ <span class="cline-any cline-yes">18x</span>
580
+ <span class="cline-any cline-yes">18x</span>
581
+ <span class="cline-any cline-yes">18x</span>
582
+ <span class="cline-any cline-neutral">&nbsp;</span>
583
+ <span class="cline-any cline-yes">62x</span>
584
+ <span class="cline-any cline-yes">62x</span>
585
+ <span class="cline-any cline-neutral">&nbsp;</span>
586
+ <span class="cline-any cline-neutral">&nbsp;</span>
587
+ <span class="cline-any cline-neutral">&nbsp;</span>
588
+ <span class="cline-any cline-neutral">&nbsp;</span>
589
+ <span class="cline-any cline-neutral">&nbsp;</span>
590
+ <span class="cline-any cline-neutral">&nbsp;</span>
591
+ <span class="cline-any cline-neutral">&nbsp;</span>
592
+ <span class="cline-any cline-neutral">&nbsp;</span>
593
+ <span class="cline-any cline-neutral">&nbsp;</span>
594
+ <span class="cline-any cline-neutral">&nbsp;</span>
595
+ <span class="cline-any cline-neutral">&nbsp;</span>
596
+ <span class="cline-any cline-neutral">&nbsp;</span>
597
+ <span class="cline-any cline-neutral">&nbsp;</span>
598
+ <span class="cline-any cline-neutral">&nbsp;</span>
599
+ <span class="cline-any cline-neutral">&nbsp;</span>
600
+ <span class="cline-any cline-neutral">&nbsp;</span>
601
+ <span class="cline-any cline-neutral">&nbsp;</span>
602
+ <span class="cline-any cline-neutral">&nbsp;</span>
603
+ <span class="cline-any cline-neutral">&nbsp;</span>
604
+ <span class="cline-any cline-neutral">&nbsp;</span>
605
+ <span class="cline-any cline-neutral">&nbsp;</span>
606
+ <span class="cline-any cline-neutral">&nbsp;</span>
607
+ <span class="cline-any cline-neutral">&nbsp;</span>
608
+ <span class="cline-any cline-neutral">&nbsp;</span>
609
+ <span class="cline-any cline-neutral">&nbsp;</span>
610
+ <span class="cline-any cline-neutral">&nbsp;</span>
611
+ <span class="cline-any cline-yes">20x</span>
612
+ <span class="cline-any cline-yes">19x</span>
613
+ <span class="cline-any cline-yes">19x</span>
614
+ <span class="cline-any cline-neutral">&nbsp;</span>
615
+ <span class="cline-any cline-neutral">&nbsp;</span>
616
+ <span class="cline-any cline-yes">20x</span>
617
+ <span class="cline-any cline-neutral">&nbsp;</span>
618
+ <span class="cline-any cline-neutral">&nbsp;</span>
619
+ <span class="cline-any cline-neutral">&nbsp;</span>
620
+ <span class="cline-any cline-neutral">&nbsp;</span>
621
+ <span class="cline-any cline-neutral">&nbsp;</span>
622
+ <span class="cline-any cline-neutral">&nbsp;</span>
623
+ <span class="cline-any cline-neutral">&nbsp;</span>
624
+ <span class="cline-any cline-neutral">&nbsp;</span>
625
+ <span class="cline-any cline-neutral">&nbsp;</span>
626
+ <span class="cline-any cline-neutral">&nbsp;</span>
627
+ <span class="cline-any cline-neutral">&nbsp;</span>
628
+ <span class="cline-any cline-neutral">&nbsp;</span>
629
+ <span class="cline-any cline-neutral">&nbsp;</span>
630
+ <span class="cline-any cline-neutral">&nbsp;</span>
631
+ <span class="cline-any cline-neutral">&nbsp;</span>
632
+ <span class="cline-any cline-neutral">&nbsp;</span>
633
+ <span class="cline-any cline-neutral">&nbsp;</span>
634
+ <span class="cline-any cline-neutral">&nbsp;</span>
635
+ <span class="cline-any cline-neutral">&nbsp;</span>
636
+ <span class="cline-any cline-neutral">&nbsp;</span>
637
+ <span class="cline-any cline-neutral">&nbsp;</span>
638
+ <span class="cline-any cline-yes">20x</span>
639
+ <span class="cline-any cline-neutral">&nbsp;</span>
640
+ <span class="cline-any cline-yes">20x</span>
641
+ <span class="cline-any cline-yes">342x</span>
642
+ <span class="cline-any cline-yes">342x</span>
643
+ <span class="cline-any cline-neutral">&nbsp;</span>
644
+ <span class="cline-any cline-yes">36x</span>
645
+ <span class="cline-any cline-yes">63x</span>
646
+ <span class="cline-any cline-yes">63x</span>
647
+ <span class="cline-any cline-yes">63x</span>
648
+ <span class="cline-any cline-neutral">&nbsp;</span>
649
+ <span class="cline-any cline-neutral">&nbsp;</span>
650
+ <span class="cline-any cline-neutral">&nbsp;</span>
651
+ <span class="cline-any cline-neutral">&nbsp;</span>
652
+ <span class="cline-any cline-neutral">&nbsp;</span>
653
+ <span class="cline-any cline-yes">63x</span>
654
+ <span class="cline-any cline-neutral">&nbsp;</span>
655
+ <span class="cline-any cline-neutral">&nbsp;</span>
656
+ <span class="cline-any cline-neutral">&nbsp;</span>
657
+ <span class="cline-any cline-neutral">&nbsp;</span>
658
+ <span class="cline-any cline-neutral">&nbsp;</span>
659
+ <span class="cline-any cline-neutral">&nbsp;</span>
660
+ <span class="cline-any cline-neutral">&nbsp;</span>
661
+ <span class="cline-any cline-neutral">&nbsp;</span>
662
+ <span class="cline-any cline-no">&nbsp;</span>
663
+ <span class="cline-any cline-neutral">&nbsp;</span>
664
+ <span class="cline-any cline-neutral">&nbsp;</span>
665
+ <span class="cline-any cline-neutral">&nbsp;</span>
666
+ <span class="cline-any cline-neutral">&nbsp;</span>
667
+ <span class="cline-any cline-yes">19x</span>
668
+ <span class="cline-any cline-no">&nbsp;</span>
669
+ <span class="cline-any cline-neutral">&nbsp;</span>
670
+ <span class="cline-any cline-neutral">&nbsp;</span>
671
+ <span class="cline-any cline-neutral">&nbsp;</span>
672
+ <span class="cline-any cline-yes">19x</span>
673
+ <span class="cline-any cline-yes">18x</span>
674
+ <span class="cline-any cline-yes">18x</span>
675
+ <span class="cline-any cline-neutral">&nbsp;</span>
676
+ <span class="cline-any cline-neutral">&nbsp;</span>
677
+ <span class="cline-any cline-neutral">&nbsp;</span>
678
+ <span class="cline-any cline-neutral">&nbsp;</span>
679
+ <span class="cline-any cline-yes">19x</span>
680
+ <span class="cline-any cline-yes">1x</span>
681
+ <span class="cline-any cline-neutral">&nbsp;</span>
682
+ <span class="cline-any cline-neutral">&nbsp;</span>
683
+ <span class="cline-any cline-neutral">&nbsp;</span>
684
+ <span class="cline-any cline-yes">18x</span>
685
+ <span class="cline-any cline-neutral">&nbsp;</span>
686
+ <span class="cline-any cline-neutral">&nbsp;</span>
687
+ <span class="cline-any cline-neutral">&nbsp;</span>
688
+ <span class="cline-any cline-neutral">&nbsp;</span>
689
+ <span class="cline-any cline-yes">18x</span>
690
+ <span class="cline-any cline-neutral">&nbsp;</span>
691
+ <span class="cline-any cline-neutral">&nbsp;</span>
692
+ <span class="cline-any cline-yes">18x</span>
693
+ <span class="cline-any cline-no">&nbsp;</span>
694
+ <span class="cline-any cline-neutral">&nbsp;</span>
695
+ <span class="cline-any cline-neutral">&nbsp;</span>
696
+ <span class="cline-any cline-neutral">&nbsp;</span>
697
+ <span class="cline-any cline-neutral">&nbsp;</span>
698
+ <span class="cline-any cline-yes">18x</span>
699
+ <span class="cline-any cline-yes">18x</span>
700
+ <span class="cline-any cline-neutral">&nbsp;</span>
701
+ <span class="cline-any cline-neutral">&nbsp;</span>
702
+ <span class="cline-any cline-neutral">&nbsp;</span>
703
+ <span class="cline-any cline-neutral">&nbsp;</span>
704
+ <span class="cline-any cline-neutral">&nbsp;</span>
705
+ <span class="cline-any cline-yes">18x</span>
706
+ <span class="cline-any cline-neutral">&nbsp;</span>
707
+ <span class="cline-any cline-yes">18x</span>
708
+ <span class="cline-any cline-yes">62x</span>
709
+ <span class="cline-any cline-neutral">&nbsp;</span>
710
+ <span class="cline-any cline-yes">62x</span>
711
+ <span class="cline-any cline-neutral">&nbsp;</span>
712
+ <span class="cline-any cline-neutral">&nbsp;</span>
713
+ <span class="cline-any cline-neutral">&nbsp;</span>
714
+ <span class="cline-any cline-yes">62x</span>
715
+ <span class="cline-any cline-yes">28x</span>
716
+ <span class="cline-any cline-yes">28x</span>
717
+ <span class="cline-any cline-neutral">&nbsp;</span>
718
+ <span class="cline-any cline-yes">34x</span>
719
+ <span class="cline-any cline-neutral">&nbsp;</span>
720
+ <span class="cline-any cline-neutral">&nbsp;</span>
721
+ <span class="cline-any cline-yes">62x</span>
722
+ <span class="cline-any cline-yes">35x</span>
723
+ <span class="cline-any cline-neutral">&nbsp;</span>
724
+ <span class="cline-any cline-neutral">&nbsp;</span>
725
+ <span class="cline-any cline-yes">62x</span>
726
+ <span class="cline-any cline-neutral">&nbsp;</span>
727
+ <span class="cline-any cline-neutral">&nbsp;</span>
728
+ <span class="cline-any cline-neutral">&nbsp;</span>
729
+ <span class="cline-any cline-neutral">&nbsp;</span>
730
+ <span class="cline-any cline-neutral">&nbsp;</span>
731
+ <span class="cline-any cline-neutral">&nbsp;</span>
732
+ <span class="cline-any cline-neutral">&nbsp;</span>
733
+ <span class="cline-any cline-neutral">&nbsp;</span>
734
+ <span class="cline-any cline-yes">18x</span>
735
+ <span class="cline-any cline-neutral">&nbsp;</span>
736
+ <span class="cline-any cline-neutral">&nbsp;</span>
737
+ <span class="cline-any cline-neutral">&nbsp;</span>
738
+ <span class="cline-any cline-neutral">&nbsp;</span>
739
+ <span class="cline-any cline-neutral">&nbsp;</span>
740
+ <span class="cline-any cline-neutral">&nbsp;</span>
741
+ <span class="cline-any cline-neutral">&nbsp;</span>
742
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
+ <span class="cline-any cline-neutral">&nbsp;</span>
744
+ <span class="cline-any cline-yes">20x</span>
745
+ <span class="cline-any cline-neutral">&nbsp;</span>
746
+ <span class="cline-any cline-neutral">&nbsp;</span>
747
+ <span class="cline-any cline-neutral">&nbsp;</span>
748
+ <span class="cline-any cline-neutral">&nbsp;</span>
749
+ <span class="cline-any cline-neutral">&nbsp;</span>
750
+ <span class="cline-any cline-neutral">&nbsp;</span>
751
+ <span class="cline-any cline-neutral">&nbsp;</span>
752
+ <span class="cline-any cline-neutral">&nbsp;</span>
753
+ <span class="cline-any cline-neutral">&nbsp;</span>
754
+ <span class="cline-any cline-neutral">&nbsp;</span>
755
+ <span class="cline-any cline-neutral">&nbsp;</span>
756
+ <span class="cline-any cline-neutral">&nbsp;</span>
757
+ <span class="cline-any cline-neutral">&nbsp;</span>
758
+ <span class="cline-any cline-neutral">&nbsp;</span>
759
+ <span class="cline-any cline-neutral">&nbsp;</span>
760
+ <span class="cline-any cline-neutral">&nbsp;</span>
761
+ <span class="cline-any cline-yes">13x</span>
762
+ <span class="cline-any cline-yes">13x</span>
763
+ <span class="cline-any cline-yes">13x</span>
764
+ <span class="cline-any cline-yes">16x</span>
765
+ <span class="cline-any cline-yes">16x</span>
766
+ <span class="cline-any cline-neutral">&nbsp;</span>
767
+ <span class="cline-any cline-no">&nbsp;</span>
768
+ <span class="cline-any cline-neutral">&nbsp;</span>
769
+ <span class="cline-any cline-neutral">&nbsp;</span>
770
+ <span class="cline-any cline-neutral">&nbsp;</span>
771
+ <span class="cline-any cline-neutral">&nbsp;</span>
772
+ <span class="cline-any cline-neutral">&nbsp;</span>
773
+ <span class="cline-any cline-neutral">&nbsp;</span>
774
+ <span class="cline-any cline-neutral">&nbsp;</span>
775
+ <span class="cline-any cline-neutral">&nbsp;</span>
776
+ <span class="cline-any cline-neutral">&nbsp;</span>
777
+ <span class="cline-any cline-neutral">&nbsp;</span>
778
+ <span class="cline-any cline-neutral">&nbsp;</span>
779
+ <span class="cline-any cline-neutral">&nbsp;</span>
780
+ <span class="cline-any cline-neutral">&nbsp;</span>
781
+ <span class="cline-any cline-yes">13x</span>
782
+ <span class="cline-any cline-yes">13x</span>
783
+ <span class="cline-any cline-yes">13x</span>
784
+ <span class="cline-any cline-yes">13x</span>
785
+ <span class="cline-any cline-yes">13x</span>
786
+ <span class="cline-any cline-yes">13x</span>
787
+ <span class="cline-any cline-neutral">&nbsp;</span>
788
+ <span class="cline-any cline-neutral">&nbsp;</span>
789
+ <span class="cline-any cline-neutral">&nbsp;</span>
790
+ <span class="cline-any cline-neutral">&nbsp;</span>
791
+ <span class="cline-any cline-neutral">&nbsp;</span>
792
+ <span class="cline-any cline-neutral">&nbsp;</span>
793
+ <span class="cline-any cline-neutral">&nbsp;</span>
794
+ <span class="cline-any cline-neutral">&nbsp;</span>
795
+ <span class="cline-any cline-neutral">&nbsp;</span>
796
+ <span class="cline-any cline-neutral">&nbsp;</span>
797
+ <span class="cline-any cline-neutral">&nbsp;</span>
798
+ <span class="cline-any cline-neutral">&nbsp;</span>
799
+ <span class="cline-any cline-neutral">&nbsp;</span>
800
+ <span class="cline-any cline-yes">124x</span>
801
+ <span class="cline-any cline-yes">2328x</span>
802
+ <span class="cline-any cline-yes">68588x</span>
803
+ <span class="cline-any cline-yes">68588x</span>
804
+ <span class="cline-any cline-yes">68588x</span>
805
+ <span class="cline-any cline-yes">68588x</span>
806
+ <span class="cline-any cline-neutral">&nbsp;</span>
807
+ <span class="cline-any cline-neutral">&nbsp;</span>
808
+ <span class="cline-any cline-neutral">&nbsp;</span>
809
+ <span class="cline-any cline-neutral">&nbsp;</span>
810
+ <span class="cline-any cline-neutral">&nbsp;</span>
811
+ <span class="cline-any cline-neutral">&nbsp;</span>
812
+ <span class="cline-any cline-neutral">&nbsp;</span>
813
+ <span class="cline-any cline-neutral">&nbsp;</span>
814
+ <span class="cline-any cline-neutral">&nbsp;</span>
815
+ <span class="cline-any cline-neutral">&nbsp;</span>
816
+ <span class="cline-any cline-neutral">&nbsp;</span>
817
+ <span class="cline-any cline-neutral">&nbsp;</span>
818
+ <span class="cline-any cline-neutral">&nbsp;</span>
819
+ <span class="cline-any cline-yes">20x</span>
820
+ <span class="cline-any cline-no">&nbsp;</span>
821
+ <span class="cline-any cline-neutral">&nbsp;</span>
822
+ <span class="cline-any cline-neutral">&nbsp;</span>
823
+ <span class="cline-any cline-neutral">&nbsp;</span>
824
+ <span class="cline-any cline-yes">45x</span>
825
+ <span class="cline-any cline-neutral">&nbsp;</span>
826
+ <span class="cline-any cline-yes">20x</span>
827
+ <span class="cline-any cline-yes">20x</span>
828
+ <span class="cline-any cline-yes">20x</span>
829
+ <span class="cline-any cline-yes">20x</span>
830
+ <span class="cline-any cline-yes">20x</span>
831
+ <span class="cline-any cline-yes">20x</span>
832
+ <span class="cline-any cline-neutral">&nbsp;</span>
833
+ <span class="cline-any cline-yes">20x</span>
834
+ <span class="cline-any cline-neutral">&nbsp;</span>
835
+ <span class="cline-any cline-yes">65x</span>
836
+ <span class="cline-any cline-neutral">&nbsp;</span>
837
+ <span class="cline-any cline-yes">6x</span>
838
+ <span class="cline-any cline-yes">6x</span>
839
+ <span class="cline-any cline-yes">6x</span>
840
+ <span class="cline-any cline-neutral">&nbsp;</span>
841
+ <span class="cline-any cline-neutral">&nbsp;</span>
842
+ <span class="cline-any cline-yes">65x</span>
843
+ <span class="cline-any cline-yes">2x</span>
844
+ <span class="cline-any cline-neutral">&nbsp;</span>
845
+ <span class="cline-any cline-neutral">&nbsp;</span>
846
+ <span class="cline-any cline-yes">63x</span>
847
+ <span class="cline-any cline-neutral">&nbsp;</span>
848
+ <span class="cline-any cline-neutral">&nbsp;</span>
849
+ <span class="cline-any cline-neutral">&nbsp;</span>
850
+ <span class="cline-any cline-neutral">&nbsp;</span>
851
+ <span class="cline-any cline-neutral">&nbsp;</span>
852
+ <span class="cline-any cline-yes">63x</span>
853
+ <span class="cline-any cline-yes">63x</span>
854
+ <span class="cline-any cline-yes">63x</span>
855
+ <span class="cline-any cline-yes">63x</span>
856
+ <span class="cline-any cline-neutral">&nbsp;</span>
857
+ <span class="cline-any cline-neutral">&nbsp;</span>
858
+ <span class="cline-any cline-yes">18x</span>
859
+ <span class="cline-any cline-yes">18x</span>
860
+ <span class="cline-any cline-neutral">&nbsp;</span>
861
+ <span class="cline-any cline-yes">18x</span>
862
+ <span class="cline-any cline-neutral">&nbsp;</span>
863
+ <span class="cline-any cline-neutral">&nbsp;</span>
864
+ <span class="cline-any cline-neutral">&nbsp;</span>
865
+ <span class="cline-any cline-neutral">&nbsp;</span>
866
+ <span class="cline-any cline-neutral">&nbsp;</span>
867
+ <span class="cline-any cline-neutral">&nbsp;</span>
868
+ <span class="cline-any cline-neutral">&nbsp;</span>
869
+ <span class="cline-any cline-neutral">&nbsp;</span>
870
+ <span class="cline-any cline-neutral">&nbsp;</span>
871
+ <span class="cline-any cline-neutral">&nbsp;</span>
872
+ <span class="cline-any cline-neutral">&nbsp;</span>
873
+ <span class="cline-any cline-neutral">&nbsp;</span>
874
+ <span class="cline-any cline-neutral">&nbsp;</span>
875
+ <span class="cline-any cline-neutral">&nbsp;</span>
876
+ <span class="cline-any cline-yes">18x</span>
877
+ <span class="cline-any cline-yes">18x</span>
878
+ <span class="cline-any cline-yes">18x</span>
879
+ <span class="cline-any cline-yes">18x</span>
880
+ <span class="cline-any cline-yes">18x</span>
881
+ <span class="cline-any cline-yes">18x</span>
882
+ <span class="cline-any cline-yes">18x</span>
883
+ <span class="cline-any cline-yes">18x</span>
884
+ <span class="cline-any cline-neutral">&nbsp;</span>
885
+ <span class="cline-any cline-neutral">&nbsp;</span>
886
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
887
+ * Atlas Sprite Sheet Generator for Cyberia Online.
888
+ * Consolidates all object layer frames (8 directions, multiple modes) into a single PNG atlas.
889
+ * @module src/projects/cyberia/atlas-sprite-sheet-generator.js
890
+ * @namespace CyberiaAtlasSpriteSheetGenerator
891
+ */
892
+ &nbsp;
893
+ import { Jimp, rgbaToInt } from 'jimp';
894
+ import { loggerFactory } from '../../server/ops/logger.js';
895
+ import { getKeyframeDirectionsByCode } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
896
+ &nbsp;
897
+ const logger = loggerFactory(import.meta);
898
+ &nbsp;
899
+ /** Direction code of the still an item is previewed by: down idle. */
900
+ export const IDLE_PREVIEW_DIRECTION_CODE = '08';
901
+ &nbsp;
902
+ /**
903
+ * Pixels per cell of the human-resolution atlas, when no factor is given.
904
+ * @memberof CyberiaAtlasSpriteSheetGenerator
905
+ */
906
+ export const DEFAULT_ATLAS_UPSCALE_FACTOR = 20;
907
+ &nbsp;
908
+ /**
909
+ * Pixels per cell of the minified atlas. The client runtime downloads this one,
910
+ * so it stays at one pixel per cell.
911
+ * @memberof CyberiaAtlasSpriteSheetGenerator
912
+ */
913
+ const MINIFY_CELL_PIXEL_DIM = 1;
914
+ &nbsp;
915
+ /**
916
+ * @typedef {Object} AtlasFrame
917
+ * @property {number} x - X position in atlas
918
+ * @property {number} y - Y position in atlas
919
+ * @property {number} width - Frame width
920
+ * @property {number} height - Frame height
921
+ * @property {number} frameIndex - Frame index in animation
922
+ * @memberof CyberiaAtlasSpriteSheetGenerator
923
+ */
924
+ &nbsp;
925
+ /**
926
+ * Atlas Sprite Sheet Generator Engine
927
+ * @class
928
+ * @memberof CyberiaAtlasSpriteSheetGenerator
929
+ */
930
+ export class AtlasSpriteSheetGenerator {
931
+ /**
932
+ * Converts frame matrix and color palette to a Jimp image
933
+ * @static
934
+ * @param {number[][]} frameMatrix - The frame matrix
935
+ * @param {number[][]} colors - Color palette
936
+ * @param {number} cellPixelDim - Pixel dimension per cell
937
+ * @returns {Promise&lt;Jimp&gt;} The generated image
938
+ * @memberof CyberiaAtlasSpriteSheetGenerator
939
+ */
940
+ static async frameMatrixToImage(frameMatrix, colors, cellPixelDim = 1) {
941
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!frameMatrix || frameMatrix.length === 0 || frameMatrix[0].length === 0) {
942
+ <span class="cstat-no" title="statement not covered" > throw new Error('Invalid frame matrix');</span>
943
+ }
944
+ &nbsp;
945
+ const width = cellPixelDim * frameMatrix[0].length;
946
+ const height = cellPixelDim * frameMatrix.length;
947
+ &nbsp;
948
+ const image = new Jimp({ width, height, color: 0x00000000 });
949
+ &nbsp;
950
+ for (let y = 0; y &lt; frameMatrix.length; y++) {
951
+ for (let x = 0; x &lt; frameMatrix[y].length; x++) {
952
+ const colorIndex = frameMatrix[y][x];
953
+ if (colorIndex === null || colorIndex === undefined) continue;
954
+ &nbsp;
955
+ const color = colors[colorIndex];
956
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!color) <span class="cstat-no" title="statement not covered" >continue;</span>
957
+ &nbsp;
958
+ const rgbaColor = color.length === 4 ? color : <span class="branch-1 cbranch-no" title="branch not covered" >[...color, 255];</span>
959
+ &nbsp;
960
+ for (let dy = 0; dy &lt; cellPixelDim; dy++) {
961
+ for (let dx = 0; dx &lt; cellPixelDim; dx++) {
962
+ const pixelX = x * cellPixelDim + dx;
963
+ const pixelY = y * cellPixelDim + dy;
964
+ image.setPixelColor(rgbaToInt(...rgbaColor), pixelX, pixelY);
965
+ }
966
+ }
967
+ }
968
+ }
969
+ &nbsp;
970
+ return image;
971
+ }
972
+ &nbsp;
973
+ /**
974
+ * Calculates optimal atlas dimensions based on frame count and size
975
+ * @static
976
+ * @param {Array} frameImages - Array of frame image objects
977
+ * @returns {number} Recommended atlas dimension (power of 2)
978
+ * @memberof CyberiaAtlasSpriteSheetGenerator
979
+ */
980
+ static calculateOptimalDimension(frameImages) {
981
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (
982
+ !frameImages?.length ||
983
+ frameImages.some(
984
+ ({ width, height }) =&gt; !Number.isInteger(width) || !Number.isInteger(height) || width &lt;= 0 || height &lt;= 0,
985
+ )
986
+ ) {
987
+ <span class="cstat-no" title="statement not covered" > throw new Error('Invalid frame dimensions');</span>
988
+ }
989
+ const cols = Math.ceil(Math.sqrt(frameImages.length));
990
+ const rows = Math.ceil(frameImages.length / cols);
991
+ return AtlasSpriteSheetGenerator.nextPowerOf2(
992
+ Math.max(
993
+ cols * Math.max(...frameImages.map((frame) =&gt; frame.width)),
994
+ rows * Math.max(...frameImages.map((frame) =&gt; frame.height)),
995
+ ),
996
+ );
997
+ }
998
+ &nbsp;
999
+ /**
1000
+ * Consolidates all frames of an ObjectLayerRenderFrames into one atlas sprite sheet.
1001
+ *
1002
+ * One packing produces two renders of the same layout: `minifyBuffer` at one
1003
+ * pixel per cell, which the client runtime downloads, and `buffer` at
1004
+ * `upscaleFactor` pixels per cell for human viewing. `metadata` describes the
1005
+ * minified render, so atlas metadata and the served blob always agree.
1006
+ *
1007
+ * @static
1008
+ * @param {Object} objectLayerRenderFrames - The ObjectLayerRenderFrames document
1009
+ * @param {string} itemKey - The item key for the atlas
1010
+ * @param {number} [upscaleFactor=DEFAULT_ATLAS_UPSCALE_FACTOR] - Pixels per cell of the human-resolution render
1011
+ * @param {number} [maxAtlasDim=null] - Maximum atlas dimension (auto-calculated if null)
1012
+ * @returns {Promise&lt;{buffer: Buffer, minifyBuffer: Buffer, metadata: Object}&gt;} Both renders and the shared metadata
1013
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1014
+ */
1015
+ static async generateAtlas(
1016
+ objectLayerRenderFrames,
1017
+ itemKey,
1018
+ upscaleFactor = DEFAULT_ATLAS_UPSCALE_FACTOR,
1019
+ maxAtlasDim = null,
1020
+ ) {
1021
+ if (!Number.isInteger(upscaleFactor) || upscaleFactor &lt; 1) throw new Error('Invalid pixel scale');
1022
+ const { frames, colors } = objectLayerRenderFrames;
1023
+ const frameDuration = Number(objectLayerRenderFrames?.frame_duration);
1024
+ &nbsp;
1025
+ // Direction order for consistent packing
1026
+ const directionOrder = [
1027
+ 'down_idle',
1028
+ 'up_idle',
1029
+ 'left_idle',
1030
+ 'right_idle',
1031
+ 'down_left_idle',
1032
+ 'down_right_idle',
1033
+ 'up_left_idle',
1034
+ 'up_right_idle',
1035
+ 'down_walking',
1036
+ 'up_walking',
1037
+ 'left_walking',
1038
+ 'right_walking',
1039
+ 'down_left_walking',
1040
+ 'down_right_walking',
1041
+ 'up_left_walking',
1042
+ 'up_right_walking',
1043
+ 'default_idle',
1044
+ 'none_idle',
1045
+ ];
1046
+ &nbsp;
1047
+ // The packing works on the minified render; the upscaled render reuses its layout.
1048
+ const frameImages = [];
1049
+ &nbsp;
1050
+ for (const direction of directionOrder) {
1051
+ const directionFrames = frames[direction];
1052
+ if (!directionFrames || directionFrames.length === 0) continue;
1053
+ &nbsp;
1054
+ for (let frameIndex = 0; frameIndex &lt; directionFrames.length; frameIndex++) {
1055
+ const frameMatrix = directionFrames[frameIndex];
1056
+ try {
1057
+ const frameImage = await AtlasSpriteSheetGenerator.frameMatrixToImage(
1058
+ frameMatrix,
1059
+ colors,
1060
+ MINIFY_CELL_PIXEL_DIM,
1061
+ );
1062
+ &nbsp;
1063
+ frameImages.push({
1064
+ image: frameImage,
1065
+ frameMatrix,
1066
+ direction,
1067
+ frameIndex,
1068
+ width: frameImage.bitmap.width,
1069
+ height: frameImage.bitmap.height,
1070
+ });
1071
+ } catch (error) {
1072
+ <span class="cstat-no" title="statement not covered" > logger.warn(`Failed to generate frame for ${itemKey} ${direction}[${frameIndex}]:`, error.message);</span>
1073
+ }
1074
+ }
1075
+ }
1076
+ &nbsp;
1077
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (frameImages.length === 0) {
1078
+ <span class="cstat-no" title="statement not covered" > throw new Error(`No valid frames found for item: ${itemKey}`);</span>
1079
+ }
1080
+ &nbsp;
1081
+ // Auto-calculate optimal dimension if not specified
1082
+ if (maxAtlasDim === null || maxAtlasDim === undefined) {
1083
+ maxAtlasDim = AtlasSpriteSheetGenerator.calculateOptimalDimension(frameImages);
1084
+ logger.info(
1085
+ `Auto-calculated optimal atlas dimension: ${maxAtlasDim}x${maxAtlasDim} for ${frameImages.length} frames`,
1086
+ );
1087
+ }
1088
+ &nbsp;
1089
+ if (!Number.isInteger(maxAtlasDim) || maxAtlasDim &lt;= 0 || maxAtlasDim &gt; 4096) {
1090
+ throw new Error('Atlas dimension must be between 1 and 4096');
1091
+ }
1092
+ &nbsp;
1093
+ // Simple grid packing algorithm
1094
+ const { packedFrames, atlasWidth, atlasHeight } = AtlasSpriteSheetGenerator.packFramesGrid(
1095
+ frameImages,
1096
+ maxAtlasDim,
1097
+ );
1098
+ &nbsp;
1099
+ logger.info(`Packing ${packedFrames.length} frames into ${atlasWidth}x${atlasHeight} atlas`);
1100
+ &nbsp;
1101
+ // Validate dimensions before creating Jimp image
1102
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (isNaN(atlasWidth) || isNaN(atlasHeight) || atlasWidth &lt;= 0 || atlasHeight &lt;= 0) {
1103
+ <span class="cstat-no" title="statement not covered" > throw new Error(</span>
1104
+ `Invalid atlas dimensions: ${atlasWidth}x${atlasHeight} (maxAtlasDim: ${maxAtlasDim}, frameCount: ${frameImages.length})`,
1105
+ );
1106
+ }
1107
+ &nbsp;
1108
+ const minifyImage = new Jimp({ width: atlasWidth, height: atlasHeight, color: 0x00000000 });
1109
+ const upscaledImage = new Jimp({
1110
+ width: atlasWidth * upscaleFactor,
1111
+ height: atlasHeight * upscaleFactor,
1112
+ color: 0x00000000,
1113
+ });
1114
+ &nbsp;
1115
+ const frameMetadata = {};
1116
+ &nbsp;
1117
+ for (const packedFrame of packedFrames) {
1118
+ const { image, frameMatrix, x, y, direction, frameIndex } = packedFrame;
1119
+ &nbsp;
1120
+ AtlasSpriteSheetGenerator.blitFrame(minifyImage, image, x, y);
1121
+ &nbsp;
1122
+ // The same cell grid at a larger scale, so the upscaled atlas is an exact
1123
+ // multiple of the layout the metadata describes.
1124
+ if (upscaleFactor &gt; MINIFY_CELL_PIXEL_DIM) {
1125
+ const upscaledFrame = await AtlasSpriteSheetGenerator.frameMatrixToImage(frameMatrix, colors, upscaleFactor);
1126
+ AtlasSpriteSheetGenerator.blitFrame(upscaledImage, upscaledFrame, x * upscaleFactor, y * upscaleFactor);
1127
+ } else {
1128
+ AtlasSpriteSheetGenerator.blitFrame(upscaledImage, image, x, y);
1129
+ }
1130
+ &nbsp;
1131
+ if (!frameMetadata[direction]) {
1132
+ frameMetadata[direction] = [];
1133
+ }
1134
+ &nbsp;
1135
+ frameMetadata[direction][frameIndex] = {
1136
+ x,
1137
+ y,
1138
+ width: image.bitmap.width,
1139
+ height: image.bitmap.height,
1140
+ frameIndex,
1141
+ };
1142
+ }
1143
+ &nbsp;
1144
+ const metadata = {
1145
+ itemKey,
1146
+ atlasWidth,
1147
+ atlasHeight,
1148
+ cellPixelDim: MINIFY_CELL_PIXEL_DIM,
1149
+ upscaleFactor,
1150
+ frame_duration: Number.isFinite(frameDuration) ? frameDuration : 100,
1151
+ frames: frameMetadata,
1152
+ };
1153
+ &nbsp;
1154
+ return {
1155
+ buffer: await upscaledImage.getBuffer('image/png'),
1156
+ minifyBuffer: await minifyImage.getBuffer('image/png'),
1157
+ metadata,
1158
+ };
1159
+ }
1160
+ &nbsp;
1161
+ /**
1162
+ * The frame an item is previewed by: the first frame of the first down-idle keyframe the
1163
+ * atlas carries, or the first frame of any direction when it has none.
1164
+ *
1165
+ * @static
1166
+ * @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
1167
+ * @returns {{x:number,y:number,width:number,height:number}|null} Frame box in minified cells.
1168
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1169
+ */
1170
+ static idlePreviewFrame(metadata) {
1171
+ const frames = metadata?.frames ?? <span class="branch-1 cbranch-no" title="branch not covered" >{};</span>
1172
+ const preferred = getKeyframeDirectionsByCode(IDLE_PREVIEW_DIRECTION_CODE);
1173
+ for (const direction of [...preferred, ...Object.keys(frames)]) {
1174
+ const frame = frames[direction]?.[0];
1175
+ if (frame) return frame;
1176
+ }
1177
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1178
+ }
1179
+ &nbsp;
1180
+ /**
1181
+ * Cuts the idle-preview still out of the human-resolution render, using the same layout
1182
+ * `metadata.frames` describes for the minified one scaled by `metadata.upscaleFactor`.
1183
+ *
1184
+ * @static
1185
+ * @param {Buffer} buffer - PNG bytes of the human-resolution atlas render.
1186
+ * @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
1187
+ * @returns {Promise&lt;Buffer|null&gt;} PNG bytes of the still, or null when the atlas has no frame.
1188
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1189
+ */
1190
+ static async idlePreviewFromAtlas(buffer, metadata) {
1191
+ const frame = AtlasSpriteSheetGenerator.idlePreviewFrame(metadata);
1192
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!frame) <span class="cstat-no" title="statement not covered" >return null;</span>
1193
+ const scale = Number(metadata.upscaleFactor) || <span class="branch-1 cbranch-no" title="branch not covered" >1;</span>
1194
+ const image = await Jimp.read(buffer);
1195
+ image.crop({ x: frame.x * scale, y: frame.y * scale, w: frame.width * scale, h: frame.height * scale });
1196
+ return await image.getBuffer('image/png');
1197
+ }
1198
+ &nbsp;
1199
+ /**
1200
+ * Copies a frame image onto an atlas canvas at the given origin.
1201
+ * @static
1202
+ * @param {Jimp} atlasImage - Destination atlas canvas.
1203
+ * @param {Jimp} frameImage - Source frame image.
1204
+ * @param {number} originX - Destination X origin.
1205
+ * @param {number} originY - Destination Y origin.
1206
+ * @returns {void}
1207
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1208
+ */
1209
+ static blitFrame(atlasImage, frameImage, originX, originY) {
1210
+ for (let srcY = 0; srcY &lt; frameImage.bitmap.height; srcY++) {
1211
+ for (let srcX = 0; srcX &lt; frameImage.bitmap.width; srcX++) {
1212
+ const destX = originX + srcX;
1213
+ const destY = originY + srcY;
1214
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (destX &gt;= atlasImage.bitmap.width || destY &gt;= atlasImage.bitmap.height) <span class="cstat-no" title="statement not covered" >continue;</span>
1215
+ atlasImage.setPixelColor(frameImage.getPixelColor(srcX, srcY), destX, destY);
1216
+ }
1217
+ }
1218
+ }
1219
+ &nbsp;
1220
+ /**
1221
+ * Simple grid-based frame packing algorithm
1222
+ * @static
1223
+ * @param {Array} frameImages - Array of frame image objects
1224
+ * @param {number} maxDim - Maximum atlas dimension
1225
+ * @returns {{packedFrames: Array, atlasWidth: number, atlasHeight: number}}
1226
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1227
+ */
1228
+ static packFramesGrid(frameImages, maxDim) {
1229
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (frameImages.length === 0) {
1230
+ <span class="cstat-no" title="statement not covered" > throw new Error('No frames to pack');</span>
1231
+ }
1232
+ &nbsp;
1233
+ // Sort frames by height (tallest first) for better packing
1234
+ const sortedFrames = [...frameImages].sort((a, b) =&gt; b.height - a.height || b.width - a.width);
1235
+ &nbsp;
1236
+ const packedFrames = [];
1237
+ let currentX = 0;
1238
+ let currentY = 0;
1239
+ let rowHeight = 0;
1240
+ let maxWidth = 0;
1241
+ let totalHeight = 0;
1242
+ &nbsp;
1243
+ for (const frame of sortedFrames) {
1244
+ // Check if frame fits in current row
1245
+ if (currentX + frame.width &gt; maxDim) {
1246
+ // Move to next row
1247
+ currentX = 0;
1248
+ currentY += rowHeight;
1249
+ rowHeight = 0;
1250
+ }
1251
+ &nbsp;
1252
+ if (frame.width &gt; maxDim || currentY + frame.height &gt; maxDim) {
1253
+ throw new Error(`Frames exceed atlas dimension ${maxDim}`);
1254
+ }
1255
+ &nbsp;
1256
+ packedFrames.push({
1257
+ ...frame,
1258
+ x: currentX,
1259
+ y: currentY,
1260
+ });
1261
+ &nbsp;
1262
+ currentX += frame.width;
1263
+ rowHeight = Math.max(rowHeight, frame.height);
1264
+ maxWidth = Math.max(maxWidth, currentX);
1265
+ totalHeight = Math.max(totalHeight, currentY + frame.height);
1266
+ }
1267
+ &nbsp;
1268
+ const atlasWidth = maxWidth;
1269
+ const atlasHeight = totalHeight;
1270
+ &nbsp;
1271
+ return {
1272
+ packedFrames,
1273
+ atlasWidth,
1274
+ atlasHeight,
1275
+ };
1276
+ }
1277
+ &nbsp;
1278
+ /**
1279
+ * Returns the next power of 2 greater than or equal to n
1280
+ * @static
1281
+ * @param {number} n - Input number
1282
+ * @returns {number} Next power of 2
1283
+ * @memberof CyberiaAtlasSpriteSheetGenerator
1284
+ */
1285
+ static nextPowerOf2(n) {
1286
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (n &lt;= 0) <span class="cstat-no" title="statement not covered" >return 1;</span>
1287
+ n--;
1288
+ n |= n &gt;&gt; 1;
1289
+ n |= n &gt;&gt; 2;
1290
+ n |= n &gt;&gt; 4;
1291
+ n |= n &gt;&gt; 8;
1292
+ n |= n &gt;&gt; 16;
1293
+ return n + 1;
1294
+ }
1295
+ }
1296
+ &nbsp;</pre></td></tr></table></pre>
1297
+
1298
+ <div class='push'></div><!-- for sticky footer -->
1299
+ </div><!-- /wrapper -->
1300
+ <div class='footer quiet pad2 space-top1 center small'>
1301
+ Code coverage generated by
1302
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1303
+ at 2026-09-13T20:21:13.226Z
1304
+ </div>
1305
+ <script src="../../prettify.js"></script>
1306
+ <script>
1307
+ window.onload = function () {
1308
+ prettyPrint();
1309
+ };
1310
+ </script>
1311
+ <script src="../../sorter.js"></script>
1312
+ <script src="../../block-navigation.js"></script>
1313
+ </body>
1314
+ </html>
1315
+