@origingame/create-game 0.0.0-stage → 0.1.1

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 (590) hide show
  1. package/README.md +57 -2
  2. package/archetypes/README.md +99 -0
  3. package/archetypes/idle/.origin/setup +14 -0
  4. package/archetypes/idle/LICENSES.md +12 -0
  5. package/archetypes/idle/README.md +234 -0
  6. package/archetypes/idle/assets/audio/buy.wav +0 -0
  7. package/archetypes/idle/assets/audio/milestone.wav +0 -0
  8. package/archetypes/idle/assets/audio/nova.wav +0 -0
  9. package/archetypes/idle/assets/audio/orbit.wav +0 -0
  10. package/archetypes/idle/assets/audio/tap.wav +0 -0
  11. package/archetypes/idle/assets/audio/ui-close.ogt +9 -0
  12. package/archetypes/idle/assets/audio/ui-error.ogt +9 -0
  13. package/archetypes/idle/assets/audio/ui-open.ogt +9 -0
  14. package/archetypes/idle/assets/audio/ui-press.ogt +9 -0
  15. package/archetypes/idle/assets/audio/ui-success.ogt +9 -0
  16. package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf +0 -0
  17. package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
  18. package/archetypes/idle/assets/fonts/OFL.txt +93 -0
  19. package/archetypes/idle/assets/fx/drift.ogt +21 -0
  20. package/archetypes/idle/assets/fx/milestone.ogt +21 -0
  21. package/archetypes/idle/assets/fx/nova.ogt +22 -0
  22. package/archetypes/idle/assets/fx/stars.ogt +20 -0
  23. package/archetypes/idle/assets/fx/tap.ogt +23 -0
  24. package/archetypes/idle/assets/looks/idle.ogt +17 -0
  25. package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
  26. package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +9 -0
  27. package/archetypes/idle/assets/skins/sparkorb/LICENSES.md +19 -0
  28. package/archetypes/idle/assets/skins/sparkorb/OFL.txt +95 -0
  29. package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf +0 -0
  30. package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf.ogt +9 -0
  31. package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf +0 -0
  32. package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf.ogt +9 -0
  33. package/archetypes/idle/assets/skins/sparkorb/atlas.png +0 -0
  34. package/archetypes/idle/assets/skins/sparkorb/atlas.png.ogt +7 -0
  35. package/archetypes/idle/assets/skins/sparkorb/icons.png +0 -0
  36. package/archetypes/idle/assets/skins/sparkorb/icons.png.ogt +7 -0
  37. package/archetypes/idle/assets/skins/sparkorb/skin.ogt +113 -0
  38. package/archetypes/idle/assets/sprites/glow.png +0 -0
  39. package/archetypes/idle/assets/sprites/glow.png.ogt +8 -0
  40. package/archetypes/idle/assets/sprites/mote-crystal.png +0 -0
  41. package/archetypes/idle/assets/sprites/mote-crystal.png.ogt +7 -0
  42. package/archetypes/idle/assets/sprites/mote-ember.png +0 -0
  43. package/archetypes/idle/assets/sprites/mote-ember.png.ogt +7 -0
  44. package/archetypes/idle/assets/sprites/mote-star.png +0 -0
  45. package/archetypes/idle/assets/sprites/mote-star.png.ogt +7 -0
  46. package/archetypes/idle/assets/sprites/mote-storm.png +0 -0
  47. package/archetypes/idle/assets/sprites/mote-storm.png.ogt +7 -0
  48. package/archetypes/idle/assets/sprites/orb.png +0 -0
  49. package/archetypes/idle/assets/sprites/orb.png.ogt +7 -0
  50. package/archetypes/idle/assets/sprites/spark.png +0 -0
  51. package/archetypes/idle/assets/sprites/spark.png.ogt +7 -0
  52. package/archetypes/idle/assets/strings/en.ogt +62 -0
  53. package/archetypes/idle/assets/strings/zh-CN.ogt +62 -0
  54. package/archetypes/idle/gitattributes +22 -0
  55. package/archetypes/idle/gitignore +8 -0
  56. package/archetypes/idle/host.d.ts +46 -0
  57. package/archetypes/idle/package.json +18 -0
  58. package/archetypes/idle/project.ogt +14 -0
  59. package/archetypes/idle/references/metal/card.png +0 -0
  60. package/archetypes/idle/references/metal/orbit.png +0 -0
  61. package/archetypes/idle/references/metal/title.png +0 -0
  62. package/archetypes/idle/src/components.ts +139 -0
  63. package/archetypes/idle/src/main.ts +247 -0
  64. package/archetypes/idle/src/numbers.ts +126 -0
  65. package/archetypes/idle/src/systems.ts +340 -0
  66. package/archetypes/idle/src/ui.ts +412 -0
  67. package/archetypes/idle/tests/away.og.ts +42 -0
  68. package/archetypes/idle/tests/flow.og.ts +57 -0
  69. package/archetypes/idle/tests/frames.og.ts +33 -0
  70. package/archetypes/idle/tests/frames.ts +134 -0
  71. package/archetypes/idle/tests/lib.ts +74 -0
  72. package/archetypes/idle/tests/milestone.og.ts +32 -0
  73. package/archetypes/idle/tests/nova.og.ts +52 -0
  74. package/archetypes/idle/tests/offline.og.ts +60 -0
  75. package/archetypes/idle/tests/pad.og.ts +55 -0
  76. package/archetypes/idle/tests/pause.og.ts +68 -0
  77. package/archetypes/idle/tests/phone.og.ts +50 -0
  78. package/archetypes/idle/tests/replay.og.ts +28 -0
  79. package/archetypes/idle/tools/make-assets.py +448 -0
  80. package/archetypes/idle/tools/oglite.py +285 -0
  81. package/archetypes/idle/tsconfig.json +17 -0
  82. package/archetypes/match3/.origin/setup +14 -0
  83. package/archetypes/match3/LICENSES.md +7 -0
  84. package/archetypes/match3/README.md +205 -0
  85. package/archetypes/match3/assets/audio/cascade.wav +0 -0
  86. package/archetypes/match3/assets/audio/clear.wav +0 -0
  87. package/archetypes/match3/assets/audio/lost.wav +0 -0
  88. package/archetypes/match3/assets/audio/refused.wav +0 -0
  89. package/archetypes/match3/assets/audio/select.wav +0 -0
  90. package/archetypes/match3/assets/audio/swap.wav +0 -0
  91. package/archetypes/match3/assets/audio/won.wav +0 -0
  92. package/archetypes/match3/assets/fonts/OFL.txt +93 -0
  93. package/archetypes/match3/assets/fonts/ui.ttf +0 -0
  94. package/archetypes/match3/assets/fonts/ui.ttf.ogt +11 -0
  95. package/archetypes/match3/assets/scenes/main.ogt +24 -0
  96. package/archetypes/match3/assets/sprites/backdrop.png +0 -0
  97. package/archetypes/match3/assets/sprites/board.ogt +21 -0
  98. package/archetypes/match3/assets/sprites/cells.ogt +7 -0
  99. package/archetypes/match3/assets/sprites/cells.png +0 -0
  100. package/archetypes/match3/assets/sprites/cells.png.ogt +7 -0
  101. package/archetypes/match3/assets/sprites/gems.ogt +75 -0
  102. package/archetypes/match3/assets/sprites/gems.png +0 -0
  103. package/archetypes/match3/assets/sprites/panel.png +0 -0
  104. package/archetypes/match3/assets/sprites/ring.png +0 -0
  105. package/archetypes/match3/gitattributes +22 -0
  106. package/archetypes/match3/gitignore +8 -0
  107. package/archetypes/match3/host.d.ts +46 -0
  108. package/archetypes/match3/package.json +18 -0
  109. package/archetypes/match3/project.ogt +18 -0
  110. package/archetypes/match3/references/metal/cascade.png +0 -0
  111. package/archetypes/match3/references/metal/clearing.png +0 -0
  112. package/archetypes/match3/references/metal/dealt.png +0 -0
  113. package/archetypes/match3/src/art.ts +54 -0
  114. package/archetypes/match3/src/board.ts +112 -0
  115. package/archetypes/match3/src/components.ts +95 -0
  116. package/archetypes/match3/src/layout.ts +25 -0
  117. package/archetypes/match3/src/main.ts +199 -0
  118. package/archetypes/match3/src/systems.ts +631 -0
  119. package/archetypes/match3/tests/drag.og.ts +31 -0
  120. package/archetypes/match3/tests/endings.og.ts +40 -0
  121. package/archetypes/match3/tests/frames.og.ts +49 -0
  122. package/archetypes/match3/tests/frames.ts +134 -0
  123. package/archetypes/match3/tests/hidpi.og.ts +41 -0
  124. package/archetypes/match3/tests/lib.ts +64 -0
  125. package/archetypes/match3/tests/replay.og.ts +28 -0
  126. package/archetypes/match3/tests/sounds.og.ts +50 -0
  127. package/archetypes/match3/tests/swap.og.ts +67 -0
  128. package/archetypes/match3/tools/make-assets.py +404 -0
  129. package/archetypes/match3/tools/oglite.py +285 -0
  130. package/archetypes/match3/tsconfig.json +17 -0
  131. package/archetypes/rhythm/.origin/setup +14 -0
  132. package/archetypes/rhythm/LICENSES.md +13 -0
  133. package/archetypes/rhythm/README.md +235 -0
  134. package/archetypes/rhythm/assets/audio/bass0.wav +0 -0
  135. package/archetypes/rhythm/assets/audio/bass1.wav +0 -0
  136. package/archetypes/rhythm/assets/audio/bass2.wav +0 -0
  137. package/archetypes/rhythm/assets/audio/bass3.wav +0 -0
  138. package/archetypes/rhythm/assets/audio/chords0.wav +0 -0
  139. package/archetypes/rhythm/assets/audio/chords1.wav +0 -0
  140. package/archetypes/rhythm/assets/audio/chords2.wav +0 -0
  141. package/archetypes/rhythm/assets/audio/chords3.wav +0 -0
  142. package/archetypes/rhythm/assets/audio/clear.wav +0 -0
  143. package/archetypes/rhythm/assets/audio/click.wav +0 -0
  144. package/archetypes/rhythm/assets/audio/drums.wav +0 -0
  145. package/archetypes/rhythm/assets/audio/fail.wav +0 -0
  146. package/archetypes/rhythm/assets/audio/leadA.wav +0 -0
  147. package/archetypes/rhythm/assets/audio/leadB.wav +0 -0
  148. package/archetypes/rhythm/assets/audio/miss.wav +0 -0
  149. package/archetypes/rhythm/assets/audio/tap0.wav +0 -0
  150. package/archetypes/rhythm/assets/audio/tap1.wav +0 -0
  151. package/archetypes/rhythm/assets/audio/tap2.wav +0 -0
  152. package/archetypes/rhythm/assets/audio/ui-close.ogt +9 -0
  153. package/archetypes/rhythm/assets/audio/ui-error.ogt +9 -0
  154. package/archetypes/rhythm/assets/audio/ui-open.ogt +9 -0
  155. package/archetypes/rhythm/assets/audio/ui-press.ogt +9 -0
  156. package/archetypes/rhythm/assets/audio/ui-success.ogt +9 -0
  157. package/archetypes/rhythm/assets/charts/neon-pulse.ogt +919 -0
  158. package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf +0 -0
  159. package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
  160. package/archetypes/rhythm/assets/fonts/OFL.txt +93 -0
  161. package/archetypes/rhythm/assets/fx/hit.ogt +22 -0
  162. package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf +0 -0
  163. package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf.ogt +9 -0
  164. package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf +0 -0
  165. package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf.ogt +9 -0
  166. package/archetypes/rhythm/assets/skins/lanebeat/LICENSES.md +17 -0
  167. package/archetypes/rhythm/assets/skins/lanebeat/OFL.txt +95 -0
  168. package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf +0 -0
  169. package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf.ogt +9 -0
  170. package/archetypes/rhythm/assets/skins/lanebeat/atlas.png +0 -0
  171. package/archetypes/rhythm/assets/skins/lanebeat/atlas.png.ogt +7 -0
  172. package/archetypes/rhythm/assets/skins/lanebeat/icons.png +0 -0
  173. package/archetypes/rhythm/assets/skins/lanebeat/icons.png.ogt +7 -0
  174. package/archetypes/rhythm/assets/skins/lanebeat/skin.ogt +110 -0
  175. package/archetypes/rhythm/assets/sprites/lane.png +0 -0
  176. package/archetypes/rhythm/assets/sprites/lane.png.ogt +7 -0
  177. package/archetypes/rhythm/assets/sprites/note.png +0 -0
  178. package/archetypes/rhythm/assets/sprites/note.png.ogt +7 -0
  179. package/archetypes/rhythm/assets/sprites/ring.png +0 -0
  180. package/archetypes/rhythm/assets/sprites/ring.png.ogt +7 -0
  181. package/archetypes/rhythm/assets/sprites/spark.png +0 -0
  182. package/archetypes/rhythm/assets/sprites/spark.png.ogt +7 -0
  183. package/archetypes/rhythm/assets/strings/en.ogt +61 -0
  184. package/archetypes/rhythm/assets/strings/zh-CN.ogt +61 -0
  185. package/archetypes/rhythm/gitattributes +22 -0
  186. package/archetypes/rhythm/gitignore +8 -0
  187. package/archetypes/rhythm/host.d.ts +46 -0
  188. package/archetypes/rhythm/package.json +18 -0
  189. package/archetypes/rhythm/project.ogt +14 -0
  190. package/archetypes/rhythm/references/metal/highway.png +0 -0
  191. package/archetypes/rhythm/references/metal/results.png +0 -0
  192. package/archetypes/rhythm/references/metal/title.png +0 -0
  193. package/archetypes/rhythm/src/components.ts +147 -0
  194. package/archetypes/rhythm/src/main.ts +202 -0
  195. package/archetypes/rhythm/src/systems.ts +490 -0
  196. package/archetypes/rhythm/src/ui.ts +421 -0
  197. package/archetypes/rhythm/tests/band.og.ts +45 -0
  198. package/archetypes/rhythm/tests/calibrate.og.ts +49 -0
  199. package/archetypes/rhythm/tests/clear.og.ts +51 -0
  200. package/archetypes/rhythm/tests/fail.og.ts +40 -0
  201. package/archetypes/rhythm/tests/flow.og.ts +54 -0
  202. package/archetypes/rhythm/tests/frames.og.ts +35 -0
  203. package/archetypes/rhythm/tests/frames.ts +134 -0
  204. package/archetypes/rhythm/tests/lib.ts +123 -0
  205. package/archetypes/rhythm/tests/pad.og.ts +41 -0
  206. package/archetypes/rhythm/tests/pause.og.ts +77 -0
  207. package/archetypes/rhythm/tests/phone.og.ts +44 -0
  208. package/archetypes/rhythm/tests/replay.og.ts +27 -0
  209. package/archetypes/rhythm/tests/timing.og.ts +37 -0
  210. package/archetypes/rhythm/tools/make-assets.py +383 -0
  211. package/archetypes/rhythm/tools/oglite.py +285 -0
  212. package/archetypes/rhythm/tsconfig.json +17 -0
  213. package/archetypes/roguelike/.origin/setup +14 -0
  214. package/archetypes/roguelike/LICENSES.md +14 -0
  215. package/archetypes/roguelike/README.md +256 -0
  216. package/archetypes/roguelike/assets/ai/foe.ogt +28 -0
  217. package/archetypes/roguelike/assets/ai/guardian.ogt +26 -0
  218. package/archetypes/roguelike/assets/audio/bump.wav +0 -0
  219. package/archetypes/roguelike/assets/audio/drink.wav +0 -0
  220. package/archetypes/roguelike/assets/audio/fall.wav +0 -0
  221. package/archetypes/roguelike/assets/audio/hit.wav +0 -0
  222. package/archetypes/roguelike/assets/audio/hurt.wav +0 -0
  223. package/archetypes/roguelike/assets/audio/item.wav +0 -0
  224. package/archetypes/roguelike/assets/audio/kill.wav +0 -0
  225. package/archetypes/roguelike/assets/audio/pickup.wav +0 -0
  226. package/archetypes/roguelike/assets/audio/roar.wav +0 -0
  227. package/archetypes/roguelike/assets/audio/stairs.wav +0 -0
  228. package/archetypes/roguelike/assets/audio/step.ogt +9 -0
  229. package/archetypes/roguelike/assets/audio/telegraph.wav +0 -0
  230. package/archetypes/roguelike/assets/audio/tomb.wav +0 -0
  231. package/archetypes/roguelike/assets/audio/ui-close.ogt +9 -0
  232. package/archetypes/roguelike/assets/audio/ui-error.ogt +9 -0
  233. package/archetypes/roguelike/assets/audio/ui-open.ogt +9 -0
  234. package/archetypes/roguelike/assets/audio/ui-press.ogt +9 -0
  235. package/archetypes/roguelike/assets/audio/ui-success.ogt +9 -0
  236. package/archetypes/roguelike/assets/audio/won.wav +0 -0
  237. package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf +0 -0
  238. package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
  239. package/archetypes/roguelike/assets/fonts/OFL.txt +93 -0
  240. package/archetypes/roguelike/assets/maps/dive.ogt +106 -0
  241. package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf +0 -0
  242. package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf.ogt +9 -0
  243. package/archetypes/roguelike/assets/skins/lampdive/LICENSES.md +16 -0
  244. package/archetypes/roguelike/assets/skins/lampdive/OFL.txt +93 -0
  245. package/archetypes/roguelike/assets/skins/lampdive/atlas.png +0 -0
  246. package/archetypes/roguelike/assets/skins/lampdive/atlas.png.ogt +7 -0
  247. package/archetypes/roguelike/assets/skins/lampdive/icons.png +0 -0
  248. package/archetypes/roguelike/assets/skins/lampdive/icons.png.ogt +7 -0
  249. package/archetypes/roguelike/assets/skins/lampdive/skin.ogt +115 -0
  250. package/archetypes/roguelike/assets/sprites/dive.ogt +50 -0
  251. package/archetypes/roguelike/assets/sprites/dive.png +0 -0
  252. package/archetypes/roguelike/assets/sprites/dive.png.ogt +7 -0
  253. package/archetypes/roguelike/assets/sprites/glow.png +0 -0
  254. package/archetypes/roguelike/assets/sprites/glow.png.ogt +7 -0
  255. package/archetypes/roguelike/assets/strings/en.ogt +57 -0
  256. package/archetypes/roguelike/assets/strings/zh-CN.ogt +57 -0
  257. package/archetypes/roguelike/assets/tiles/tomb.ogt +31 -0
  258. package/archetypes/roguelike/assets/tiles/tomb.png +0 -0
  259. package/archetypes/roguelike/assets/tiles/tomb.png.ogt +7 -0
  260. package/archetypes/roguelike/gitattributes +22 -0
  261. package/archetypes/roguelike/gitignore +8 -0
  262. package/archetypes/roguelike/host.d.ts +46 -0
  263. package/archetypes/roguelike/package.json +18 -0
  264. package/archetypes/roguelike/project.ogt +14 -0
  265. package/archetypes/roguelike/references/metal/dive.png +0 -0
  266. package/archetypes/roguelike/references/metal/over.png +0 -0
  267. package/archetypes/roguelike/references/metal/title.png +0 -0
  268. package/archetypes/roguelike/src/art.ts +23 -0
  269. package/archetypes/roguelike/src/components.ts +169 -0
  270. package/archetypes/roguelike/src/dive.ts +144 -0
  271. package/archetypes/roguelike/src/foes.ts +174 -0
  272. package/archetypes/roguelike/src/main.ts +258 -0
  273. package/archetypes/roguelike/src/systems.ts +620 -0
  274. package/archetypes/roguelike/src/ui.ts +350 -0
  275. package/archetypes/roguelike/tests/flow.og.ts +54 -0
  276. package/archetypes/roguelike/tests/foes.og.ts +45 -0
  277. package/archetypes/roguelike/tests/frames.og.ts +32 -0
  278. package/archetypes/roguelike/tests/frames.ts +134 -0
  279. package/archetypes/roguelike/tests/lib.ts +83 -0
  280. package/archetypes/roguelike/tests/lose.og.ts +44 -0
  281. package/archetypes/roguelike/tests/pad.og.ts +54 -0
  282. package/archetypes/roguelike/tests/pause.og.ts +59 -0
  283. package/archetypes/roguelike/tests/phone.og.ts +54 -0
  284. package/archetypes/roguelike/tests/replay.og.ts +24 -0
  285. package/archetypes/roguelike/tests/win.og.ts +60 -0
  286. package/archetypes/roguelike/tools/make-assets.py +518 -0
  287. package/archetypes/roguelike/tools/oglite.py +285 -0
  288. package/archetypes/roguelike/tsconfig.json +17 -0
  289. package/archetypes/runner/.origin/setup +14 -0
  290. package/archetypes/runner/LICENSES.md +10 -0
  291. package/archetypes/runner/README.md +225 -0
  292. package/archetypes/runner/assets/audio/coin.wav +0 -0
  293. package/archetypes/runner/assets/audio/crash.wav +0 -0
  294. package/archetypes/runner/assets/audio/hold.ogt +9 -0
  295. package/archetypes/runner/assets/audio/jump.wav +0 -0
  296. package/archetypes/runner/assets/audio/start.wav +0 -0
  297. package/archetypes/runner/assets/fonts/OFL.txt +93 -0
  298. package/archetypes/runner/assets/fonts/ui.ttf +0 -0
  299. package/archetypes/runner/assets/fonts/ui.ttf.ogt +11 -0
  300. package/archetypes/runner/assets/maps/far.ogt +25 -0
  301. package/archetypes/runner/assets/maps/near.ogt +25 -0
  302. package/archetypes/runner/assets/maps/roofs.ogt +19 -0
  303. package/archetypes/runner/assets/scenes/main.ogt +35 -0
  304. package/archetypes/runner/assets/sprites/coin.ogt +11 -0
  305. package/archetypes/runner/assets/sprites/coin.png +0 -0
  306. package/archetypes/runner/assets/sprites/coin.png.ogt +7 -0
  307. package/archetypes/runner/assets/sprites/courier.ogt +32 -0
  308. package/archetypes/runner/assets/sprites/courier.png +0 -0
  309. package/archetypes/runner/assets/sprites/courier.png.ogt +7 -0
  310. package/archetypes/runner/assets/sprites/props.ogt +21 -0
  311. package/archetypes/runner/assets/sprites/props.png +0 -0
  312. package/archetypes/runner/assets/sprites/props.png.ogt +7 -0
  313. package/archetypes/runner/assets/sprites/sky.png +0 -0
  314. package/archetypes/runner/assets/sprites/sky.png.ogt +7 -0
  315. package/archetypes/runner/assets/tiles/city.ogt +18 -0
  316. package/archetypes/runner/assets/tiles/city.png +0 -0
  317. package/archetypes/runner/assets/tiles/city.png.ogt +7 -0
  318. package/archetypes/runner/gitattributes +22 -0
  319. package/archetypes/runner/gitignore +8 -0
  320. package/archetypes/runner/host.d.ts +46 -0
  321. package/archetypes/runner/package.json +18 -0
  322. package/archetypes/runner/project.ogt +18 -0
  323. package/archetypes/runner/references/metal/frames-coin.png +0 -0
  324. package/archetypes/runner/references/metal/frames-crash.png +0 -0
  325. package/archetypes/runner/references/metal/frames-title.png +0 -0
  326. package/archetypes/runner/src/art.ts +57 -0
  327. package/archetypes/runner/src/components.ts +94 -0
  328. package/archetypes/runner/src/main.ts +241 -0
  329. package/archetypes/runner/src/systems.ts +553 -0
  330. package/archetypes/runner/tests/coins.og.ts +30 -0
  331. package/archetypes/runner/tests/flow.og.ts +72 -0
  332. package/archetypes/runner/tests/frames.og.ts +37 -0
  333. package/archetypes/runner/tests/frames.ts +134 -0
  334. package/archetypes/runner/tests/jump.og.ts +44 -0
  335. package/archetypes/runner/tests/lib.ts +42 -0
  336. package/archetypes/runner/tests/records.og.ts +43 -0
  337. package/archetypes/runner/tests/replay.og.ts +36 -0
  338. package/archetypes/runner/tests/sounds.og.ts +43 -0
  339. package/archetypes/runner/tests/tune.og.ts +36 -0
  340. package/archetypes/runner/tools/make-assets.py +603 -0
  341. package/archetypes/runner/tools/oglite.py +285 -0
  342. package/archetypes/runner/tsconfig.json +17 -0
  343. package/archetypes/third-person/.origin/setup +14 -0
  344. package/archetypes/third-person/LICENSES.md +23 -0
  345. package/archetypes/third-person/README.md +249 -0
  346. package/archetypes/third-person/assets/ai/sentinel-east.ogt +69 -0
  347. package/archetypes/third-person/assets/ai/sentinel-north.ogt +69 -0
  348. package/archetypes/third-person/assets/ai/sentinel-west.ogt +69 -0
  349. package/archetypes/third-person/assets/anims/humanoid-clips.glb +0 -0
  350. package/archetypes/third-person/assets/audio/alarm.wav +0 -0
  351. package/archetypes/third-person/assets/audio/dawn.wav +0 -0
  352. package/archetypes/third-person/assets/audio/grab.wav +0 -0
  353. package/archetypes/third-person/assets/audio/ignite.wav +0 -0
  354. package/archetypes/third-person/assets/audio/jump.wav +0 -0
  355. package/archetypes/third-person/assets/audio/night.wav +0 -0
  356. package/archetypes/third-person/assets/audio/splash.wav +0 -0
  357. package/archetypes/third-person/assets/audio/step.ogt +9 -0
  358. package/archetypes/third-person/assets/audio/ui-close.ogt +9 -0
  359. package/archetypes/third-person/assets/audio/ui-error.ogt +9 -0
  360. package/archetypes/third-person/assets/audio/ui-open.ogt +9 -0
  361. package/archetypes/third-person/assets/audio/ui-press.ogt +9 -0
  362. package/archetypes/third-person/assets/audio/ui-success.ogt +9 -0
  363. package/archetypes/third-person/assets/audio/vale.wav +0 -0
  364. package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf +0 -0
  365. package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
  366. package/archetypes/third-person/assets/fonts/OFL.txt +93 -0
  367. package/archetypes/third-person/assets/fx/burst.ogt +25 -0
  368. package/archetypes/third-person/assets/fx/fire.ogt +24 -0
  369. package/archetypes/third-person/assets/fx/smoke.ogt +24 -0
  370. package/archetypes/third-person/assets/fx/splash.ogt +24 -0
  371. package/archetypes/third-person/assets/luts/teal-orange.cube +4917 -0
  372. package/archetypes/third-person/assets/models/stout.glb +0 -0
  373. package/archetypes/third-person/assets/models/strider.glb +0 -0
  374. package/archetypes/third-person/assets/models/strider.glb.ogt +9 -0
  375. package/archetypes/third-person/assets/nav/vale.ogt +43 -0
  376. package/archetypes/third-person/assets/rigs/strider.ogt +6 -0
  377. package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf +0 -0
  378. package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf.ogt +9 -0
  379. package/archetypes/third-person/assets/skins/ember/LICENSES.md +13 -0
  380. package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf +0 -0
  381. package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf.ogt +9 -0
  382. package/archetypes/third-person/assets/skins/ember/OFL.txt +94 -0
  383. package/archetypes/third-person/assets/skins/ember/atlas.png +0 -0
  384. package/archetypes/third-person/assets/skins/ember/atlas.png.ogt +7 -0
  385. package/archetypes/third-person/assets/skins/ember/icons.png +0 -0
  386. package/archetypes/third-person/assets/skins/ember/icons.png.ogt +7 -0
  387. package/archetypes/third-person/assets/skins/ember/skin.ogt +109 -0
  388. package/archetypes/third-person/assets/strings/en.ogt +47 -0
  389. package/archetypes/third-person/assets/strings/zh-CN.ogt +47 -0
  390. package/archetypes/third-person/assets/terrain/vale-control.png +0 -0
  391. package/archetypes/third-person/assets/terrain/vale.ogt +42 -0
  392. package/archetypes/third-person/assets/terrain/vale.png +0 -0
  393. package/archetypes/third-person/assets/textures/decals.png +0 -0
  394. package/archetypes/third-person/assets/textures/flame.png +0 -0
  395. package/archetypes/third-person/assets/textures/smoke.png +0 -0
  396. package/archetypes/third-person/assets/textures/spark.png +0 -0
  397. package/archetypes/third-person/assets/textures/terrain-grass.png +0 -0
  398. package/archetypes/third-person/assets/textures/terrain-path.png +0 -0
  399. package/archetypes/third-person/assets/textures/terrain-rock.png +0 -0
  400. package/archetypes/third-person/assets/textures/terrain-sand.png +0 -0
  401. package/archetypes/third-person/gitattributes +22 -0
  402. package/archetypes/third-person/gitignore +8 -0
  403. package/archetypes/third-person/host.d.ts +46 -0
  404. package/archetypes/third-person/package.json +18 -0
  405. package/archetypes/third-person/project.ogt +14 -0
  406. package/archetypes/third-person/references/metal/beacon.png +0 -0
  407. package/archetypes/third-person/references/metal/dawn.png +0 -0
  408. package/archetypes/third-person/references/metal/title.png +0 -0
  409. package/archetypes/third-person/references/webgl2/beacon.png +0 -0
  410. package/archetypes/third-person/src/anim.ts +87 -0
  411. package/archetypes/third-person/src/beacons.ts +80 -0
  412. package/archetypes/third-person/src/components.ts +148 -0
  413. package/archetypes/third-person/src/hero.ts +293 -0
  414. package/archetypes/third-person/src/main.ts +164 -0
  415. package/archetypes/third-person/src/sentinels.ts +159 -0
  416. package/archetypes/third-person/src/sound.ts +66 -0
  417. package/archetypes/third-person/src/ui.ts +451 -0
  418. package/archetypes/third-person/src/world.ts +212 -0
  419. package/archetypes/third-person/tests/flow.og.ts +70 -0
  420. package/archetypes/third-person/tests/frames.og.ts +35 -0
  421. package/archetypes/third-person/tests/frames.ts +134 -0
  422. package/archetypes/third-person/tests/hitstop.og.ts +26 -0
  423. package/archetypes/third-person/tests/lib.ts +87 -0
  424. package/archetypes/third-person/tests/lose.og.ts +44 -0
  425. package/archetypes/third-person/tests/pause.og.ts +64 -0
  426. package/archetypes/third-person/tests/phone.og.ts +65 -0
  427. package/archetypes/third-person/tests/replay.og.ts +32 -0
  428. package/archetypes/third-person/tests/restart.og.ts +28 -0
  429. package/archetypes/third-person/tests/sentinels.og.ts +48 -0
  430. package/archetypes/third-person/tests/win.og.ts +62 -0
  431. package/archetypes/third-person/tools/make-assets.py +353 -0
  432. package/archetypes/third-person/tools/oglite.py +285 -0
  433. package/archetypes/third-person/tsconfig.json +17 -0
  434. package/archetypes/topdown/.origin/setup +14 -0
  435. package/archetypes/topdown/LICENSES.md +10 -0
  436. package/archetypes/topdown/README.md +235 -0
  437. package/archetypes/topdown/assets/ai/slime.ogt +49 -0
  438. package/archetypes/topdown/assets/audio/coin.wav +0 -0
  439. package/archetypes/topdown/assets/audio/door.wav +0 -0
  440. package/archetypes/topdown/assets/audio/hit.wav +0 -0
  441. package/archetypes/topdown/assets/audio/hurt.wav +0 -0
  442. package/archetypes/topdown/assets/audio/key.wav +0 -0
  443. package/archetypes/topdown/assets/audio/step.ogt +9 -0
  444. package/archetypes/topdown/assets/fonts/OFL.txt +93 -0
  445. package/archetypes/topdown/assets/fonts/ui.ttf +0 -0
  446. package/archetypes/topdown/assets/fonts/ui.ttf.ogt +9 -0
  447. package/archetypes/topdown/assets/maps/tomb.tmj +2183 -0
  448. package/archetypes/topdown/assets/sprites/hero.ogt +58 -0
  449. package/archetypes/topdown/assets/sprites/hero.png +0 -0
  450. package/archetypes/topdown/assets/sprites/hero.png.ogt +7 -0
  451. package/archetypes/topdown/assets/sprites/items.ogt +15 -0
  452. package/archetypes/topdown/assets/sprites/items.png +0 -0
  453. package/archetypes/topdown/assets/sprites/items.png.ogt +7 -0
  454. package/archetypes/topdown/assets/sprites/slime.ogt +17 -0
  455. package/archetypes/topdown/assets/sprites/slime.png +0 -0
  456. package/archetypes/topdown/assets/sprites/slime.png.ogt +7 -0
  457. package/archetypes/topdown/assets/tiles/dungeon.ogt +65 -0
  458. package/archetypes/topdown/assets/tiles/dungeon.png +0 -0
  459. package/archetypes/topdown/assets/tiles/dungeon.png.ogt +7 -0
  460. package/archetypes/topdown/gitattributes +22 -0
  461. package/archetypes/topdown/gitignore +8 -0
  462. package/archetypes/topdown/host.d.ts +46 -0
  463. package/archetypes/topdown/package.json +18 -0
  464. package/archetypes/topdown/project.ogt +14 -0
  465. package/archetypes/topdown/references/metal/crit.png +0 -0
  466. package/archetypes/topdown/references/metal/dash.png +0 -0
  467. package/archetypes/topdown/references/metal/east.png +0 -0
  468. package/archetypes/topdown/references/metal/start.png +0 -0
  469. package/archetypes/topdown/src/art.ts +48 -0
  470. package/archetypes/topdown/src/components.ts +128 -0
  471. package/archetypes/topdown/src/main.ts +275 -0
  472. package/archetypes/topdown/src/systems.ts +503 -0
  473. package/archetypes/topdown/tests/ai.og.ts +64 -0
  474. package/archetypes/topdown/tests/combat.og.ts +72 -0
  475. package/archetypes/topdown/tests/door.og.ts +62 -0
  476. package/archetypes/topdown/tests/flee.og.ts +49 -0
  477. package/archetypes/topdown/tests/frames.og.ts +45 -0
  478. package/archetypes/topdown/tests/frames.ts +134 -0
  479. package/archetypes/topdown/tests/lib.ts +71 -0
  480. package/archetypes/topdown/tests/replay.og.ts +40 -0
  481. package/archetypes/topdown/tests/walk.og.ts +46 -0
  482. package/archetypes/topdown/tools/make-assets.py +623 -0
  483. package/archetypes/topdown/tools/oglite.py +285 -0
  484. package/archetypes/topdown/tsconfig.json +17 -0
  485. package/archetypes/tower-defense/.origin/setup +14 -0
  486. package/archetypes/tower-defense/LICENSES.md +15 -0
  487. package/archetypes/tower-defense/README.md +240 -0
  488. package/archetypes/tower-defense/assets/ai/plant.ogt +16 -0
  489. package/archetypes/tower-defense/assets/audio/clear.wav +0 -0
  490. package/archetypes/tower-defense/assets/audio/frost.wav +0 -0
  491. package/archetypes/tower-defense/assets/audio/leak.wav +0 -0
  492. package/archetypes/tower-defense/assets/audio/lost.wav +0 -0
  493. package/archetypes/tower-defense/assets/audio/night.wav +0 -0
  494. package/archetypes/tower-defense/assets/audio/plant.wav +0 -0
  495. package/archetypes/tower-defense/assets/audio/pop.wav +0 -0
  496. package/archetypes/tower-defense/assets/audio/thorn.wav +0 -0
  497. package/archetypes/tower-defense/assets/audio/ui-close.ogt +9 -0
  498. package/archetypes/tower-defense/assets/audio/ui-error.ogt +9 -0
  499. package/archetypes/tower-defense/assets/audio/ui-open.ogt +9 -0
  500. package/archetypes/tower-defense/assets/audio/ui-press.ogt +9 -0
  501. package/archetypes/tower-defense/assets/audio/ui-success.ogt +9 -0
  502. package/archetypes/tower-defense/assets/audio/wave.wav +0 -0
  503. package/archetypes/tower-defense/assets/audio/won.wav +0 -0
  504. package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf +0 -0
  505. package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
  506. package/archetypes/tower-defense/assets/fonts/OFL.txt +93 -0
  507. package/archetypes/tower-defense/assets/fx/fireflies.ogt +21 -0
  508. package/archetypes/tower-defense/assets/fx/pop.ogt +21 -0
  509. package/archetypes/tower-defense/assets/maps/garden.ogt +91 -0
  510. package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf +0 -0
  511. package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf.ogt +9 -0
  512. package/archetypes/tower-defense/assets/skins/pathguard/LICENSES.md +13 -0
  513. package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf +0 -0
  514. package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf.ogt +9 -0
  515. package/archetypes/tower-defense/assets/skins/pathguard/OFL.txt +94 -0
  516. package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
  517. package/archetypes/tower-defense/assets/skins/pathguard/atlas.png.ogt +7 -0
  518. package/archetypes/tower-defense/assets/skins/pathguard/icons.png +0 -0
  519. package/archetypes/tower-defense/assets/skins/pathguard/icons.png.ogt +7 -0
  520. package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +111 -0
  521. package/archetypes/tower-defense/assets/sprites/garden.ogt +66 -0
  522. package/archetypes/tower-defense/assets/sprites/garden.png +0 -0
  523. package/archetypes/tower-defense/assets/sprites/garden.png.ogt +7 -0
  524. package/archetypes/tower-defense/assets/sprites/glow.png +0 -0
  525. package/archetypes/tower-defense/assets/sprites/glow.png.ogt +7 -0
  526. package/archetypes/tower-defense/assets/sprites/ring.png +0 -0
  527. package/archetypes/tower-defense/assets/sprites/ring.png.ogt +7 -0
  528. package/archetypes/tower-defense/assets/sprites/spark.png +0 -0
  529. package/archetypes/tower-defense/assets/sprites/spark.png.ogt +7 -0
  530. package/archetypes/tower-defense/assets/strings/en.ogt +47 -0
  531. package/archetypes/tower-defense/assets/strings/zh-CN.ogt +47 -0
  532. package/archetypes/tower-defense/assets/tiles/garden.ogt +10 -0
  533. package/archetypes/tower-defense/assets/tiles/garden.png +0 -0
  534. package/archetypes/tower-defense/assets/tiles/garden.png.ogt +7 -0
  535. package/archetypes/tower-defense/gitattributes +22 -0
  536. package/archetypes/tower-defense/gitignore +8 -0
  537. package/archetypes/tower-defense/host.d.ts +46 -0
  538. package/archetypes/tower-defense/package.json +18 -0
  539. package/archetypes/tower-defense/project.ogt +14 -0
  540. package/archetypes/tower-defense/references/metal/end.png +0 -0
  541. package/archetypes/tower-defense/references/metal/title.png +0 -0
  542. package/archetypes/tower-defense/references/metal/wave.png +0 -0
  543. package/archetypes/tower-defense/src/art.ts +24 -0
  544. package/archetypes/tower-defense/src/components.ts +156 -0
  545. package/archetypes/tower-defense/src/main.ts +253 -0
  546. package/archetypes/tower-defense/src/systems.ts +658 -0
  547. package/archetypes/tower-defense/src/ui.ts +382 -0
  548. package/archetypes/tower-defense/tests/flow.og.ts +56 -0
  549. package/archetypes/tower-defense/tests/frames.og.ts +31 -0
  550. package/archetypes/tower-defense/tests/frames.ts +134 -0
  551. package/archetypes/tower-defense/tests/lib.ts +81 -0
  552. package/archetypes/tower-defense/tests/lose.og.ts +40 -0
  553. package/archetypes/tower-defense/tests/maze.og.ts +53 -0
  554. package/archetypes/tower-defense/tests/pad.og.ts +41 -0
  555. package/archetypes/tower-defense/tests/pause.og.ts +59 -0
  556. package/archetypes/tower-defense/tests/phone.og.ts +49 -0
  557. package/archetypes/tower-defense/tests/replay.og.ts +28 -0
  558. package/archetypes/tower-defense/tests/win.og.ts +58 -0
  559. package/archetypes/tower-defense/tools/make-assets.py +640 -0
  560. package/archetypes/tower-defense/tools/oglite.py +285 -0
  561. package/archetypes/tower-defense/tsconfig.json +17 -0
  562. package/bin/create.ts +503 -0
  563. package/looks/catalogue.json +1871 -0
  564. package/package.json +31 -4
  565. package/template/.origin/setup +14 -0
  566. package/template/LICENSES.md +23 -0
  567. package/template/README.md +37 -0
  568. package/template/assets/ai/guard.ogt +48 -0
  569. package/template/assets/audio/blip.wav +0 -0
  570. package/template/assets/audio/click.ogt +9 -0
  571. package/template/assets/fonts/NotoSansSC-Regular-subset.ttf +0 -0
  572. package/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
  573. package/template/assets/fonts/OFL.txt +93 -0
  574. package/template/assets/looks/origin-game.ogt +12 -0
  575. package/template/assets/models/dropship.glb +0 -0
  576. package/template/assets/models/walker.glb +0 -0
  577. package/template/assets/nav/yard.ogt +27 -0
  578. package/template/assets/scenes/main.ogt +34 -0
  579. package/template/assets/strings/en.ogt +14 -0
  580. package/template/assets/textures/stone.png +0 -0
  581. package/template/assets/textures/stone_n.png +0 -0
  582. package/template/assets/textures/stone_n.png.ogt +7 -0
  583. package/template/gitattributes +22 -0
  584. package/template/gitignore +8 -0
  585. package/template/package.json +18 -0
  586. package/template/project.ogt +25 -0
  587. package/template/src/main.ts +246 -0
  588. package/template/tests/guard.og.ts +67 -0
  589. package/template/tests/smoke.og.ts +43 -0
  590. package/template/tsconfig.json +17 -0
