psinetron-opencode-visualizer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/README.md +113 -0
  2. package/index.ts +307 -0
  3. package/package.json +13 -0
  4. package/visualizer/index.html +869 -0
  5. package/visualizer/person1/person1/animations/coffee/south/frame_000.png +0 -0
  6. package/visualizer/person1/person1/animations/coffee/south/frame_001.png +0 -0
  7. package/visualizer/person1/person1/animations/coffee/south/frame_002.png +0 -0
  8. package/visualizer/person1/person1/animations/coffee/south/frame_003.png +0 -0
  9. package/visualizer/person1/person1/animations/coffee/south/frame_004.png +0 -0
  10. package/visualizer/person1/person1/animations/coffee/south/frame_005.png +0 -0
  11. package/visualizer/person1/person1/animations/error/south/frame_000.png +0 -0
  12. package/visualizer/person1/person1/animations/error/south/frame_001.png +0 -0
  13. package/visualizer/person1/person1/animations/error/south/frame_002.png +0 -0
  14. package/visualizer/person1/person1/animations/error/south/frame_003.png +0 -0
  15. package/visualizer/person1/person1/animations/error/south/frame_004.png +0 -0
  16. package/visualizer/person1/person1/animations/error/south/frame_005.png +0 -0
  17. package/visualizer/person1/person1/animations/idle/south/frame_000.png +0 -0
  18. package/visualizer/person1/person1/animations/idle/south/frame_001.png +0 -0
  19. package/visualizer/person1/person1/animations/idle/south/frame_002.png +0 -0
  20. package/visualizer/person1/person1/animations/idle/south/frame_003.png +0 -0
  21. package/visualizer/person1/person1/animations/scratch/south/frame_000.png +0 -0
  22. package/visualizer/person1/person1/animations/scratch/south/frame_001.png +0 -0
  23. package/visualizer/person1/person1/animations/scratch/south/frame_002.png +0 -0
  24. package/visualizer/person1/person1/animations/scratch/south/frame_003.png +0 -0
  25. package/visualizer/person1/person1/animations/scratch/south/frame_004.png +0 -0
  26. package/visualizer/person1/person1/animations/scratch/south/frame_005.png +0 -0
  27. package/visualizer/person1/person1/animations/sit/south/frame_000.png +0 -0
  28. package/visualizer/person1/person1/animations/sit/south/frame_001.png +0 -0
  29. package/visualizer/person1/person1/animations/sit/south/frame_002.png +0 -0
  30. package/visualizer/person1/person1/animations/sit/south/frame_003.png +0 -0
  31. package/visualizer/person1/person1/animations/sit/south/frame_004.png +0 -0
  32. package/visualizer/person1/person1/animations/sit/south/frame_005.png +0 -0
  33. package/visualizer/person1/person1/animations/sit/south/frame_006.png +0 -0
  34. package/visualizer/person1/person1/animations/walk/east/frame_000.png +0 -0
  35. package/visualizer/person1/person1/animations/walk/east/frame_001.png +0 -0
  36. package/visualizer/person1/person1/animations/walk/east/frame_002.png +0 -0
  37. package/visualizer/person1/person1/animations/walk/east/frame_003.png +0 -0
  38. package/visualizer/person1/person1/animations/walk/east/frame_004.png +0 -0
  39. package/visualizer/person1/person1/animations/walk/east/frame_005.png +0 -0
  40. package/visualizer/person1/person1/animations/walk/north/frame_000.png +0 -0
  41. package/visualizer/person1/person1/animations/walk/north/frame_001.png +0 -0
  42. package/visualizer/person1/person1/animations/walk/north/frame_002.png +0 -0
  43. package/visualizer/person1/person1/animations/walk/north/frame_003.png +0 -0
  44. package/visualizer/person1/person1/animations/walk/north/frame_004.png +0 -0
  45. package/visualizer/person1/person1/animations/walk/north/frame_005.png +0 -0
  46. package/visualizer/person1/person1/animations/walk/south/frame_000.png +0 -0
  47. package/visualizer/person1/person1/animations/walk/south/frame_001.png +0 -0
  48. package/visualizer/person1/person1/animations/walk/south/frame_002.png +0 -0
  49. package/visualizer/person1/person1/animations/walk/south/frame_003.png +0 -0
  50. package/visualizer/person1/person1/animations/walk/south/frame_004.png +0 -0
  51. package/visualizer/person1/person1/animations/walk/south/frame_005.png +0 -0
  52. package/visualizer/person1/person1/animations/walk/west/frame_000.png +0 -0
  53. package/visualizer/person1/person1/animations/walk/west/frame_001.png +0 -0
  54. package/visualizer/person1/person1/animations/walk/west/frame_002.png +0 -0
  55. package/visualizer/person1/person1/animations/walk/west/frame_003.png +0 -0
  56. package/visualizer/person1/person1/animations/walk/west/frame_004.png +0 -0
  57. package/visualizer/person1/person1/animations/walk/west/frame_005.png +0 -0
  58. package/visualizer/person1/person1/animations/work/south/frame_000.png +0 -0
  59. package/visualizer/person1/person1/animations/work/south/frame_001.png +0 -0
  60. package/visualizer/person1/person1/animations/work/south/frame_002.png +0 -0
  61. package/visualizer/person1/person1/animations/work/south/frame_003.png +0 -0
  62. package/visualizer/person1/person1/animations/work/south/frame_004.png +0 -0
  63. package/visualizer/person1/person1/animations/work/south/frame_005.png +0 -0
  64. package/visualizer/person1/person1/animations/yawn/south/frame_000.png +0 -0
  65. package/visualizer/person1/person1/animations/yawn/south/frame_001.png +0 -0
  66. package/visualizer/person1/person1/animations/yawn/south/frame_002.png +0 -0
  67. package/visualizer/person1/person1/animations/yawn/south/frame_003.png +0 -0
  68. package/visualizer/person1/person1/animations/yawn/south/frame_004.png +0 -0
  69. package/visualizer/person1/person1/animations/yawn/south/frame_005.png +0 -0
  70. package/visualizer/person1/person1/animations/yawn/south/frame_006.png +0 -0
  71. package/visualizer/person1/person1/rotations/east.png +0 -0
  72. package/visualizer/person1/person1/rotations/north-east.png +0 -0
  73. package/visualizer/person1/person1/rotations/north-west.png +0 -0
  74. package/visualizer/person1/person1/rotations/north.png +0 -0
  75. package/visualizer/person1/person1/rotations/south-east.png +0 -0
  76. package/visualizer/person1/person1/rotations/south-west.png +0 -0
  77. package/visualizer/person1/person1/rotations/south.png +0 -0
  78. package/visualizer/person1/person1/rotations/west.png +0 -0
  79. package/visualizer/person2/person2/animations/coffee/south/frame_000.png +0 -0
  80. package/visualizer/person2/person2/animations/coffee/south/frame_001.png +0 -0
  81. package/visualizer/person2/person2/animations/coffee/south/frame_002.png +0 -0
  82. package/visualizer/person2/person2/animations/coffee/south/frame_003.png +0 -0
  83. package/visualizer/person2/person2/animations/coffee/south/frame_004.png +0 -0
  84. package/visualizer/person2/person2/animations/coffee/south/frame_005.png +0 -0
  85. package/visualizer/person2/person2/animations/coffee/south/frame_006.png +0 -0
  86. package/visualizer/person2/person2/animations/idle/south/frame_000.png +0 -0
  87. package/visualizer/person2/person2/animations/idle/south/frame_001.png +0 -0
  88. package/visualizer/person2/person2/animations/idle/south/frame_002.png +0 -0
  89. package/visualizer/person2/person2/animations/idle/south/frame_003.png +0 -0
  90. package/visualizer/person2/person2/animations/scratch/south/frame_000.png +0 -0
  91. package/visualizer/person2/person2/animations/scratch/south/frame_001.png +0 -0
  92. package/visualizer/person2/person2/animations/scratch/south/frame_002.png +0 -0
  93. package/visualizer/person2/person2/animations/scratch/south/frame_003.png +0 -0
  94. package/visualizer/person2/person2/animations/scratch/south/frame_004.png +0 -0
  95. package/visualizer/person2/person2/animations/scratch/south/frame_005.png +0 -0
  96. package/visualizer/person2/person2/animations/sit/south/frame_000.png +0 -0
  97. package/visualizer/person2/person2/animations/sit/south/frame_001.png +0 -0
  98. package/visualizer/person2/person2/animations/sit/south/frame_002.png +0 -0
  99. package/visualizer/person2/person2/animations/sit/south/frame_003.png +0 -0
  100. package/visualizer/person2/person2/animations/sit/south/frame_004.png +0 -0
  101. package/visualizer/person2/person2/animations/sit/south/frame_005.png +0 -0
  102. package/visualizer/person2/person2/animations/sit/south/frame_006.png +0 -0
  103. package/visualizer/person2/person2/animations/walk/east/frame_000.png +0 -0
  104. package/visualizer/person2/person2/animations/walk/east/frame_001.png +0 -0
  105. package/visualizer/person2/person2/animations/walk/east/frame_002.png +0 -0
  106. package/visualizer/person2/person2/animations/walk/east/frame_003.png +0 -0
  107. package/visualizer/person2/person2/animations/walk/east/frame_004.png +0 -0
  108. package/visualizer/person2/person2/animations/walk/east/frame_005.png +0 -0
  109. package/visualizer/person2/person2/animations/walk/north/frame_000.png +0 -0
  110. package/visualizer/person2/person2/animations/walk/north/frame_001.png +0 -0
  111. package/visualizer/person2/person2/animations/walk/north/frame_002.png +0 -0
  112. package/visualizer/person2/person2/animations/walk/north/frame_003.png +0 -0
  113. package/visualizer/person2/person2/animations/walk/north/frame_004.png +0 -0
  114. package/visualizer/person2/person2/animations/walk/north/frame_005.png +0 -0
  115. package/visualizer/person2/person2/animations/walk/south/frame_000.png +0 -0
  116. package/visualizer/person2/person2/animations/walk/south/frame_001.png +0 -0
  117. package/visualizer/person2/person2/animations/walk/south/frame_002.png +0 -0
  118. package/visualizer/person2/person2/animations/walk/south/frame_003.png +0 -0
  119. package/visualizer/person2/person2/animations/walk/south/frame_004.png +0 -0
  120. package/visualizer/person2/person2/animations/walk/south/frame_005.png +0 -0
  121. package/visualizer/person2/person2/animations/walk/west/frame_000.png +0 -0
  122. package/visualizer/person2/person2/animations/walk/west/frame_001.png +0 -0
  123. package/visualizer/person2/person2/animations/walk/west/frame_002.png +0 -0
  124. package/visualizer/person2/person2/animations/walk/west/frame_003.png +0 -0
  125. package/visualizer/person2/person2/animations/walk/west/frame_004.png +0 -0
  126. package/visualizer/person2/person2/animations/walk/west/frame_005.png +0 -0
  127. package/visualizer/person2/person2/animations/work/south/frame_000.png +0 -0
  128. package/visualizer/person2/person2/animations/work/south/frame_001.png +0 -0
  129. package/visualizer/person2/person2/animations/work/south/frame_002.png +0 -0
  130. package/visualizer/person2/person2/animations/work/south/frame_003.png +0 -0
  131. package/visualizer/person2/person2/animations/work/south/frame_004.png +0 -0
  132. package/visualizer/person2/person2/animations/work/south/frame_005.png +0 -0
  133. package/visualizer/person2/person2/animations/work/south/frame_006.png +0 -0
  134. package/visualizer/person2/person2/animations/work/south/frame_007.png +0 -0
  135. package/visualizer/person2/person2/animations/yawn/south/frame_000.png +0 -0
  136. package/visualizer/person2/person2/animations/yawn/south/frame_001.png +0 -0
  137. package/visualizer/person2/person2/animations/yawn/south/frame_002.png +0 -0
  138. package/visualizer/person2/person2/animations/yawn/south/frame_003.png +0 -0
  139. package/visualizer/person2/person2/animations/yawn/south/frame_004.png +0 -0
  140. package/visualizer/person2/person2/animations/yawn/south/frame_005.png +0 -0
  141. package/visualizer/person2/person2/animations/yawn/south/frame_006.png +0 -0
  142. package/visualizer/person2/person2/animations/yawn/south/frame_007.png +0 -0
  143. package/visualizer/person2/person2/animations/yawn/south/frame_008.png +0 -0
  144. package/visualizer/person2/person2/animations/yawn/south/frame_009.png +0 -0
  145. package/visualizer/person2/person2/animations/yawn/south/frame_010.png +0 -0
  146. package/visualizer/person2/person2/animations/yawn/south/frame_011.png +0 -0
  147. package/visualizer/person2/person2/animations/yawn/south/frame_012.png +0 -0
  148. package/visualizer/person2/person2/rotations/east.png +0 -0
  149. package/visualizer/person2/person2/rotations/north-east.png +0 -0
  150. package/visualizer/person2/person2/rotations/north-west.png +0 -0
  151. package/visualizer/person2/person2/rotations/north.png +0 -0
  152. package/visualizer/person2/person2/rotations/south-east.png +0 -0
  153. package/visualizer/person2/person2/rotations/south-west.png +0 -0
  154. package/visualizer/person2/person2/rotations/south.png +0 -0
  155. package/visualizer/person2/person2/rotations/west.png +0 -0
  156. package/visualizer/person3/person3/animations/coffee/south/frame_000.png +0 -0
  157. package/visualizer/person3/person3/animations/coffee/south/frame_001.png +0 -0
  158. package/visualizer/person3/person3/animations/coffee/south/frame_002.png +0 -0
  159. package/visualizer/person3/person3/animations/coffee/south/frame_003.png +0 -0
  160. package/visualizer/person3/person3/animations/coffee/south/frame_004.png +0 -0
  161. package/visualizer/person3/person3/animations/coffee/south/frame_005.png +0 -0
  162. package/visualizer/person3/person3/animations/coffee/south/frame_006.png +0 -0
  163. package/visualizer/person3/person3/animations/coffee/south/frame_007.png +0 -0
  164. package/visualizer/person3/person3/animations/coffee/south/frame_008.png +0 -0
  165. package/visualizer/person3/person3/animations/coffee/south/frame_009.png +0 -0
  166. package/visualizer/person3/person3/animations/coffee/south/frame_010.png +0 -0
  167. package/visualizer/person3/person3/animations/coffee/south/frame_011.png +0 -0
  168. package/visualizer/person3/person3/animations/coffee/south/frame_012.png +0 -0
  169. package/visualizer/person3/person3/animations/idle/south/frame_000.png +0 -0
  170. package/visualizer/person3/person3/animations/idle/south/frame_001.png +0 -0
  171. package/visualizer/person3/person3/animations/idle/south/frame_002.png +0 -0
  172. package/visualizer/person3/person3/animations/idle/south/frame_003.png +0 -0
  173. package/visualizer/person3/person3/animations/scratch/south/frame_000.png +0 -0
  174. package/visualizer/person3/person3/animations/scratch/south/frame_001.png +0 -0
  175. package/visualizer/person3/person3/animations/scratch/south/frame_002.png +0 -0
  176. package/visualizer/person3/person3/animations/scratch/south/frame_003.png +0 -0
  177. package/visualizer/person3/person3/animations/scratch/south/frame_004.png +0 -0
  178. package/visualizer/person3/person3/animations/scratch/south/frame_005.png +0 -0
  179. package/visualizer/person3/person3/animations/sit/south/frame_000.png +0 -0
  180. package/visualizer/person3/person3/animations/sit/south/frame_001.png +0 -0
  181. package/visualizer/person3/person3/animations/sit/south/frame_002.png +0 -0
  182. package/visualizer/person3/person3/animations/sit/south/frame_003.png +0 -0
  183. package/visualizer/person3/person3/animations/sit/south/frame_004.png +0 -0
  184. package/visualizer/person3/person3/animations/sit/south/frame_005.png +0 -0
  185. package/visualizer/person3/person3/animations/sit/south/frame_006.png +0 -0
  186. package/visualizer/person3/person3/animations/walk/east/frame_000.png +0 -0
  187. package/visualizer/person3/person3/animations/walk/east/frame_001.png +0 -0
  188. package/visualizer/person3/person3/animations/walk/east/frame_002.png +0 -0
  189. package/visualizer/person3/person3/animations/walk/east/frame_003.png +0 -0
  190. package/visualizer/person3/person3/animations/walk/east/frame_004.png +0 -0
  191. package/visualizer/person3/person3/animations/walk/east/frame_005.png +0 -0
  192. package/visualizer/person3/person3/animations/walk/north/frame_000.png +0 -0
  193. package/visualizer/person3/person3/animations/walk/north/frame_001.png +0 -0
  194. package/visualizer/person3/person3/animations/walk/north/frame_002.png +0 -0
  195. package/visualizer/person3/person3/animations/walk/north/frame_003.png +0 -0
  196. package/visualizer/person3/person3/animations/walk/north/frame_004.png +0 -0
  197. package/visualizer/person3/person3/animations/walk/north/frame_005.png +0 -0
  198. package/visualizer/person3/person3/animations/walk/south/frame_000.png +0 -0
  199. package/visualizer/person3/person3/animations/walk/south/frame_001.png +0 -0
  200. package/visualizer/person3/person3/animations/walk/south/frame_002.png +0 -0
  201. package/visualizer/person3/person3/animations/walk/south/frame_003.png +0 -0
  202. package/visualizer/person3/person3/animations/walk/south/frame_004.png +0 -0
  203. package/visualizer/person3/person3/animations/walk/south/frame_005.png +0 -0
  204. package/visualizer/person3/person3/animations/walk/west/frame_000.png +0 -0
  205. package/visualizer/person3/person3/animations/walk/west/frame_001.png +0 -0
  206. package/visualizer/person3/person3/animations/walk/west/frame_002.png +0 -0
  207. package/visualizer/person3/person3/animations/walk/west/frame_003.png +0 -0
  208. package/visualizer/person3/person3/animations/walk/west/frame_004.png +0 -0
  209. package/visualizer/person3/person3/animations/walk/west/frame_005.png +0 -0
  210. package/visualizer/person3/person3/animations/work/south/frame_000.png +0 -0
  211. package/visualizer/person3/person3/animations/work/south/frame_001.png +0 -0
  212. package/visualizer/person3/person3/animations/work/south/frame_002.png +0 -0
  213. package/visualizer/person3/person3/animations/work/south/frame_003.png +0 -0
  214. package/visualizer/person3/person3/animations/work/south/frame_004.png +0 -0
  215. package/visualizer/person3/person3/animations/work/south/frame_005.png +0 -0
  216. package/visualizer/person3/person3/animations/yawn/south/frame_000.png +0 -0
  217. package/visualizer/person3/person3/animations/yawn/south/frame_001.png +0 -0
  218. package/visualizer/person3/person3/animations/yawn/south/frame_002.png +0 -0
  219. package/visualizer/person3/person3/animations/yawn/south/frame_003.png +0 -0
  220. package/visualizer/person3/person3/animations/yawn/south/frame_004.png +0 -0
  221. package/visualizer/person3/person3/animations/yawn/south/frame_005.png +0 -0
  222. package/visualizer/person3/person3/animations/yawn/south/frame_006.png +0 -0
  223. package/visualizer/person3/person3/animations/yawn/south/frame_007.png +0 -0
  224. package/visualizer/person3/person3/animations/yawn/south/frame_008.png +0 -0
  225. package/visualizer/person3/person3/animations/yawn/south/frame_009.png +0 -0
  226. package/visualizer/person3/person3/animations/yawn/south/frame_010.png +0 -0
  227. package/visualizer/person3/person3/animations/yawn/south/frame_011.png +0 -0
  228. package/visualizer/person3/person3/rotations/east.png +0 -0
  229. package/visualizer/person3/person3/rotations/north-east.png +0 -0
  230. package/visualizer/person3/person3/rotations/north-west.png +0 -0
  231. package/visualizer/person3/person3/rotations/north.png +0 -0
  232. package/visualizer/person3/person3/rotations/south-east.png +0 -0
  233. package/visualizer/person3/person3/rotations/south-west.png +0 -0
  234. package/visualizer/person3/person3/rotations/south.png +0 -0
  235. package/visualizer/person3/person3/rotations/west.png +0 -0
  236. package/visualizer/person4/person4/animations/coffee/south/frame_000.png +0 -0
  237. package/visualizer/person4/person4/animations/coffee/south/frame_001.png +0 -0
  238. package/visualizer/person4/person4/animations/coffee/south/frame_002.png +0 -0
  239. package/visualizer/person4/person4/animations/coffee/south/frame_003.png +0 -0
  240. package/visualizer/person4/person4/animations/coffee/south/frame_004.png +0 -0
  241. package/visualizer/person4/person4/animations/coffee/south/frame_005.png +0 -0
  242. package/visualizer/person4/person4/animations/coffee/south/frame_006.png +0 -0
  243. package/visualizer/person4/person4/animations/coffee/south/frame_007.png +0 -0
  244. package/visualizer/person4/person4/animations/coffee/south/frame_008.png +0 -0
  245. package/visualizer/person4/person4/animations/idle/south/frame_000.png +0 -0
  246. package/visualizer/person4/person4/animations/idle/south/frame_001.png +0 -0
  247. package/visualizer/person4/person4/animations/idle/south/frame_002.png +0 -0
  248. package/visualizer/person4/person4/animations/idle/south/frame_003.png +0 -0
  249. package/visualizer/person4/person4/animations/scratch/south/frame_000.png +0 -0
  250. package/visualizer/person4/person4/animations/scratch/south/frame_001.png +0 -0
  251. package/visualizer/person4/person4/animations/scratch/south/frame_002.png +0 -0
  252. package/visualizer/person4/person4/animations/scratch/south/frame_003.png +0 -0
  253. package/visualizer/person4/person4/animations/scratch/south/frame_004.png +0 -0
  254. package/visualizer/person4/person4/animations/scratch/south/frame_005.png +0 -0
  255. package/visualizer/person4/person4/animations/scratch/south/frame_006.png +0 -0
  256. package/visualizer/person4/person4/animations/sit/south/frame_000.png +0 -0
  257. package/visualizer/person4/person4/animations/sit/south/frame_001.png +0 -0
  258. package/visualizer/person4/person4/animations/sit/south/frame_002.png +0 -0
  259. package/visualizer/person4/person4/animations/sit/south/frame_003.png +0 -0
  260. package/visualizer/person4/person4/animations/sit/south/frame_004.png +0 -0
  261. package/visualizer/person4/person4/animations/sit/south/frame_005.png +0 -0
  262. package/visualizer/person4/person4/animations/sit/south/frame_006.png +0 -0
  263. package/visualizer/person4/person4/animations/walk/east/frame_000.png +0 -0
  264. package/visualizer/person4/person4/animations/walk/east/frame_001.png +0 -0
  265. package/visualizer/person4/person4/animations/walk/east/frame_002.png +0 -0
  266. package/visualizer/person4/person4/animations/walk/east/frame_003.png +0 -0
  267. package/visualizer/person4/person4/animations/walk/east/frame_004.png +0 -0
  268. package/visualizer/person4/person4/animations/walk/east/frame_005.png +0 -0
  269. package/visualizer/person4/person4/animations/walk/north/frame_000.png +0 -0
  270. package/visualizer/person4/person4/animations/walk/north/frame_001.png +0 -0
  271. package/visualizer/person4/person4/animations/walk/north/frame_002.png +0 -0
  272. package/visualizer/person4/person4/animations/walk/north/frame_003.png +0 -0
  273. package/visualizer/person4/person4/animations/walk/north/frame_004.png +0 -0
  274. package/visualizer/person4/person4/animations/walk/north/frame_005.png +0 -0
  275. package/visualizer/person4/person4/animations/walk/south/frame_000.png +0 -0
  276. package/visualizer/person4/person4/animations/walk/south/frame_001.png +0 -0
  277. package/visualizer/person4/person4/animations/walk/south/frame_002.png +0 -0
  278. package/visualizer/person4/person4/animations/walk/south/frame_003.png +0 -0
  279. package/visualizer/person4/person4/animations/walk/south/frame_004.png +0 -0
  280. package/visualizer/person4/person4/animations/walk/south/frame_005.png +0 -0
  281. package/visualizer/person4/person4/animations/walk/west/frame_000.png +0 -0
  282. package/visualizer/person4/person4/animations/walk/west/frame_001.png +0 -0
  283. package/visualizer/person4/person4/animations/walk/west/frame_002.png +0 -0
  284. package/visualizer/person4/person4/animations/walk/west/frame_003.png +0 -0
  285. package/visualizer/person4/person4/animations/walk/west/frame_004.png +0 -0
  286. package/visualizer/person4/person4/animations/walk/west/frame_005.png +0 -0
  287. package/visualizer/person4/person4/animations/work/south/frame_000.png +0 -0
  288. package/visualizer/person4/person4/animations/work/south/frame_001.png +0 -0
  289. package/visualizer/person4/person4/animations/work/south/frame_002.png +0 -0
  290. package/visualizer/person4/person4/animations/work/south/frame_003.png +0 -0
  291. package/visualizer/person4/person4/animations/work/south/frame_004.png +0 -0
  292. package/visualizer/person4/person4/animations/work/south/frame_005.png +0 -0
  293. package/visualizer/person4/person4/animations/yawn/south/frame_000.png +0 -0
  294. package/visualizer/person4/person4/animations/yawn/south/frame_001.png +0 -0
  295. package/visualizer/person4/person4/animations/yawn/south/frame_002.png +0 -0
  296. package/visualizer/person4/person4/animations/yawn/south/frame_003.png +0 -0
  297. package/visualizer/person4/person4/animations/yawn/south/frame_004.png +0 -0
  298. package/visualizer/person4/person4/animations/yawn/south/frame_005.png +0 -0
  299. package/visualizer/person4/person4/animations/yawn/south/frame_006.png +0 -0
  300. package/visualizer/person4/person4/animations/yawn/south/frame_007.png +0 -0
  301. package/visualizer/person4/person4/rotations/east.png +0 -0
  302. package/visualizer/person4/person4/rotations/north-east.png +0 -0
  303. package/visualizer/person4/person4/rotations/north-west.png +0 -0
  304. package/visualizer/person4/person4/rotations/north.png +0 -0
  305. package/visualizer/person4/person4/rotations/south-east.png +0 -0
  306. package/visualizer/person4/person4/rotations/south-west.png +0 -0
  307. package/visualizer/person4/person4/rotations/south.png +0 -0
  308. package/visualizer/person4/person4/rotations/west.png +0 -0
  309. package/visualizer/person5/person5/animations/coffee/south/frame_000.png +0 -0
  310. package/visualizer/person5/person5/animations/coffee/south/frame_001.png +0 -0
  311. package/visualizer/person5/person5/animations/coffee/south/frame_002.png +0 -0
  312. package/visualizer/person5/person5/animations/coffee/south/frame_003.png +0 -0
  313. package/visualizer/person5/person5/animations/coffee/south/frame_004.png +0 -0
  314. package/visualizer/person5/person5/animations/coffee/south/frame_005.png +0 -0
  315. package/visualizer/person5/person5/animations/coffee/south/frame_006.png +0 -0
  316. package/visualizer/person5/person5/animations/coffee/south/frame_007.png +0 -0
  317. package/visualizer/person5/person5/animations/coffee/south/frame_008.png +0 -0
  318. package/visualizer/person5/person5/animations/coffee/south/frame_009.png +0 -0
  319. package/visualizer/person5/person5/animations/coffee/south/frame_010.png +0 -0
  320. package/visualizer/person5/person5/animations/idle/south/frame_000.png +0 -0
  321. package/visualizer/person5/person5/animations/idle/south/frame_001.png +0 -0
  322. package/visualizer/person5/person5/animations/idle/south/frame_002.png +0 -0
  323. package/visualizer/person5/person5/animations/idle/south/frame_003.png +0 -0
  324. package/visualizer/person5/person5/animations/scratch/south/frame_000.png +0 -0
  325. package/visualizer/person5/person5/animations/scratch/south/frame_001.png +0 -0
  326. package/visualizer/person5/person5/animations/scratch/south/frame_002.png +0 -0
  327. package/visualizer/person5/person5/animations/scratch/south/frame_003.png +0 -0
  328. package/visualizer/person5/person5/animations/scratch/south/frame_004.png +0 -0
  329. package/visualizer/person5/person5/animations/scratch/south/frame_005.png +0 -0
  330. package/visualizer/person5/person5/animations/scratch/south/frame_006.png +0 -0
  331. package/visualizer/person5/person5/animations/scratch/south/frame_007.png +0 -0
  332. package/visualizer/person5/person5/animations/scratch/south/frame_008.png +0 -0
  333. package/visualizer/person5/person5/animations/scratch/south/frame_009.png +0 -0
  334. package/visualizer/person5/person5/animations/scratch/south/frame_010.png +0 -0
  335. package/visualizer/person5/person5/animations/scratch/south/frame_011.png +0 -0
  336. package/visualizer/person5/person5/animations/sit/south/frame_000.png +0 -0
  337. package/visualizer/person5/person5/animations/sit/south/frame_001.png +0 -0
  338. package/visualizer/person5/person5/animations/sit/south/frame_002.png +0 -0
  339. package/visualizer/person5/person5/animations/sit/south/frame_003.png +0 -0
  340. package/visualizer/person5/person5/animations/sit/south/frame_004.png +0 -0
  341. package/visualizer/person5/person5/animations/sit/south/frame_005.png +0 -0
  342. package/visualizer/person5/person5/animations/sit/south/frame_006.png +0 -0
  343. package/visualizer/person5/person5/animations/walk/east/frame_000.png +0 -0
  344. package/visualizer/person5/person5/animations/walk/east/frame_001.png +0 -0
  345. package/visualizer/person5/person5/animations/walk/east/frame_002.png +0 -0
  346. package/visualizer/person5/person5/animations/walk/east/frame_003.png +0 -0
  347. package/visualizer/person5/person5/animations/walk/east/frame_004.png +0 -0
  348. package/visualizer/person5/person5/animations/walk/east/frame_005.png +0 -0
  349. package/visualizer/person5/person5/animations/walk/north/frame_000.png +0 -0
  350. package/visualizer/person5/person5/animations/walk/north/frame_001.png +0 -0
  351. package/visualizer/person5/person5/animations/walk/north/frame_002.png +0 -0
  352. package/visualizer/person5/person5/animations/walk/north/frame_003.png +0 -0
  353. package/visualizer/person5/person5/animations/walk/north/frame_004.png +0 -0
  354. package/visualizer/person5/person5/animations/walk/north/frame_005.png +0 -0
  355. package/visualizer/person5/person5/animations/walk/south/frame_000.png +0 -0
  356. package/visualizer/person5/person5/animations/walk/south/frame_001.png +0 -0
  357. package/visualizer/person5/person5/animations/walk/south/frame_002.png +0 -0
  358. package/visualizer/person5/person5/animations/walk/south/frame_003.png +0 -0
  359. package/visualizer/person5/person5/animations/walk/south/frame_004.png +0 -0
  360. package/visualizer/person5/person5/animations/walk/south/frame_005.png +0 -0
  361. package/visualizer/person5/person5/animations/walk/west/frame_000.png +0 -0
  362. package/visualizer/person5/person5/animations/walk/west/frame_001.png +0 -0
  363. package/visualizer/person5/person5/animations/walk/west/frame_002.png +0 -0
  364. package/visualizer/person5/person5/animations/walk/west/frame_003.png +0 -0
  365. package/visualizer/person5/person5/animations/walk/west/frame_004.png +0 -0
  366. package/visualizer/person5/person5/animations/walk/west/frame_005.png +0 -0
  367. package/visualizer/person5/person5/animations/work/south/frame_000.png +0 -0
  368. package/visualizer/person5/person5/animations/work/south/frame_001.png +0 -0
  369. package/visualizer/person5/person5/animations/work/south/frame_002.png +0 -0
  370. package/visualizer/person5/person5/animations/work/south/frame_003.png +0 -0
  371. package/visualizer/person5/person5/animations/work/south/frame_004.png +0 -0
  372. package/visualizer/person5/person5/animations/work/south/frame_005.png +0 -0
  373. package/visualizer/person5/person5/animations/work/south/frame_006.png +0 -0
  374. package/visualizer/person5/person5/animations/yawn/south/frame_000.png +0 -0
  375. package/visualizer/person5/person5/animations/yawn/south/frame_001.png +0 -0
  376. package/visualizer/person5/person5/animations/yawn/south/frame_002.png +0 -0
  377. package/visualizer/person5/person5/animations/yawn/south/frame_003.png +0 -0
  378. package/visualizer/person5/person5/animations/yawn/south/frame_004.png +0 -0
  379. package/visualizer/person5/person5/animations/yawn/south/frame_005.png +0 -0
  380. package/visualizer/person5/person5/animations/yawn/south/frame_006.png +0 -0
  381. package/visualizer/person5/person5/animations/yawn/south/frame_007.png +0 -0
  382. package/visualizer/person5/person5/animations/yawn/south/frame_008.png +0 -0
  383. package/visualizer/person5/person5/rotations/east.png +0 -0
  384. package/visualizer/person5/person5/rotations/north-east.png +0 -0
  385. package/visualizer/person5/person5/rotations/north-west.png +0 -0
  386. package/visualizer/person5/person5/rotations/north.png +0 -0
  387. package/visualizer/person5/person5/rotations/south-east.png +0 -0
  388. package/visualizer/person5/person5/rotations/south-west.png +0 -0
  389. package/visualizer/person5/person5/rotations/south.png +0 -0
  390. package/visualizer/person5/person5/rotations/west.png +0 -0
  391. package/visualizer/textures/background.png +0 -0
  392. package/visualizer/textures/coffeetable.png +0 -0
  393. package/visualizer/textures/desktop.png +0 -0