@@ -0,0 +1,35 @@
1
+ // Three frames at 1280×720 compared with references/<tier>/: the title,
2
+ // the highway in bar 9 (the lead's notes on three lanes, a Perfect, the
3
+ // combo) and the results of a song cleared from bar 23. `OG_FX_CAPTURE=1
4
+ // og test .` records this tier's references.
5
+ import { playtest } from "@origingame/engine/playtest";
6
+ import { checkFrames } from "./frames.ts";
7
+ import { BEAT, chart, play, songStart, sorted } from "./lib.ts";
8
+
9
+ const FILES = ["title.png", "highway.png", "results.png"];
10
+ const notes = chart();
11
+ const t0 = songStart(1.2, 7);
12
+ const t1 = songStart(9.0, 23);
13
+ const last = notes[notes.length - 1]!.beat;
14
+
15
+ export default playtest({
16
+ name: "the title, the highway and the results match their reference frames",
17
+ size: "1280x720",
18
+ commands: sorted([
19
+ "1.0:screenshot:title.png",
20
+ "1.2:play:7",
21
+ ...play(notes, t0, 24, 33.5),
22
+ `${(t0 + 33.6 * BEAT).toFixed(3)}:screenshot:highway.png`,
23
+ "9.0:play:23",
24
+ ...play(notes, t1, 88, last + 1),
25
+ `${(t1 + last * BEAT + 2.4).toFixed(3)}:screenshot:results.png`,
26
+ `${(t1 + last * BEAT + 2.5).toFixed(3)}:quit`,
27
+ ]),
28
+ check(run, t) {
29
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
30
+ t.frame(FILES, () => {
31
+ for (const f of FILES) t.equal(run.png(f).width, 1280, `${f} is 1280 wide`);
32
+ });
33
+ checkFrames(run, t, FILES, "");
34
+ },
35
+ });
@@ -0,0 +1,134 @@
1
+ // The reference-frame check: decode the PNGs a run wrote and compare them
2
+ // with references/<tier>/ under og-tests' tolerance (a channel off by more
3
+ // than 32/255 is an outlier; fail above 0.5 % outliers or an RGB RMSE above
4
+ // 0.01). A tier without its own references compares with `metal` and says
5
+ // so. `OG_FX_CAPTURE=1 og test .` records this tier's references instead.
6
+ // (Copied from templates/fx-showcase/tests/frames.ts so the archetype stands alone.)
7
+
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { inflateSync } from "node:zlib";
12
+ import type { PlaytestChecks, PlaytestRun } from "@origingame/engine/playtest";
13
+
14
+ export interface Image {
15
+ width: number;
16
+ height: number;
17
+ /** RGBA8 rows. */
18
+ rgba: Uint8Array;
19
+ }
20
+
21
+ /** An 8-bit RGB or RGBA, non-interlaced PNG, unfiltered. */
22
+ export function decodePng(bytes: Uint8Array): Image {
23
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24
+ let at = 8;
25
+ let width = 0;
26
+ let height = 0;
27
+ let channels = 4;
28
+ const idat: Uint8Array[] = [];
29
+ while (at < bytes.length) {
30
+ const len = view.getUint32(at);
31
+ const type = String.fromCharCode(...bytes.subarray(at + 4, at + 8));
32
+ const data = bytes.subarray(at + 8, at + 8 + len);
33
+ if (type === "IHDR") {
34
+ width = view.getUint32(at + 8);
35
+ height = view.getUint32(at + 12);
36
+ const depth = data[8]!;
37
+ const color = data[9]!;
38
+ if (depth !== 8 || (color !== 6 && color !== 2) || data[12] !== 0) throw new Error(`unsupported PNG (depth ${depth}, colour ${color}, interlace ${data[12]})`);
39
+ channels = color === 6 ? 4 : 3;
40
+ } else if (type === "IDAT") idat.push(data);
41
+ else if (type === "IEND") break;
42
+ at += 12 + len;
43
+ }
44
+ const raw = inflateSync(Buffer.concat(idat));
45
+ const stride = width * channels;
46
+ const out = new Uint8Array(width * height * 4);
47
+ const prev = new Uint8Array(stride);
48
+ const cur = new Uint8Array(stride);
49
+ for (let y = 0; y < height; y++) {
50
+ const filter = raw[y * (stride + 1)]!;
51
+ const line = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
52
+ for (let i = 0; i < stride; i++) {
53
+ const a = i >= channels ? cur[i - channels]! : 0;
54
+ const b = prev[i]!;
55
+ const c = i >= channels ? prev[i - channels]! : 0;
56
+ let v = line[i]!;
57
+ if (filter === 1) v += a;
58
+ else if (filter === 2) v += b;
59
+ else if (filter === 3) v += (a + b) >> 1;
60
+ else if (filter === 4) {
61
+ const p = a + b - c;
62
+ const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
63
+ v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
64
+ }
65
+ cur[i] = v & 255;
66
+ }
67
+ for (let x = 0; x < width; x++) {
68
+ for (let k = 0; k < 4; k++) out[(y * width + x) * 4 + k] = k < channels ? cur[x * channels + k]! : 255;
69
+ }
70
+ prev.set(cur);
71
+ }
72
+ return { width, height, rgba: out };
73
+ }
74
+
75
+ /** og-tests' rule: outlier fraction and RGB RMSE between two equal-size frames. */
76
+ export function difference(a: Image, b: Image): { outliers: number; rmse: number } {
77
+ if (a.width !== b.width || a.height !== b.height) return { outliers: 1, rmse: 1 };
78
+ let outliers = 0;
79
+ let sum = 0;
80
+ const n = a.width * a.height;
81
+ for (let p = 0; p < n; p++) {
82
+ let out = false;
83
+ for (let k = 0; k < 3; k++) {
84
+ const d = Math.abs(a.rgba[p * 4 + k]! - b.rgba[p * 4 + k]!);
85
+ if (d > 32) out = true;
86
+ const f = d / 255;
87
+ sum += f * f;
88
+ }
89
+ if (out) outliers++;
90
+ }
91
+ return { outliers: outliers / n, rmse: Math.sqrt(sum / (n * 3)) };
92
+ }
93
+
94
+ /** The playtest's output directory (the bundled playtest runs from it). */
95
+ export function outDir(): string {
96
+ return dirname(fileURLToPath(import.meta.url));
97
+ }
98
+
99
+ /** The project's references/, from its og-out/test/<name>/. */
100
+ export function referencesDir(): string {
101
+ return resolve(outDir(), "..", "..", "..", "references");
102
+ }
103
+
104
+ /** Compares each screenshot with its reference (`<prefix><file>`), or records it. */
105
+ export function checkFrames(run: PlaytestRun, t: PlaytestChecks, files: readonly string[], prefix: string): void {
106
+ // Read only where there is a renderer: without one (tier `none`) each frame is
107
+ // reported "no renderer, frame unchecked" and every other check still runs.
108
+ t.frame(files, () => {
109
+ const refs = referencesDir();
110
+ t.ok(existsSync(refs) || process.env.OG_FX_CAPTURE !== undefined, `the references directory exists at ${refs}`);
111
+ const own = join(refs, run.tier);
112
+ const capture = process.env.OG_FX_CAPTURE !== undefined;
113
+ for (const f of files) {
114
+ const actual = join(outDir(), f);
115
+ // Each file on its own: this tier's reference where it has one, metal's otherwise.
116
+ const mine = join(own, prefix + f);
117
+ const ref = capture || existsSync(mine) ? mine : join(refs, "metal", prefix + f);
118
+ if (ref !== mine) process.stdout.write(` note: no ${run.tier} reference for ${prefix}${f}; comparing with metal\n`);
119
+ if (capture) {
120
+ mkdirSync(own, { recursive: true });
121
+ copyFileSync(actual, ref);
122
+ t.ok(true, `recorded ${ref}`);
123
+ continue;
124
+ }
125
+ if (!existsSync(ref)) {
126
+ t.ok(false, `${ref} is missing: record it with OG_FX_CAPTURE=1 og test .`);
127
+ continue;
128
+ }
129
+ const d = difference(decodePng(readFileSync(actual)), decodePng(readFileSync(ref)));
130
+ process.stdout.write(` ${f}: ${(d.outliers * 100).toFixed(3)} % outliers, RMSE ${d.rmse.toFixed(5)} against ${ref}\n`);
131
+ t.ok(d.outliers <= 0.005 && d.rmse <= 0.01, `${f} matches ${prefix}${f}: ${(d.outliers * 100).toFixed(3)} % outliers (≤ 0.5 %), RMSE ${d.rmse.toFixed(5)} (≤ 0.01)`);
132
+ }
133
+ });
134
+ }
@@ -0,0 +1,123 @@
1
+ // Shared readers and the chart for Three-Lane Beat's playtests.
2
+ // `get:Game:<file>` writes the play's resource, the state the game's
3
+ // `facts` (src/main.ts) report to the probe; `dump_ui` the screen;
4
+ // `dump_entity` a row; `dump_platform` the calls to og.platform. The chart
5
+ // is read from assets/charts/neon-pulse.ogt (data), so a script can press
6
+ // each lane at its notes' times.
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import type { PlaytestRun } from "@origingame/engine/playtest";
11
+
12
+ export interface Game {
13
+ phase: number;
14
+ paused: boolean;
15
+ held: boolean;
16
+ songStart: number;
17
+ pausedAt: number;
18
+ countTo: number;
19
+ score: number;
20
+ combo: number;
21
+ maxCombo: number;
22
+ perfect: number;
23
+ great: number;
24
+ good: number;
25
+ miss: number;
26
+ hits: number;
27
+ judged: number;
28
+ notes: number;
29
+ taps: number;
30
+ groove: number;
31
+ failed: boolean;
32
+ cleared: boolean;
33
+ lastJudgement: number;
34
+ lastOffsetMs: number;
35
+ calTaps: number;
36
+ calSum: number;
37
+ best: number;
38
+ newBest: boolean;
39
+ language: number;
40
+ }
41
+
42
+ /** Phase.* of src/components.ts. */
43
+ export const TITLE = 0;
44
+ export const PLAY = 1;
45
+ export const RESULTS = 2;
46
+ export const CALIBRATE = 3;
47
+
48
+ /** The Game resource from a `get:Game:<file>` step. */
49
+ export function game(run: PlaytestRun, file: string): Game {
50
+ return run.json<{ value: Game }>(file).value;
51
+ }
52
+
53
+ export interface UiElement { id: string; parent: string; kind: string; text: string; x: number; y: number; width: number; height: number }
54
+
55
+ export function ui(run: PlaytestRun, file: string): { surface: { width: number; height: number }; elements: UiElement[] } {
56
+ return run.json(file);
57
+ }
58
+
59
+ /** The text of the first element with this id (under `parent` when given). */
60
+ export function text(run: PlaytestRun, file: string, id: string, parent?: string): string | undefined {
61
+ return ui(run, file).elements.find((e) => e.id === id && (parent === undefined || e.parent === parent))?.text;
62
+ }
63
+
64
+ export interface PlatformDump {
65
+ calls: Array<{ method: string; args: Record<string, unknown>; tick: number; result?: { ok: boolean; data?: Record<string, unknown> } }>;
66
+ }
67
+
68
+ /** The chart's notes in time order: lane and beat. */
69
+ export interface ChartNote { lane: number; beat: number }
70
+
71
+ /** The project's assets/charts/neon-pulse.ogt: from the bundled playtest's directory up (og test runs it from og-out/test/<name>/, og polish from elsewhere), else from the working directory. */
72
+ function chartPath(): string {
73
+ const rel = join("assets", "charts", "neon-pulse.ogt");
74
+ for (let dir = dirname(fileURLToPath(import.meta.url)), n = 0; n < 8; dir = dirname(dir), n++) {
75
+ if (existsSync(join(dir, rel)) && existsSync(join(dir, "project.ogt"))) return join(dir, rel);
76
+ }
77
+ return resolve(process.cwd(), rel);
78
+ }
79
+
80
+ /** The chart's notes, read as data. */
81
+ export function chart(): ChartNote[] {
82
+ const textOf = readFileSync(chartPath(), "utf8");
83
+ const notes: ChartNote[] = [];
84
+ const re = /Note \{\s*index (\d+)\s*lane (\d+)\s*beat ([\d.]+)\s*\}/g;
85
+ for (let m = re.exec(textOf); m !== null; m = re.exec(textOf)) notes.push({ lane: Number(m[2]), beat: Number(m[3]) });
86
+ return notes;
87
+ }
88
+
89
+ /** The song's beat (120 BPM). */
90
+ export const BEAT = 0.5;
91
+
92
+ /**
93
+ * Where bar 1 starts for a `play` (or a Play tap's start) at time t: the
94
+ * next whole tick a quarter second on, then the four beats of the count-in
95
+ * (src/ui.ts `begin`), less the bars skipped by `play:<bar>`.
96
+ */
97
+ export function songStart(t: number, fromBar = 1): number {
98
+ return Math.ceil((Math.round(t * 60) / 60 + 0.25) * 60) / 60 + 4 * BEAT - (fromBar - 1) * 4 * BEAT;
99
+ }
100
+
101
+ /** The lanes' keys. */
102
+ export const KEYS = ["KeyA", "KeyS", "KeyD"] as const;
103
+
104
+ /** A key pressed at t (down, then up two ticks later). */
105
+ export const press = (key: string, t: number) => `${t.toFixed(4)}:key_down:${key};${(t + 0.034).toFixed(4)}:key_up:${key}`;
106
+
107
+ /** Presses for the notes in [fromBeat, toBeat), each `late` seconds after its time (a chord's lanes together). */
108
+ export function play(notes: readonly ChartNote[], start: number, fromBeat: number, toBeat: number, late = 0): string[] {
109
+ return notes.filter((n) => n.beat >= fromBeat && n.beat < toBeat).map((n) => press(KEYS[n.lane]!, start + n.beat * BEAT + late));
110
+ }
111
+
112
+ /** Commands in time order (the probe wants ascending times). */
113
+ export function sorted(cmds: readonly string[]): string {
114
+ return cmds
115
+ .flatMap((c) => c.split(";"))
116
+ .map((c) => ({ t: Number(c.slice(0, c.indexOf(":"))), c }))
117
+ .sort((a, b) => a.t - b.t)
118
+ .map((x) => x.c)
119
+ .join(";");
120
+ }
121
+
122
+ /** A tap on a UI element by its id path (pressed, released a tick later). */
123
+ export const tap = (id: string, t: number) => `${t}:touch_move_to:${id};${(t + 0.017).toFixed(3)}:touch_start;${(t + 0.05).toFixed(3)}:touch_end`;
@@ -0,0 +1,41 @@
1
+ // A gamepad alone: the d-pad and A play from the title; bar 1 and bar 2
2
+ // are hit with the d-pad (left, down, right) and the face buttons (X, A,
3
+ // B); Start pauses, the d-pad moves the menu's focus and A on Resume
4
+ // resumes.
5
+ import { playtest } from "@origingame/engine/playtest";
6
+ import { BEAT, chart, game, PLAY, songStart, sorted } from "./lib.ts";
7
+
8
+ const notes = chart();
9
+ /** A pad button pressed at t and let go 0.05 s later (the pad set aside, so the release is no input of its own). */
10
+ const pad = (button: string, t: number) => `${t.toFixed(4)}:gamepad:${button};${(t + 0.05).toFixed(4)}:gamepad:off`;
11
+ const t0 = songStart(1.1);
12
+ const DPAD = ["left", "down", "right"];
13
+ const FACE = ["x", "a", "b"];
14
+
15
+ export default playtest({
16
+ name: "the d-pad and the face buttons hit the lanes; Start pauses, A resumes",
17
+ commands: sorted([
18
+ // The title's first item holds the focus when the menu opens (og-ui §4), so A plays at once; no d-pad press first.
19
+ pad("a", 1.1),
20
+ "1.4:get:Game:started.json",
21
+ ...notes.filter((n) => n.beat < 8).map((n) => pad(n.beat < 4 ? DPAD[n.lane]! : FACE[n.lane]!, t0 + n.beat * BEAT)),
22
+ `${(t0 + 7.9 * BEAT).toFixed(3)}:get:Game:hit.json`,
23
+ pad("start", t0 + 8.2 * BEAT),
24
+ `${(t0 + 8.8 * BEAT).toFixed(3)}:get:Game:paused.json`,
25
+ pad("down", t0 + 9.0 * BEAT),
26
+ pad("up", t0 + 9.2 * BEAT),
27
+ pad("a", t0 + 9.4 * BEAT),
28
+ `${(t0 + 10 * BEAT).toFixed(3)}:get:Game:resumed.json`,
29
+ `${(t0 + 10.1 * BEAT).toFixed(3)}:gamepad:off`,
30
+ `${(t0 + 10.2 * BEAT).toFixed(3)}:quit`,
31
+ ]),
32
+ check(run, t) {
33
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
34
+ t.equal(game(run, "started.json").phase, PLAY, "the pad started the song from the title");
35
+ const h = game(run, "hit.json");
36
+ t.equal(h.hits, 8, "the d-pad hit bar 1's notes and the face buttons bar 2's");
37
+ t.equal(h.miss, 0, "none missed");
38
+ t.equal(game(run, "paused.json").paused, true, "Start paused");
39
+ t.equal(game(run, "resumed.json").paused, false, "A on Resume resumed");
40
+ },
41
+ });
@@ -0,0 +1,77 @@
1
+ // Pause, settings and the resume: bar 1 hit, then Escape in bar 2: the
2
+ // clock freezes (no note is missed while paused), the band stops, the
3
+ // time scale holds; the settings switch to 简体中文 and save; Resume counts
4
+ // in again and continues from the beat it paused on: the drums start again
5
+ // part-way into bar 2's stem (AudioSource.startOffset), and bar 2's last
6
+ // notes are hit at their new times.
7
+ import { playtest } from "@origingame/engine/playtest";
8
+ import { BEAT, chart, game, type PlatformDump, play, press, songStart, sorted, tap, text } from "./lib.ts";
9
+
10
+ const notes = chart();
11
+ const t0 = songStart(1.0);
12
+ const pauseAt = t0 + 5.3 * BEAT;
13
+ /** The resume (the `unhold` command, as Resume does): the count-in ends where a start's would. */
14
+ const resumeAt = 10.0;
15
+ const countTo = songStart(resumeAt);
16
+ /** The beat Escape paused on: its tick's time into the song. */
17
+ const pausedBeat = (Math.round(pauseAt * 60) / 60 - t0) / BEAT;
18
+ /** Bar 1's time after the resume: the paused beat comes at the count-in's end. */
19
+ const t1 = countTo - pausedBeat * BEAT;
20
+
21
+ export default playtest({
22
+ name: "Escape freezes the clock and stops the band; 简体中文 is saved; Resume counts in and continues from the beat it paused on",
23
+ commands: sorted([
24
+ "1.0:play",
25
+ ...play(notes, t0, 0, 5.3),
26
+ press("Escape", pauseAt),
27
+ `${(pauseAt + 0.3).toFixed(3)}:get:Game:paused.json`,
28
+ `${(pauseAt + 0.3).toFixed(3)}:get:og.TimeScale.scale:scale.json`,
29
+ `${(pauseAt + 0.3).toFixed(3)}:dump_entity:stem-drums:drums.json`,
30
+ `${(pauseAt + 0.4).toFixed(3)}:screenshot:pause.png`,
31
+ tap("settings", 6.6),
32
+ "7.2:move_to_text:简体中文",
33
+ "7.22:touch_start",
34
+ "7.25:touch_end",
35
+ "7.6:screenshot:settings-zh.png",
36
+ tap("Done", 7.8),
37
+ "8.9:get:Game:held.json",
38
+ "8.9:get:Prefs:prefs.json",
39
+ "8.9:dump_platform:platform.json",
40
+ `${resumeAt}:unhold`,
41
+ `${(resumeAt + 0.1).toFixed(3)}:get:Game:resumed.json`,
42
+ `${(resumeAt + 0.15).toFixed(3)}:dump_entity:stem-drums:drums2.json`,
43
+ ...play(notes, t1, pausedBeat, 8),
44
+ `${(t1 + 7.9 * BEAT).toFixed(3)}:get:Game:after.json`,
45
+ `${(t1 + 7.9 * BEAT).toFixed(3)}:dump_ui:play-zh.json`,
46
+ `${(t1 + 7.95 * BEAT).toFixed(3)}:screenshot:play-zh.png`,
47
+ `${(t1 + 8.0 * BEAT).toFixed(3)}:quit`,
48
+ ]),
49
+ check(run, t) {
50
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
51
+ const p = game(run, "paused.json");
52
+ t.equal(p.paused, true, "Escape paused");
53
+ t.equal(p.judged, 6, "after bar 1's four notes and bar 2's first two");
54
+ t.equal(run.json<{ value: number }>("scale.json").value, 0, "the time scale holds at 0");
55
+ t.equal(run.json<{ components: { "og.AudioSource": { playing: boolean } } }>("drums.json").components["og.AudioSource"].playing, false, "the band stopped");
56
+ const h = game(run, "held.json");
57
+ t.ok(h.judged === 6 && h.miss === 0, "three seconds later nothing was missed: the clock is frozen");
58
+ const prefs = run.json<{ value: { language: number; saves: number } }>("prefs.json").value;
59
+ t.ok(prefs.language === 1 && prefs.saves === 1, "简体中文 was chosen and saved");
60
+ const pd = run.json<PlatformDump>("platform.json");
61
+ t.ok(pd.calls.some((c) => c.method === "store.set" && c.args["key"] === "og.settings" && (c.args["value"] as { language?: string }).language === "zh-CN"), "through og.platform");
62
+ const r = game(run, "resumed.json");
63
+ t.equal(r.paused, false, "Resume");
64
+ t.ok(Math.abs(r.songStart - t1) < 1e-6, `continued from beat ${pausedBeat.toFixed(3)}: bar 1 now at ${t1.toFixed(3)} s, the paused beat at the count-in's end`);
65
+ t.ok(Math.abs(r.countTo - countTo) < 1e-6, `after a count-in to ${countTo.toFixed(3)} s`);
66
+ const drums = run.json<{ components: { "og.AudioSource": { startAt: number; startOffset: number } } }>("drums2.json").components["og.AudioSource"];
67
+ t.ok(Math.abs(drums.startAt - countTo) < 1e-6, "the drums start again at the count-in's end");
68
+ t.ok(Math.abs(drums.startOffset - (pausedBeat - 4) * BEAT) < 1e-6, `part-way into bar 2's stem: ${((pausedBeat - 4) * BEAT).toFixed(3)} s in, not from its start`);
69
+ const a = game(run, "after.json");
70
+ t.equal(a.perfect, 8, "bar 2's last two notes were hit at their new times: eight Perfects");
71
+ t.equal(a.miss, 0, "and nothing missed, bar 1 not judged twice");
72
+ t.equal(text(run, "play-zh.json", "Label", "Score"), "得分", "the HUD speaks 简体中文");
73
+ t.frame(["pause.png", "settings-zh.png", "play-zh.png"], () => {
74
+ for (const f of ["pause.png", "settings-zh.png", "play-zh.png"]) t.equal(run.png(f).width, 1280, `${f} captured`);
75
+ });
76
+ },
77
+ });
@@ -0,0 +1,44 @@
1
+ // The phone layout (390×844 at 3×, a notch and a home bar): the title's
2
+ // menu, then the score, the groove and Pause below the notch, the combo
3
+ // above the highway, three 2 m lanes across the width with the hit line
4
+ // above the home bar; a finger on a lane at its note's time hits it.
5
+ import { playtest } from "@origingame/engine/playtest";
6
+ import { BEAT, chart, game, songStart, sorted, tap, type UiElement, ui } from "./lib.ts";
7
+
8
+ const notes = chart();
9
+ const t0 = songStart(1.0);
10
+ /** CSS px per metre: the camera is 7.2 m across 390 px. */
11
+ const scale = 390 / 7.2;
12
+ const laneX = (lane: number) => 195 + (lane - 1) * 2 * scale;
13
+ const touch = (lane: number, t: number) => `${t.toFixed(4)}:touch_move:${laneX(lane).toFixed(1)},690;${(t + 0.0001).toFixed(4)}:touch_start;${(t + 0.05).toFixed(4)}:touch_end`;
14
+
15
+ export default playtest({
16
+ name: "the phone layout: a HUD inside the safe area, three lanes across, fingers hit the notes",
17
+ size: "1170x2532",
18
+ commands: sorted([
19
+ "0.1:viewport:phone",
20
+ "0.9:dump_ui:title.json",
21
+ "1.0:play",
22
+ ...notes.filter((n) => n.beat < 8).map((n) => touch(n.lane, t0 + n.beat * BEAT)),
23
+ `${(t0 + 7.9 * BEAT).toFixed(3)}:get:Game:hit.json`,
24
+ `${(t0 + 7.9 * BEAT).toFixed(3)}:dump_ui:hud.json`,
25
+ `${(t0 + 7.95 * BEAT).toFixed(3)}:screenshot:play.png`,
26
+ `${(t0 + 8 * BEAT).toFixed(3)}:quit`,
27
+ ]),
28
+ check(run, t) {
29
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
30
+ const title = ui(run, "title.json").elements;
31
+ t.ok(title.some((e) => e.id === "play") && title.every((e) => e.y >= 47 && e.y + e.height <= 844 - 34), "the title's menu sits inside the safe area");
32
+ const h = game(run, "hit.json");
33
+ t.equal(h.hits, 8, "eight notes hit by fingers on their lanes");
34
+ t.equal(h.perfect, 8, "all on time");
35
+ const hud = ui(run, "hud.json");
36
+ const el = (id: string): UiElement | undefined => hud.elements.find((e) => e.id === id);
37
+ t.equal(hud.surface.width, 390, "laid out for a 390 px wide phone");
38
+ for (const id of ["Status", "Corner", "Combo"]) t.ok((el(id)?.y ?? 0) >= 47, `${id} sits below the notch`);
39
+ t.ok(hud.elements.every((e) => e.x >= 0 && e.x + e.width <= 390 && e.y + e.height <= 844 - 34), "nothing runs off the sides or under the home bar");
40
+ const pause = el("Pause");
41
+ t.ok(pause !== undefined && pause.width >= 44 && pause.height >= 44, "Pause is touch-sized");
42
+ t.frame("play.png", () => t.equal(run.png("play.png").width, 1170, "play.png captured at the phone's device pixels"));
43
+ },
44
+ });
@@ -0,0 +1,27 @@
1
+ // Determinism: a play with hits on time, late and early, misses and the
2
+ // band's stems, run twice in fresh processes, ends on the same world hash.
3
+ import { playtest } from "@origingame/engine/playtest";
4
+ import { chart, game, play, songStart, sorted } from "./lib.ts";
5
+
6
+ const notes = chart();
7
+ const t0 = songStart(1.0);
8
+
9
+ export default playtest({
10
+ name: "a play with hits, misses and the band replays to the same hash",
11
+ runs: 2,
12
+ commands: sorted([
13
+ "1.0:play",
14
+ ...play(notes, t0, 0, 4),
15
+ ...play(notes, t0, 4, 8, 0.07),
16
+ ...play(notes, t0, 8, 10, -0.1),
17
+ `${(t0 + 13 * 0.5).toFixed(3)}:get:Game:end.json`,
18
+ `${(t0 + 13.1 * 0.5).toFixed(3)}:quit`,
19
+ ]),
20
+ check(run, t) {
21
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
22
+ t.equal(run.hashes.length, 2, "two runs");
23
+ t.equal(run.hashes[1], run.hashes[0], `both runs end on hash ${run.hashes[0]}`);
24
+ const end = game(run, "end.json");
25
+ t.ok(end.perfect >= 4 && end.great >= 4 && end.good >= 1 && end.miss >= 1, `perfects, greats, goods and misses (${end.perfect}, ${end.great}, ${end.good}, ${end.miss})`);
26
+ },
27
+ });
@@ -0,0 +1,37 @@
1
+ // A press is judged when it happened, not at the start of the tick that
2
+ // reads it: bars 1 and 2's notes pressed 44 ms late and early (a tick's
3
+ // start would read them 50 ms off: Great), 7 ms either way, 89 ms either
4
+ // way (Great, inside 90), 20 ms late and 30 ms early. Each judgement's
5
+ // error is the press's offset to a tenth of a millisecond: the key's
6
+ // og.KeyDown carries its time within the tick (the command step's here,
7
+ // the browser event's timeStamp in a page).
8
+ import { playtest } from "@origingame/engine/playtest";
9
+ import { BEAT, chart, game, KEYS, press, songStart, sorted, tap } from "./lib.ts";
10
+
11
+ const notes = chart().filter((n) => n.beat < 8);
12
+ const t0 = songStart(1.05);
13
+ const offsets = [44, -44, 7, -7, 89, -89, 20, -30];
14
+ /** Perfect within 45 ms, Great within 90 (Judgement.* of src/components.ts: Perfect 0, Great 1). */
15
+ const expected = offsets.map((ms) => (Math.abs(ms) <= 45 ? 0 : 1));
16
+
17
+ export default playtest({
18
+ name: "a press is judged at the millisecond it happened within its tick",
19
+ commands: sorted([
20
+ tap("play", 1.0),
21
+ ...notes.flatMap((n, i) => {
22
+ const at = t0 + n.beat * BEAT;
23
+ return [press(KEYS[n.lane]!, at + offsets[i]! / 1000), `${(at + 0.2).toFixed(4)}:get:Game:note-${i}.json`];
24
+ }),
25
+ `${(t0 + 8 * BEAT).toFixed(3)}:quit`,
26
+ ]),
27
+ check(run, t) {
28
+ t.equal(run.warnings.join("; "), "", "the script ran without warnings");
29
+ t.equal(notes.length, 8, "bars 1 and 2 hold eight notes");
30
+ for (let i = 0; i < notes.length; i++) {
31
+ const g = game(run, `note-${i}.json`) as unknown as { lastOffsetMs: number; lastJudgement: number; judged: number };
32
+ t.equal(g.judged, i + 1, `note ${i} was judged`);
33
+ t.near(g.lastOffsetMs, offsets[i]!, 0.1, `note ${i}'s error is the press's ${offsets[i]} ms`);
34
+ t.equal(g.lastJudgement, expected[i], `note ${i} at ${offsets[i]} ms is ${expected[i] === 0 ? "Perfect" : "Great"}`);
35
+ }
36
+ },
37
+ });