@@ -0,0 +1,869 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenCode Visualizer</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { background: #1a1a2e; overflow: hidden; }
10
+ canvas { display: block; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <canvas id="canvas"></canvas>
15
+
16
+ <script>
17
+ // ============================================================
18
+ // CONSTANTS
19
+ // ============================================================
20
+ const W = 640, H = 480
21
+
22
+ const SPAWN_POINT = { x: 330, y: 120 }
23
+ const REST_AREA = { x1: 240, y1: 190, x2: 400, y2: 230 }
24
+
25
+ const DESKS = [
26
+ { deskX: 125, deskY: 220, agentX: 145, agentY: 175 },
27
+ { deskX: 125, deskY: 325, agentX: 145, agentY: 280 },
28
+ { deskX: 125, deskY: 430, agentX: 145, agentY: 395 },
29
+ { deskX: 325, deskY: 325, agentX: 345, agentY: 280 },
30
+ { deskX: 325, deskY: 430, agentX: 345, agentY: 395 },
31
+ ]
32
+
33
+ const COFFEE_TABLES = [
34
+ { x: 550, y: 250 },
35
+ { x: 550, y: 420 },
36
+ ]
37
+
38
+ const COFFEE_SPOTS = [
39
+ { x: 500, y: 210 }, { x: 545, y: 195 }, { x: 590, y: 210 },
40
+ { x: 500, y: 365 }, { x: 545, y: 350 }, { x: 590, y: 365 },
41
+ ]
42
+
43
+ const WALK_SPEED = 80 // px/sec
44
+
45
+ const VERT_CORRIDORS = [220, 450]
46
+
47
+ const SKINS = {
48
+ person1: {
49
+ base: "person1/person1",
50
+ anims: {
51
+ idle: { folder: "idle", frames: 4, dirs: ["south"] },
52
+ walk: { folder: "walk", frames: 6, dirs: ["south","east","north","west"] },
53
+ sit: { folder: "sit", frames: 7, dirs: ["south"] },
54
+ work: { folder: "work", frames: 6, dirs: ["south"] },
55
+ error: { folder: "error", frames: 6, dirs: ["south"] },
56
+ coffee: { folder: "coffee", frames: 6, dirs: ["south"] },
57
+ scratch: { folder: "scratch", frames: 6, dirs: ["south"] },
58
+ yawn: { folder: "yawn", frames: 7, dirs: ["south"] },
59
+ }
60
+ },
61
+ person2: {
62
+ base: "person2/person2",
63
+ anims: {
64
+ idle: { folder: "idle", frames: 4, dirs: ["south"] },
65
+ walk: { folder: "walk", frames: 6, dirs: ["south","east","north","west"] },
66
+ sit: { folder: "sit", frames: 7, dirs: ["south"] },
67
+ work: { folder: "work", frames: 8, dirs: ["south"] },
68
+ coffee: { folder: "coffee", frames: 7, dirs: ["south"] },
69
+ scratch: { folder: "scratch", frames: 6, dirs: ["south"] },
70
+ yawn: { folder: "yawn", frames: 13, dirs: ["south"] },
71
+ }
72
+ },
73
+ person3: {
74
+ base: "person3/person3",
75
+ anims: {
76
+ idle: { folder: "idle", frames: 4, dirs: ["south"] },
77
+ walk: { folder: "walk", frames: 6, dirs: ["south","east","north","west"] },
78
+ sit: { folder: "sit", frames: 7, dirs: ["south"] },
79
+ work: { folder: "work", frames: 6, dirs: ["south"] },
80
+ coffee: { folder: "coffee", frames: 13, dirs: ["south"] },
81
+ scratch: { folder: "scratch", frames: 6, dirs: ["south"] },
82
+ yawn: { folder: "yawn", frames: 12, dirs: ["south"] },
83
+ }
84
+ },
85
+ person4: {
86
+ base: "person4/person4",
87
+ anims: {
88
+ idle: { folder: "idle", frames: 4, dirs: ["south"] },
89
+ walk: { folder: "walk", frames: 6, dirs: ["south","east","north","west"] },
90
+ sit: { folder: "sit", frames: 7, dirs: ["south"] },
91
+ work: { folder: "work", frames: 6, dirs: ["south"] },
92
+ coffee: { folder: "coffee", frames: 9, dirs: ["south"] },
93
+ scratch: { folder: "scratch", frames: 7, dirs: ["south"] },
94
+ yawn: { folder: "yawn", frames: 8, dirs: ["south"] },
95
+ }
96
+ },
97
+ person5: {
98
+ base: "person5/person5",
99
+ anims: {
100
+ idle: { folder: "idle", frames: 4, dirs: ["south"] },
101
+ walk: { folder: "walk", frames: 6, dirs: ["south","east","north","west"] },
102
+ sit: { folder: "sit", frames: 7, dirs: ["south"] },
103
+ work: { folder: "work", frames: 7, dirs: ["south"] },
104
+ coffee: { folder: "coffee", frames: 11, dirs: ["south"] },
105
+ scratch: { folder: "scratch", frames: 12, dirs: ["south"] },
106
+ yawn: { folder: "yawn", frames: 9, dirs: ["south"] },
107
+ }
108
+ },
109
+ }
110
+
111
+ const WORK_ACTIONS = ["scratch", "yawn"]
112
+
113
+ const ANIM_FPS = 6
114
+
115
+ let animFrames = {} // key: { south: [img,...], east: [img,...], ... }
116
+ let deskImg = null, coffeeTableImg = null, floorPattern = null
117
+ let drawObjects = []
118
+
119
+ function loadImage(src) {
120
+ return new Promise((resolve, reject) => {
121
+ const img = new Image()
122
+ img.onload = () => resolve(img)
123
+ img.onerror = () => { console.warn("fail:", src); resolve(null) }
124
+ img.src = src
125
+ })
126
+ }
127
+
128
+ async function preloadAll() {
129
+ const animPromises = []
130
+ const animSlots = []
131
+ for (const [skinId, skin] of Object.entries(SKINS)) {
132
+ animFrames[skinId] = {}
133
+ for (const [key, def] of Object.entries(skin.anims)) {
134
+ animFrames[skinId][key] = {}
135
+ for (const dir of def.dirs) {
136
+ animFrames[skinId][key][dir] = new Array(def.frames)
137
+ for (let i = 0; i < def.frames; i++) {
138
+ const num = String(i).padStart(3, "0")
139
+ const path = `${skin.base}/animations/${def.folder}/${dir}/frame_${num}.png`
140
+ animPromises.push(loadImage(path))
141
+ animSlots.push({ skinId, key, dir, i })
142
+ }
143
+ }
144
+ }
145
+ }
146
+ const animResults = await Promise.all(animPromises)
147
+ for (let j = 0; j < animSlots.length; j++) {
148
+ const { skinId, key, dir, i } = animSlots[j]
149
+ animFrames[skinId][key][dir][i] = animResults[j]
150
+ }
151
+ // fill missing dirs with south
152
+ for (const [skinId, skin] of Object.entries(SKINS)) {
153
+ for (const key of Object.keys(skin.anims)) {
154
+ for (const dir of ["north","south","east","west","north-east","south-east","south-west","north-west"]) {
155
+ if (!animFrames[skinId][key][dir]) animFrames[skinId][key][dir] = animFrames[skinId][key]["south"] || []
156
+ }
157
+ }
158
+ }
159
+ // Objects + background in parallel
160
+ const [desk, coffee, bg] = await Promise.all([
161
+ loadImage("textures/desktop.png"),
162
+ loadImage("textures/coffeetable.png"),
163
+ loadImage("textures/background.png"),
164
+ ])
165
+ deskImg = desk
166
+ coffeeTableImg = coffee
167
+ if (bg) {
168
+ const c = document.createElement("canvas")
169
+ c.width = bg.width; c.height = bg.height
170
+ c.getContext("2d").drawImage(bg, 0, 0)
171
+ floorPattern = ctx.createPattern(c, "repeat")
172
+ }
173
+ }
174
+
175
+ // ============================================================
176
+ // PATH FINDING
177
+ // ============================================================
178
+ function findPath(fromX, fromY, toX, toY, viaRestArea = false, direct = false) {
179
+ if (!direct && !viaRestArea) {
180
+ const inRest = fromX >= REST_AREA.x1 - 30 && fromX <= REST_AREA.x2 + 30
181
+ && fromY >= REST_AREA.y1 - 20 && fromY <= REST_AREA.y2 + 20
182
+ if (inRest) direct = true
183
+ }
184
+ if (direct) {
185
+ return [{x:fromX, y:fromY}, {x:toX, y:toY}]
186
+ }
187
+ const path = [{x:fromX, y:fromY}]
188
+ let cx = fromX, cy = fromY
189
+
190
+ function goH(tx) { if (Math.abs(tx - cx) > 1) { path.push({x:tx, y:cy}); cx = tx } }
191
+ function goV(ty) { if (Math.abs(ty - cy) > 1) { path.push({x:cx, y:ty}); cy = ty } }
192
+
193
+ if (viaRestArea) {
194
+ const corridor = VERT_CORRIDORS.reduce((a,b) => Math.abs(a-cx) < Math.abs(b-cx) ? a : b)
195
+ goH(corridor)
196
+ const restY = REST_AREA.y1 + Math.random() * (REST_AREA.y2 - REST_AREA.y1)
197
+ goV(restY)
198
+ const targetCorridor = toX > 450 ? 450 : 220
199
+ goH(targetCorridor)
200
+ goV(toY)
201
+ goH(toX)
202
+ } else {
203
+ const corridor = VERT_CORRIDORS.reduce((a,b) => Math.abs(a-cx) < Math.abs(b-cx) ? a : b)
204
+ if (Math.abs(toX - cx) > 30 || Math.abs(toY - cy) > 30) {
205
+ goH(corridor)
206
+ goV(toY)
207
+ goH(toX)
208
+ } else {
209
+ goH(toX)
210
+ goV(toY)
211
+ }
212
+ }
213
+
214
+ if (cx !== toX || cy !== toY) {
215
+ path.push({x:toX, y:toY})
216
+ }
217
+ return path
218
+ }
219
+
220
+ function randomRestPos() {
221
+ const pad = 20
222
+ return {
223
+ x: REST_AREA.x1 + pad + Math.random() * (REST_AREA.x2 - REST_AREA.x1 - pad*2),
224
+ y: REST_AREA.y1 + pad + Math.random() * (REST_AREA.y2 - REST_AREA.y1 - pad*2),
225
+ }
226
+ }
227
+
228
+ // ============================================================
229
+ // AGENT
230
+ // ============================================================
231
+ let agentIdCounter = 0
232
+ const agents = []
233
+ const reservedCoffeeSpots = new Set()
234
+
235
+ function getFreeDeskIndex() {
236
+ const used = new Set(agents.filter(a => a.deskIndex >= 0).map(a => a.deskIndex))
237
+ for (let i = 0; i < DESKS.length; i++) {
238
+ if (!used.has(i)) return i
239
+ }
240
+ return -1
241
+ }
242
+
243
+ function getFreeCoffeeSpot() {
244
+ for (let i = 0; i < COFFEE_SPOTS.length; i++) {
245
+ if (!reservedCoffeeSpots.has(i)) return i
246
+ }
247
+ return -1
248
+ }
249
+
250
+ function createAgent(instanceId, cwd, skin) {
251
+ if (agents.length >= 5) return null
252
+ const id = agentIdCounter++
253
+ const skinIds = Object.keys(SKINS)
254
+ const usedSkins = new Set(agents.map(a => a.skinId))
255
+ if (skin && SKINS[skin] && !usedSkins.has(skin)) {
256
+ // Provided skin is valid and free — use it
257
+ } else {
258
+ skin = skinIds.find(s => !usedSkins.has(s))
259
+ if (!skin) {
260
+ skin = skinIds[Math.floor(Math.random() * skinIds.length)]
261
+ }
262
+ }
263
+ localStorage.setItem('viz-last-skin', skin)
264
+ const a = {
265
+ id, instanceId, cwd,
266
+ skinId: skin,
267
+ state: "spawning",
268
+ x: SPAWN_POINT.x, y: SPAWN_POINT.y,
269
+ opacity: 0,
270
+ animKey: "idle",
271
+ animFrame: 0, animTimer: 0,
272
+ direction: "south",
273
+ path: [],
274
+ pathIndex: 0,
275
+ pathProgress: 0,
276
+ deskIndex: -1,
277
+ coffeeSpot: -1,
278
+ spawnOrder: agents.length,
279
+ coffeeHoldTimer: 0,
280
+ exitingAfterRest: false,
281
+ workActionTimer: 3 + Math.random() * 7,
282
+ workActionAnim: null,
283
+ workActionReverse: false,
284
+ workReverse: false,
285
+ coffeeReverse: false,
286
+ }
287
+ agents.push(a)
288
+ console.log(`[viz] Agent ${id} spawning`)
289
+ return a
290
+ }
291
+
292
+ function removeAgent(instanceId) {
293
+ const a = agents.find(a => a.instanceId === instanceId)
294
+ if (!a) return
295
+ // Release resources
296
+ if (a.coffeeSpot >= 0) reservedCoffeeSpots.delete(a.coffeeSpot)
297
+ a.deskIndex = -1
298
+ a.coffeeSpot = -1
299
+ // Walk to rest area first, then exit
300
+ const restPos = randomRestPos()
301
+ a.state = "walking"
302
+ a.goalState = "rest_area"
303
+ a.exitingAfterRest = true
304
+ a.path = findPath(a.x, a.y, restPos.x, restPos.y)
305
+ a.pathIndex = 0
306
+ a.pathProgress = 0
307
+ }
308
+
309
+ function assignAgentWork(a) {
310
+ if (a.state === "working" || a.state === "sitting" || a.state === "error") return
311
+ if (a.state === "walking_to_desk") return
312
+
313
+ // If still walking to rest area, defer work assignment
314
+ if (a.state === "walking" && a.goalState === "rest_area") {
315
+ a.pendingWork = true
316
+ return
317
+ }
318
+
319
+ // Release coffee spot if any
320
+ if (a.coffeeSpot >= 0) { reservedCoffeeSpots.delete(a.coffeeSpot); a.coffeeSpot = -1 }
321
+
322
+ // Get a desk
323
+ if (a.deskIndex < 0) {
324
+ a.deskIndex = getFreeDeskIndex()
325
+ }
326
+ if (a.deskIndex < 0) return // no free desks
327
+
328
+ const desk = DESKS[a.deskIndex]
329
+ const destX = a.skinId !== "person1" ? desk.deskX : desk.agentX
330
+ const destY = desk.agentY
331
+
332
+ const fromRest = a.state === "rest_area"
333
+ // Approach desk from the side: first go to offset point, then walk horizontally to desk
334
+ const approachX = desk.deskX < 200 ? destX + 25 : destX - 25
335
+
336
+ a.state = "walking_to_desk"
337
+ a.path = findPath(a.x, a.y, approachX, destY, false, fromRest)
338
+ a.path.push({ x: destX, y: destY })
339
+ a.pathIndex = 0
340
+ a.pathProgress = 0
341
+ console.log(`[viz] Agent ${a.id} walking to desk ${a.deskIndex}`)
342
+ }
343
+
344
+ function assignAgentCoffee(a) {
345
+ if (a.state === "coffee" || a.state === "walking_to_coffee") return
346
+
347
+ const spotIdx = getFreeCoffeeSpot()
348
+ if (spotIdx < 0) return // no free spots
349
+
350
+ reservedCoffeeSpots.add(spotIdx)
351
+ a.coffeeSpot = spotIdx
352
+
353
+ const spot = COFFEE_SPOTS[spotIdx]
354
+ const viaRest = a.deskIndex >= 0 && a.deskIndex <= 2
355
+ const fromRest = a.state === "rest_area"
356
+
357
+ a.state = "walking_to_coffee"
358
+ a.path = findPath(a.x, a.y, spot.x, spot.y, viaRest, fromRest)
359
+ a.pathIndex = 0
360
+ a.pathProgress = 0
361
+ console.log(`[viz] Agent ${a.id} walking to coffee spot ${spotIdx}`)
362
+ }
363
+
364
+ function updateAgent(a, dt) {
365
+ // State transitions
366
+ switch (a.state) {
367
+ case "spawning":
368
+ a.opacity = Math.min(1, a.opacity + dt * 2)
369
+ if (a.opacity >= 1) {
370
+ const pos = randomRestPos()
371
+ a.state = "walking"
372
+ a.path = [{x:a.x, y:a.y}, pos]
373
+ a.pathIndex = 0
374
+ a.pathProgress = 0
375
+ a.goalState = "rest_area"
376
+ a.opacity = 1
377
+ }
378
+ break
379
+
380
+ case "rest_area":
381
+ a.animKey = "idle"
382
+ a.direction = "south"
383
+ break
384
+
385
+ case "walking":
386
+ case "walking_to_desk":
387
+ case "walking_to_coffee":
388
+ case "walking_to_exit":
389
+ a.animKey = "walk"
390
+ break
391
+
392
+ case "sitting":
393
+ a.animKey = "sit"
394
+ a.direction = "south"
395
+ a.animTimer += dt
396
+ while (a.animTimer >= 1 / ANIM_FPS) {
397
+ a.animTimer -= 1 / ANIM_FPS
398
+ a.animFrame++
399
+ if (a.animFrame >= SKINS[a.skinId].anims["sit"].frames) {
400
+ a.animFrame = 0; a.animTimer = 0
401
+ a.state = "working"
402
+ }
403
+ }
404
+ break
405
+
406
+ case "working":
407
+ if (a.workActionAnim) {
408
+ a.animKey = a.workActionAnim
409
+ a.direction = "south"
410
+ a.animTimer += dt
411
+ while (a.animTimer >= 1 / ANIM_FPS) {
412
+ a.animTimer -= 1 / ANIM_FPS
413
+ if (a.workActionReverse) {
414
+ a.animFrame--
415
+ if (a.animFrame < 0) {
416
+ a.animFrame = 0
417
+ a.animTimer = 0
418
+ a.animKey = "work"
419
+ a.workActionAnim = null
420
+ a.workActionReverse = false
421
+ a.workActionTimer = 3 + Math.random() * 7
422
+ break
423
+ }
424
+ } else {
425
+ a.animFrame++
426
+ if (a.animFrame >= SKINS[a.skinId].anims[a.workActionAnim].frames) {
427
+ a.workActionReverse = true
428
+ a.animFrame = SKINS[a.skinId].anims[a.workActionAnim].frames - 2
429
+ if (a.animFrame < 0) { a.animFrame = 0; a.workActionReverse = false }
430
+ break
431
+ }
432
+ }
433
+ }
434
+ } else {
435
+ a.workActionTimer -= dt
436
+ if (a.workActionTimer <= 0) {
437
+ a.workActionAnim = WORK_ACTIONS[Math.floor(Math.random() * WORK_ACTIONS.length)]
438
+ a.animFrame = 0
439
+ a.animTimer = 0
440
+ a.workActionReverse = false
441
+ a.animKey = a.workActionAnim
442
+ } else {
443
+ a.animKey = "work"
444
+ a.direction = "south"
445
+ const def = SKINS[a.skinId].anims["work"]
446
+ a.animTimer += dt
447
+ while (a.animTimer >= 1 / ANIM_FPS) {
448
+ a.animTimer -= 1 / ANIM_FPS
449
+ if (a.workReverse) {
450
+ a.animFrame--
451
+ if (a.animFrame < 0) { a.animFrame = 1; a.workReverse = false }
452
+ } else {
453
+ a.animFrame++
454
+ if (a.animFrame >= def.frames) { a.animFrame = def.frames - 2; a.workReverse = true }
455
+ }
456
+ }
457
+ }
458
+ }
459
+ break
460
+
461
+ case "error":
462
+ a.animKey = "error"
463
+ a.direction = "south"
464
+ const errDef = SKINS[a.skinId].anims["error"]
465
+ if (!errDef) { a.state = "working"; break }
466
+ a.animTimer += dt
467
+ while (a.animTimer >= 1 / ANIM_FPS) {
468
+ a.animTimer -= 1 / ANIM_FPS
469
+ a.animFrame++
470
+ if (a.animFrame >= errDef.frames) {
471
+ a.animFrame = 0; a.animTimer = 0
472
+ a.state = "working"
473
+ }
474
+ }
475
+ break
476
+
477
+ case "standing":
478
+ a.animKey = "sit"
479
+ a.direction = "south"
480
+ a.animTimer += dt
481
+ while (a.animTimer >= 1 / ANIM_FPS) {
482
+ a.animTimer -= 1 / ANIM_FPS
483
+ a.animFrame--
484
+ if (a.animFrame < 0) {
485
+ a.animFrame = 0; a.animTimer = 0
486
+ // Release coffee spot reservation if we already have one
487
+ // (it was reserved in assignAgentCoffee or in idle handler)
488
+ if (a.coffeeSpot < 0) {
489
+ const spotIdx = getFreeCoffeeSpot()
490
+ if (spotIdx >= 0) {
491
+ reservedCoffeeSpots.add(spotIdx)
492
+ a.coffeeSpot = spotIdx
493
+ }
494
+ }
495
+ if (a.coffeeSpot >= 0) {
496
+ const spot = COFFEE_SPOTS[a.coffeeSpot]
497
+ const viaRest = a.deskIndex >= 0 && a.deskIndex <= 2
498
+ a.state = "walking_to_coffee"
499
+ a.path = findPath(a.x, a.y, spot.x, spot.y, viaRest)
500
+ a.pathIndex = 0; a.pathProgress = 0
501
+ } else {
502
+ a.state = "rest_area"
503
+ }
504
+ }
505
+ }
506
+ break
507
+
508
+ case "coffee":
509
+ a.animKey = "coffee"
510
+ a.direction = "south"
511
+ const coffeeDef = SKINS[a.skinId].anims["coffee"]
512
+ if (a.coffeeHoldTimer > 0) {
513
+ a.coffeeHoldTimer -= dt
514
+ if (a.coffeeHoldTimer <= 0) {
515
+ a.coffeeHoldTimer = 0
516
+ a.animTimer = 0
517
+ }
518
+ } else {
519
+ a.animTimer += dt
520
+ while (a.animTimer >= 1 / ANIM_FPS) {
521
+ a.animTimer -= 1 / ANIM_FPS
522
+ if (a.coffeeReverse) {
523
+ a.animFrame--
524
+ if (a.animFrame < 0) {
525
+ a.animFrame = 0
526
+ a.coffeeReverse = false
527
+ a.coffeeHoldTimer = 2.0
528
+ a.animTimer = 0
529
+ break
530
+ }
531
+ } else {
532
+ a.animFrame++
533
+ if (a.animFrame >= coffeeDef.frames) {
534
+ a.coffeeReverse = true
535
+ a.animFrame = coffeeDef.frames - 2
536
+ if (a.animFrame < 0) { a.animFrame = 0; a.coffeeReverse = false }
537
+ break
538
+ }
539
+ }
540
+ }
541
+ }
542
+ break
543
+
544
+ case "exiting":
545
+ a.opacity = Math.max(0, a.opacity - dt * 2)
546
+ if (a.opacity <= 0) {
547
+ const idx = agents.indexOf(a)
548
+ if (idx >= 0) agents.splice(idx, 1)
549
+ if (a.coffeeSpot >= 0) reservedCoffeeSpots.delete(a.coffeeSpot)
550
+ }
551
+ break
552
+ }
553
+
554
+ // Movement along path
555
+ while (a.path && a.path.length > 1 && a.pathIndex < a.path.length - 1) {
556
+ const from = a.path[a.pathIndex]
557
+ const to = a.path[a.pathIndex + 1]
558
+ const dx = to.x - from.x
559
+ const dy = to.y - from.y
560
+ const segLen = Math.sqrt(dx*dx + dy*dy)
561
+ if (segLen < 0.1) {
562
+ a.x = to.x
563
+ a.y = to.y
564
+ a.pathIndex++
565
+ a.pathProgress = 0
566
+ continue
567
+ }
568
+ a.pathProgress += (WALK_SPEED * dt) / segLen
569
+ if (a.pathProgress >= 1) {
570
+ a.x = to.x
571
+ a.y = to.y
572
+ a.pathIndex++
573
+ a.pathProgress = 0
574
+ if (a.pathIndex >= a.path.length - 1) {
575
+ const goal = a.path[a.pathIndex]
576
+ a.x = goal.x
577
+ a.y = goal.y
578
+ a.path = []
579
+ switch (a.state) {
580
+ case "walking": if (a.goalState === "rest_area") {
581
+ if (a.exitingAfterRest) {
582
+ a.exitingAfterRest = false
583
+ a.state = "walking_to_exit"
584
+ a.path = findPath(a.x, a.y, SPAWN_POINT.x, SPAWN_POINT.y, false, true)
585
+ a.pathIndex = 0
586
+ a.pathProgress = 0
587
+ } else {
588
+ a.state = "rest_area"
589
+ if (a.pendingWork) { a.pendingWork = false; assignAgentWork(a) }
590
+ }
591
+ } else {
592
+ a.state = "rest_area"
593
+ } break
594
+ case "walking_to_desk": a.state = "sitting"; a.animFrame = 0; a.animTimer = 0; break
595
+ case "walking_to_coffee": a.state = "coffee"; a.animFrame = 0; a.animTimer = 0; a.coffeeHoldTimer = 2.0; a.coffeeReverse = false; break
596
+ case "walking_to_exit": a.state = "exiting"; break
597
+ }
598
+ break
599
+ }
600
+ continue
601
+ }
602
+ a.x = from.x + dx * a.pathProgress
603
+ a.y = from.y + dy * a.pathProgress
604
+ if (Math.abs(dx) > Math.abs(dy)) {
605
+ a.direction = dx > 0 ? "east" : "west"
606
+ } else {
607
+ a.direction = dy > 0 ? "south" : "north"
608
+ }
609
+ a.animKey = "walk"
610
+ break
611
+ }
612
+
613
+ // Animation frame (skip for sit/stand/error/coffee/working — they manage their own frames)
614
+ if (a.state !== "sitting" && a.state !== "standing" && a.state !== "error" && a.state !== "coffee" && a.state !== "working") {
615
+ const def = SKINS[a.skinId].anims[a.animKey]
616
+ if (def) {
617
+ a.animTimer += dt
618
+ while (a.animTimer >= 1 / ANIM_FPS) {
619
+ a.animTimer -= 1 / ANIM_FPS
620
+ a.animFrame = (a.animFrame + 1) % def.frames
621
+ }
622
+ }
623
+ }
624
+
625
+ // Cap opacity
626
+ a.opacity = Math.max(0, Math.min(1, a.opacity))
627
+
628
+ }
629
+
630
+ // ============================================================
631
+ // RENDERING
632
+ // ============================================================
633
+ function drawAgent(a) {
634
+ const dir = a.direction || "south"
635
+ const frames = animFrames[a.skinId]?.[a.animKey]?.[dir] || animFrames[a.skinId]?.[a.animKey]?.["south"]
636
+ if (!frames || !frames.length) return
637
+ const img = frames[a.animFrame % frames.length]
638
+ if (!img) return
639
+
640
+ ctx.save()
641
+ ctx.globalAlpha = a.opacity
642
+ ctx.drawImage(img, a.x - img.width, a.y - img.height, img.width * 2, img.height * 2)
643
+ ctx.restore()
644
+ }
645
+
646
+ function drawAll() {
647
+ ctx.clearRect(0, 0, W, H)
648
+ ctx.imageSmoothingEnabled = false
649
+
650
+ // Background
651
+ if (floorPattern) {
652
+ ctx.fillStyle = floorPattern
653
+ ctx.fillRect(0, 0, W, H)
654
+ } else {
655
+ ctx.fillStyle = "#1a1a2e"
656
+ ctx.fillRect(0, 0, W, H)
657
+ }
658
+
659
+ // Collect all objects for Y-sorting — reuse array
660
+ drawObjects.length = 0
661
+
662
+ // Desks
663
+ if (deskImg) {
664
+ for (const d of DESKS) {
665
+ drawObjects.push({
666
+ y: d.deskY,
667
+ draw() { ctx.drawImage(deskImg, d.deskX - deskImg.width/2, d.deskY - deskImg.height/2) }
668
+ })
669
+ }
670
+ }
671
+
672
+ // Coffee tables
673
+ if (coffeeTableImg) {
674
+ for (const t of COFFEE_TABLES) {
675
+ drawObjects.push({
676
+ y: t.y,
677
+ draw() { ctx.drawImage(coffeeTableImg, t.x - coffeeTableImg.width/2, t.y - coffeeTableImg.height/2) }
678
+ })
679
+ }
680
+ }
681
+
682
+ // Agents
683
+ for (const a of agents) {
684
+ drawObjects.push({ y: a.y, draw: () => drawAgent(a) })
685
+ }
686
+
687
+ drawObjects.sort((a, b) => a.y - b.y)
688
+ for (const o of drawObjects) o.draw()
689
+
690
+ // Desk labels on top — project name for each occupied desk
691
+ ctx.font = "10px monospace"
692
+ ctx.textAlign = "center"
693
+ ctx.textBaseline = "top"
694
+ ctx.fillStyle = "#400d03"
695
+ const LABEL_W = 100, LINE_H = 12, MAX_LINES = 2
696
+ const seenDesks = new Set()
697
+ for (const a of agents) {
698
+ if (a.deskIndex < 0 || seenDesks.has(a.deskIndex)) continue
699
+ seenDesks.add(a.deskIndex)
700
+ const d = DESKS[a.deskIndex]
701
+ const name = (a.cwd || "").split("/").pop() || a.cwd || ""
702
+ const lines = []
703
+ let cur = ""
704
+ for (let i = 0; i < name.length; i++) {
705
+ const test = cur + name[i]
706
+ if (ctx.measureText(test).width > LABEL_W && cur.length > 0) {
707
+ lines.push(cur)
708
+ cur = name[i]
709
+ if (lines.length >= MAX_LINES) break
710
+ } else {
711
+ cur = test
712
+ }
713
+ }
714
+ if (cur && lines.length < MAX_LINES) lines.push(cur)
715
+ for (let li = 0; li < lines.length; li++) {
716
+ ctx.fillText(lines[li], d.deskX, d.deskY + li * LINE_H)
717
+ }
718
+ }
719
+ }
720
+
721
+ // ============================================================
722
+ // MAIN LOOP
723
+ // ============================================================
724
+ const canvas = document.getElementById("canvas")
725
+ const ctx = canvas.getContext("2d")
726
+
727
+ canvas.width = W * devicePixelRatio
728
+ canvas.height = H * devicePixelRatio
729
+ canvas.style.width = W + "px"
730
+ canvas.style.height = H + "px"
731
+ ctx.scale(devicePixelRatio, devicePixelRatio)
732
+
733
+ let lastTime = performance.now()
734
+ function loop(time) {
735
+ const dt = Math.min((time - lastTime) / 1000, 0.1)
736
+ lastTime = time
737
+
738
+ for (let i = agents.length - 1; i >= 0; i--) updateAgent(agents[i], dt)
739
+ drawAll()
740
+ requestAnimationFrame(loop)
741
+ }
742
+
743
+ // ============================================================
744
+ // WEBSOCKET
745
+ // ============================================================
746
+ function connect() {
747
+ const sock = new WebSocket(`ws://${location.host}`)
748
+ sock.onopen = () => sock.send(JSON.stringify({ type: "frontend.register" }))
749
+
750
+ sock.onmessage = (e) => {
751
+ let data
752
+ try { data = JSON.parse(e.data) } catch { return }
753
+
754
+ switch (data.type) {
755
+ case "state.sync":
756
+ agents.length = 0
757
+ agentIdCounter = 0
758
+ reservedCoffeeSpots.clear()
759
+ for (const inst of data.instances || []) {
760
+ createAgent(inst.instanceId, inst.cwd, inst.skin)
761
+ }
762
+ break
763
+ case "instance.added":
764
+ createAgent(data.instanceId, data.cwd, data.skin)
765
+ break
766
+ case "instance.removed":
767
+ removeAgent(data.instanceId)
768
+ break
769
+ case "instance.event": {
770
+ const a = agents.find(a => a.instanceId === data.instanceId)
771
+ if (!a) break
772
+ const evt = data.event
773
+ switch (evt.type) {
774
+ case "session.status": {
775
+ const st = evt.status?.type || evt.status
776
+ if (st === "busy") assignAgentWork(a)
777
+ else if (st === "retry") assignAgentWork(a)
778
+ else {
779
+ // idle: finish work, go to coffee
780
+ if (a.state === "working" || a.state === "error" || a.state === "sitting") {
781
+ a.state = "standing"
782
+ a.animFrame = SKINS[a.skinId].anims["sit"].frames - 1
783
+ a.animTimer = 0
784
+ } else if (a.state === "walking_to_desk") {
785
+ if (a.deskIndex >= 0) a.deskIndex = -1
786
+ const restPos = randomRestPos()
787
+ a.state = "walking"
788
+ a.goalState = "rest_area"
789
+ a.path = findPath(a.x, a.y, restPos.x, restPos.y, false, true)
790
+ a.pathIndex = 0
791
+ a.pathProgress = 0
792
+ } else if (a.state === "rest_area" || a.state === "coffee" || a.state === "walking" || a.state === "walking_to_exit") {
793
+ // Already doing something, don't interrupt
794
+ } else if (a.state !== "walking_to_coffee" && a.state !== "standing" && a.state !== "exiting" && a.state !== "spawning") {
795
+ assignAgentCoffee(a)
796
+ }
797
+ }
798
+ break
799
+ }
800
+ case "session.idle":
801
+ if (a.state === "working" || a.state === "error" || a.state === "sitting") {
802
+ a.state = "standing"
803
+ a.animFrame = SKINS[a.skinId].anims["sit"].frames - 1
804
+ a.animTimer = 0
805
+ } else if (a.state === "walking_to_desk") {
806
+ if (a.deskIndex >= 0) a.deskIndex = -1
807
+ const restPos = randomRestPos()
808
+ a.state = "walking"
809
+ a.goalState = "rest_area"
810
+ a.path = findPath(a.x, a.y, restPos.x, restPos.y, false, true)
811
+ a.pathIndex = 0
812
+ a.pathProgress = 0
813
+ } else if (a.state !== "walking_to_coffee" && a.state !== "walking" && a.state !== "coffee" && a.state !== "standing" && a.state !== "exiting" && a.state !== "spawning" && a.state !== "walking_to_exit") {
814
+ assignAgentCoffee(a)
815
+ }
816
+ break
817
+ case "session.error":
818
+ if (a.state === "working") {
819
+ a.state = "error"
820
+ a.animFrame = 0
821
+ a.animTimer = 0
822
+ }
823
+ break
824
+ case "tool.execute.after":
825
+ case "tool.execute.before":
826
+ if (a.state === "rest_area" || a.state === "coffee" || a.state === "walking_to_coffee" || a.state === "walking") {
827
+ if (a.coffeeSpot >= 0) { reservedCoffeeSpots.delete(a.coffeeSpot); a.coffeeSpot = -1 }
828
+ assignAgentWork(a)
829
+ }
830
+ break
831
+ case "session.diff":
832
+ break
833
+ }
834
+ break
835
+ }
836
+ }
837
+ }
838
+
839
+ sock.onclose = () => {
840
+ // Walk all agents to rest area, then exit
841
+ for (const a of agents) {
842
+ if (a.state === "walking_to_exit" || a.state === "exiting") continue
843
+ if (a.state === "walking" && a.exitingAfterRest) continue
844
+ if (a.coffeeSpot >= 0) reservedCoffeeSpots.delete(a.coffeeSpot)
845
+ a.deskIndex = -1
846
+ a.coffeeSpot = -1
847
+ const restPos = randomRestPos()
848
+ a.state = "walking"
849
+ a.goalState = "rest_area"
850
+ a.exitingAfterRest = true
851
+ a.path = findPath(a.x, a.y, restPos.x, restPos.y)
852
+ a.pathIndex = 0
853
+ a.pathProgress = 0
854
+ }
855
+ setTimeout(connect, 2000)
856
+ }
857
+ }
858
+
859
+ // ============================================================
860
+ // START
861
+ // ============================================================
862
+ preloadAll().then(() => {
863
+ console.log("[viz] Assets loaded")
864
+ connect()
865
+ requestAnimationFrame(loop)
866
+ })
867
+ </script>
868
+ </body>
869
+ </html>