Initial Commit

This commit is contained in:
ookami125 2026-08-18 00:52:29 -04:00
commit 35f3810632
90 changed files with 29267 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__

View file

@ -0,0 +1,788 @@
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":24767,"arena":"drift","attempt":1,"arena_seconds":0,"type":"session_started","viewport":[1379,805]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":24768,"arena":"drift","attempt":1,"arena_seconds":0,"type":"arena_started","reason":"session_started","enemies":[{"id":1,"kind":"wisp","position":[0.18,0.2]},{"id":2,"kind":"wisp","position":[0.5,0.14]},{"id":3,"kind":"wisp","position":[0.82,0.21]},{"id":4,"kind":"wisp","position":[0.2,0.72]},{"id":5,"kind":"wisp","position":[0.8,0.76]},{"id":6,"kind":"wisp","position":[0.5,0.84]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":26283,"arena":"drift","attempt":1,"arena_seconds":1.516,"type":"arena_snapshot","player":[0.5,0.54],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":26939,"arena":"drift","attempt":1,"arena_seconds":2.167,"type":"strike_used","source":"pointer","aim":[0.026,-1],"hits":0,"reflections":0,"position":[0.5,0.54]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":27686,"arena":"drift","attempt":1,"arena_seconds":2.917,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":27783,"arena":"drift","attempt":1,"arena_seconds":3.016,"type":"arena_snapshot","player":[0.5,0.535],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28220,"arena":"drift","attempt":1,"arena_seconds":3.449,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28629,"arena":"drift","attempt":1,"arena_seconds":3.849,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28717,"arena":"drift","attempt":1,"arena_seconds":3.95,"type":"enemy_damaged","enemy_id":2,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.5,0.276]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28718,"arena":"drift","attempt":1,"arena_seconds":3.95,"type":"enemy_killed","enemy_id":2,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28718,"arena":"drift","attempt":1,"arena_seconds":3.95,"type":"strike_used","source":"pointer","aim":[-0.03,-1],"hits":1,"reflections":0,"position":[0.5,0.411]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":28767,"arena":"drift","attempt":1,"arena_seconds":4,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":29266,"arena":"drift","attempt":1,"arena_seconds":4.5,"type":"arena_snapshot","player":[0.5,0.431],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"wisp":5},"projectiles":0,"actions":{"strikes":2,"strikeHits":1,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":29590,"arena":"drift","attempt":1,"arena_seconds":4.816,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":29695,"arena":"drift","attempt":1,"arena_seconds":4.917,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":30253,"arena":"drift","attempt":1,"arena_seconds":5.483,"type":"tether_used","source":"pointer","hit":true,"target_id":3,"target_kind":"wisp","effect":"enemy_pulled","distance":0.187}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":30783,"arena":"drift","attempt":1,"arena_seconds":6.016,"type":"arena_snapshot","player":[0.5,0.408],"health":5,"cooldowns":{"strike":0,"tether":0.187,"dash":0},"remaining":5,"enemy_counts":{"wisp":5},"projectiles":0,"actions":{"strikes":2,"strikeHits":1,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":30894,"arena":"drift","attempt":1,"arena_seconds":6.116,"type":"enemy_damaged","enemy_id":3,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.613,0.361]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":30894,"arena":"drift","attempt":1,"arena_seconds":6.116,"type":"enemy_killed","enemy_id":3,"enemy_kind":"wisp","cause":"strike","chain":2,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":30894,"arena":"drift","attempt":1,"arena_seconds":6.116,"type":"strike_used","source":"pointer","aim":[0.944,-0.331],"hits":1,"reflections":0,"position":[0.5,0.408]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":31235,"arena":"drift","attempt":1,"arena_seconds":6.466,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":31546,"arena":"drift","attempt":1,"arena_seconds":6.767,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":32282,"arena":"drift","attempt":1,"arena_seconds":7.516,"type":"arena_snapshot","player":[0.579,0.407],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"wisp":4},"projectiles":0,"actions":{"strikes":3,"strikeHits":2,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":32788,"arena":"drift","attempt":1,"arena_seconds":8.017,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":32918,"arena":"drift","attempt":1,"arena_seconds":8.149,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":33782,"arena":"drift","attempt":1,"arena_seconds":9.016,"type":"arena_snapshot","player":[0.583,0.372],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"wisp":4},"projectiles":0,"actions":{"strikes":3,"strikeHits":2,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34044,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_damaged","enemy_id":1,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.466,0.372]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34044,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_killed","enemy_id":1,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34045,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_damaged","enemy_id":5,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.599,0.497]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34045,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_killed","enemy_id":5,"enemy_kind":"wisp","cause":"strike","chain":2,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34045,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_damaged","enemy_id":6,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.531,0.502]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34045,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"enemy_killed","enemy_id":6,"enemy_kind":"wisp","cause":"strike","chain":3,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":34045,"arena":"drift","attempt":1,"arena_seconds":9.266,"type":"strike_used","source":"pointer","aim":[-0.737,0.675],"hits":3,"reflections":0,"position":[0.583,0.371]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35284,"arena":"drift","attempt":1,"arena_seconds":10.517,"type":"arena_snapshot","player":[0.583,0.371],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":1,"enemy_counts":{"wisp":1},"projectiles":0,"actions":{"strikes":4,"strikeHits":5,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":0,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35417,"arena":"drift","attempt":1,"arena_seconds":10.649,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35646,"arena":"drift","attempt":1,"arena_seconds":10.866,"type":"enemy_damaged","enemy_id":4,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.501,0.453]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35646,"arena":"drift","attempt":1,"arena_seconds":10.866,"type":"enemy_killed","enemy_id":4,"enemy_kind":"wisp","cause":"strike","chain":4,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35646,"arena":"drift","attempt":1,"arena_seconds":10.866,"type":"strike_used","source":"pointer","aim":[-0.928,0.372],"hits":1,"reflections":0,"position":[0.583,0.389]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":35649,"arena":"drift","attempt":1,"arena_seconds":10.883,"type":"arena_completed","duration_seconds":10.883,"actions":{"strikes":5,"strikeHits":6,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":0,"kills":6},"max_chain":4,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":36128,"arena":"drift","attempt":1,"arena_seconds":10.883,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":62287,"arena":"crossfire","attempt":2,"arena_seconds":0,"type":"arena_started","reason":"selected","enemies":[{"id":7,"kind":"gunner","position":[0.14,0.15]},{"id":8,"kind":"gunner","position":[0.86,0.15]},{"id":9,"kind":"gunner","position":[0.5,0.86]},{"id":10,"kind":"wisp","position":[0.25,0.46]},{"id":11,"kind":"wisp","position":[0.75,0.46]},{"id":12,"kind":"wisp","position":[0.5,0.25]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63175,"arena":"crossfire","attempt":2,"arena_seconds":0.884,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63232,"arena":"crossfire","attempt":2,"arena_seconds":0.95,"type":"projectile_fired","projectile_id":1,"enemy_id":7,"position":[0.168,0.184],"direction":[0.68,0.733]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63265,"arena":"crossfire","attempt":2,"arena_seconds":0.983,"type":"projectile_fired","projectile_id":2,"enemy_id":9,"position":[0.503,0.826],"direction":[-0.01,-1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63336,"arena":"crossfire","attempt":2,"arena_seconds":1.05,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63572,"arena":"crossfire","attempt":2,"arena_seconds":1.283,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63783,"arena":"crossfire","attempt":2,"arena_seconds":1.5,"type":"arena_snapshot","player":[0.5,0.56],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":2,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":63999,"arena":"crossfire","attempt":2,"arena_seconds":1.716,"type":"projectile_fired","projectile_id":3,"enemy_id":8,"position":[0.816,0.187],"direction":[-0.684,0.729]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":64007,"arena":"crossfire","attempt":2,"arena_seconds":1.716,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":64123,"arena":"crossfire","attempt":2,"arena_seconds":1.834,"type":"dash_used","source":"keyboard","direction":[0.707,-0.707],"position":[0.504,0.501]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":64148,"arena":"crossfire","attempt":2,"arena_seconds":1.85,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":64448,"arena":"crossfire","attempt":2,"arena_seconds":2.151,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":64712,"arena":"crossfire","attempt":2,"arena_seconds":2.417,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65283,"arena":"crossfire","attempt":2,"arena_seconds":3,"type":"arena_snapshot","player":[0.741,0.483],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":3,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65448,"arena":"crossfire","attempt":2,"arena_seconds":3.15,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65499,"arena":"crossfire","attempt":2,"arena_seconds":3.217,"type":"projectile_fired","projectile_id":4,"enemy_id":7,"position":[0.198,0.21],"direction":[0.858,0.514]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65530,"arena":"crossfire","attempt":2,"arena_seconds":3.233,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65649,"arena":"crossfire","attempt":2,"arena_seconds":3.367,"type":"projectile_fired","projectile_id":5,"enemy_id":9,"position":[0.543,0.815],"direction":[0.607,-0.795]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65701,"arena":"crossfire","attempt":2,"arena_seconds":3.417,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65768,"arena":"crossfire","attempt":2,"arena_seconds":3.484,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65911,"arena":"crossfire","attempt":2,"arena_seconds":3.617,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65977,"arena":"crossfire","attempt":2,"arena_seconds":3.683,"type":"enemy_damaged","enemy_id":11,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.691,0.466]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65977,"arena":"crossfire","attempt":2,"arena_seconds":3.683,"type":"enemy_killed","enemy_id":11,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":65977,"arena":"crossfire","attempt":2,"arena_seconds":3.683,"type":"strike_used","source":"pointer","aim":[-0.318,-0.948],"hits":1,"reflections":0,"position":[0.693,0.575]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":66077,"arena":"crossfire","attempt":2,"arena_seconds":3.784,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":66351,"arena":"crossfire","attempt":2,"arena_seconds":4.067,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":66499,"arena":"crossfire","attempt":2,"arena_seconds":4.217,"type":"projectile_fired","projectile_id":6,"enemy_id":8,"position":[0.808,0.212],"direction":[-0.221,0.975]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":66783,"arena":"crossfire","attempt":2,"arena_seconds":4.5,"type":"arena_snapshot","player":[0.779,0.522],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":5,"actions":{"strikes":1,"strikeHits":1,"reflections":0,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":66943,"arena":"crossfire","attempt":2,"arena_seconds":4.65,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":67172,"arena":"crossfire","attempt":2,"arena_seconds":4.883,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":67356,"arena":"crossfire","attempt":2,"arena_seconds":5.067,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":67600,"arena":"crossfire","attempt":2,"arena_seconds":5.317,"type":"projectile_fired","projectile_id":7,"enemy_id":9,"position":[0.567,0.806],"direction":[0.677,-0.736]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":67799,"arena":"crossfire","attempt":2,"arena_seconds":5.5,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":67999,"arena":"crossfire","attempt":2,"arena_seconds":5.717,"type":"projectile_fired","projectile_id":8,"enemy_id":7,"position":[0.236,0.23],"direction":[0.993,0.118]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":68283,"arena":"crossfire","attempt":2,"arena_seconds":6,"type":"arena_snapshot","player":[0.908,0.255],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":5,"actions":{"strikes":1,"strikeHits":1,"reflections":0,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":68363,"arena":"crossfire","attempt":2,"arena_seconds":6.066,"type":"enemy_damaged","enemy_id":8,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":2,"position":[0.799,0.19]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":68364,"arena":"crossfire","attempt":2,"arena_seconds":6.066,"type":"strike_used","source":"pointer","aim":[-0.989,-0.147],"hits":1,"reflections":0,"position":[0.897,0.241]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":68445,"arena":"crossfire","attempt":2,"arena_seconds":6.15,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":68932,"arena":"crossfire","attempt":2,"arena_seconds":6.65,"type":"projectile_fired","projectile_id":9,"enemy_id":8,"position":[0.778,0.162],"direction":[0.755,-0.655]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69037,"arena":"crossfire","attempt":2,"arena_seconds":6.751,"type":"enemy_damaged","enemy_id":8,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":1,"position":[0.747,0.185]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69037,"arena":"crossfire","attempt":2,"arena_seconds":6.751,"type":"strike_used","source":"pointer","aim":[-0.495,0.869],"hits":1,"reflections":1,"position":[0.842,0.078]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69066,"arena":"crossfire","attempt":2,"arena_seconds":6.767,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69104,"arena":"crossfire","attempt":2,"arena_seconds":6.817,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69200,"arena":"crossfire","attempt":2,"arena_seconds":6.917,"type":"enemy_damaged","enemy_id":8,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.736,0.2]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69200,"arena":"crossfire","attempt":2,"arena_seconds":6.917,"type":"enemy_killed","enemy_id":8,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69616,"arena":"crossfire","attempt":2,"arena_seconds":7.333,"type":"projectile_fired","projectile_id":10,"enemy_id":9,"position":[0.58,0.773],"direction":[0.225,-0.974]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69799,"arena":"crossfire","attempt":2,"arena_seconds":7.517,"type":"arena_snapshot","player":[0.706,0.032],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":3,"strikeHits":3,"reflections":1,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":69866,"arena":"crossfire","attempt":2,"arena_seconds":7.583,"type":"projectile_fired","projectile_id":11,"enemy_id":7,"position":[0.267,0.221],"direction":[0.913,-0.408]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70203,"arena":"crossfire","attempt":2,"arena_seconds":7.917,"type":"movement_key_down","key":"KeyD","active_keys":["KeyA","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70276,"arena":"crossfire","attempt":2,"arena_seconds":7.983,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70276,"arena":"crossfire","attempt":2,"arena_seconds":7.983,"type":"movement_key_up","key":"KeyA","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70422,"arena":"crossfire","attempt":2,"arena_seconds":8.134,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70513,"arena":"crossfire","attempt":2,"arena_seconds":8.217,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70661,"arena":"crossfire","attempt":2,"arena_seconds":8.367,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70757,"arena":"crossfire","attempt":2,"arena_seconds":8.466,"type":"strike_used","source":"pointer","aim":[-0.934,0.357],"hits":0,"reflections":1,"position":[0.57,0.09]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":70975,"arena":"crossfire","attempt":2,"arena_seconds":8.684,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71158,"arena":"crossfire","attempt":2,"arena_seconds":8.867,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71249,"arena":"crossfire","attempt":2,"arena_seconds":8.967,"type":"enemy_damaged","enemy_id":7,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.26,0.233]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71283,"arena":"crossfire","attempt":2,"arena_seconds":9,"type":"arena_snapshot","player":[0.552,0.2],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":1,"actions":{"strikes":4,"strikeHits":3,"reflections":2,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71747,"arena":"crossfire","attempt":2,"arena_seconds":9.451,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71802,"arena":"crossfire","attempt":2,"arena_seconds":9.517,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":71899,"arena":"crossfire","attempt":2,"arena_seconds":9.616,"type":"projectile_fired","projectile_id":12,"enemy_id":9,"position":[0.577,0.733],"direction":[-0.305,-0.952]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":72379,"arena":"crossfire","attempt":2,"arena_seconds":10.084,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":72399,"arena":"crossfire","attempt":2,"arena_seconds":10.117,"type":"projectile_fired","projectile_id":13,"enemy_id":7,"position":[0.234,0.273],"direction":[0.894,0.448]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":72748,"arena":"crossfire","attempt":2,"arena_seconds":10.451,"type":"strike_used","source":"pointer","aim":[-0.263,-0.965],"hits":0,"reflections":1,"position":[0.343,0.42]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":72799,"arena":"crossfire","attempt":2,"arena_seconds":10.517,"type":"arena_snapshot","player":[0.333,0.433],"health":5,"cooldowns":{"strike":0.244,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":5,"strikeHits":3,"reflections":3,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":73421,"arena":"crossfire","attempt":2,"arena_seconds":11.133,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":74066,"arena":"crossfire","attempt":2,"arena_seconds":11.783,"type":"projectile_fired","projectile_id":14,"enemy_id":9,"position":[0.542,0.728],"direction":[-0.999,-0.034]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":74093,"arena":"crossfire","attempt":2,"arena_seconds":11.801,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":74283,"arena":"crossfire","attempt":2,"arena_seconds":12,"type":"arena_snapshot","player":[0.17,0.757],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":2,"actions":{"strikes":5,"strikeHits":3,"reflections":3,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":74533,"arena":"crossfire","attempt":2,"arena_seconds":12.251,"type":"projectile_fired","projectile_id":15,"enemy_id":7,"position":[0.178,0.303],"direction":[-0.021,1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":74673,"arena":"crossfire","attempt":2,"arena_seconds":12.383,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75100,"arena":"crossfire","attempt":2,"arena_seconds":12.817,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75160,"arena":"crossfire","attempt":2,"arena_seconds":12.866,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75218,"arena":"crossfire","attempt":2,"arena_seconds":12.934,"type":"strike_used","source":"pointer","aim":[0.997,0.073],"hits":0,"reflections":1,"position":[0.172,0.715]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75782,"arena":"crossfire","attempt":2,"arena_seconds":13.5,"type":"arena_snapshot","player":[0.28,0.675],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":2,"actions":{"strikes":6,"strikeHits":3,"reflections":4,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75799,"arena":"crossfire","attempt":2,"arena_seconds":13.5,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75849,"arena":"crossfire","attempt":2,"arena_seconds":13.566,"type":"enemy_damaged","enemy_id":9,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.546,0.718]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":75874,"arena":"crossfire","attempt":2,"arena_seconds":13.583,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76009,"arena":"crossfire","attempt":2,"arena_seconds":13.717,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76131,"arena":"crossfire","attempt":2,"arena_seconds":13.834,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76409,"arena":"crossfire","attempt":2,"arena_seconds":14.117,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76416,"arena":"crossfire","attempt":2,"arena_seconds":14.134,"type":"projectile_fired","projectile_id":16,"enemy_id":9,"position":[0.557,0.716],"direction":[-0.996,-0.088]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76599,"arena":"crossfire","attempt":2,"arena_seconds":14.317,"type":"projectile_fired","projectile_id":17,"enemy_id":7,"position":[0.187,0.336],"direction":[0.473,0.881]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":76717,"arena":"crossfire","attempt":2,"arena_seconds":14.433,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77283,"arena":"crossfire","attempt":2,"arena_seconds":15,"type":"arena_snapshot","player":[0.281,0.806],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":6,"strikeHits":3,"reflections":4,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77313,"arena":"crossfire","attempt":2,"arena_seconds":15.016,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77389,"arena":"crossfire","attempt":2,"arena_seconds":15.1,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77673,"arena":"crossfire","attempt":2,"arena_seconds":15.384,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77701,"arena":"crossfire","attempt":2,"arena_seconds":15.417,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":77723,"arena":"crossfire","attempt":2,"arena_seconds":15.433,"type":"strike_used","source":"pointer","aim":[-0.537,-0.844],"hits":0,"reflections":1,"position":[0.26,0.811]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78056,"arena":"crossfire","attempt":2,"arena_seconds":15.766,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78120,"arena":"crossfire","attempt":2,"arena_seconds":15.834,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78283,"arena":"crossfire","attempt":2,"arena_seconds":16,"type":"projectile_fired","projectile_id":18,"enemy_id":9,"position":[0.575,0.716],"direction":[-0.998,0.069]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78465,"arena":"crossfire","attempt":2,"arena_seconds":16.183,"type":"projectile_fired","projectile_id":19,"enemy_id":7,"position":[0.179,0.375],"direction":[0.188,0.982]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78611,"arena":"crossfire","attempt":2,"arena_seconds":16.317,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78799,"arena":"crossfire","attempt":2,"arena_seconds":16.517,"type":"arena_snapshot","player":[0.177,0.727],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":7,"strikeHits":3,"reflections":5,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":78893,"arena":"crossfire","attempt":2,"arena_seconds":16.6,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":79163,"arena":"crossfire","attempt":2,"arena_seconds":16.867,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":79303,"arena":"crossfire","attempt":2,"arena_seconds":17.016,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":79535,"arena":"crossfire","attempt":2,"arena_seconds":17.25,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":79734,"arena":"crossfire","attempt":2,"arena_seconds":17.45,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":79843,"arena":"crossfire","attempt":2,"arena_seconds":17.55,"type":"strike_used","source":"pointer","aim":[-0.027,-1],"hits":0,"reflections":2,"position":[0.197,0.835]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80048,"arena":"crossfire","attempt":2,"arena_seconds":17.75,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80215,"arena":"crossfire","attempt":2,"arena_seconds":17.933,"type":"projectile_fired","projectile_id":20,"enemy_id":9,"position":[0.545,0.715],"direction":[-0.981,0.193]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80283,"arena":"crossfire","attempt":2,"arena_seconds":18,"type":"arena_snapshot","player":[0.225,0.772],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":8,"strikeHits":3,"reflections":7,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80289,"arena":"crossfire","attempt":2,"arena_seconds":18,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80632,"arena":"crossfire","attempt":2,"arena_seconds":18.35,"type":"enemy_damaged","enemy_id":7,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.163,0.382]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80633,"arena":"crossfire","attempt":2,"arena_seconds":18.35,"type":"enemy_killed","enemy_id":7,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80864,"arena":"crossfire","attempt":2,"arena_seconds":18.567,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":80933,"arena":"crossfire","attempt":2,"arena_seconds":18.633,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":81257,"arena":"crossfire","attempt":2,"arena_seconds":18.967,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":81348,"arena":"crossfire","attempt":2,"arena_seconds":19.05,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":81783,"arena":"crossfire","attempt":2,"arena_seconds":19.5,"type":"arena_snapshot","player":[0.068,0.714],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"gunner":1,"wisp":2},"projectiles":1,"actions":{"strikes":8,"strikeHits":3,"reflections":7,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":3}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":81976,"arena":"crossfire","attempt":2,"arena_seconds":19.683,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":81988,"arena":"crossfire","attempt":2,"arena_seconds":19.7,"type":"strike_used","source":"pointer","aim":[0.957,-0.29],"hits":0,"reflections":1,"position":[0.066,0.756]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82266,"arena":"crossfire","attempt":2,"arena_seconds":19.967,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82416,"arena":"crossfire","attempt":2,"arena_seconds":20.133,"type":"projectile_fired","projectile_id":21,"enemy_id":9,"position":[0.504,0.708],"direction":[-0.976,0.217]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82445,"arena":"crossfire","attempt":2,"arena_seconds":20.151,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82546,"arena":"crossfire","attempt":2,"arena_seconds":20.25,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82933,"arena":"crossfire","attempt":2,"arena_seconds":20.65,"type":"enemy_damaged","enemy_id":9,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.527,0.699]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":82933,"arena":"crossfire","attempt":2,"arena_seconds":20.65,"type":"enemy_killed","enemy_id":9,"enemy_kind":"gunner","cause":"reflected_projectile","chain":2,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":83299,"arena":"crossfire","attempt":2,"arena_seconds":21.017,"type":"arena_snapshot","player":[0.25,0.816],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":2,"enemy_counts":{"wisp":2},"projectiles":1,"actions":{"strikes":9,"strikeHits":3,"reflections":8,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":4}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":83505,"arena":"crossfire","attempt":2,"arena_seconds":21.217,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":83531,"arena":"crossfire","attempt":2,"arena_seconds":21.234,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84072,"arena":"crossfire","attempt":2,"arena_seconds":21.783,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84130,"arena":"crossfire","attempt":2,"arena_seconds":21.834,"type":"enemy_damaged","enemy_id":10,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.26,0.675]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84130,"arena":"crossfire","attempt":2,"arena_seconds":21.834,"type":"enemy_killed","enemy_id":10,"enemy_kind":"wisp","cause":"strike","chain":3,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84131,"arena":"crossfire","attempt":2,"arena_seconds":21.834,"type":"enemy_damaged","enemy_id":12,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.33,0.604]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84131,"arena":"crossfire","attempt":2,"arena_seconds":21.834,"type":"enemy_killed","enemy_id":12,"enemy_kind":"wisp","cause":"strike","chain":4,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84131,"arena":"crossfire","attempt":2,"arena_seconds":21.834,"type":"strike_used","source":"pointer","aim":[-0.89,-0.455],"hits":2,"reflections":0,"position":[0.371,0.722]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":84132,"arena":"crossfire","attempt":2,"arena_seconds":21.85,"type":"arena_completed","duration_seconds":21.85,"actions":{"strikes":10,"strikeHits":5,"reflections":8,"tethers":0,"tetherHits":0,"dashes":1,"dashHits":0,"collisions":0,"kills":6},"max_chain":4,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":99467,"arena":"weight","attempt":3,"arena_seconds":0,"type":"arena_started","reason":"selected","enemies":[{"id":13,"kind":"anchor","position":[0.18,0.2]},{"id":14,"kind":"anchor","position":[0.82,0.2]},{"id":15,"kind":"gunner","position":[0.18,0.79]},{"id":16,"kind":"gunner","position":[0.82,0.79]},{"id":17,"kind":"wisp","position":[0.34,0.28]},{"id":18,"kind":"wisp","position":[0.66,0.28]},{"id":19,"kind":"wisp","position":[0.32,0.72]},{"id":20,"kind":"wisp","position":[0.68,0.72]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100521,"arena":"weight","attempt":3,"arena_seconds":1.066,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100532,"arena":"weight","attempt":3,"arena_seconds":1.082,"type":"projectile_fired","projectile_id":22,"enemy_id":16,"position":[0.786,0.759],"direction":[-0.793,-0.609]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100662,"arena":"weight","attempt":3,"arena_seconds":1.2,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100782,"arena":"weight","attempt":3,"arena_seconds":1.333,"type":"anchor_windup","enemy_id":14,"direction":[-0.706,0.709],"position":[0.815,0.205]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100808,"arena":"weight","attempt":3,"arena_seconds":1.35,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100888,"arena":"weight","attempt":3,"arena_seconds":1.433,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":100966,"arena":"weight","attempt":3,"arena_seconds":1.516,"type":"arena_snapshot","player":[0.527,0.494],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":8,"enemy_counts":{"anchor":2,"gunner":2,"wisp":4},"projectiles":1,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":101049,"arena":"weight","attempt":3,"arena_seconds":1.6,"type":"projectile_fired","projectile_id":23,"enemy_id":15,"position":[0.226,0.759],"direction":[0.75,-0.661]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":101105,"arena":"weight","attempt":3,"arena_seconds":1.65,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":101516,"arena":"weight","attempt":3,"arena_seconds":2.067,"type":"anchor_charged","enemy_id":14,"direction":[-0.706,0.709],"position":[0.814,0.207]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":101668,"arena":"weight","attempt":3,"arena_seconds":2.217,"type":"tether_used","source":"pointer","hit":true,"target_id":14,"target_kind":"anchor","effect":"player_pulled","distance":0.206}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":101966,"arena":"weight","attempt":3,"arena_seconds":2.516,"type":"anchor_windup","enemy_id":13,"direction":[0.949,0.315],"position":[0.195,0.213]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102083,"arena":"weight","attempt":3,"arena_seconds":2.633,"type":"enemy_damaged","enemy_id":14,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":4,"position":[0.677,0.345]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102083,"arena":"weight","attempt":3,"arena_seconds":2.633,"type":"enemy_damaged","enemy_id":18,"enemy_kind":"wisp","amount":2,"cause":"body_collision","hp_after":0,"position":[0.628,0.353]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102083,"arena":"weight","attempt":3,"arena_seconds":2.633,"type":"enemy_killed","enemy_id":18,"enemy_kind":"wisp","cause":"body_collision","chain":1,"remaining_after":7}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102083,"arena":"weight","attempt":3,"arena_seconds":2.633,"type":"damaging_collision","a_id":14,"b_id":18,"relative_speed":0.217,"position":[0.652,0.349]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102315,"arena":"weight","attempt":3,"arena_seconds":2.849,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102389,"arena":"weight","attempt":3,"arena_seconds":2.933,"type":"strike_used","source":"pointer","aim":[-0.976,0.217],"hits":0,"reflections":0,"position":[0.858,0.377]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102396,"arena":"weight","attempt":3,"arena_seconds":2.933,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102466,"arena":"weight","attempt":3,"arena_seconds":3.016,"type":"arena_snapshot","player":[0.876,0.369],"health":5,"cooldowns":{"strike":0.227,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":2,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102699,"arena":"weight","attempt":3,"arena_seconds":3.25,"type":"anchor_charged","enemy_id":13,"direction":[0.949,0.315],"position":[0.197,0.215]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102737,"arena":"weight","attempt":3,"arena_seconds":3.283,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":102899,"arena":"weight","attempt":3,"arena_seconds":3.45,"type":"projectile_fired","projectile_id":24,"enemy_id":16,"position":[0.811,0.706],"direction":[0.229,-0.973]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103019,"arena":"weight","attempt":3,"arena_seconds":3.566,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103212,"arena":"weight","attempt":3,"arena_seconds":3.75,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103216,"arena":"weight","attempt":3,"arena_seconds":3.766,"type":"projectile_fired","projectile_id":25,"enemy_id":15,"position":[0.265,0.746],"direction":[0.765,-0.644]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103300,"arena":"weight","attempt":3,"arena_seconds":3.85,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103378,"arena":"weight","attempt":3,"arena_seconds":3.916,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103439,"arena":"weight","attempt":3,"arena_seconds":3.983,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103730,"arena":"weight","attempt":3,"arena_seconds":4.267,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103858,"arena":"weight","attempt":3,"arena_seconds":4.4,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103967,"arena":"weight","attempt":3,"arena_seconds":4.517,"type":"arena_snapshot","player":[0.886,0.241],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":4,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":103968,"arena":"weight","attempt":3,"arena_seconds":4.517,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":104249,"arena":"weight","attempt":3,"arena_seconds":4.8,"type":"player_damaged","amount":1,"cause":"projectile","source_id":23,"health_after":4,"position":[0.843,0.239]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":104898,"arena":"weight","attempt":3,"arena_seconds":5.433,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":104965,"arena":"weight","attempt":3,"arena_seconds":5.516,"type":"projectile_fired","projectile_id":26,"enemy_id":16,"position":[0.813,0.67],"direction":[-0.189,-0.982]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":105249,"arena":"weight","attempt":3,"arena_seconds":5.8,"type":"projectile_fired","projectile_id":27,"enemy_id":15,"position":[0.291,0.722],"direction":[0.498,-0.867]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":105467,"arena":"weight","attempt":3,"arena_seconds":6.017,"type":"arena_snapshot","player":[0.586,0.101],"health":4,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":4,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":105616,"arena":"weight","attempt":3,"arena_seconds":6.166,"type":"anchor_windup","enemy_id":14,"direction":[-0.052,-0.999],"position":[0.568,0.513]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106139,"arena":"weight","attempt":3,"arena_seconds":6.683,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106232,"arena":"weight","attempt":3,"arena_seconds":6.783,"type":"anchor_windup","enemy_id":13,"direction":[-0.761,-0.649],"position":[0.647,0.325]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106340,"arena":"weight","attempt":3,"arena_seconds":6.883,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106349,"arena":"weight","attempt":3,"arena_seconds":6.899,"type":"anchor_charged","enemy_id":14,"direction":[-0.052,-0.999],"position":[0.568,0.512]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106966,"arena":"weight","attempt":3,"arena_seconds":7.516,"type":"anchor_charged","enemy_id":13,"direction":[-0.761,-0.649],"position":[0.65,0.324]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":106966,"arena":"weight","attempt":3,"arena_seconds":7.516,"type":"arena_snapshot","player":[0.296,0.234],"health":4,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":3,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107049,"arena":"weight","attempt":3,"arena_seconds":7.6,"type":"enemy_damaged","enemy_id":13,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":4,"position":[0.621,0.298]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107049,"arena":"weight","attempt":3,"arena_seconds":7.6,"type":"enemy_damaged","enemy_id":14,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":2,"position":[0.559,0.29]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107049,"arena":"weight","attempt":3,"arena_seconds":7.6,"type":"damaging_collision","a_id":13,"b_id":14,"relative_speed":0.308,"position":[0.59,0.294]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107249,"arena":"weight","attempt":3,"arena_seconds":7.8,"type":"projectile_fired","projectile_id":28,"enemy_id":16,"position":[0.785,0.643],"direction":[-0.828,-0.561]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107567,"arena":"weight","attempt":3,"arena_seconds":8.116,"type":"projectile_fired","projectile_id":29,"enemy_id":15,"position":[0.297,0.681],"direction":[-0.029,-1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":107986,"arena":"weight","attempt":3,"arena_seconds":8.533,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":108067,"arena":"weight","attempt":3,"arena_seconds":8.617,"type":"player_damaged","amount":1,"cause":"projectile","source_id":29,"health_after":3,"position":[0.285,0.529]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":108106,"arena":"weight","attempt":3,"arena_seconds":8.65,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":108466,"arena":"weight","attempt":3,"arena_seconds":9.016,"type":"arena_snapshot","player":[0.226,0.589],"health":3,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":2,"actions":{"strikes":1,"strikeHits":0,"reflections":0,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":108499,"arena":"weight","attempt":3,"arena_seconds":9.033,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":108576,"arena":"weight","attempt":3,"arena_seconds":9.117,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109016,"arena":"weight","attempt":3,"arena_seconds":9.567,"type":"projectile_fired","projectile_id":30,"enemy_id":16,"position":[0.754,0.643],"direction":[-0.998,0.065]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109204,"arena":"weight","attempt":3,"arena_seconds":9.75,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109392,"arena":"weight","attempt":3,"arena_seconds":9.933,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109424,"arena":"weight","attempt":3,"arena_seconds":9.966,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109432,"arena":"weight","attempt":3,"arena_seconds":9.983,"type":"projectile_fired","projectile_id":31,"enemy_id":15,"position":[0.285,0.729],"direction":[-0.945,0.328]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109616,"arena":"weight","attempt":3,"arena_seconds":10.166,"type":"anchor_windup","enemy_id":14,"direction":[-0.259,0.966],"position":[0.321,0.065]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109705,"arena":"weight","attempt":3,"arena_seconds":10.25,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109794,"arena":"weight","attempt":3,"arena_seconds":10.333,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109820,"arena":"weight","attempt":3,"arena_seconds":10.366,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109951,"arena":"weight","attempt":3,"arena_seconds":10.5,"type":"strike_used","source":"pointer","aim":[0.971,-0.24],"hits":0,"reflections":1,"position":[0.097,0.824]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":109967,"arena":"weight","attempt":3,"arena_seconds":10.517,"type":"arena_snapshot","player":[0.096,0.822],"health":3,"cooldowns":{"strike":0.293,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":3,"actions":{"strikes":2,"strikeHits":0,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110070,"arena":"weight","attempt":3,"arena_seconds":10.616,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110138,"arena":"weight","attempt":3,"arena_seconds":10.683,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110255,"arena":"weight","attempt":3,"arena_seconds":10.799,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110299,"arena":"weight","attempt":3,"arena_seconds":10.85,"type":"enemy_damaged","enemy_id":15,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.325,0.714]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110349,"arena":"weight","attempt":3,"arena_seconds":10.9,"type":"anchor_charged","enemy_id":14,"direction":[-0.259,0.966],"position":[0.315,0.062]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110400,"arena":"weight","attempt":3,"arena_seconds":10.95,"type":"anchor_windup","enemy_id":13,"direction":[-0.53,0.848],"position":[0.535,0.084]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110406,"arena":"weight","attempt":3,"arena_seconds":10.95,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110709,"arena":"weight","attempt":3,"arena_seconds":11.249,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":110862,"arena":"weight","attempt":3,"arena_seconds":11.4,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111133,"arena":"weight","attempt":3,"arena_seconds":11.683,"type":"anchor_charged","enemy_id":13,"direction":[-0.53,0.848],"position":[0.532,0.084]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111219,"arena":"weight","attempt":3,"arena_seconds":11.766,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111334,"arena":"weight","attempt":3,"arena_seconds":11.883,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111465,"arena":"weight","attempt":3,"arena_seconds":12.016,"type":"arena_snapshot","player":[0.12,0.754],"health":3,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":1,"actions":{"strikes":2,"strikeHits":0,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111486,"arena":"weight","attempt":3,"arena_seconds":12.033,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111498,"arena":"weight","attempt":3,"arena_seconds":12.049,"type":"projectile_fired","projectile_id":32,"enemy_id":16,"position":[0.708,0.641],"direction":[-0.984,0.177]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111500,"arena":"weight","attempt":3,"arena_seconds":12.049,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111584,"arena":"weight","attempt":3,"arena_seconds":12.133,"type":"strike_used","source":"pointer","aim":[0.991,-0.136],"hits":0,"reflections":0,"position":[0.129,0.731]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111712,"arena":"weight","attempt":3,"arena_seconds":12.25,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":111982,"arena":"weight","attempt":3,"arena_seconds":12.533,"type":"projectile_fired","projectile_id":33,"enemy_id":15,"position":[0.348,0.696],"direction":[-0.991,0.135]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":112141,"arena":"weight","attempt":3,"arena_seconds":12.683,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":112693,"arena":"weight","attempt":3,"arena_seconds":13.233,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":112801,"arena":"weight","attempt":3,"arena_seconds":13.35,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":112966,"arena":"weight","attempt":3,"arena_seconds":13.517,"type":"arena_snapshot","player":[0.272,0.881],"health":3.405,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":2,"actions":{"strikes":3,"strikeHits":0,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":113437,"arena":"weight","attempt":3,"arena_seconds":13.983,"type":"strike_used","source":"pointer","aim":[0.014,-1],"hits":0,"reflections":1,"position":[0.274,0.81]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":113473,"arena":"weight","attempt":3,"arena_seconds":14.017,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":113552,"arena":"weight","attempt":3,"arena_seconds":14.1,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":113723,"arena":"weight","attempt":3,"arena_seconds":14.267,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":113966,"arena":"weight","attempt":3,"arena_seconds":14.517,"type":"projectile_fired","projectile_id":34,"enemy_id":16,"position":[0.662,0.647],"direction":[-0.943,0.331]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114116,"arena":"weight","attempt":3,"arena_seconds":14.667,"type":"anchor_windup","enemy_id":14,"direction":[0.532,0.847],"position":[0.195,0.536]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114165,"arena":"weight","attempt":3,"arena_seconds":14.716,"type":"projectile_fired","projectile_id":35,"enemy_id":15,"position":[0.387,0.698],"direction":[-0.322,0.947]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114407,"arena":"weight","attempt":3,"arena_seconds":14.95,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114450,"arena":"weight","attempt":3,"arena_seconds":15,"type":"arena_snapshot","player":[0.41,0.829],"health":4.073,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"anchor":2,"gunner":2,"wisp":3},"projectiles":3,"actions":{"strikes":4,"strikeHits":0,"reflections":2,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114549,"arena":"weight","attempt":3,"arena_seconds":15.1,"type":"anchor_windup","enemy_id":13,"direction":[0.359,0.934],"position":[0.291,0.475]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114655,"arena":"weight","attempt":3,"arena_seconds":15.2,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114737,"arena":"weight","attempt":3,"arena_seconds":15.283,"type":"strike_used","source":"pointer","aim":[-0.702,-0.712],"hits":0,"reflections":2,"position":[0.474,0.852]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114850,"arena":"weight","attempt":3,"arena_seconds":15.4,"type":"anchor_charged","enemy_id":14,"direction":[0.532,0.847],"position":[0.194,0.542]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114916,"arena":"weight","attempt":3,"arena_seconds":15.466,"type":"enemy_damaged","enemy_id":15,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.398,0.653]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":114916,"arena":"weight","attempt":3,"arena_seconds":15.466,"type":"enemy_killed","enemy_id":15,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":6}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115249,"arena":"weight","attempt":3,"arena_seconds":15.8,"type":"enemy_damaged","enemy_id":14,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":0,"position":[0.274,0.668]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115249,"arena":"weight","attempt":3,"arena_seconds":15.8,"type":"enemy_killed","enemy_id":14,"enemy_kind":"anchor","cause":"body_collision","chain":2,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115250,"arena":"weight","attempt":3,"arena_seconds":15.8,"type":"enemy_damaged","enemy_id":19,"enemy_kind":"wisp","amount":2,"cause":"body_collision","hp_after":0,"position":[0.321,0.68]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115250,"arena":"weight","attempt":3,"arena_seconds":15.8,"type":"enemy_killed","enemy_id":19,"enemy_kind":"wisp","cause":"body_collision","chain":3,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115250,"arena":"weight","attempt":3,"arena_seconds":15.8,"type":"damaging_collision","a_id":14,"b_id":19,"relative_speed":0.186,"position":[0.298,0.674]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115283,"arena":"weight","attempt":3,"arena_seconds":15.833,"type":"anchor_charged","enemy_id":13,"direction":[0.359,0.934],"position":[0.289,0.481]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115433,"arena":"weight","attempt":3,"arena_seconds":15.983,"type":"enemy_damaged","enemy_id":13,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":2,"position":[0.312,0.542]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115433,"arena":"weight","attempt":3,"arena_seconds":15.983,"type":"enemy_damaged","enemy_id":17,"enemy_kind":"wisp","amount":2,"cause":"body_collision","hp_after":0,"position":[0.331,0.587]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115434,"arena":"weight","attempt":3,"arena_seconds":15.983,"type":"enemy_killed","enemy_id":17,"enemy_kind":"wisp","cause":"body_collision","chain":4,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115434,"arena":"weight","attempt":3,"arena_seconds":15.983,"type":"damaging_collision","a_id":13,"b_id":17,"relative_speed":0.335,"position":[0.321,0.565]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115837,"arena":"weight","attempt":3,"arena_seconds":16.382,"type":"strike_used","source":"pointer","aim":[-1,0.02],"hits":0,"reflections":0,"position":[0.697,0.695]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115965,"arena":"weight","attempt":3,"arena_seconds":16.516,"type":"arena_snapshot","player":[0.723,0.669],"health":4.755,"cooldowns":{"strike":0.176,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"anchor":1,"gunner":1,"wisp":1},"projectiles":0,"actions":{"strikes":6,"strikeHits":0,"reflections":4,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":115978,"arena":"weight","attempt":3,"arena_seconds":16.516,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":116210,"arena":"weight","attempt":3,"arena_seconds":16.75,"type":"enemy_damaged","enemy_id":16,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":2,"position":[0.673,0.623]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":116211,"arena":"weight","attempt":3,"arena_seconds":16.75,"type":"strike_used","source":"pointer","aim":[-0.984,0.18],"hits":1,"reflections":0,"position":[0.754,0.618]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":116316,"arena":"weight","attempt":3,"arena_seconds":16.867,"type":"projectile_fired","projectile_id":36,"enemy_id":16,"position":[0.693,0.614],"direction":[0.942,-0.335]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":116542,"arena":"weight","attempt":3,"arena_seconds":17.083,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":117466,"arena":"weight","attempt":3,"arena_seconds":18.017,"type":"arena_snapshot","player":[0.651,0.333],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"anchor":1,"gunner":1,"wisp":1},"projectiles":1,"actions":{"strikes":7,"strikeHits":1,"reflections":4,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":117542,"arena":"weight","attempt":3,"arena_seconds":18.083,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118166,"arena":"weight","attempt":3,"arena_seconds":18.717,"type":"projectile_fired","projectile_id":37,"enemy_id":16,"position":[0.599,0.606],"direction":[-0.315,-0.949]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118578,"arena":"weight","attempt":3,"arena_seconds":19.116,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118643,"arena":"weight","attempt":3,"arena_seconds":19.183,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118699,"arena":"weight","attempt":3,"arena_seconds":19.25,"type":"anchor_windup","enemy_id":13,"direction":[-0.157,-0.988],"position":[0.44,0.811]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118925,"arena":"weight","attempt":3,"arena_seconds":19.466,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":118950,"arena":"weight","attempt":3,"arena_seconds":19.5,"type":"arena_snapshot","player":[0.357,0.262],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"anchor":1,"gunner":1,"wisp":1},"projectiles":1,"actions":{"strikes":7,"strikeHits":1,"reflections":4,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":119431,"arena":"weight","attempt":3,"arena_seconds":19.982,"type":"anchor_charged","enemy_id":13,"direction":[-0.157,-0.988],"position":[0.442,0.812]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":119623,"arena":"weight","attempt":3,"arena_seconds":20.166,"type":"strike_used","source":"pointer","aim":[-0.136,0.991],"hits":0,"reflections":1,"position":[0.461,0.177]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":119848,"arena":"weight","attempt":3,"arena_seconds":20.383,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":120199,"arena":"weight","attempt":3,"arena_seconds":20.749,"type":"projectile_fired","projectile_id":38,"enemy_id":16,"position":[0.603,0.57],"direction":[-0.039,-0.999]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":120466,"arena":"weight","attempt":3,"arena_seconds":21.017,"type":"arena_snapshot","player":[0.654,0.09],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"anchor":1,"gunner":1,"wisp":1},"projectiles":2,"actions":{"strikes":8,"strikeHits":1,"reflections":5,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":120762,"arena":"weight","attempt":3,"arena_seconds":21.3,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":120763,"arena":"weight","attempt":3,"arena_seconds":21.3,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":120925,"arena":"weight","attempt":3,"arena_seconds":21.467,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121010,"arena":"weight","attempt":3,"arena_seconds":21.55,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121254,"arena":"weight","attempt":3,"arena_seconds":21.8,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121552,"arena":"weight","attempt":3,"arena_seconds":22.1,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121902,"arena":"weight","attempt":3,"arena_seconds":22.449,"type":"strike_used","source":"pointer","aim":[-1,0.025],"hits":0,"reflections":1,"position":[0.681,0.112]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121966,"arena":"weight","attempt":3,"arena_seconds":22.516,"type":"arena_snapshot","player":[0.678,0.127],"health":5,"cooldowns":{"strike":0.243,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"anchor":1,"gunner":1,"wisp":1},"projectiles":1,"actions":{"strikes":9,"strikeHits":1,"reflections":6,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":121973,"arena":"weight","attempt":3,"arena_seconds":22.516,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122001,"arena":"weight","attempt":3,"arena_seconds":22.55,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122065,"arena":"weight","attempt":3,"arena_seconds":22.616,"type":"projectile_fired","projectile_id":39,"enemy_id":16,"position":[0.622,0.538],"direction":[0.143,-0.99]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122263,"arena":"weight","attempt":3,"arena_seconds":22.8,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122589,"arena":"weight","attempt":3,"arena_seconds":23.133,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122616,"arena":"weight","attempt":3,"arena_seconds":23.166,"type":"anchor_windup","enemy_id":13,"direction":[0.948,-0.318],"position":[0.386,0.35]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122772,"arena":"weight","attempt":3,"arena_seconds":23.316,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":122780,"arena":"weight","attempt":3,"arena_seconds":23.316,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123006,"arena":"weight","attempt":3,"arena_seconds":23.55,"type":"strike_used","source":"pointer","aim":[-0.986,0.168],"hits":0,"reflections":1,"position":[0.772,0.288]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123208,"arena":"weight","attempt":3,"arena_seconds":23.75,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123216,"arena":"weight","attempt":3,"arena_seconds":23.75,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123349,"arena":"weight","attempt":3,"arena_seconds":23.899,"type":"anchor_charged","enemy_id":13,"direction":[0.948,-0.318],"position":[0.387,0.343]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123450,"arena":"weight","attempt":3,"arena_seconds":24,"type":"enemy_damaged","enemy_id":13,"enemy_kind":"anchor","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.432,0.328]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123450,"arena":"weight","attempt":3,"arena_seconds":24,"type":"enemy_killed","enemy_id":13,"enemy_kind":"anchor","cause":"reflected_projectile","chain":1,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123465,"arena":"weight","attempt":3,"arena_seconds":24.016,"type":"arena_snapshot","player":[0.709,0.337],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":2,"enemy_counts":{"gunner":1,"wisp":1},"projectiles":0,"actions":{"strikes":10,"strikeHits":1,"reflections":7,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":6}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":123982,"arena":"weight","attempt":3,"arena_seconds":24.516,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124016,"arena":"weight","attempt":3,"arena_seconds":24.567,"type":"projectile_fired","projectile_id":40,"enemy_id":16,"position":[0.65,0.529],"direction":[0.545,-0.838]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124149,"arena":"weight","attempt":3,"arena_seconds":24.699,"type":"player_damaged","amount":1,"cause":"projectile","source_id":40,"health_after":4,"position":[0.699,0.497]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124254,"arena":"weight","attempt":3,"arena_seconds":24.8,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124586,"arena":"weight","attempt":3,"arena_seconds":25.133,"type":"enemy_damaged","enemy_id":16,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":1,"position":[0.628,0.565]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124587,"arena":"weight","attempt":3,"arena_seconds":25.133,"type":"strike_used","source":"pointer","aim":[-0.818,-0.575],"hits":1,"reflections":0,"position":[0.731,0.6]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124608,"arena":"weight","attempt":3,"arena_seconds":25.149,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124757,"arena":"weight","attempt":3,"arena_seconds":25.3,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":124966,"arena":"weight","attempt":3,"arena_seconds":25.516,"type":"arena_snapshot","player":[0.702,0.676],"health":4,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":2,"enemy_counts":{"gunner":1,"wisp":1},"projectiles":0,"actions":{"strikes":11,"strikeHits":2,"reflections":7,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":6}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125104,"arena":"weight","attempt":3,"arena_seconds":25.65,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125521,"arena":"weight","attempt":3,"arena_seconds":26.067,"type":"enemy_damaged","enemy_id":16,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":0,"position":[0.575,0.531]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125521,"arena":"weight","attempt":3,"arena_seconds":26.067,"type":"enemy_killed","enemy_id":16,"enemy_kind":"gunner","cause":"strike","chain":2,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125522,"arena":"weight","attempt":3,"arena_seconds":26.067,"type":"strike_used","source":"pointer","aim":[0.035,-0.999],"hits":1,"reflections":0,"position":[0.592,0.665]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125532,"arena":"weight","attempt":3,"arena_seconds":26.067,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":125642,"arena":"weight","attempt":3,"arena_seconds":26.183,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126105,"arena":"weight","attempt":3,"arena_seconds":26.65,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126205,"arena":"weight","attempt":3,"arena_seconds":26.749,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126249,"arena":"weight","attempt":3,"arena_seconds":26.783,"type":"enemy_damaged","enemy_id":20,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.611,0.476]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126249,"arena":"weight","attempt":3,"arena_seconds":26.783,"type":"enemy_killed","enemy_id":20,"enemy_kind":"wisp","cause":"strike","chain":3,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126250,"arena":"weight","attempt":3,"arena_seconds":26.783,"type":"strike_used","source":"pointer","aim":[0.715,-0.699],"hits":1,"reflections":0,"position":[0.54,0.595]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":126250,"arena":"weight","attempt":3,"arena_seconds":26.8,"type":"arena_completed","duration_seconds":26.8,"actions":{"strikes":13,"strikeHits":4,"reflections":7,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":4,"kills":8},"max_chain":4,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":134739,"arena":"remix","attempt":4,"arena_seconds":0,"type":"arena_started","reason":"selected","enemies":[{"id":21,"kind":"wisp","position":[0.865,0.213]},{"id":22,"kind":"wisp","position":[0.231,0.755]},{"id":23,"kind":"wisp","position":[0.744,0.668]},{"id":24,"kind":"wisp","position":[0.738,0.152]},{"id":25,"kind":"wisp","position":[0.204,0.832]},{"id":26,"kind":"wisp","position":[0.841,0.445]},{"id":27,"kind":"gunner","position":[0.805,0.505]},{"id":28,"kind":"gunner","position":[0.591,0.226]},{"id":29,"kind":"anchor","position":[0.2,0.641]},{"id":30,"kind":"anchor","position":[0.141,0.21]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":135599,"arena":"remix","attempt":4,"arena_seconds":0.866,"type":"projectile_fired","projectile_id":41,"enemy_id":28,"position":[0.579,0.258],"direction":[-0.271,0.963]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":135746,"arena":"remix","attempt":4,"arena_seconds":0.999,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":135901,"arena":"remix","attempt":4,"arena_seconds":1.166,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":135966,"arena":"remix","attempt":4,"arena_seconds":1.232,"type":"projectile_fired","projectile_id":42,"enemy_id":27,"position":[0.77,0.505],"direction":[-0.993,0.116]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":136249,"arena":"remix","attempt":4,"arena_seconds":1.517,"type":"arena_snapshot","player":[0.435,0.511],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":10,"enemy_counts":{"wisp":6,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":136388,"arena":"remix","attempt":4,"arena_seconds":1.649,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":136415,"arena":"remix","attempt":4,"arena_seconds":1.682,"type":"anchor_windup","enemy_id":30,"direction":[0.693,0.721],"position":[0.149,0.218]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":136932,"arena":"remix","attempt":4,"arena_seconds":2.2,"type":"anchor_windup","enemy_id":29,"direction":[0.499,-0.867],"position":[0.215,0.634]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":136973,"arena":"remix","attempt":4,"arena_seconds":2.233,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137149,"arena":"remix","attempt":4,"arena_seconds":2.416,"type":"anchor_charged","enemy_id":30,"direction":[0.693,0.721],"position":[0.151,0.219]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137665,"arena":"remix","attempt":4,"arena_seconds":2.933,"type":"anchor_charged","enemy_id":29,"direction":[0.499,-0.867],"position":[0.217,0.633]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137749,"arena":"remix","attempt":4,"arena_seconds":3.016,"type":"arena_snapshot","player":[0.461,0.19],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":10,"enemy_counts":{"wisp":6,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137849,"arena":"remix","attempt":4,"arena_seconds":3.117,"type":"projectile_fired","projectile_id":43,"enemy_id":28,"position":[0.554,0.204],"direction":[-0.914,-0.406]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137925,"arena":"remix","attempt":4,"arena_seconds":3.183,"type":"enemy_damaged","enemy_id":28,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":2,"position":[0.586,0.217]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137925,"arena":"remix","attempt":4,"arena_seconds":3.183,"type":"strike_used","source":"pointer","aim":[0.56,0.829],"hits":1,"reflections":1,"position":[0.491,0.157]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":137928,"arena":"remix","attempt":4,"arena_seconds":3.183,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138030,"arena":"remix","attempt":4,"arena_seconds":3.282,"type":"movement_key_down","key":"KeyA","active_keys":["KeyD","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138073,"arena":"remix","attempt":4,"arena_seconds":3.333,"type":"movement_key_up","key":"KeyD","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138217,"arena":"remix","attempt":4,"arena_seconds":3.483,"type":"enemy_damaged","enemy_id":29,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":4,"position":[0.312,0.468]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138217,"arena":"remix","attempt":4,"arena_seconds":3.483,"type":"enemy_damaged","enemy_id":30,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":4,"position":[0.36,0.429]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138218,"arena":"remix","attempt":4,"arena_seconds":3.483,"type":"damaging_collision","a_id":29,"b_id":30,"relative_speed":0.224,"position":[0.336,0.449]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138233,"arena":"remix","attempt":4,"arena_seconds":3.499,"type":"projectile_fired","projectile_id":44,"enemy_id":27,"position":[0.759,0.452],"direction":[-0.566,-0.825]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138323,"arena":"remix","attempt":4,"arena_seconds":3.583,"type":"tether_used","source":"pointer","hit":true,"target_id":28,"target_kind":"gunner","effect":"enemy_pulled","distance":0.156}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138583,"arena":"remix","attempt":4,"arena_seconds":3.85,"type":"enemy_damaged","enemy_id":26,"enemy_kind":"wisp","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.721,0.432]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":138584,"arena":"remix","attempt":4,"arena_seconds":3.85,"type":"enemy_killed","enemy_id":26,"enemy_kind":"wisp","cause":"reflected_projectile","chain":1,"remaining_after":9}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":139249,"arena":"remix","attempt":4,"arena_seconds":4.516,"type":"arena_snapshot","player":[0.324,0.101],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":9,"enemy_counts":{"wisp":5,"gunner":2,"anchor":2},"projectiles":1,"actions":{"strikes":1,"strikeHits":1,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":139460,"arena":"remix","attempt":4,"arena_seconds":4.716,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":139669,"arena":"remix","attempt":4,"arena_seconds":4.933,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":139783,"arena":"remix","attempt":4,"arena_seconds":5.05,"type":"projectile_fired","projectile_id":45,"enemy_id":28,"position":[0.543,0.193],"direction":[-0.984,-0.181]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140015,"arena":"remix","attempt":4,"arena_seconds":5.283,"type":"projectile_fired","projectile_id":46,"enemy_id":27,"position":[0.731,0.436],"direction":[-0.906,-0.422]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140351,"arena":"remix","attempt":4,"arena_seconds":5.616,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140472,"arena":"remix","attempt":4,"arena_seconds":5.733,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140633,"arena":"remix","attempt":4,"arena_seconds":5.9,"type":"anchor_windup","enemy_id":30,"direction":[-0.979,-0.204],"position":[0.599,0.416]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140685,"arena":"remix","attempt":4,"arena_seconds":5.95,"type":"enemy_damaged","enemy_id":29,"enemy_kind":"anchor","amount":1,"cause":"strike","hp_after":3,"position":[0.309,0.335]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140685,"arena":"remix","attempt":4,"arena_seconds":5.95,"type":"strike_used","source":"pointer","aim":[0.991,-0.131],"hits":1,"reflections":0,"position":[0.165,0.338]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140749,"arena":"remix","attempt":4,"arena_seconds":6.016,"type":"arena_snapshot","player":[0.166,0.356],"health":5,"cooldowns":{"strike":0.244,"tether":0,"dash":0},"remaining":9,"enemy_counts":{"wisp":5,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":2,"strikeHits":2,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":140821,"arena":"remix","attempt":4,"arena_seconds":6.083,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":141322,"arena":"remix","attempt":4,"arena_seconds":6.583,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":141366,"arena":"remix","attempt":4,"arena_seconds":6.633,"type":"anchor_charged","enemy_id":30,"direction":[-0.979,-0.204],"position":[0.603,0.415]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":141499,"arena":"remix","attempt":4,"arena_seconds":6.766,"type":"anchor_windup","enemy_id":29,"direction":[-0.756,0.654],"position":[0.339,0.32]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142082,"arena":"remix","attempt":4,"arena_seconds":7.349,"type":"projectile_fired","projectile_id":47,"enemy_id":28,"position":[0.499,0.222],"direction":[-0.677,0.736]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142095,"arena":"remix","attempt":4,"arena_seconds":7.349,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142175,"arena":"remix","attempt":4,"arena_seconds":7.433,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142232,"arena":"remix","attempt":4,"arena_seconds":7.499,"type":"anchor_charged","enemy_id":29,"direction":[-0.756,0.654],"position":[0.338,0.307]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142250,"arena":"remix","attempt":4,"arena_seconds":7.516,"type":"arena_snapshot","player":[0.086,0.719],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":9,"enemy_counts":{"wisp":5,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":2,"strikeHits":2,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142466,"arena":"remix","attempt":4,"arena_seconds":7.732,"type":"projectile_fired","projectile_id":48,"enemy_id":27,"position":[0.691,0.448],"direction":[-0.886,0.464]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142782,"arena":"remix","attempt":4,"arena_seconds":8.033,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":142831,"arena":"remix","attempt":4,"arena_seconds":8.083,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143224,"arena":"remix","attempt":4,"arena_seconds":8.483,"type":"enemy_damaged","enemy_id":25,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.219,0.621]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143225,"arena":"remix","attempt":4,"arena_seconds":8.483,"type":"enemy_killed","enemy_id":25,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":8}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143225,"arena":"remix","attempt":4,"arena_seconds":8.483,"type":"strike_used","source":"pointer","aim":[-0.381,-0.925],"hits":1,"reflections":0,"position":[0.248,0.714]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143226,"arena":"remix","attempt":4,"arena_seconds":8.483,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143280,"arena":"remix","attempt":4,"arena_seconds":8.533,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143344,"arena":"remix","attempt":4,"arena_seconds":8.599,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143461,"arena":"remix","attempt":4,"arena_seconds":8.716,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143750,"arena":"remix","attempt":4,"arena_seconds":9.016,"type":"arena_snapshot","player":[0.293,0.712],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":8,"enemy_counts":{"wisp":4,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":3,"strikeHits":3,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143980,"arena":"remix","attempt":4,"arena_seconds":9.233,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":143982,"arena":"remix","attempt":4,"arena_seconds":9.233,"type":"strike_used","source":"pointer","aim":[0.603,-0.798],"hits":0,"reflections":0,"position":[0.3,0.755]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":144087,"arena":"remix","attempt":4,"arena_seconds":9.35,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":144139,"arena":"remix","attempt":4,"arena_seconds":9.4,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":144450,"arena":"remix","attempt":4,"arena_seconds":9.716,"type":"projectile_fired","projectile_id":49,"enemy_id":28,"position":[0.477,0.259],"direction":[-0.255,0.967]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":144500,"arena":"remix","attempt":4,"arena_seconds":9.767,"type":"projectile_fired","projectile_id":50,"enemy_id":27,"position":[0.659,0.465],"direction":[-0.707,0.708]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":144570,"arena":"remix","attempt":4,"arena_seconds":9.833,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145125,"arena":"remix","attempt":4,"arena_seconds":10.383,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145155,"arena":"remix","attempt":4,"arena_seconds":10.416,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145249,"arena":"remix","attempt":4,"arena_seconds":10.516,"type":"arena_snapshot","player":[0.509,0.734],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":8,"enemy_counts":{"wisp":4,"gunner":2,"anchor":2},"projectiles":3,"actions":{"strikes":4,"strikeHits":3,"reflections":1,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145282,"arena":"remix","attempt":4,"arena_seconds":10.549,"type":"anchor_windup","enemy_id":30,"direction":[0.755,0.656],"position":[0.242,0.494]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145398,"arena":"remix","attempt":4,"arena_seconds":10.65,"type":"strike_used","source":"pointer","aim":[-0.21,-0.978],"hits":0,"reflections":1,"position":[0.526,0.716]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145406,"arena":"remix","attempt":4,"arena_seconds":10.666,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145438,"arena":"remix","attempt":4,"arena_seconds":10.699,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":145957,"arena":"remix","attempt":4,"arena_seconds":11.216,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146016,"arena":"remix","attempt":4,"arena_seconds":11.283,"type":"anchor_charged","enemy_id":30,"direction":[0.755,0.656],"position":[0.241,0.498]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146066,"arena":"remix","attempt":4,"arena_seconds":11.333,"type":"anchor_windup","enemy_id":29,"direction":[0.991,0.134],"position":[0.088,0.592]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146348,"arena":"remix","attempt":4,"arena_seconds":11.599,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146512,"arena":"remix","attempt":4,"arena_seconds":11.766,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146532,"arena":"remix","attempt":4,"arena_seconds":11.799,"type":"projectile_fired","projectile_id":51,"enemy_id":28,"position":[0.493,0.291],"direction":[0.54,0.842]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146565,"arena":"remix","attempt":4,"arena_seconds":11.832,"type":"projectile_fired","projectile_id":52,"enemy_id":27,"position":[0.673,0.48],"direction":[0.389,0.921]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146648,"arena":"remix","attempt":4,"arena_seconds":11.9,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146750,"arena":"remix","attempt":4,"arena_seconds":12.016,"type":"arena_snapshot","player":[0.808,0.707],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":8,"enemy_counts":{"wisp":4,"gunner":2,"anchor":2},"projectiles":4,"actions":{"strikes":5,"strikeHits":3,"reflections":2,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":1,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":146800,"arena":"remix","attempt":4,"arena_seconds":12.067,"type":"anchor_charged","enemy_id":29,"direction":[0.991,0.134],"position":[0.092,0.595]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147039,"arena":"remix","attempt":4,"arena_seconds":12.3,"type":"strike_used","source":"pointer","aim":[-0.876,-0.482],"hits":0,"reflections":1,"position":[0.835,0.659]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147232,"arena":"remix","attempt":4,"arena_seconds":12.482,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147433,"arena":"remix","attempt":4,"arena_seconds":12.699,"type":"enemy_damaged","enemy_id":22,"enemy_kind":"wisp","amount":2,"cause":"body_collision","hp_after":0,"position":[0.341,0.653]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147433,"arena":"remix","attempt":4,"arena_seconds":12.699,"type":"enemy_killed","enemy_id":22,"enemy_kind":"wisp","cause":"body_collision","chain":1,"remaining_after":7}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147433,"arena":"remix","attempt":4,"arena_seconds":12.699,"type":"enemy_damaged","enemy_id":29,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":1,"position":[0.302,0.623]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147434,"arena":"remix","attempt":4,"arena_seconds":12.699,"type":"damaging_collision","a_id":22,"b_id":29,"relative_speed":0.181,"position":[0.321,0.638]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147503,"arena":"remix","attempt":4,"arena_seconds":12.767,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147551,"arena":"remix","attempt":4,"arena_seconds":12.816,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":147567,"arena":"remix","attempt":4,"arena_seconds":12.833,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":148169,"arena":"remix","attempt":4,"arena_seconds":13.433,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":148249,"arena":"remix","attempt":4,"arena_seconds":13.516,"type":"arena_snapshot","player":[0.864,0.432],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"wisp":3,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":6,"strikeHits":3,"reflections":3,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":3}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":148599,"arena":"remix","attempt":4,"arena_seconds":13.866,"type":"projectile_fired","projectile_id":53,"enemy_id":28,"position":[0.526,0.302],"direction":[0.985,0.173]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149050,"arena":"remix","attempt":4,"arena_seconds":14.316,"type":"projectile_fired","projectile_id":54,"enemy_id":27,"position":[0.662,0.433],"direction":[0.465,-0.885]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149074,"arena":"remix","attempt":4,"arena_seconds":14.332,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149415,"arena":"remix","attempt":4,"arena_seconds":14.666,"type":"strike_used","source":"pointer","aim":[-0.832,0.555],"hits":0,"reflections":0,"position":[0.711,0.185]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149616,"arena":"remix","attempt":4,"arena_seconds":14.883,"type":"anchor_windup","enemy_id":30,"direction":[0.139,-0.99],"position":[0.61,0.785]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149750,"arena":"remix","attempt":4,"arena_seconds":15.016,"type":"arena_snapshot","player":[0.699,0.093],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":7,"enemy_counts":{"wisp":3,"gunner":2,"anchor":2},"projectiles":3,"actions":{"strikes":7,"strikeHits":3,"reflections":3,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":2,"kills":3}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149754,"arena":"remix","attempt":4,"arena_seconds":15.016,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":149861,"arena":"remix","attempt":4,"arena_seconds":15.116,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":150049,"arena":"remix","attempt":4,"arena_seconds":15.317,"type":"anchor_windup","enemy_id":29,"direction":[0.238,-0.971],"position":[0.524,0.618]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":150349,"arena":"remix","attempt":4,"arena_seconds":15.616,"type":"anchor_charged","enemy_id":30,"direction":[0.139,-0.99],"position":[0.614,0.785]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":150782,"arena":"remix","attempt":4,"arena_seconds":16.049,"type":"anchor_charged","enemy_id":29,"direction":[0.238,-0.971],"position":[0.53,0.616]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":150832,"arena":"remix","attempt":4,"arena_seconds":16.099,"type":"projectile_fired","projectile_id":55,"enemy_id":28,"position":[0.501,0.275],"direction":[-0.081,-0.997]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151032,"arena":"remix","attempt":4,"arena_seconds":16.299,"type":"enemy_damaged","enemy_id":23,"enemy_kind":"wisp","amount":2,"cause":"body_collision","hp_after":0,"position":[0.605,0.535]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151033,"arena":"remix","attempt":4,"arena_seconds":16.299,"type":"enemy_killed","enemy_id":23,"enemy_kind":"wisp","cause":"body_collision","chain":1,"remaining_after":6}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151033,"arena":"remix","attempt":4,"arena_seconds":16.299,"type":"enemy_damaged","enemy_id":30,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":2,"position":[0.644,0.564]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151034,"arena":"remix","attempt":4,"arena_seconds":16.299,"type":"damaging_collision","a_id":23,"b_id":30,"relative_speed":0.172,"position":[0.625,0.549]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151040,"arena":"remix","attempt":4,"arena_seconds":16.299,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151086,"arena":"remix","attempt":4,"arena_seconds":16.349,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151249,"arena":"remix","attempt":4,"arena_seconds":16.516,"type":"arena_snapshot","player":[0.381,0.047],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":2,"gunner":2,"anchor":2},"projectiles":1,"actions":{"strikes":7,"strikeHits":3,"reflections":3,"tethers":1,"tetherHits":1,"dashes":0,"dashHits":0,"collisions":3,"kills":4}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":151383,"arena":"remix","attempt":4,"arena_seconds":16.65,"type":"projectile_fired","projectile_id":56,"enemy_id":27,"position":[0.633,0.42],"direction":[-0.605,-0.796]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":152331,"arena":"remix","attempt":4,"arena_seconds":17.583,"type":"tether_used","source":"pointer","hit":true,"target_id":24,"target_kind":"wisp","effect":"enemy_pulled","distance":0.275}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":152749,"arena":"remix","attempt":4,"arena_seconds":18.016,"type":"arena_snapshot","player":[0.338,0.42],"health":5,"cooldowns":{"strike":0,"tether":0.287,"dash":0},"remaining":6,"enemy_counts":{"wisp":2,"gunner":2,"anchor":2},"projectiles":1,"actions":{"strikes":7,"strikeHits":3,"reflections":3,"tethers":2,"tetherHits":2,"dashes":0,"dashHits":0,"collisions":3,"kills":4}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":153032,"arena":"remix","attempt":4,"arena_seconds":18.299,"type":"projectile_fired","projectile_id":57,"enemy_id":28,"position":[0.498,0.331],"direction":[-0.693,0.721]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":153320,"arena":"remix","attempt":4,"arena_seconds":18.583,"type":"tether_used","source":"pointer","hit":true,"target_id":29,"target_kind":"anchor","effect":"player_pulled","distance":0.384}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":153533,"arena":"remix","attempt":4,"arena_seconds":18.799,"type":"anchor_windup","enemy_id":30,"direction":[-0.866,0.5],"position":[0.699,0.387]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":153932,"arena":"remix","attempt":4,"arena_seconds":19.199,"type":"projectile_fired","projectile_id":58,"enemy_id":27,"position":[0.607,0.396],"direction":[-0.644,0.765]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":154250,"arena":"remix","attempt":4,"arena_seconds":19.516,"type":"arena_snapshot","player":[0.407,0.719],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":2,"gunner":2,"anchor":2},"projectiles":2,"actions":{"strikes":7,"strikeHits":3,"reflections":3,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":3,"kills":4}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":154265,"arena":"remix","attempt":4,"arena_seconds":19.533,"type":"anchor_charged","enemy_id":30,"direction":[-0.866,0.5],"position":[0.699,0.383]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":154471,"arena":"remix","attempt":4,"arena_seconds":19.733,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":154580,"arena":"remix","attempt":4,"arena_seconds":19.833,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":154699,"arena":"remix","attempt":4,"arena_seconds":19.966,"type":"anchor_windup","enemy_id":29,"direction":[-0.181,0.983],"position":[0.515,0.237]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155207,"arena":"remix","attempt":4,"arena_seconds":20.466,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155208,"arena":"remix","attempt":4,"arena_seconds":20.466,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155273,"arena":"remix","attempt":4,"arena_seconds":20.533,"type":"strike_used","source":"pointer","aim":[0.17,-0.985],"hits":0,"reflections":1,"position":[0.412,0.73]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155433,"arena":"remix","attempt":4,"arena_seconds":20.7,"type":"anchor_charged","enemy_id":29,"direction":[-0.181,0.983],"position":[0.513,0.237]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155465,"arena":"remix","attempt":4,"arena_seconds":20.733,"type":"enemy_damaged","enemy_id":30,"enemy_kind":"anchor","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.447,0.565]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155467,"arena":"remix","attempt":4,"arena_seconds":20.733,"type":"enemy_killed","enemy_id":30,"enemy_kind":"anchor","cause":"reflected_projectile","chain":1,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155499,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"projectile_fired","projectile_id":59,"enemy_id":28,"position":[0.503,0.356],"direction":[-0.173,0.985]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155500,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"enemy_damaged","enemy_id":28,"enemy_kind":"gunner","amount":2,"cause":"body_collision","hp_after":0,"position":[0.509,0.324]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155500,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"enemy_killed","enemy_id":28,"enemy_kind":"gunner","cause":"body_collision","chain":2,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155501,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"enemy_damaged","enemy_id":29,"enemy_kind":"anchor","amount":2,"cause":"body_collision","hp_after":0,"position":[0.506,0.271]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155502,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"enemy_killed","enemy_id":29,"enemy_kind":"anchor","cause":"body_collision","chain":3,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155502,"arena":"remix","attempt":4,"arena_seconds":20.766,"type":"damaging_collision","a_id":28,"b_id":29,"relative_speed":0.374,"position":[0.507,0.297]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":155750,"arena":"remix","attempt":4,"arena_seconds":21.016,"type":"arena_snapshot","player":[0.494,0.689],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"wisp":2,"gunner":1},"projectiles":2,"actions":{"strikes":8,"strikeHits":3,"reflections":4,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":4,"kills":7}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":156199,"arena":"remix","attempt":4,"arena_seconds":21.466,"type":"projectile_fired","projectile_id":60,"enemy_id":27,"position":[0.563,0.393],"direction":[0.157,0.988]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":156204,"arena":"remix","attempt":4,"arena_seconds":21.466,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":156363,"arena":"remix","attempt":4,"arena_seconds":21.616,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157246,"arena":"remix","attempt":4,"arena_seconds":22.499,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157249,"arena":"remix","attempt":4,"arena_seconds":22.516,"type":"arena_snapshot","player":[0.683,0.507],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"wisp":2,"gunner":1},"projectiles":2,"actions":{"strikes":8,"strikeHits":3,"reflections":4,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":4,"kills":7}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157353,"arena":"remix","attempt":4,"arena_seconds":22.617,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157471,"arena":"remix","attempt":4,"arena_seconds":22.733,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157483,"arena":"remix","attempt":4,"arena_seconds":22.749,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157552,"arena":"remix","attempt":4,"arena_seconds":22.817,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157587,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"enemy_damaged","enemy_id":21,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.551,0.465]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157587,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"enemy_killed","enemy_id":21,"enemy_kind":"wisp","cause":"strike","chain":4,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157588,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"enemy_damaged","enemy_id":24,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.582,0.356]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157589,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"enemy_killed","enemy_id":24,"enemy_kind":"wisp","cause":"strike","chain":5,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157590,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"enemy_damaged","enemy_id":27,"enemy_kind":"gunner","amount":1,"cause":"strike","hp_after":2,"position":[0.542,0.374]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157590,"arena":"remix","attempt":4,"arena_seconds":22.85,"type":"strike_used","source":"pointer","aim":[-0.886,-0.464],"hits":3,"reflections":0,"position":[0.656,0.44]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":157743,"arena":"remix","attempt":4,"arena_seconds":22.999,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":158441,"arena":"remix","attempt":4,"arena_seconds":23.7,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":158538,"arena":"remix","attempt":4,"arena_seconds":23.799,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":158659,"arena":"remix","attempt":4,"arena_seconds":23.916,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":158665,"arena":"remix","attempt":4,"arena_seconds":23.933,"type":"projectile_fired","projectile_id":61,"enemy_id":27,"position":[0.518,0.353],"direction":[0.997,0.078]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":158749,"arena":"remix","attempt":4,"arena_seconds":24.016,"type":"arena_snapshot","player":[0.845,0.386],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":1,"enemy_counts":{"gunner":1},"projectiles":1,"actions":{"strikes":9,"strikeHits":6,"reflections":4,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":4,"kills":9}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":159337,"arena":"remix","attempt":4,"arena_seconds":24.599,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":159508,"arena":"remix","attempt":4,"arena_seconds":24.766,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":159811,"arena":"remix","attempt":4,"arena_seconds":25.067,"type":"strike_used","source":"pointer","aim":[-1,-0.014],"hits":0,"reflections":1,"position":[0.877,0.376]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":160250,"arena":"remix","attempt":4,"arena_seconds":25.517,"type":"arena_snapshot","player":[0.878,0.367],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":1,"enemy_counts":{"gunner":1},"projectiles":1,"actions":{"strikes":10,"strikeHits":6,"reflections":5,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":4,"kills":9}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":160433,"arena":"remix","attempt":4,"arena_seconds":25.7,"type":"enemy_damaged","enemy_id":27,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.501,0.359]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":160433,"arena":"remix","attempt":4,"arena_seconds":25.7,"type":"enemy_killed","enemy_id":27,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":160449,"arena":"remix","attempt":4,"arena_seconds":25.716,"type":"arena_completed","duration_seconds":25.716,"actions":{"strikes":10,"strikeHits":6,"reflections":5,"tethers":3,"tetherHits":3,"dashes":0,"dashHits":0,"collisions":4,"kills":10},"max_chain":5,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":164403,"arena":"remix","attempt":4,"arena_seconds":25.716,"type":"save_requested","event_count_before_save":516,"completed":["drift","crossfire","weight","remix"],"deaths":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":164419,"arena":"remix","attempt":4,"arena_seconds":25.716,"type":"log_saved","path":"JSONL/breakline-360f3f3f-1e5c-442d-8fdd-7f4976b17b29.jsonl","saved_events":517}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":166912,"arena":"drift","attempt":5,"arena_seconds":0,"type":"arena_started","reason":"selected","enemies":[{"id":31,"kind":"wisp","position":[0.18,0.2]},{"id":32,"kind":"wisp","position":[0.5,0.14]},{"id":33,"kind":"wisp","position":[0.82,0.21]},{"id":34,"kind":"wisp","position":[0.2,0.72]},{"id":35,"kind":"wisp","position":[0.8,0.76]},{"id":36,"kind":"wisp","position":[0.5,0.84]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":168400,"arena":"drift","attempt":5,"arena_seconds":1.5,"type":"arena_snapshot","player":[0.5,0.54],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":168818,"arena":"drift","attempt":5,"arena_seconds":1.917,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":169154,"arena":"drift","attempt":5,"arena_seconds":2.249,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":169369,"arena":"drift","attempt":5,"arena_seconds":2.466,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":169482,"arena":"drift","attempt":5,"arena_seconds":2.567,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":169801,"arena":"drift","attempt":5,"arena_seconds":2.9,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":169917,"arena":"drift","attempt":5,"arena_seconds":3.017,"type":"arena_snapshot","player":[0.462,0.399],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":170173,"arena":"drift","attempt":5,"arena_seconds":3.266,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":170182,"arena":"drift","attempt":5,"arena_seconds":3.266,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":171175,"arena":"drift","attempt":5,"arena_seconds":4.267,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":171416,"arena":"drift","attempt":5,"arena_seconds":4.516,"type":"arena_snapshot","player":[0.215,0.479],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":172018,"arena":"drift","attempt":5,"arena_seconds":5.116,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":172916,"arena":"drift","attempt":5,"arena_seconds":6.017,"type":"arena_snapshot","player":[0.182,0.651],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":173091,"arena":"drift","attempt":5,"arena_seconds":6.184,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":173534,"arena":"drift","attempt":5,"arena_seconds":6.633,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":174416,"arena":"drift","attempt":5,"arena_seconds":7.516,"type":"arena_snapshot","player":[0.435,0.858],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":174797,"arena":"drift","attempt":5,"arena_seconds":7.883,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":175336,"arena":"drift","attempt":5,"arena_seconds":8.433,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":175468,"arena":"drift","attempt":5,"arena_seconds":8.567,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":175645,"arena":"drift","attempt":5,"arena_seconds":8.734,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":175917,"arena":"drift","attempt":5,"arena_seconds":9.017,"type":"arena_snapshot","player":[0.736,0.678],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":176139,"arena":"drift","attempt":5,"arena_seconds":9.233,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":177399,"arena":"drift","attempt":5,"arena_seconds":10.5,"type":"arena_snapshot","player":[0.754,0.548],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":177440,"arena":"drift","attempt":5,"arena_seconds":10.533,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":177576,"arena":"drift","attempt":5,"arena_seconds":10.667,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":178151,"arena":"drift","attempt":5,"arena_seconds":11.25,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":178335,"arena":"drift","attempt":5,"arena_seconds":11.433,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":178917,"arena":"drift","attempt":5,"arena_seconds":12.017,"type":"arena_snapshot","player":[0.835,0.547],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":178978,"arena":"drift","attempt":5,"arena_seconds":12.066,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":179105,"arena":"drift","attempt":5,"arena_seconds":12.2,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":179665,"arena":"drift","attempt":5,"arena_seconds":12.75,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":179765,"arena":"drift","attempt":5,"arena_seconds":12.85,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":180390,"arena":"drift","attempt":5,"arena_seconds":13.483,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":180416,"arena":"drift","attempt":5,"arena_seconds":13.517,"type":"arena_snapshot","player":[0.903,0.547],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":180490,"arena":"drift","attempt":5,"arena_seconds":13.583,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":180910,"arena":"drift","attempt":5,"arena_seconds":14,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":181062,"arena":"drift","attempt":5,"arena_seconds":14.149,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":181272,"arena":"drift","attempt":5,"arena_seconds":14.366,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":181450,"arena":"drift","attempt":5,"arena_seconds":14.533,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":181916,"arena":"drift","attempt":5,"arena_seconds":15.016,"type":"arena_snapshot","player":[0.923,0.459],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":182764,"arena":"drift","attempt":5,"arena_seconds":15.849,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":182785,"arena":"drift","attempt":5,"arena_seconds":15.884,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":183131,"arena":"drift","attempt":5,"arena_seconds":16.216,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":183234,"arena":"drift","attempt":5,"arena_seconds":16.333,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":183416,"arena":"drift","attempt":5,"arena_seconds":16.517,"type":"arena_snapshot","player":[0.705,0.242],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":184900,"arena":"drift","attempt":5,"arena_seconds":18,"type":"arena_snapshot","player":[0.686,0.235],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":186147,"arena":"drift","attempt":5,"arena_seconds":19.234,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":186358,"arena":"drift","attempt":5,"arena_seconds":19.45,"type":"movement_key_down","key":"KeyS","active_keys":["KeyA","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":186417,"arena":"drift","attempt":5,"arena_seconds":19.517,"type":"arena_snapshot","player":[0.657,0.237],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":186669,"arena":"drift","attempt":5,"arena_seconds":19.767,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":186970,"arena":"drift","attempt":5,"arena_seconds":20.066,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187161,"arena":"drift","attempt":5,"arena_seconds":20.25,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187221,"arena":"drift","attempt":5,"arena_seconds":20.316,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187394,"arena":"drift","attempt":5,"arena_seconds":20.483,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187463,"arena":"drift","attempt":5,"arena_seconds":20.55,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187900,"arena":"drift","attempt":5,"arena_seconds":21,"type":"arena_snapshot","player":[0.522,0.421],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":187996,"arena":"drift","attempt":5,"arena_seconds":21.083,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":188118,"arena":"drift","attempt":5,"arena_seconds":21.216,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":188385,"arena":"drift","attempt":5,"arena_seconds":21.484,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":188681,"arena":"drift","attempt":5,"arena_seconds":21.766,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":189399,"arena":"drift","attempt":5,"arena_seconds":22.5,"type":"arena_snapshot","player":[0.513,0.54],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":189867,"arena":"drift","attempt":5,"arena_seconds":22.966,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":190238,"arena":"drift","attempt":5,"arena_seconds":23.334,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":190523,"arena":"drift","attempt":5,"arena_seconds":23.616,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":190613,"arena":"drift","attempt":5,"arena_seconds":23.7,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":190916,"arena":"drift","attempt":5,"arena_seconds":24.017,"type":"arena_snapshot","player":[0.625,0.545],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"wisp":6},"projectiles":0,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191306,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":31,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.562,0.458]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191307,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":31,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191307,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":32,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.59,0.434]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191308,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":32,"enemy_kind":"wisp","cause":"strike","chain":2,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191308,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":33,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.628,0.436]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191308,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":33,"enemy_kind":"wisp","cause":"strike","chain":3,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191308,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":34,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.55,0.499]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191309,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":34,"enemy_kind":"wisp","cause":"strike","chain":4,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191309,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":35,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.62,0.472]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191311,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":35,"enemy_kind":"wisp","cause":"strike","chain":5,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191311,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_damaged","enemy_id":36,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.586,0.495]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191311,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"enemy_killed","enemy_id":36,"enemy_kind":"wisp","cause":"strike","chain":6,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191312,"arena":"drift","attempt":5,"arena_seconds":24.4,"type":"strike_used","source":"pointer","aim":[-0.415,-0.91],"hits":6,"reflections":0,"position":[0.634,0.545]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":191316,"arena":"drift","attempt":5,"arena_seconds":24.417,"type":"arena_completed","duration_seconds":24.417,"actions":{"strikes":1,"strikeHits":6,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":6},"max_chain":6,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":193073,"arena":"crossfire","attempt":6,"arena_seconds":0,"type":"arena_started","reason":"selected","enemies":[{"id":37,"kind":"gunner","position":[0.14,0.15]},{"id":38,"kind":"gunner","position":[0.86,0.15]},{"id":39,"kind":"gunner","position":[0.5,0.86]},{"id":40,"kind":"wisp","position":[0.25,0.46]},{"id":41,"kind":"wisp","position":[0.75,0.46]},{"id":42,"kind":"wisp","position":[0.5,0.25]}]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":193932,"arena":"crossfire","attempt":6,"arena_seconds":0.866,"type":"projectile_fired","projectile_id":62,"enemy_id":38,"position":[0.829,0.18],"direction":[-0.675,0.738]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194183,"arena":"crossfire","attempt":6,"arena_seconds":1.116,"type":"projectile_fired","projectile_id":63,"enemy_id":37,"position":[0.169,0.187],"direction":[0.684,0.73]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194196,"arena":"crossfire","attempt":6,"arena_seconds":1.116,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194490,"arena":"crossfire","attempt":6,"arena_seconds":1.416,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194500,"arena":"crossfire","attempt":6,"arena_seconds":1.433,"type":"projectile_fired","projectile_id":64,"enemy_id":39,"position":[0.505,0.826],"direction":[-0.019,-1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194583,"arena":"crossfire","attempt":6,"arena_seconds":1.517,"type":"arena_snapshot","player":[0.5,0.586],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":3,"actions":{"strikes":0,"strikeHits":0,"reflections":0,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":194811,"arena":"crossfire","attempt":6,"arena_seconds":1.733,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195060,"arena":"crossfire","attempt":6,"arena_seconds":1.983,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195100,"arena":"crossfire","attempt":6,"arena_seconds":2.033,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195225,"arena":"crossfire","attempt":6,"arena_seconds":2.15,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195435,"arena":"crossfire","attempt":6,"arena_seconds":2.367,"type":"strike_used","source":"pointer","aim":[-0.377,-0.926],"hits":0,"reflections":1,"position":[0.54,0.57]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195492,"arena":"crossfire","attempt":6,"arena_seconds":2.416,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195885,"arena":"crossfire","attempt":6,"arena_seconds":2.816,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":195996,"arena":"crossfire","attempt":6,"arena_seconds":2.917,"type":"movement_key_up","key":"KeyS","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196066,"arena":"crossfire","attempt":6,"arena_seconds":3,"type":"arena_snapshot","player":[0.598,0.678],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":3,"actions":{"strikes":1,"strikeHits":0,"reflections":1,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196328,"arena":"crossfire","attempt":6,"arena_seconds":3.249,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196382,"arena":"crossfire","attempt":6,"arena_seconds":3.317,"type":"projectile_fired","projectile_id":65,"enemy_id":38,"position":[0.806,0.214],"direction":[-0.29,0.957]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196496,"arena":"crossfire","attempt":6,"arena_seconds":3.416,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196573,"arena":"crossfire","attempt":6,"arena_seconds":3.499,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196616,"arena":"crossfire","attempt":6,"arena_seconds":3.55,"type":"projectile_fired","projectile_id":66,"enemy_id":37,"position":[0.19,0.227],"direction":[0.713,0.701]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196839,"arena":"crossfire","attempt":6,"arena_seconds":3.767,"type":"strike_used","source":"pointer","aim":[-0.625,-0.781],"hits":0,"reflections":1,"position":[0.694,0.707]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196856,"arena":"crossfire","attempt":6,"arena_seconds":3.783,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":196866,"arena":"crossfire","attempt":6,"arena_seconds":3.8,"type":"projectile_fired","projectile_id":67,"enemy_id":39,"position":[0.539,0.846],"direction":[0.746,-0.666]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197108,"arena":"crossfire","attempt":6,"arena_seconds":4.033,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197204,"arena":"crossfire","attempt":6,"arena_seconds":4.134,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197328,"arena":"crossfire","attempt":6,"arena_seconds":4.25,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197527,"arena":"crossfire","attempt":6,"arena_seconds":4.45,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197566,"arena":"crossfire","attempt":6,"arena_seconds":4.5,"type":"arena_snapshot","player":[0.794,0.756],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":5,"actions":{"strikes":2,"strikeHits":0,"reflections":2,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197754,"arena":"crossfire","attempt":6,"arena_seconds":4.683,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197799,"arena":"crossfire","attempt":6,"arena_seconds":4.716,"type":"movement_key_up","key":"KeyS","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197911,"arena":"crossfire","attempt":6,"arena_seconds":4.833,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":197927,"arena":"crossfire","attempt":6,"arena_seconds":4.85,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198479,"arena":"crossfire","attempt":6,"arena_seconds":5.4,"type":"strike_used","source":"pointer","aim":[0.034,-0.999],"hits":0,"reflections":0,"position":[0.794,0.77]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198670,"arena":"crossfire","attempt":6,"arena_seconds":5.6,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198837,"arena":"crossfire","attempt":6,"arena_seconds":5.767,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198849,"arena":"crossfire","attempt":6,"arena_seconds":5.783,"type":"projectile_fired","projectile_id":68,"enemy_id":38,"position":[0.795,0.257],"direction":[-0.007,1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198895,"arena":"crossfire","attempt":6,"arena_seconds":5.817,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":198966,"arena":"crossfire","attempt":6,"arena_seconds":5.899,"type":"projectile_fired","projectile_id":69,"enemy_id":37,"position":[0.216,0.262],"direction":[0.811,0.585]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199066,"arena":"crossfire","attempt":6,"arena_seconds":6,"type":"projectile_fired","projectile_id":70,"enemy_id":39,"position":[0.546,0.862],"direction":[0.825,-0.566]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199084,"arena":"crossfire","attempt":6,"arena_seconds":6.017,"type":"arena_snapshot","player":[0.81,0.683],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":6,"actions":{"strikes":3,"strikeHits":0,"reflections":2,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199354,"arena":"crossfire","attempt":6,"arena_seconds":6.283,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199403,"arena":"crossfire","attempt":6,"arena_seconds":6.334,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199621,"arena":"crossfire","attempt":6,"arena_seconds":6.55,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199811,"arena":"crossfire","attempt":6,"arena_seconds":6.733,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199890,"arena":"crossfire","attempt":6,"arena_seconds":6.816,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":199979,"arena":"crossfire","attempt":6,"arena_seconds":6.9,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200228,"arena":"crossfire","attempt":6,"arena_seconds":7.15,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200238,"arena":"crossfire","attempt":6,"arena_seconds":7.167,"type":"strike_used","source":"pointer","aim":[-0.208,-0.978],"hits":0,"reflections":1,"position":[0.866,0.702]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200261,"arena":"crossfire","attempt":6,"arena_seconds":7.183,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200447,"arena":"crossfire","attempt":6,"arena_seconds":7.367,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200529,"arena":"crossfire","attempt":6,"arena_seconds":7.45,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200583,"arena":"crossfire","attempt":6,"arena_seconds":7.517,"type":"arena_snapshot","player":[0.887,0.669],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":4,"actions":{"strikes":4,"strikeHits":0,"reflections":3,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200654,"arena":"crossfire","attempt":6,"arena_seconds":7.583,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200733,"arena":"crossfire","attempt":6,"arena_seconds":7.666,"type":"projectile_fired","projectile_id":71,"enemy_id":38,"position":[0.797,0.291],"direction":[0.262,0.965]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200833,"arena":"crossfire","attempt":6,"arena_seconds":7.767,"type":"projectile_fired","projectile_id":72,"enemy_id":37,"position":[0.24,0.287],"direction":[0.866,0.5]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":200964,"arena":"crossfire","attempt":6,"arena_seconds":7.883,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":201092,"arena":"crossfire","attempt":6,"arena_seconds":8.016,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":201549,"arena":"crossfire","attempt":6,"arena_seconds":8.483,"type":"projectile_fired","projectile_id":73,"enemy_id":39,"position":[0.592,0.869],"direction":[0.982,-0.189]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":201654,"arena":"crossfire","attempt":6,"arena_seconds":8.583,"type":"movement_key_down","key":"KeyA","active_keys":["KeyS","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":201809,"arena":"crossfire","attempt":6,"arena_seconds":8.734,"type":"movement_key_up","key":"KeyA","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":201835,"arena":"crossfire","attempt":6,"arena_seconds":8.767,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202019,"arena":"crossfire","attempt":6,"arena_seconds":8.95,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202046,"arena":"crossfire","attempt":6,"arena_seconds":8.967,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202070,"arena":"crossfire","attempt":6,"arena_seconds":9,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202084,"arena":"crossfire","attempt":6,"arena_seconds":9.017,"type":"arena_snapshot","player":[0.871,0.919],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":4,"actions":{"strikes":4,"strikeHits":0,"reflections":3,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202460,"arena":"crossfire","attempt":6,"arena_seconds":9.383,"type":"strike_used","source":"pointer","aim":[-1,0.004],"hits":0,"reflections":1,"position":[0.871,0.889]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202462,"arena":"crossfire","attempt":6,"arena_seconds":9.383,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202650,"arena":"crossfire","attempt":6,"arena_seconds":9.584,"type":"projectile_fired","projectile_id":74,"enemy_id":37,"position":[0.257,0.321],"direction":[0.749,0.663]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":202923,"arena":"crossfire","attempt":6,"arena_seconds":9.85,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203166,"arena":"crossfire","attempt":6,"arena_seconds":10.1,"type":"projectile_fired","projectile_id":75,"enemy_id":38,"position":[0.787,0.338],"direction":[0.118,0.993]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203396,"arena":"crossfire","attempt":6,"arena_seconds":10.317,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203445,"arena":"crossfire","attempt":6,"arena_seconds":10.367,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203449,"arena":"crossfire","attempt":6,"arena_seconds":10.383,"type":"projectile_fired","projectile_id":76,"enemy_id":39,"position":[0.601,0.876],"direction":[0.982,-0.191]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203522,"arena":"crossfire","attempt":6,"arena_seconds":10.45,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203566,"arena":"crossfire","attempt":6,"arena_seconds":10.5,"type":"arena_snapshot","player":[0.765,0.834],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":6,"enemy_counts":{"gunner":3,"wisp":3},"projectiles":6,"actions":{"strikes":5,"strikeHits":0,"reflections":4,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":0}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":203770,"arena":"crossfire","attempt":6,"arena_seconds":10.7,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204406,"arena":"crossfire","attempt":6,"arena_seconds":11.333,"type":"movement_key_down","key":"KeyW","active_keys":["KeyA","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204434,"arena":"crossfire","attempt":6,"arena_seconds":11.367,"type":"movement_key_up","key":"KeyA","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204542,"arena":"crossfire","attempt":6,"arena_seconds":11.466,"type":"enemy_damaged","enemy_id":40,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.614,0.666]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204542,"arena":"crossfire","attempt":6,"arena_seconds":11.466,"type":"enemy_killed","enemy_id":40,"enemy_kind":"wisp","cause":"strike","chain":1,"remaining_after":5}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204543,"arena":"crossfire","attempt":6,"arena_seconds":11.466,"type":"strike_used","source":"pointer","aim":[0.801,-0.599],"hits":1,"reflections":0,"position":[0.534,0.776]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204741,"arena":"crossfire","attempt":6,"arena_seconds":11.666,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204758,"arena":"crossfire","attempt":6,"arena_seconds":11.684,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":204999,"arena":"crossfire","attempt":6,"arena_seconds":11.933,"type":"projectile_fired","projectile_id":77,"enemy_id":37,"position":[0.269,0.367],"direction":[0.499,0.867]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205066,"arena":"crossfire","attempt":6,"arena_seconds":12,"type":"arena_snapshot","player":[0.454,0.708],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":4,"actions":{"strikes":6,"strikeHits":1,"reflections":4,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205350,"arena":"crossfire","attempt":6,"arena_seconds":12.283,"type":"projectile_fired","projectile_id":78,"enemy_id":39,"position":[0.543,0.879],"direction":[-0.65,-0.76]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205500,"arena":"crossfire","attempt":6,"arena_seconds":12.433,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205612,"arena":"crossfire","attempt":6,"arena_seconds":12.534,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205666,"arena":"crossfire","attempt":6,"arena_seconds":12.6,"type":"projectile_fired","projectile_id":79,"enemy_id":38,"position":[0.733,0.365],"direction":[-0.784,0.621]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205831,"arena":"crossfire","attempt":6,"arena_seconds":12.75,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":205985,"arena":"crossfire","attempt":6,"arena_seconds":12.917,"type":"strike_used","source":"pointer","aim":[-0.415,-0.91],"hits":0,"reflections":1,"position":[0.333,0.683]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":206022,"arena":"crossfire","attempt":6,"arena_seconds":12.95,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":206102,"arena":"crossfire","attempt":6,"arena_seconds":13.033,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":206474,"arena":"crossfire","attempt":6,"arena_seconds":13.4,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":206583,"arena":"crossfire","attempt":6,"arena_seconds":13.517,"type":"arena_snapshot","player":[0.43,0.605],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":4,"actions":{"strikes":7,"strikeHits":1,"reflections":5,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":207399,"arena":"crossfire","attempt":6,"arena_seconds":14.333,"type":"projectile_fired","projectile_id":80,"enemy_id":37,"position":[0.284,0.372],"direction":[0.987,0.161]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":207466,"arena":"crossfire","attempt":6,"arena_seconds":14.4,"type":"projectile_fired","projectile_id":81,"enemy_id":39,"position":[0.565,0.862],"direction":[-0.206,-0.978]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":207599,"arena":"crossfire","attempt":6,"arena_seconds":14.533,"type":"projectile_fired","projectile_id":82,"enemy_id":38,"position":[0.698,0.353],"direction":[-1,-0.026]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":207857,"arena":"crossfire","attempt":6,"arena_seconds":14.783,"type":"movement_key_down","key":"KeyD","active_keys":["KeyW","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":207935,"arena":"crossfire","attempt":6,"arena_seconds":14.867,"type":"strike_used","source":"pointer","aim":[-0.253,0.967],"hits":0,"reflections":0,"position":[0.469,0.257]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":208028,"arena":"crossfire","attempt":6,"arena_seconds":14.95,"type":"movement_key_up","key":"KeyW","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":208083,"arena":"crossfire","attempt":6,"arena_seconds":15.017,"type":"arena_snapshot","player":[0.482,0.222],"health":5,"cooldowns":{"strike":0.16,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":5,"actions":{"strikes":8,"strikeHits":1,"reflections":5,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":208793,"arena":"crossfire","attempt":6,"arena_seconds":15.716,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":208852,"arena":"crossfire","attempt":6,"arena_seconds":15.783,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209111,"arena":"crossfire","attempt":6,"arena_seconds":16.033,"type":"movement_key_down","key":"KeyW","active_keys":["KeyD","KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209248,"arena":"crossfire","attempt":6,"arena_seconds":16.167,"type":"movement_key_up","key":"KeyD","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209478,"arena":"crossfire","attempt":6,"arena_seconds":16.399,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209533,"arena":"crossfire","attempt":6,"arena_seconds":16.467,"type":"projectile_fired","projectile_id":83,"enemy_id":39,"position":[0.589,0.825],"direction":[0.262,-0.965]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209583,"arena":"crossfire","attempt":6,"arena_seconds":16.517,"type":"arena_snapshot","player":[0.785,0.112],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":4,"actions":{"strikes":8,"strikeHits":1,"reflections":5,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":209733,"arena":"crossfire","attempt":6,"arena_seconds":16.667,"type":"projectile_fired","projectile_id":84,"enemy_id":37,"position":[0.293,0.355],"direction":[0.89,-0.456]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":210100,"arena":"crossfire","attempt":6,"arena_seconds":17.033,"type":"projectile_fired","projectile_id":85,"enemy_id":38,"position":[0.748,0.326],"direction":[0.216,-0.977]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":210264,"arena":"crossfire","attempt":6,"arena_seconds":17.183,"type":"movement_key_down","key":"KeyS","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":210341,"arena":"crossfire","attempt":6,"arena_seconds":17.267,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":210691,"arena":"crossfire","attempt":6,"arena_seconds":17.616,"type":"strike_used","source":"pointer","aim":[-0.24,0.971],"hits":0,"reflections":1,"position":[0.805,0.094]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":210886,"arena":"crossfire","attempt":6,"arena_seconds":17.817,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211049,"arena":"crossfire","attempt":6,"arena_seconds":17.983,"type":"enemy_damaged","enemy_id":38,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.746,0.362]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211083,"arena":"crossfire","attempt":6,"arena_seconds":18.017,"type":"arena_snapshot","player":[0.821,0.098],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":2,"actions":{"strikes":9,"strikeHits":1,"reflections":6,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211231,"arena":"crossfire","attempt":6,"arena_seconds":18.15,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211270,"arena":"crossfire","attempt":6,"arena_seconds":18.2,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211299,"arena":"crossfire","attempt":6,"arena_seconds":18.233,"type":"projectile_fired","projectile_id":86,"enemy_id":39,"position":[0.609,0.797],"direction":[0.337,-0.942]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211601,"arena":"crossfire","attempt":6,"arena_seconds":18.533,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211642,"arena":"crossfire","attempt":6,"arena_seconds":18.567,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211716,"arena":"crossfire","attempt":6,"arena_seconds":18.65,"type":"projectile_fired","projectile_id":87,"enemy_id":37,"position":[0.332,0.353],"direction":[0.947,-0.322]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211769,"arena":"crossfire","attempt":6,"arena_seconds":18.7,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":211982,"arena":"crossfire","attempt":6,"arena_seconds":18.916,"type":"projectile_fired","projectile_id":88,"enemy_id":38,"position":[0.755,0.386],"direction":[0.526,-0.85]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":212583,"arena":"crossfire","attempt":6,"arena_seconds":19.517,"type":"arena_snapshot","player":[0.871,0.199],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":5,"actions":{"strikes":9,"strikeHits":1,"reflections":6,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":212670,"arena":"crossfire","attempt":6,"arena_seconds":19.6,"type":"strike_used","source":"pointer","aim":[-0.973,0.232],"hits":0,"reflections":2,"position":[0.871,0.2]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":212689,"arena":"crossfire","attempt":6,"arena_seconds":19.617,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":212771,"arena":"crossfire","attempt":6,"arena_seconds":19.7,"type":"movement_key_down","key":"KeyS","active_keys":["KeyD","KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":212864,"arena":"crossfire","attempt":6,"arena_seconds":19.783,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213128,"arena":"crossfire","attempt":6,"arena_seconds":20.05,"type":"movement_key_down","key":"KeyD","active_keys":["KeyS","KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213382,"arena":"crossfire","attempt":6,"arena_seconds":20.3,"type":"movement_key_up","key":"KeyD","active_keys":["KeyS"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213549,"arena":"crossfire","attempt":6,"arena_seconds":20.483,"type":"projectile_fired","projectile_id":89,"enemy_id":39,"position":[0.644,0.767],"direction":[0.553,-0.833]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213582,"arena":"crossfire","attempt":6,"arena_seconds":20.517,"type":"projectile_fired","projectile_id":90,"enemy_id":37,"position":[0.369,0.361],"direction":[0.999,-0.053]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213799,"arena":"crossfire","attempt":6,"arena_seconds":20.734,"type":"projectile_fired","projectile_id":91,"enemy_id":38,"position":[0.775,0.422],"direction":[0.978,-0.209]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":213816,"arena":"crossfire","attempt":6,"arena_seconds":20.75,"type":"enemy_damaged","enemy_id":37,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.34,0.364]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":214083,"arena":"crossfire","attempt":6,"arena_seconds":21.017,"type":"arena_snapshot","player":[0.955,0.459],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":5,"enemy_counts":{"gunner":3,"wisp":2},"projectiles":6,"actions":{"strikes":10,"strikeHits":1,"reflections":8,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":1}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":214191,"arena":"crossfire","attempt":6,"arena_seconds":21.116,"type":"movement_key_up","key":"KeyS","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":214500,"arena":"crossfire","attempt":6,"arena_seconds":21.433,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":214879,"arena":"crossfire","attempt":6,"arena_seconds":21.8,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":214914,"arena":"crossfire","attempt":6,"arena_seconds":21.833,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215111,"arena":"crossfire","attempt":6,"arena_seconds":22.033,"type":"strike_used","source":"pointer","aim":[-0.977,-0.213],"hits":0,"reflections":1,"position":[0.938,0.482]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215121,"arena":"crossfire","attempt":6,"arena_seconds":22.05,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215316,"arena":"crossfire","attempt":6,"arena_seconds":22.25,"type":"enemy_damaged","enemy_id":38,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.743,0.441]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215317,"arena":"crossfire","attempt":6,"arena_seconds":22.25,"type":"enemy_killed","enemy_id":38,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":4}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215532,"arena":"crossfire","attempt":6,"arena_seconds":22.466,"type":"projectile_fired","projectile_id":92,"enemy_id":39,"position":[0.678,0.749],"direction":[0.618,-0.786]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215566,"arena":"crossfire","attempt":6,"arena_seconds":22.5,"type":"arena_snapshot","player":[0.901,0.462],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":2,"actions":{"strikes":11,"strikeHits":1,"reflections":9,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215688,"arena":"crossfire","attempt":6,"arena_seconds":22.617,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":215949,"arena":"crossfire","attempt":6,"arena_seconds":22.883,"type":"projectile_fired","projectile_id":93,"enemy_id":37,"position":[0.343,0.397],"direction":[0.998,0.064]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":216572,"arena":"crossfire","attempt":6,"arena_seconds":23.5,"type":"movement_key_down","key":"KeyA","active_keys":["KeyW","KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":216989,"arena":"crossfire","attempt":6,"arena_seconds":23.917,"type":"movement_key_up","key":"KeyW","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217083,"arena":"crossfire","attempt":6,"arena_seconds":24.017,"type":"arena_snapshot","player":[0.836,0.176],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":2,"actions":{"strikes":11,"strikeHits":1,"reflections":9,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217596,"arena":"crossfire","attempt":6,"arena_seconds":24.517,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217783,"arena":"crossfire","attempt":6,"arena_seconds":24.717,"type":"projectile_fired","projectile_id":94,"enemy_id":39,"position":[0.687,0.712],"direction":[-0.006,-1]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217863,"arena":"crossfire","attempt":6,"arena_seconds":24.783,"type":"movement_key_down","key":"KeyD","active_keys":["KeyD"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217899,"arena":"crossfire","attempt":6,"arena_seconds":24.833,"type":"projectile_fired","projectile_id":95,"enemy_id":37,"position":[0.371,0.378],"direction":[0.779,-0.627]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":217981,"arena":"crossfire","attempt":6,"arena_seconds":24.9,"type":"movement_key_up","key":"KeyD","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":218583,"arena":"crossfire","attempt":6,"arena_seconds":25.517,"type":"arena_snapshot","player":[0.677,0.133],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":4,"enemy_counts":{"gunner":2,"wisp":2},"projectiles":3,"actions":{"strikes":11,"strikeHits":1,"reflections":9,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":2}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":219056,"arena":"crossfire","attempt":6,"arena_seconds":25.983,"type":"strike_used","source":"pointer","aim":[-0.768,0.641],"hits":0,"reflections":1,"position":[0.677,0.132]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":219666,"arena":"crossfire","attempt":6,"arena_seconds":26.6,"type":"enemy_damaged","enemy_id":37,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.376,0.387]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":219668,"arena":"crossfire","attempt":6,"arena_seconds":26.6,"type":"enemy_killed","enemy_id":37,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":3}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":219793,"arena":"crossfire","attempt":6,"arena_seconds":26.717,"type":"strike_used","source":"pointer","aim":[-0.768,0.64],"hits":0,"reflections":1,"position":[0.677,0.132]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":220066,"arena":"crossfire","attempt":6,"arena_seconds":27,"type":"projectile_fired","projectile_id":96,"enemy_id":39,"position":[0.698,0.67],"direction":[-0.04,-0.999]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":220067,"arena":"crossfire","attempt":6,"arena_seconds":27,"type":"arena_snapshot","player":[0.677,0.132],"health":5,"cooldowns":{"strike":0.027,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"gunner":1,"wisp":2},"projectiles":2,"actions":{"strikes":13,"strikeHits":1,"reflections":11,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":3}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":220566,"arena":"crossfire","attempt":6,"arena_seconds":27.5,"type":"movement_key_down","key":"KeyA","active_keys":["KeyA"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":220813,"arena":"crossfire","attempt":6,"arena_seconds":27.733,"type":"movement_key_up","key":"KeyA","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":221268,"arena":"crossfire","attempt":6,"arena_seconds":28.2,"type":"movement_key_down","key":"KeyW","active_keys":["KeyW"]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":221350,"arena":"crossfire","attempt":6,"arena_seconds":28.266,"type":"movement_key_up","key":"KeyW","active_keys":[]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":221583,"arena":"crossfire","attempt":6,"arena_seconds":28.517,"type":"arena_snapshot","player":[0.616,0.121],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":3,"enemy_counts":{"gunner":1,"wisp":2},"projectiles":2,"actions":{"strikes":13,"strikeHits":1,"reflections":11,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":3}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":221945,"arena":"crossfire","attempt":6,"arena_seconds":28.866,"type":"strike_used","source":"pointer","aim":[0.146,0.989],"hits":0,"reflections":1,"position":[0.614,0.116]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":222000,"arena":"crossfire","attempt":6,"arena_seconds":28.933,"type":"enemy_damaged","enemy_id":42,"enemy_kind":"wisp","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.709,0.243]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":222001,"arena":"crossfire","attempt":6,"arena_seconds":28.933,"type":"enemy_killed","enemy_id":42,"enemy_kind":"wisp","cause":"reflected_projectile","chain":2,"remaining_after":2}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":222483,"arena":"crossfire","attempt":6,"arena_seconds":29.417,"type":"projectile_fired","projectile_id":97,"enemy_id":39,"position":[0.703,0.625],"direction":[-0.173,-0.985]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":223083,"arena":"crossfire","attempt":6,"arena_seconds":30.017,"type":"arena_snapshot","player":[0.613,0.114],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":2,"enemy_counts":{"gunner":1,"wisp":1},"projectiles":1,"actions":{"strikes":14,"strikeHits":1,"reflections":12,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":4}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":224202,"arena":"crossfire","attempt":6,"arena_seconds":31.133,"type":"enemy_damaged","enemy_id":41,"enemy_kind":"wisp","amount":1,"cause":"strike","hp_after":0,"position":[0.7,0.224]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":224202,"arena":"crossfire","attempt":6,"arena_seconds":31.133,"type":"enemy_killed","enemy_id":41,"enemy_kind":"wisp","cause":"strike","chain":3,"remaining_after":1}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":224203,"arena":"crossfire","attempt":6,"arena_seconds":31.133,"type":"strike_used","source":"pointer","aim":[0.179,0.984],"hits":1,"reflections":1,"position":[0.613,0.114]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":224550,"arena":"crossfire","attempt":6,"arena_seconds":31.483,"type":"projectile_fired","projectile_id":98,"enemy_id":39,"position":[0.707,0.586],"direction":[-0.195,-0.981]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":224582,"arena":"crossfire","attempt":6,"arena_seconds":31.517,"type":"arena_snapshot","player":[0.613,0.114],"health":5,"cooldowns":{"strike":0,"tether":0,"dash":0},"remaining":1,"enemy_counts":{"gunner":1},"projectiles":2,"actions":{"strikes":15,"strikeHits":2,"reflections":13,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":225099,"arena":"crossfire","attempt":6,"arena_seconds":32.033,"type":"enemy_damaged","enemy_id":39,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":1,"position":[0.714,0.608]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":225887,"arena":"crossfire","attempt":6,"arena_seconds":32.816,"type":"strike_used","source":"pointer","aim":[0.182,0.983],"hits":0,"reflections":0,"position":[0.613,0.114]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":226083,"arena":"crossfire","attempt":6,"arena_seconds":33.017,"type":"arena_snapshot","player":[0.613,0.114],"health":5,"cooldowns":{"strike":0.109,"tether":0,"dash":0},"remaining":1,"enemy_counts":{"gunner":1},"projectiles":1,"actions":{"strikes":16,"strikeHits":2,"reflections":13,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":5}}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":226333,"arena":"crossfire","attempt":6,"arena_seconds":33.266,"type":"strike_used","source":"pointer","aim":[0.182,0.983],"hits":0,"reflections":1,"position":[0.613,0.114]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":226666,"arena":"crossfire","attempt":6,"arena_seconds":33.6,"type":"projectile_fired","projectile_id":99,"enemy_id":39,"position":[0.721,0.613],"direction":[-0.212,-0.977]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":227432,"arena":"crossfire","attempt":6,"arena_seconds":34.367,"type":"enemy_damaged","enemy_id":39,"enemy_kind":"gunner","amount":2,"cause":"reflected_projectile","hp_after":0,"position":[0.73,0.633]}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":227433,"arena":"crossfire","attempt":6,"arena_seconds":34.367,"type":"enemy_killed","enemy_id":39,"enemy_kind":"gunner","cause":"reflected_projectile","chain":1,"remaining_after":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":227449,"arena":"crossfire","attempt":6,"arena_seconds":34.384,"type":"arena_completed","duration_seconds":34.384,"actions":{"strikes":17,"strikeHits":2,"reflections":14,"tethers":0,"tetherHits":0,"dashes":0,"dashHits":0,"collisions":0,"kills":6},"max_chain":3,"deaths_before_clear":0}
{"schema":1,"experiment":"006_breakline","prototype_revision":1,"session_id":"360f3f3f-1e5c-442d-8fdd-7f4976b17b29","elapsed_ms":231792,"arena":"crossfire","attempt":6,"arena_seconds":34.384,"type":"save_requested","event_count_before_save":787,"completed":["drift","crossfire","weight","remix"],"deaths":0}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,383 @@
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":6912,"room":"first_pair","room_index":0,"room_elapsed_ms":6912,"room_moves":0,"type":"session_started","source":"overlay","viewport":[1379,805],"room_count":4}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":6913,"room":"first_pair","room_index":0,"room_elapsed_ms":0,"room_moves":0,"type":"room_entered","reason":"session_started","start":{"self":[4,4],"echo":[8,4]},"sockets":[[2,4],[10,4]],"wall_count":40}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":12494,"room":"first_pair","room_index":0,"room_elapsed_ms":5581,"room_moves":1,"type":"command","direction":"left","source":"keyboard","before":{"self":[4,4],"echo":[8,4]},"after":{"self":[3,4],"echo":[9,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":1,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":12656,"room":"first_pair","room_index":0,"room_elapsed_ms":5743,"room_moves":1,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[9,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":13649,"room":"first_pair","room_index":0,"room_elapsed_ms":6736,"room_moves":2,"type":"command","direction":"left","source":"keyboard","before":{"self":[3,4],"echo":[9,4]},"after":{"self":[2,4],"echo":[10,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":2,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":13806,"room":"first_pair","room_index":0,"room_elapsed_ms":6893,"room_moves":2,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[10,4]},"completed":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":13806,"room":"first_pair","room_index":0,"room_elapsed_ms":6893,"room_moves":2,"type":"room_completed","moves":2,"duration_ms":6893,"resets":0}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":14587,"room":"one_holds","room_index":1,"room_elapsed_ms":1,"room_moves":0,"type":"room_entered","reason":"previous_completed","start":{"self":[3,4],"echo":[9,4]},"sockets":[[3,4],[6,4]],"wall_count":41}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":16564,"room":"one_holds","room_index":1,"room_elapsed_ms":1978,"room_moves":1,"type":"command","direction":"left","source":"keyboard","before":{"self":[3,4],"echo":[9,4]},"after":{"self":[2,4],"echo":[10,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":1,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":16723,"room":"one_holds","room_index":1,"room_elapsed_ms":2137,"room_moves":1,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[10,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":17598,"room":"one_holds","room_index":1,"room_elapsed_ms":3012,"room_moves":2,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,4],"echo":[10,4]},"after":{"self":[3,4],"echo":[9,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":2,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":17756,"room":"one_holds","room_index":1,"room_elapsed_ms":3170,"room_moves":2,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[9,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":17884,"room":"one_holds","room_index":1,"room_elapsed_ms":3298,"room_moves":3,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,4],"echo":[9,4]},"after":{"self":[3,4],"echo":[8,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[5.5,4],"history_depth":3,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18040,"room":"one_holds","room_index":1,"room_elapsed_ms":3454,"room_moves":3,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[8,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18358,"room":"one_holds","room_index":1,"room_elapsed_ms":3772,"room_moves":4,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,4],"echo":[8,4]},"after":{"self":[3,4],"echo":[7,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,4],"midpoint_after":[5,4],"history_depth":4,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18506,"room":"one_holds","room_index":1,"room_elapsed_ms":3920,"room_moves":4,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[7,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18795,"room":"one_holds","room_index":1,"room_elapsed_ms":4209,"room_moves":5,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,4],"echo":[7,4]},"after":{"self":[3,4],"echo":[6,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[5,4],"midpoint_after":[4.5,4],"history_depth":5,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18955,"room":"one_holds","room_index":1,"room_elapsed_ms":4369,"room_moves":5,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[6,4]},"completed":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":18955,"room":"one_holds","room_index":1,"room_elapsed_ms":4369,"room_moves":5,"type":"room_completed","moves":5,"duration_ms":4369,"resets":0}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":19737,"room":"transfer","room_index":2,"room_elapsed_ms":1,"room_moves":0,"type":"room_entered","reason":"previous_completed","start":{"self":[3,6],"echo":[9,2]},"sockets":[[6,2],[7,4]],"wall_count":49}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":23835,"room":"transfer","room_index":2,"room_elapsed_ms":4099,"room_moves":1,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,6],"echo":[9,2]},"after":{"self":[3,5],"echo":[9,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":1,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":23990,"room":"transfer","room_index":2,"room_elapsed_ms":4254,"room_moves":1,"type":"command_animation_finished","positions":{"self":[3,5],"echo":[9,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":24952,"room":"transfer","room_index":2,"room_elapsed_ms":5216,"room_moves":2,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,5],"echo":[9,3]},"after":{"self":[3,4],"echo":[9,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":2,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":25106,"room":"transfer","room_index":2,"room_elapsed_ms":5370,"room_moves":2,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[9,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":29176,"room":"transfer","room_index":2,"room_elapsed_ms":9440,"room_moves":3,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,4],"echo":[9,4]},"after":{"self":[3,4],"echo":[8,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[5.5,4],"history_depth":3,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":29322,"room":"transfer","room_index":2,"room_elapsed_ms":9586,"room_moves":3,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[8,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":29724,"room":"transfer","room_index":2,"room_elapsed_ms":9988,"room_moves":4,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,4],"echo":[8,4]},"after":{"self":[3,4],"echo":[7,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,4],"midpoint_after":[5,4],"history_depth":4,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":29873,"room":"transfer","room_index":2,"room_elapsed_ms":10137,"room_moves":4,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[7,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":30270,"room":"transfer","room_index":2,"room_elapsed_ms":10534,"room_moves":5,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,4],"echo":[7,4]},"after":{"self":[3,3],"echo":[7,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":5,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":30423,"room":"transfer","room_index":2,"room_elapsed_ms":10687,"room_moves":5,"type":"command_animation_finished","positions":{"self":[3,3],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":31194,"room":"transfer","room_index":2,"room_elapsed_ms":11458,"room_moves":6,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,3],"echo":[7,5]},"after":{"self":[4,3],"echo":[6,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":6,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":31357,"room":"transfer","room_index":2,"room_elapsed_ms":11621,"room_moves":6,"type":"command_animation_finished","positions":{"self":[4,3],"echo":[6,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":34487,"room":"transfer","room_index":2,"room_elapsed_ms":14751,"room_moves":7,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,3],"echo":[6,5]},"after":{"self":[5,3],"echo":[5,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":7,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":34639,"room":"transfer","room_index":2,"room_elapsed_ms":14903,"room_moves":7,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[5,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":34940,"room":"transfer","room_index":2,"room_elapsed_ms":15204,"room_moves":8,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,3],"echo":[5,5]},"after":{"self":[5,4],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":8,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":35090,"room":"transfer","room_index":2,"room_elapsed_ms":15354,"room_moves":8,"type":"command_animation_finished","positions":{"self":[5,4],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":35415,"room":"transfer","room_index":2,"room_elapsed_ms":15679,"room_moves":9,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,4],"echo":[5,4]},"after":{"self":[5,5],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":9,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":35572,"room":"transfer","room_index":2,"room_elapsed_ms":15836,"room_moves":9,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":36736,"room":"transfer","room_index":2,"room_elapsed_ms":17000,"room_moves":10,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,5],"echo":[5,3]},"after":{"self":[6,5],"echo":[4,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":10,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":36890,"room":"transfer","room_index":2,"room_elapsed_ms":17154,"room_moves":10,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":38543,"room":"transfer","room_index":2,"room_elapsed_ms":18807,"room_moves":11,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,5],"echo":[4,3]},"after":{"self":[5,5],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":11,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":38689,"room":"transfer","room_index":2,"room_elapsed_ms":18953,"room_moves":11,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":39037,"room":"transfer","room_index":2,"room_elapsed_ms":19301,"room_moves":12,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,5],"echo":[5,3]},"after":{"self":[5,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4],"midpoint_after":[5,4],"history_depth":12,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":39190,"room":"transfer","room_index":2,"room_elapsed_ms":19454,"room_moves":12,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":39399,"room":"transfer","room_index":2,"room_elapsed_ms":19663,"room_moves":13,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,6],"echo":[5,2]},"after":{"self":[5,6],"echo":[6,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[1,0],"midpoint_before":[5,4],"midpoint_after":[5.5,4],"history_depth":13,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":39556,"room":"transfer","room_index":2,"room_elapsed_ms":19820,"room_moves":13,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41083,"room":"transfer","room_index":2,"room_elapsed_ms":21347,"room_moves":14,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,6],"echo":[6,2]},"after":{"self":[5,6],"echo":[7,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[1,0],"midpoint_before":[5.5,4],"midpoint_after":[6,4],"history_depth":14,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41240,"room":"transfer","room_index":2,"room_elapsed_ms":21504,"room_moves":14,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[7,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41464,"room":"transfer","room_index":2,"room_elapsed_ms":21728,"room_moves":15,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,6],"echo":[7,2]},"after":{"self":[6,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":15,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41622,"room":"transfer","room_index":2,"room_elapsed_ms":21886,"room_moves":15,"type":"command_animation_finished","positions":{"self":[6,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41807,"room":"transfer","room_index":2,"room_elapsed_ms":22071,"room_moves":16,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,6],"echo":[6,2]},"after":{"self":[7,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":16,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":41956,"room":"transfer","room_index":2,"room_elapsed_ms":22220,"room_moves":16,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":44117,"room":"transfer","room_index":2,"room_elapsed_ms":24381,"room_moves":17,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,6],"echo":[5,2]},"after":{"self":[6,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":17,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":44272,"room":"transfer","room_index":2,"room_elapsed_ms":24536,"room_moves":17,"type":"command_animation_finished","positions":{"self":[6,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":44416,"room":"transfer","room_index":2,"room_elapsed_ms":24680,"room_moves":18,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,6],"echo":[6,2]},"after":{"self":[5,6],"echo":[7,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":18,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":44573,"room":"transfer","room_index":2,"room_elapsed_ms":24837,"room_moves":18,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[7,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":44857,"room":"transfer","room_index":2,"room_elapsed_ms":25121,"room_moves":18,"type":"command_no_effect","direction":"left","source":"keyboard","before":{"self":[5,6],"echo":[7,2]},"self_blocked":true,"echo_blocked":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":45713,"room":"transfer","room_index":2,"room_elapsed_ms":25977,"room_moves":19,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,6],"echo":[7,2]},"after":{"self":[6,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":19,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":45873,"room":"transfer","room_index":2,"room_elapsed_ms":26137,"room_moves":19,"type":"command_animation_finished","positions":{"self":[6,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":46508,"room":"transfer","room_index":2,"room_elapsed_ms":26772,"room_moves":20,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,6],"echo":[6,2]},"after":{"self":[7,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":20,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":46656,"room":"transfer","room_index":2,"room_elapsed_ms":26920,"room_moves":20,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":47133,"room":"transfer","room_index":2,"room_elapsed_ms":27397,"room_moves":21,"type":"command","direction":"right","source":"keyboard","before":{"self":[7,6],"echo":[5,2]},"after":{"self":[8,6],"echo":[4,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":21,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":47290,"room":"transfer","room_index":2,"room_elapsed_ms":27554,"room_moves":21,"type":"command_animation_finished","positions":{"self":[8,6],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":49149,"room":"transfer","room_index":2,"room_elapsed_ms":29413,"room_moves":22,"type":"command","direction":"left","source":"keyboard","before":{"self":[8,6],"echo":[4,2]},"after":{"self":[7,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":22,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":49307,"room":"transfer","room_index":2,"room_elapsed_ms":29571,"room_moves":22,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":49475,"room":"transfer","room_index":2,"room_elapsed_ms":29739,"room_moves":23,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,6],"echo":[5,2]},"after":{"self":[6,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":23,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":49622,"room":"transfer","room_index":2,"room_elapsed_ms":29886,"room_moves":23,"type":"command_animation_finished","positions":{"self":[6,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":50534,"room":"transfer","room_index":2,"room_elapsed_ms":30798,"room_moves":24,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,6],"echo":[6,2]},"after":{"self":[7,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":24,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":50691,"room":"transfer","room_index":2,"room_elapsed_ms":30955,"room_moves":24,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":50876,"room":"transfer","room_index":2,"room_elapsed_ms":31140,"room_moves":25,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,6],"echo":[5,2]},"after":{"self":[7,5],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":25,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":51022,"room":"transfer","room_index":2,"room_elapsed_ms":31286,"room_moves":25,"type":"command_animation_finished","positions":{"self":[7,5],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":51387,"room":"transfer","room_index":2,"room_elapsed_ms":31651,"room_moves":26,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,5],"echo":[5,3]},"after":{"self":[7,4],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":26,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":51539,"room":"transfer","room_index":2,"room_elapsed_ms":31803,"room_moves":26,"type":"command_animation_finished","positions":{"self":[7,4],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52237,"room":"transfer","room_index":2,"room_elapsed_ms":32501,"room_moves":27,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,4],"echo":[5,4]},"after":{"self":[7,3],"echo":[5,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":27,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52390,"room":"transfer","room_index":2,"room_elapsed_ms":32654,"room_moves":27,"type":"command_animation_finished","positions":{"self":[7,3],"echo":[5,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52507,"room":"transfer","room_index":2,"room_elapsed_ms":32771,"room_moves":28,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,3],"echo":[5,5]},"after":{"self":[7,2],"echo":[5,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":28,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52656,"room":"transfer","room_index":2,"room_elapsed_ms":32920,"room_moves":28,"type":"command_animation_finished","positions":{"self":[7,2],"echo":[5,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52822,"room":"transfer","room_index":2,"room_elapsed_ms":33086,"room_moves":29,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,2],"echo":[5,6]},"after":{"self":[6,2],"echo":[6,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":29,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":52972,"room":"transfer","room_index":2,"room_elapsed_ms":33236,"room_moves":29,"type":"command_animation_finished","positions":{"self":[6,2],"echo":[6,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":54380,"room":"transfer","room_index":2,"room_elapsed_ms":34644,"room_moves":30,"type":"command","direction":"down","source":"keyboard","before":{"self":[6,2],"echo":[6,6]},"after":{"self":[6,2],"echo":[6,5]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,-1],"midpoint_before":[6,4],"midpoint_after":[6,3.5],"history_depth":30,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":54540,"room":"transfer","room_index":2,"room_elapsed_ms":34804,"room_moves":30,"type":"command_animation_finished","positions":{"self":[6,2],"echo":[6,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":54999,"room":"transfer","room_index":2,"room_elapsed_ms":35263,"room_moves":31,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,2],"echo":[6,5]},"after":{"self":[5,2],"echo":[7,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":31,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":55156,"room":"transfer","room_index":2,"room_elapsed_ms":35420,"room_moves":31,"type":"command_animation_finished","positions":{"self":[5,2],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":56021,"room":"transfer","room_index":2,"room_elapsed_ms":36285,"room_moves":32,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,2],"echo":[7,5]},"after":{"self":[5,3],"echo":[7,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":32,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":56174,"room":"transfer","room_index":2,"room_elapsed_ms":36438,"room_moves":32,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[7,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":57835,"room":"transfer","room_index":2,"room_elapsed_ms":38099,"room_moves":33,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,3],"echo":[7,4]},"after":{"self":[5,4],"echo":[7,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":33,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":57989,"room":"transfer","room_index":2,"room_elapsed_ms":38253,"room_moves":33,"type":"command_animation_finished","positions":{"self":[5,4],"echo":[7,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":58177,"room":"transfer","room_index":2,"room_elapsed_ms":38441,"room_moves":34,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,4],"echo":[7,3]},"after":{"self":[5,5],"echo":[7,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":34,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":58323,"room":"transfer","room_index":2,"room_elapsed_ms":38587,"room_moves":34,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[7,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":58410,"room":"transfer","room_index":2,"room_elapsed_ms":38674,"room_moves":35,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,5],"echo":[7,2]},"after":{"self":[5,6],"echo":[7,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":35,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":58556,"room":"transfer","room_index":2,"room_elapsed_ms":38820,"room_moves":35,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[7,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":60943,"room":"transfer","room_index":2,"room_elapsed_ms":41207,"room_moves":36,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,6],"echo":[7,1]},"after":{"self":[5,7],"echo":[7,1]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[6,3.5],"midpoint_after":[6,4],"history_depth":36,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":61089,"room":"transfer","room_index":2,"room_elapsed_ms":41353,"room_moves":36,"type":"command_animation_finished","positions":{"self":[5,7],"echo":[7,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":61696,"room":"transfer","room_index":2,"room_elapsed_ms":41960,"room_moves":37,"type":"command","direction":"up","source":"keyboard","before":{"self":[5,7],"echo":[7,1]},"after":{"self":[5,6],"echo":[7,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":37,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":61856,"room":"transfer","room_index":2,"room_elapsed_ms":42120,"room_moves":37,"type":"command_animation_finished","positions":{"self":[5,6],"echo":[7,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":61965,"room":"transfer","room_index":2,"room_elapsed_ms":42229,"room_moves":38,"type":"command","direction":"up","source":"keyboard","before":{"self":[5,6],"echo":[7,2]},"after":{"self":[5,5],"echo":[7,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":38,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":62124,"room":"transfer","room_index":2,"room_elapsed_ms":42388,"room_moves":38,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[7,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":62369,"room":"transfer","room_index":2,"room_elapsed_ms":42633,"room_moves":39,"type":"command","direction":"up","source":"keyboard","before":{"self":[5,5],"echo":[7,3]},"after":{"self":[5,4],"echo":[7,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":39,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":62523,"room":"transfer","room_index":2,"room_elapsed_ms":42787,"room_moves":39,"type":"command_animation_finished","positions":{"self":[5,4],"echo":[7,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":62993,"room":"transfer","room_index":2,"room_elapsed_ms":43257,"room_moves":40,"type":"command","direction":"up","source":"keyboard","before":{"self":[5,4],"echo":[7,4]},"after":{"self":[5,3],"echo":[7,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":40,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":63139,"room":"transfer","room_index":2,"room_elapsed_ms":43403,"room_moves":40,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":63527,"room":"transfer","room_index":2,"room_elapsed_ms":43791,"room_moves":41,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,3],"echo":[7,5]},"after":{"self":[4,3],"echo":[8,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":41,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":63672,"room":"transfer","room_index":2,"room_elapsed_ms":43936,"room_moves":41,"type":"command_animation_finished","positions":{"self":[4,3],"echo":[8,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":63854,"room":"transfer","room_index":2,"room_elapsed_ms":44118,"room_moves":42,"type":"command","direction":"left","source":"keyboard","before":{"self":[4,3],"echo":[8,5]},"after":{"self":[3,3],"echo":[9,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":42,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64006,"room":"transfer","room_index":2,"room_elapsed_ms":44270,"room_moves":42,"type":"command_animation_finished","positions":{"self":[3,3],"echo":[9,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64096,"room":"transfer","room_index":2,"room_elapsed_ms":44360,"room_moves":43,"type":"command","direction":"down","source":"keyboard","before":{"self":[3,3],"echo":[9,5]},"after":{"self":[3,4],"echo":[9,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":43,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64257,"room":"transfer","room_index":2,"room_elapsed_ms":44521,"room_moves":43,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[9,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64387,"room":"transfer","room_index":2,"room_elapsed_ms":44651,"room_moves":44,"type":"command","direction":"down","source":"keyboard","before":{"self":[3,4],"echo":[9,4]},"after":{"self":[3,5],"echo":[9,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":44,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64540,"room":"transfer","room_index":2,"room_elapsed_ms":44804,"room_moves":44,"type":"command_animation_finished","positions":{"self":[3,5],"echo":[9,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":64866,"room":"transfer","room_index":2,"room_elapsed_ms":45130,"room_moves":45,"type":"command","direction":"down","source":"keyboard","before":{"self":[3,5],"echo":[9,3]},"after":{"self":[3,6],"echo":[9,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":45,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":65023,"room":"transfer","room_index":2,"room_elapsed_ms":45287,"room_moves":45,"type":"command_animation_finished","positions":{"self":[3,6],"echo":[9,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":65071,"room":"transfer","room_index":2,"room_elapsed_ms":45335,"room_moves":46,"type":"command","direction":"left","source":"keyboard","before":{"self":[3,6],"echo":[9,2]},"after":{"self":[2,6],"echo":[10,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":46,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":65223,"room":"transfer","room_index":2,"room_elapsed_ms":45487,"room_moves":46,"type":"command_animation_finished","positions":{"self":[2,6],"echo":[10,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":65447,"room":"transfer","room_index":2,"room_elapsed_ms":45711,"room_moves":47,"type":"command","direction":"up","source":"keyboard","before":{"self":[2,6],"echo":[10,2]},"after":{"self":[2,6],"echo":[10,3]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4.5],"history_depth":47,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":65607,"room":"transfer","room_index":2,"room_elapsed_ms":45871,"room_moves":47,"type":"command_animation_finished","positions":{"self":[2,6],"echo":[10,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":66279,"room":"transfer","room_index":2,"room_elapsed_ms":46543,"room_moves":48,"type":"command","direction":"up","source":"keyboard","before":{"self":[2,6],"echo":[10,3]},"after":{"self":[2,6],"echo":[10,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[6,4.5],"midpoint_after":[6,5],"history_depth":48,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":66439,"room":"transfer","room_index":2,"room_elapsed_ms":46703,"room_moves":48,"type":"command_animation_finished","positions":{"self":[2,6],"echo":[10,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":66602,"room":"transfer","room_index":2,"room_elapsed_ms":46866,"room_moves":49,"type":"command","direction":"left","source":"keyboard","before":{"self":[2,6],"echo":[10,4]},"after":{"self":[1,6],"echo":[11,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":49,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":66756,"room":"transfer","room_index":2,"room_elapsed_ms":47020,"room_moves":49,"type":"command_animation_finished","positions":{"self":[1,6],"echo":[11,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67075,"room":"transfer","room_index":2,"room_elapsed_ms":47339,"room_moves":50,"type":"command","direction":"right","source":"keyboard","before":{"self":[1,6],"echo":[11,4]},"after":{"self":[2,6],"echo":[10,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":50,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67223,"room":"transfer","room_index":2,"room_elapsed_ms":47487,"room_moves":50,"type":"command_animation_finished","positions":{"self":[2,6],"echo":[10,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67337,"room":"transfer","room_index":2,"room_elapsed_ms":47601,"room_moves":51,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,6],"echo":[10,4]},"after":{"self":[3,6],"echo":[9,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":51,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67490,"room":"transfer","room_index":2,"room_elapsed_ms":47754,"room_moves":51,"type":"command_animation_finished","positions":{"self":[3,6],"echo":[9,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67694,"room":"transfer","room_index":2,"room_elapsed_ms":47958,"room_moves":52,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,6],"echo":[9,4]},"after":{"self":[3,6],"echo":[8,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[5.5,5],"history_depth":52,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":67839,"room":"transfer","room_index":2,"room_elapsed_ms":48103,"room_moves":52,"type":"command_animation_finished","positions":{"self":[3,6],"echo":[8,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":68175,"room":"transfer","room_index":2,"room_elapsed_ms":48439,"room_moves":53,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,6],"echo":[8,4]},"after":{"self":[3,6],"echo":[7,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,5],"midpoint_after":[5,5],"history_depth":53,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":68323,"room":"transfer","room_index":2,"room_elapsed_ms":48587,"room_moves":53,"type":"command_animation_finished","positions":{"self":[3,6],"echo":[7,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":68461,"room":"transfer","room_index":2,"room_elapsed_ms":48725,"room_moves":54,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,6],"echo":[7,4]},"after":{"self":[3,5],"echo":[7,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[5,5],"midpoint_after":[5,5],"history_depth":54,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":68623,"room":"transfer","room_index":2,"room_elapsed_ms":48887,"room_moves":54,"type":"command_animation_finished","positions":{"self":[3,5],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":68888,"room":"transfer","room_index":2,"room_elapsed_ms":49152,"room_moves":55,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,5],"echo":[7,5]},"after":{"self":[3,4],"echo":[7,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[5,5],"midpoint_after":[5,5],"history_depth":55,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69039,"room":"transfer","room_index":2,"room_elapsed_ms":49303,"room_moves":55,"type":"command_animation_finished","positions":{"self":[3,4],"echo":[7,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69236,"room":"transfer","room_index":2,"room_elapsed_ms":49500,"room_moves":56,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,4],"echo":[7,6]},"after":{"self":[3,3],"echo":[7,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[5,5],"midpoint_after":[5,5],"history_depth":56,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69390,"room":"transfer","room_index":2,"room_elapsed_ms":49654,"room_moves":56,"type":"command_animation_finished","positions":{"self":[3,3],"echo":[7,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69564,"room":"transfer","room_index":2,"room_elapsed_ms":49828,"room_moves":57,"type":"command","direction":"up","source":"keyboard","before":{"self":[3,3],"echo":[7,7]},"after":{"self":[3,2],"echo":[7,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[5,5],"midpoint_after":[5,4.5],"history_depth":57,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69723,"room":"transfer","room_index":2,"room_elapsed_ms":49987,"room_moves":57,"type":"command_animation_finished","positions":{"self":[3,2],"echo":[7,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":69976,"room":"transfer","room_index":2,"room_elapsed_ms":50240,"room_moves":58,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,2],"echo":[7,7]},"after":{"self":[4,2],"echo":[6,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,4.5],"midpoint_after":[5,4.5],"history_depth":58,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":70123,"room":"transfer","room_index":2,"room_elapsed_ms":50387,"room_moves":58,"type":"command_animation_finished","positions":{"self":[4,2],"echo":[6,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":70273,"room":"transfer","room_index":2,"room_elapsed_ms":50537,"room_moves":59,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,2],"echo":[6,7]},"after":{"self":[5,2],"echo":[5,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,4.5],"midpoint_after":[5,4.5],"history_depth":59,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":70424,"room":"transfer","room_index":2,"room_elapsed_ms":50688,"room_moves":59,"type":"command_animation_finished","positions":{"self":[5,2],"echo":[5,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":70813,"room":"transfer","room_index":2,"room_elapsed_ms":51077,"room_moves":60,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,2],"echo":[5,7]},"after":{"self":[5,3],"echo":[5,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4.5],"midpoint_after":[5,4.5],"history_depth":60,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":70973,"room":"transfer","room_index":2,"room_elapsed_ms":51237,"room_moves":60,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[5,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71172,"room":"transfer","room_index":2,"room_elapsed_ms":51436,"room_moves":61,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,3],"echo":[5,6]},"after":{"self":[5,4],"echo":[5,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4.5],"midpoint_after":[5,4.5],"history_depth":61,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71323,"room":"transfer","room_index":2,"room_elapsed_ms":51587,"room_moves":61,"type":"command_animation_finished","positions":{"self":[5,4],"echo":[5,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71417,"room":"transfer","room_index":2,"room_elapsed_ms":51681,"room_moves":62,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,4],"echo":[5,5]},"after":{"self":[5,5],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,4.5],"midpoint_after":[5,4.5],"history_depth":62,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71573,"room":"transfer","room_index":2,"room_elapsed_ms":51837,"room_moves":62,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71799,"room":"transfer","room_index":2,"room_elapsed_ms":52063,"room_moves":63,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,5],"echo":[5,4]},"after":{"self":[6,5],"echo":[5,4]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5,4.5],"midpoint_after":[5.5,4.5],"history_depth":63,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":71956,"room":"transfer","room_index":2,"room_elapsed_ms":52220,"room_moves":63,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":72426,"room":"transfer","room_index":2,"room_elapsed_ms":52690,"room_moves":64,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,5],"echo":[5,4]},"after":{"self":[7,5],"echo":[5,4]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5.5,4.5],"midpoint_after":[6,4.5],"history_depth":64,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":72573,"room":"transfer","room_index":2,"room_elapsed_ms":52837,"room_moves":64,"type":"command_animation_finished","positions":{"self":[7,5],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":72831,"room":"transfer","room_index":2,"room_elapsed_ms":53095,"room_moves":65,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,5],"echo":[5,4]},"after":{"self":[7,4],"echo":[5,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4.5],"midpoint_after":[6,4.5],"history_depth":65,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":72989,"room":"transfer","room_index":2,"room_elapsed_ms":53253,"room_moves":65,"type":"command_animation_finished","positions":{"self":[7,4],"echo":[5,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":73664,"room":"transfer","room_index":2,"room_elapsed_ms":53928,"room_moves":66,"type":"command","direction":"down","source":"keyboard","before":{"self":[7,4],"echo":[5,5]},"after":{"self":[7,5],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4.5],"midpoint_after":[6,4.5],"history_depth":66,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":73823,"room":"transfer","room_index":2,"room_elapsed_ms":54087,"room_moves":66,"type":"command_animation_finished","positions":{"self":[7,5],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":74058,"room":"transfer","room_index":2,"room_elapsed_ms":54322,"room_moves":67,"type":"command","direction":"down","source":"keyboard","before":{"self":[7,5],"echo":[5,4]},"after":{"self":[7,6],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4.5],"midpoint_after":[6,4.5],"history_depth":67,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":74207,"room":"transfer","room_index":2,"room_elapsed_ms":54471,"room_moves":67,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":74620,"room":"transfer","room_index":2,"room_elapsed_ms":54884,"room_moves":68,"type":"command","direction":"down","source":"keyboard","before":{"self":[7,6],"echo":[5,3]},"after":{"self":[7,7],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4.5],"midpoint_after":[6,4.5],"history_depth":68,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":74773,"room":"transfer","room_index":2,"room_elapsed_ms":55037,"room_moves":68,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":74882,"room":"transfer","room_index":2,"room_elapsed_ms":55146,"room_moves":69,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,7],"echo":[5,2]},"after":{"self":[6,7],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4.5],"midpoint_after":[6,4.5],"history_depth":69,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":75039,"room":"transfer","room_index":2,"room_elapsed_ms":55303,"room_moves":69,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":75621,"room":"transfer","room_index":2,"room_elapsed_ms":55885,"room_moves":70,"type":"command","direction":"up","source":"keyboard","before":{"self":[6,7],"echo":[6,2]},"after":{"self":[6,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[6,4.5],"midpoint_after":[6,4],"history_depth":70,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":75773,"room":"transfer","room_index":2,"room_elapsed_ms":56037,"room_moves":70,"type":"command_animation_finished","positions":{"self":[6,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":76430,"room":"transfer","room_index":2,"room_elapsed_ms":56694,"room_moves":71,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,6],"echo":[6,2]},"after":{"self":[7,6],"echo":[5,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":71,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":76590,"room":"transfer","room_index":2,"room_elapsed_ms":56854,"room_moves":71,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":79140,"room":"transfer","room_index":2,"room_elapsed_ms":59404,"room_moves":72,"type":"command","direction":"right","source":"keyboard","before":{"self":[7,6],"echo":[5,2]},"after":{"self":[8,6],"echo":[4,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":72,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":79290,"room":"transfer","room_index":2,"room_elapsed_ms":59554,"room_moves":72,"type":"command_animation_finished","positions":{"self":[8,6],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":79665,"room":"transfer","room_index":2,"room_elapsed_ms":59929,"room_moves":73,"type":"command","direction":"down","source":"keyboard","before":{"self":[8,6],"echo":[4,2]},"after":{"self":[8,7],"echo":[4,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":73,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":79824,"room":"transfer","room_index":2,"room_elapsed_ms":60088,"room_moves":73,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[4,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":79898,"room":"transfer","room_index":2,"room_elapsed_ms":60162,"room_moves":74,"type":"command","direction":"right","source":"keyboard","before":{"self":[8,7],"echo":[4,1]},"after":{"self":[9,7],"echo":[3,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":74,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":80056,"room":"transfer","room_index":2,"room_elapsed_ms":60320,"room_moves":74,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":80172,"room":"transfer","room_index":2,"room_elapsed_ms":60436,"room_moves":75,"type":"command","direction":"up","source":"keyboard","before":{"self":[9,7],"echo":[3,1]},"after":{"self":[9,7],"echo":[3,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4.5],"history_depth":75,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":80322,"room":"transfer","room_index":2,"room_elapsed_ms":60586,"room_moves":75,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":80790,"room":"transfer","room_index":2,"room_elapsed_ms":61054,"room_moves":76,"type":"command","direction":"up","source":"keyboard","before":{"self":[9,7],"echo":[3,2]},"after":{"self":[9,7],"echo":[3,3]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[6,4.5],"midpoint_after":[6,5],"history_depth":76,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":80940,"room":"transfer","room_index":2,"room_elapsed_ms":61204,"room_moves":76,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":81592,"room":"transfer","room_index":2,"room_elapsed_ms":61856,"room_moves":77,"type":"command","direction":"left","source":"keyboard","before":{"self":[9,7],"echo":[3,3]},"after":{"self":[8,7],"echo":[4,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":77,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":81740,"room":"transfer","room_index":2,"room_elapsed_ms":62004,"room_moves":77,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":81971,"room":"transfer","room_index":2,"room_elapsed_ms":62235,"room_moves":78,"type":"command","direction":"left","source":"keyboard","before":{"self":[8,7],"echo":[4,3]},"after":{"self":[7,7],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":78,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":82123,"room":"transfer","room_index":2,"room_elapsed_ms":62387,"room_moves":78,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":87683,"room":"transfer","room_index":2,"room_elapsed_ms":67947,"room_moves":79,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,7],"echo":[5,3]},"after":{"self":[7,6],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":79,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":87840,"room":"transfer","room_index":2,"room_elapsed_ms":68104,"room_moves":79,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":88108,"room":"transfer","room_index":2,"room_elapsed_ms":68372,"room_moves":80,"type":"command","direction":"down","source":"keyboard","before":{"self":[7,6],"echo":[5,4]},"after":{"self":[7,7],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":80,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":88256,"room":"transfer","room_index":2,"room_elapsed_ms":68520,"room_moves":80,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":88775,"room":"transfer","room_index":2,"room_elapsed_ms":69039,"room_moves":81,"type":"command","direction":"right","source":"keyboard","before":{"self":[7,7],"echo":[5,3]},"after":{"self":[8,7],"echo":[4,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":81,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":88923,"room":"transfer","room_index":2,"room_elapsed_ms":69187,"room_moves":81,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89085,"room":"transfer","room_index":2,"room_elapsed_ms":69349,"room_moves":82,"type":"command","direction":"right","source":"keyboard","before":{"self":[8,7],"echo":[4,3]},"after":{"self":[9,7],"echo":[3,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":82,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89239,"room":"transfer","room_index":2,"room_elapsed_ms":69503,"room_moves":82,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89495,"room":"transfer","room_index":2,"room_elapsed_ms":69759,"room_moves":83,"type":"command","direction":"right","source":"keyboard","before":{"self":[9,7],"echo":[3,3]},"after":{"self":[10,7],"echo":[2,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":83,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89656,"room":"transfer","room_index":2,"room_elapsed_ms":69920,"room_moves":83,"type":"command_animation_finished","positions":{"self":[10,7],"echo":[2,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89778,"room":"transfer","room_index":2,"room_elapsed_ms":70042,"room_moves":84,"type":"command","direction":"up","source":"keyboard","before":{"self":[10,7],"echo":[2,3]},"after":{"self":[10,6],"echo":[2,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,5],"midpoint_after":[6,5],"history_depth":84,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":89939,"room":"transfer","room_index":2,"room_elapsed_ms":70203,"room_moves":84,"type":"command_animation_finished","positions":{"self":[10,6],"echo":[2,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":90609,"room":"transfer","room_index":2,"room_elapsed_ms":70873,"room_moves":85,"type":"command","direction":"left","source":"keyboard","before":{"self":[10,6],"echo":[2,4]},"after":{"self":[10,6],"echo":[3,4]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[1,0],"midpoint_before":[6,5],"midpoint_after":[6.5,5],"history_depth":85,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":90757,"room":"transfer","room_index":2,"room_elapsed_ms":71021,"room_moves":85,"type":"command_animation_finished","positions":{"self":[10,6],"echo":[3,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":91173,"room":"transfer","room_index":2,"room_elapsed_ms":71437,"room_moves":86,"type":"command","direction":"down","source":"keyboard","before":{"self":[10,6],"echo":[3,4]},"after":{"self":[10,7],"echo":[3,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6.5,5],"midpoint_after":[6.5,5],"history_depth":86,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":91322,"room":"transfer","room_index":2,"room_elapsed_ms":71586,"room_moves":86,"type":"command_animation_finished","positions":{"self":[10,7],"echo":[3,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":91564,"room":"transfer","room_index":2,"room_elapsed_ms":71828,"room_moves":87,"type":"command","direction":"left","source":"keyboard","before":{"self":[10,7],"echo":[3,3]},"after":{"self":[9,7],"echo":[4,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6.5,5],"midpoint_after":[6.5,5],"history_depth":87,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":91723,"room":"transfer","room_index":2,"room_elapsed_ms":71987,"room_moves":87,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":91909,"room":"transfer","room_index":2,"room_elapsed_ms":72173,"room_moves":88,"type":"command","direction":"left","source":"keyboard","before":{"self":[9,7],"echo":[4,3]},"after":{"self":[8,7],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6.5,5],"midpoint_after":[6.5,5],"history_depth":88,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":92057,"room":"transfer","room_index":2,"room_elapsed_ms":72321,"room_moves":88,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":96341,"room":"transfer","room_index":2,"room_elapsed_ms":76605,"room_moves":89,"type":"command","direction":"down","source":"keyboard","before":{"self":[8,7],"echo":[5,3]},"after":{"self":[8,7],"echo":[5,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,-1],"midpoint_before":[6.5,5],"midpoint_after":[6.5,4.5],"history_depth":89,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":96490,"room":"transfer","room_index":2,"room_elapsed_ms":76754,"room_moves":89,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":98691,"room":"transfer","room_index":2,"room_elapsed_ms":78955,"room_moves":90,"type":"command","direction":"left","source":"keyboard","before":{"self":[8,7],"echo":[5,2]},"after":{"self":[7,7],"echo":[6,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6.5,4.5],"midpoint_after":[6.5,4.5],"history_depth":90,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":98839,"room":"transfer","room_index":2,"room_elapsed_ms":79103,"room_moves":90,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":99696,"room":"transfer","room_index":2,"room_elapsed_ms":79960,"room_moves":91,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,7],"echo":[6,2]},"after":{"self":[7,6],"echo":[6,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[6.5,4.5],"midpoint_after":[6.5,4],"history_depth":91,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":99857,"room":"transfer","room_index":2,"room_elapsed_ms":80121,"room_moves":91,"type":"command_animation_finished","positions":{"self":[7,6],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":100063,"room":"transfer","room_index":2,"room_elapsed_ms":80327,"room_moves":92,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,6],"echo":[6,2]},"after":{"self":[7,5],"echo":[6,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[6.5,4],"midpoint_after":[6.5,3.5],"history_depth":92,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":100224,"room":"transfer","room_index":2,"room_elapsed_ms":80488,"room_moves":92,"type":"command_animation_finished","positions":{"self":[7,5],"echo":[6,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":100446,"room":"transfer","room_index":2,"room_elapsed_ms":80710,"room_moves":93,"type":"command","direction":"up","source":"keyboard","before":{"self":[7,5],"echo":[6,2]},"after":{"self":[7,4],"echo":[6,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[6.5,3.5],"midpoint_after":[6.5,3],"history_depth":93,"open_chamber":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":100606,"room":"transfer","room_index":2,"room_elapsed_ms":80870,"room_moves":93,"type":"command_animation_finished","positions":{"self":[7,4],"echo":[6,2]},"completed":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":100606,"room":"transfer","room_index":2,"room_elapsed_ms":80870,"room_moves":93,"type":"room_completed","moves":93,"duration_ms":80870,"resets":0}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":101387,"room":"open_chamber","room_index":3,"room_elapsed_ms":1,"room_moves":0,"type":"room_entered","reason":"previous_completed","start":{"self":[4,5],"echo":[8,3]},"sockets":[],"wall_count":50}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":115524,"room":"open_chamber","room_index":3,"room_elapsed_ms":14138,"room_moves":1,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,5],"echo":[8,3]},"after":{"self":[5,5],"echo":[7,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":1,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":115673,"room":"open_chamber","room_index":3,"room_elapsed_ms":14287,"room_moves":1,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[7,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":115977,"room":"open_chamber","room_index":3,"room_elapsed_ms":14591,"room_moves":2,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,5],"echo":[7,3]},"after":{"self":[6,5],"echo":[6,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":2,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":116124,"room":"open_chamber","room_index":3,"room_elapsed_ms":14738,"room_moves":2,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[6,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":116360,"room":"open_chamber","room_index":3,"room_elapsed_ms":14974,"room_moves":3,"type":"command","direction":"up","source":"keyboard","before":{"self":[6,5],"echo":[6,3]},"after":{"self":[6,4],"echo":[6,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":3,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":116508,"room":"open_chamber","room_index":3,"room_elapsed_ms":15122,"room_moves":3,"type":"command_animation_finished","positions":{"self":[6,4],"echo":[6,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":116812,"room":"open_chamber","room_index":3,"room_elapsed_ms":15426,"room_moves":4,"type":"command","direction":"up","source":"keyboard","before":{"self":[6,4],"echo":[6,4]},"after":{"self":[6,3],"echo":[6,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":4,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":116973,"room":"open_chamber","room_index":3,"room_elapsed_ms":15587,"room_moves":4,"type":"command_animation_finished","positions":{"self":[6,3],"echo":[6,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":117571,"room":"open_chamber","room_index":3,"room_elapsed_ms":16185,"room_moves":5,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,3],"echo":[6,5]},"after":{"self":[5,3],"echo":[7,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":5,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":117723,"room":"open_chamber","room_index":3,"room_elapsed_ms":16337,"room_moves":5,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":117914,"room":"open_chamber","room_index":3,"room_elapsed_ms":16528,"room_moves":6,"type":"command","direction":"up","source":"keyboard","before":{"self":[5,3],"echo":[7,5]},"after":{"self":[5,2],"echo":[7,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,-1],"echo_displacement":[0,0],"midpoint_before":[6,4],"midpoint_after":[6,3.5],"history_depth":6,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":118074,"room":"open_chamber","room_index":3,"room_elapsed_ms":16688,"room_moves":6,"type":"command_animation_finished","positions":{"self":[5,2],"echo":[7,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":118195,"room":"open_chamber","room_index":3,"room_elapsed_ms":16809,"room_moves":7,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,2],"echo":[7,5]},"after":{"self":[5,2],"echo":[6,5]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[6,3.5],"midpoint_after":[5.5,3.5],"history_depth":7,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":118356,"room":"open_chamber","room_index":3,"room_elapsed_ms":16970,"room_moves":7,"type":"command_animation_finished","positions":{"self":[5,2],"echo":[6,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":118625,"room":"open_chamber","room_index":3,"room_elapsed_ms":17239,"room_moves":8,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,2],"echo":[6,5]},"after":{"self":[5,2],"echo":[5,5]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,3.5],"midpoint_after":[5,3.5],"history_depth":8,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":118774,"room":"open_chamber","room_index":3,"room_elapsed_ms":17388,"room_moves":8,"type":"command_animation_finished","positions":{"self":[5,2],"echo":[5,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":119249,"room":"open_chamber","room_index":3,"room_elapsed_ms":17863,"room_moves":9,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,2],"echo":[5,5]},"after":{"self":[5,3],"echo":[5,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,3.5],"midpoint_after":[5,3.5],"history_depth":9,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":119407,"room":"open_chamber","room_index":3,"room_elapsed_ms":18021,"room_moves":9,"type":"command_animation_finished","positions":{"self":[5,3],"echo":[5,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":119697,"room":"open_chamber","room_index":3,"room_elapsed_ms":18311,"room_moves":10,"type":"command","direction":"down","source":"keyboard","before":{"self":[5,3],"echo":[5,4]},"after":{"self":[5,4],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,3.5],"midpoint_after":[5,3.5],"history_depth":10,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":119856,"room":"open_chamber","room_index":3,"room_elapsed_ms":18470,"room_moves":10,"type":"command_animation_finished","positions":{"self":[5,4],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":120326,"room":"open_chamber","room_index":3,"room_elapsed_ms":18940,"room_moves":11,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,4],"echo":[5,3]},"after":{"self":[6,4],"echo":[4,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5,3.5],"midpoint_after":[5,3.5],"history_depth":11,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":120473,"room":"open_chamber","room_index":3,"room_elapsed_ms":19087,"room_moves":11,"type":"command_animation_finished","positions":{"self":[6,4],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":121125,"room":"open_chamber","room_index":3,"room_elapsed_ms":19739,"room_moves":12,"type":"command","direction":"down","source":"keyboard","before":{"self":[6,4],"echo":[4,3]},"after":{"self":[6,5],"echo":[4,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[5,3.5],"midpoint_after":[5,3.5],"history_depth":12,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":121274,"room":"open_chamber","room_index":3,"room_elapsed_ms":19888,"room_moves":12,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":121669,"room":"open_chamber","room_index":3,"room_elapsed_ms":20283,"room_moves":13,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,5],"echo":[4,2]},"after":{"self":[7,5],"echo":[4,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5,3.5],"midpoint_after":[5.5,3.5],"history_depth":13,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":121823,"room":"open_chamber","room_index":3,"room_elapsed_ms":20437,"room_moves":13,"type":"command_animation_finished","positions":{"self":[7,5],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":122130,"room":"open_chamber","room_index":3,"room_elapsed_ms":20744,"room_moves":14,"type":"command","direction":"right","source":"keyboard","before":{"self":[7,5],"echo":[4,2]},"after":{"self":[8,5],"echo":[4,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5.5,3.5],"midpoint_after":[6,3.5],"history_depth":14,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":122291,"room":"open_chamber","room_index":3,"room_elapsed_ms":20905,"room_moves":14,"type":"command_animation_finished","positions":{"self":[8,5],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":122637,"room":"open_chamber","room_index":3,"room_elapsed_ms":21251,"room_moves":15,"type":"command","direction":"down","source":"keyboard","before":{"self":[8,5],"echo":[4,2]},"after":{"self":[8,6],"echo":[4,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":15,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":122791,"room":"open_chamber","room_index":3,"room_elapsed_ms":21405,"room_moves":15,"type":"command_animation_finished","positions":{"self":[8,6],"echo":[4,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":122914,"room":"open_chamber","room_index":3,"room_elapsed_ms":21528,"room_moves":16,"type":"command","direction":"right","source":"keyboard","before":{"self":[8,6],"echo":[4,1]},"after":{"self":[9,6],"echo":[3,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":16,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123073,"room":"open_chamber","room_index":3,"room_elapsed_ms":21687,"room_moves":16,"type":"command_animation_finished","positions":{"self":[9,6],"echo":[3,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123258,"room":"open_chamber","room_index":3,"room_elapsed_ms":21872,"room_moves":17,"type":"command","direction":"right","source":"keyboard","before":{"self":[9,6],"echo":[3,1]},"after":{"self":[10,6],"echo":[2,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":17,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123406,"room":"open_chamber","room_index":3,"room_elapsed_ms":22020,"room_moves":17,"type":"command_animation_finished","positions":{"self":[10,6],"echo":[2,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123527,"room":"open_chamber","room_index":3,"room_elapsed_ms":22141,"room_moves":18,"type":"command","direction":"up","source":"keyboard","before":{"self":[10,6],"echo":[2,1]},"after":{"self":[10,5],"echo":[2,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":18,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123672,"room":"open_chamber","room_index":3,"room_elapsed_ms":22286,"room_moves":18,"type":"command_animation_finished","positions":{"self":[10,5],"echo":[2,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":123815,"room":"open_chamber","room_index":3,"room_elapsed_ms":22429,"room_moves":18,"type":"command_no_effect","direction":"left","source":"keyboard","before":{"self":[10,5],"echo":[2,2]},"self_blocked":true,"echo_blocked":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":124470,"room":"open_chamber","room_index":3,"room_elapsed_ms":23084,"room_moves":19,"type":"command","direction":"down","source":"keyboard","before":{"self":[10,5],"echo":[2,2]},"after":{"self":[10,6],"echo":[2,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[6,3.5],"midpoint_after":[6,3.5],"history_depth":19,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":124624,"room":"open_chamber","room_index":3,"room_elapsed_ms":23238,"room_moves":19,"type":"command_animation_finished","positions":{"self":[10,6],"echo":[2,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":124780,"room":"open_chamber","room_index":3,"room_elapsed_ms":23394,"room_moves":20,"type":"command","direction":"down","source":"keyboard","before":{"self":[10,6],"echo":[2,1]},"after":{"self":[10,7],"echo":[2,1]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[6,3.5],"midpoint_after":[6,4],"history_depth":20,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":124940,"room":"open_chamber","room_index":3,"room_elapsed_ms":23554,"room_moves":20,"type":"command_animation_finished","positions":{"self":[10,7],"echo":[2,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125192,"room":"open_chamber","room_index":3,"room_elapsed_ms":23806,"room_moves":21,"type":"command","direction":"left","source":"keyboard","before":{"self":[10,7],"echo":[2,1]},"after":{"self":[9,7],"echo":[3,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":21,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125340,"room":"open_chamber","room_index":3,"room_elapsed_ms":23954,"room_moves":21,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125542,"room":"open_chamber","room_index":3,"room_elapsed_ms":24156,"room_moves":22,"type":"command","direction":"left","source":"keyboard","before":{"self":[9,7],"echo":[3,1]},"after":{"self":[8,7],"echo":[4,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":22,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125690,"room":"open_chamber","room_index":3,"room_elapsed_ms":24304,"room_moves":22,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[4,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125777,"room":"open_chamber","room_index":3,"room_elapsed_ms":24391,"room_moves":23,"type":"command","direction":"left","source":"keyboard","before":{"self":[8,7],"echo":[4,1]},"after":{"self":[7,7],"echo":[5,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":23,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":125922,"room":"open_chamber","room_index":3,"room_elapsed_ms":24536,"room_moves":23,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[5,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":126210,"room":"open_chamber","room_index":3,"room_elapsed_ms":24824,"room_moves":24,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,7],"echo":[5,1]},"after":{"self":[6,7],"echo":[6,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":24,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":126356,"room":"open_chamber","room_index":3,"room_elapsed_ms":24970,"room_moves":24,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[6,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":126618,"room":"open_chamber","room_index":3,"room_elapsed_ms":25232,"room_moves":24,"type":"command_no_effect","direction":"up","source":"keyboard","before":{"self":[6,7],"echo":[6,1]},"self_blocked":true,"echo_blocked":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127089,"room":"open_chamber","room_index":3,"room_elapsed_ms":25703,"room_moves":25,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,7],"echo":[6,1]},"after":{"self":[5,7],"echo":[7,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":25,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127239,"room":"open_chamber","room_index":3,"room_elapsed_ms":25853,"room_moves":25,"type":"command_animation_finished","positions":{"self":[5,7],"echo":[7,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127388,"room":"open_chamber","room_index":3,"room_elapsed_ms":26002,"room_moves":26,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,7],"echo":[7,1]},"after":{"self":[4,7],"echo":[8,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":26,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127539,"room":"open_chamber","room_index":3,"room_elapsed_ms":26153,"room_moves":26,"type":"command_animation_finished","positions":{"self":[4,7],"echo":[8,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127681,"room":"open_chamber","room_index":3,"room_elapsed_ms":26295,"room_moves":27,"type":"command","direction":"up","source":"keyboard","before":{"self":[4,7],"echo":[8,1]},"after":{"self":[4,6],"echo":[8,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[6,4],"midpoint_after":[6,4],"history_depth":27,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127840,"room":"open_chamber","room_index":3,"room_elapsed_ms":26454,"room_moves":27,"type":"command_animation_finished","positions":{"self":[4,6],"echo":[8,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":127882,"room":"open_chamber","room_index":3,"room_elapsed_ms":26496,"room_moves":28,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,6],"echo":[8,2]},"after":{"self":[4,6],"echo":[7,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[6,4],"midpoint_after":[5.5,4],"history_depth":28,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":128040,"room":"open_chamber","room_index":3,"room_elapsed_ms":26654,"room_moves":28,"type":"command_animation_finished","positions":{"self":[4,6],"echo":[7,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":128398,"room":"open_chamber","room_index":3,"room_elapsed_ms":27012,"room_moves":28,"type":"command_no_effect","direction":"right","source":"keyboard","before":{"self":[4,6],"echo":[7,2]},"self_blocked":true,"echo_blocked":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":128856,"room":"open_chamber","room_index":3,"room_elapsed_ms":27470,"room_moves":29,"type":"command","direction":"up","source":"keyboard","before":{"self":[4,6],"echo":[7,2]},"after":{"self":[4,5],"echo":[7,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[5.5,4],"midpoint_after":[5.5,4],"history_depth":29,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":129007,"room":"open_chamber","room_index":3,"room_elapsed_ms":27621,"room_moves":29,"type":"command_animation_finished","positions":{"self":[4,5],"echo":[7,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":129395,"room":"open_chamber","room_index":3,"room_elapsed_ms":28009,"room_moves":30,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,5],"echo":[7,3]},"after":{"self":[5,5],"echo":[6,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,4],"midpoint_after":[5.5,4],"history_depth":30,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":129556,"room":"open_chamber","room_index":3,"room_elapsed_ms":28170,"room_moves":30,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[6,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":129924,"room":"open_chamber","room_index":3,"room_elapsed_ms":28538,"room_moves":31,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,5],"echo":[6,3]},"after":{"self":[6,5],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[5.5,4],"midpoint_after":[5.5,4],"history_depth":31,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":130073,"room":"open_chamber","room_index":3,"room_elapsed_ms":28687,"room_moves":31,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":130301,"room":"open_chamber","room_index":3,"room_elapsed_ms":28915,"room_moves":32,"type":"command","direction":"down","source":"keyboard","before":{"self":[6,5],"echo":[5,3]},"after":{"self":[6,5],"echo":[5,2]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,-1],"midpoint_before":[5.5,4],"midpoint_after":[5.5,3.5],"history_depth":32,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":130457,"room":"open_chamber","room_index":3,"room_elapsed_ms":29071,"room_moves":32,"type":"command_animation_finished","positions":{"self":[6,5],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":130813,"room":"open_chamber","room_index":3,"room_elapsed_ms":29427,"room_moves":33,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,5],"echo":[5,2]},"after":{"self":[5,5],"echo":[5,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[5.5,3.5],"midpoint_after":[5,3.5],"history_depth":33,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":130973,"room":"open_chamber","room_index":3,"room_elapsed_ms":29587,"room_moves":33,"type":"command_animation_finished","positions":{"self":[5,5],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":131261,"room":"open_chamber","room_index":3,"room_elapsed_ms":29875,"room_moves":34,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,5],"echo":[5,2]},"after":{"self":[4,5],"echo":[5,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[5,3.5],"midpoint_after":[4.5,3.5],"history_depth":34,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":131423,"room":"open_chamber","room_index":3,"room_elapsed_ms":30037,"room_moves":34,"type":"command_animation_finished","positions":{"self":[4,5],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":131692,"room":"open_chamber","room_index":3,"room_elapsed_ms":30306,"room_moves":35,"type":"command","direction":"left","source":"keyboard","before":{"self":[4,5],"echo":[5,2]},"after":{"self":[3,5],"echo":[5,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[4.5,3.5],"midpoint_after":[4,3.5],"history_depth":35,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":131840,"room":"open_chamber","room_index":3,"room_elapsed_ms":30454,"room_moves":35,"type":"command_animation_finished","positions":{"self":[3,5],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":132037,"room":"open_chamber","room_index":3,"room_elapsed_ms":30651,"room_moves":36,"type":"command","direction":"left","source":"keyboard","before":{"self":[3,5],"echo":[5,2]},"after":{"self":[2,5],"echo":[5,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[4,3.5],"midpoint_after":[3.5,3.5],"history_depth":36,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":132190,"room":"open_chamber","room_index":3,"room_elapsed_ms":30804,"room_moves":36,"type":"command_animation_finished","positions":{"self":[2,5],"echo":[5,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":132377,"room":"open_chamber","room_index":3,"room_elapsed_ms":30991,"room_moves":37,"type":"command","direction":"up","source":"keyboard","before":{"self":[2,5],"echo":[5,2]},"after":{"self":[2,4],"echo":[5,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[3.5,3.5],"midpoint_after":[3.5,3.5],"history_depth":37,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":132523,"room":"open_chamber","room_index":3,"room_elapsed_ms":31137,"room_moves":37,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[5,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":133236,"room":"open_chamber","room_index":3,"room_elapsed_ms":31850,"room_moves":38,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,4],"echo":[5,3]},"after":{"self":[2,4],"echo":[4,3]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[3.5,3.5],"midpoint_after":[3,3.5],"history_depth":38,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":133389,"room":"open_chamber","room_index":3,"room_elapsed_ms":32003,"room_moves":38,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[4,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134056,"room":"open_chamber","room_index":3,"room_elapsed_ms":32670,"room_moves":39,"type":"command","direction":"down","source":"keyboard","before":{"self":[2,4],"echo":[4,3]},"after":{"self":[2,5],"echo":[4,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[3,3.5],"midpoint_after":[3,3.5],"history_depth":39,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134207,"room":"open_chamber","room_index":3,"room_elapsed_ms":32821,"room_moves":39,"type":"command_animation_finished","positions":{"self":[2,5],"echo":[4,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134440,"room":"open_chamber","room_index":3,"room_elapsed_ms":33054,"room_moves":40,"type":"command","direction":"down","source":"keyboard","before":{"self":[2,5],"echo":[4,2]},"after":{"self":[2,6],"echo":[4,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[3,3.5],"midpoint_after":[3,3.5],"history_depth":40,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134589,"room":"open_chamber","room_index":3,"room_elapsed_ms":33203,"room_moves":40,"type":"command_animation_finished","positions":{"self":[2,6],"echo":[4,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134749,"room":"open_chamber","room_index":3,"room_elapsed_ms":33363,"room_moves":41,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,6],"echo":[4,1]},"after":{"self":[3,6],"echo":[3,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[3,3.5],"midpoint_after":[3,3.5],"history_depth":41,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":134907,"room":"open_chamber","room_index":3,"room_elapsed_ms":33521,"room_moves":41,"type":"command_animation_finished","positions":{"self":[3,6],"echo":[3,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":135207,"room":"open_chamber","room_index":3,"room_elapsed_ms":33821,"room_moves":42,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,6],"echo":[3,1]},"after":{"self":[4,6],"echo":[2,1]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[3,3.5],"midpoint_after":[3,3.5],"history_depth":42,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":135356,"room":"open_chamber","room_index":3,"room_elapsed_ms":33970,"room_moves":42,"type":"command_animation_finished","positions":{"self":[4,6],"echo":[2,1]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":135527,"room":"open_chamber","room_index":3,"room_elapsed_ms":34141,"room_moves":43,"type":"command","direction":"up","source":"keyboard","before":{"self":[4,6],"echo":[2,1]},"after":{"self":[4,5],"echo":[2,2]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[3,3.5],"midpoint_after":[3,3.5],"history_depth":43,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":135673,"room":"open_chamber","room_index":3,"room_elapsed_ms":34287,"room_moves":43,"type":"command_animation_finished","positions":{"self":[4,5],"echo":[2,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":135926,"room":"open_chamber","room_index":3,"room_elapsed_ms":34540,"room_moves":44,"type":"command","direction":"left","source":"keyboard","before":{"self":[4,5],"echo":[2,2]},"after":{"self":[3,5],"echo":[2,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[3,3.5],"midpoint_after":[2.5,3.5],"history_depth":44,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":136074,"room":"open_chamber","room_index":3,"room_elapsed_ms":34688,"room_moves":44,"type":"command_animation_finished","positions":{"self":[3,5],"echo":[2,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":136350,"room":"open_chamber","room_index":3,"room_elapsed_ms":34964,"room_moves":45,"type":"command","direction":"left","source":"keyboard","before":{"self":[3,5],"echo":[2,2]},"after":{"self":[2,5],"echo":[2,2]},"self_blocked":false,"echo_blocked":true,"self_displacement":[-1,0],"echo_displacement":[0,0],"midpoint_before":[2.5,3.5],"midpoint_after":[2,3.5],"history_depth":45,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":136506,"room":"open_chamber","room_index":3,"room_elapsed_ms":35120,"room_moves":45,"type":"command_animation_finished","positions":{"self":[2,5],"echo":[2,2]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":136742,"room":"open_chamber","room_index":3,"room_elapsed_ms":35356,"room_moves":46,"type":"command","direction":"up","source":"keyboard","before":{"self":[2,5],"echo":[2,2]},"after":{"self":[2,4],"echo":[2,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[2,3.5],"midpoint_after":[2,3.5],"history_depth":46,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":136890,"room":"open_chamber","room_index":3,"room_elapsed_ms":35504,"room_moves":46,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[2,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":137549,"room":"open_chamber","room_index":3,"room_elapsed_ms":36163,"room_moves":47,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,4],"echo":[2,3]},"after":{"self":[2,4],"echo":[1,3]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[-1,0],"midpoint_before":[2,3.5],"midpoint_after":[1.5,3.5],"history_depth":47,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":137706,"room":"open_chamber","room_index":3,"room_elapsed_ms":36320,"room_moves":47,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[1,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":138600,"room":"open_chamber","room_index":3,"room_elapsed_ms":37214,"room_moves":48,"type":"command","direction":"left","source":"keyboard","before":{"self":[2,4],"echo":[1,3]},"after":{"self":[1,4],"echo":[2,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":48,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":138755,"room":"open_chamber","room_index":3,"room_elapsed_ms":37369,"room_moves":48,"type":"command_animation_finished","positions":{"self":[1,4],"echo":[2,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":139211,"room":"open_chamber","room_index":3,"room_elapsed_ms":37825,"room_moves":49,"type":"command","direction":"right","source":"keyboard","before":{"self":[1,4],"echo":[2,3]},"after":{"self":[2,4],"echo":[1,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":49,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":139373,"room":"open_chamber","room_index":3,"room_elapsed_ms":37987,"room_moves":49,"type":"command_animation_finished","positions":{"self":[2,4],"echo":[1,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":139571,"room":"open_chamber","room_index":3,"room_elapsed_ms":38185,"room_moves":50,"type":"command","direction":"left","source":"keyboard","before":{"self":[2,4],"echo":[1,3]},"after":{"self":[1,4],"echo":[2,3]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":50,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":139723,"room":"open_chamber","room_index":3,"room_elapsed_ms":38337,"room_moves":50,"type":"command_animation_finished","positions":{"self":[1,4],"echo":[2,3]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":139859,"room":"open_chamber","room_index":3,"room_elapsed_ms":38473,"room_moves":51,"type":"command","direction":"up","source":"keyboard","before":{"self":[1,4],"echo":[2,3]},"after":{"self":[1,3],"echo":[2,4]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":51,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":140007,"room":"open_chamber","room_index":3,"room_elapsed_ms":38621,"room_moves":51,"type":"command_animation_finished","positions":{"self":[1,3],"echo":[2,4]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":140229,"room":"open_chamber","room_index":3,"room_elapsed_ms":38843,"room_moves":52,"type":"command","direction":"up","source":"keyboard","before":{"self":[1,3],"echo":[2,4]},"after":{"self":[1,2],"echo":[2,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":52,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":140390,"room":"open_chamber","room_index":3,"room_elapsed_ms":39004,"room_moves":52,"type":"command_animation_finished","positions":{"self":[1,2],"echo":[2,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":140491,"room":"open_chamber","room_index":3,"room_elapsed_ms":39105,"room_moves":53,"type":"command","direction":"up","source":"keyboard","before":{"self":[1,2],"echo":[2,5]},"after":{"self":[1,1],"echo":[2,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,-1],"echo_displacement":[0,1],"midpoint_before":[1.5,3.5],"midpoint_after":[1.5,3.5],"history_depth":53,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":140641,"room":"open_chamber","room_index":3,"room_elapsed_ms":39255,"room_moves":53,"type":"command_animation_finished","positions":{"self":[1,1],"echo":[2,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":141410,"room":"open_chamber","room_index":3,"room_elapsed_ms":40024,"room_moves":54,"type":"command","direction":"left","source":"keyboard","before":{"self":[1,1],"echo":[2,6]},"after":{"self":[1,1],"echo":[3,6]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[1,0],"midpoint_before":[1.5,3.5],"midpoint_after":[2,3.5],"history_depth":54,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":141557,"room":"open_chamber","room_index":3,"room_elapsed_ms":40171,"room_moves":54,"type":"command_animation_finished","positions":{"self":[1,1],"echo":[3,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":142002,"room":"open_chamber","room_index":3,"room_elapsed_ms":40616,"room_moves":55,"type":"command","direction":"right","source":"keyboard","before":{"self":[1,1],"echo":[3,6]},"after":{"self":[2,1],"echo":[2,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[2,3.5],"midpoint_after":[2,3.5],"history_depth":55,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":142156,"room":"open_chamber","room_index":3,"room_elapsed_ms":40770,"room_moves":55,"type":"command_animation_finished","positions":{"self":[2,1],"echo":[2,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":143000,"room":"open_chamber","room_index":3,"room_elapsed_ms":41614,"room_moves":56,"type":"command","direction":"left","source":"keyboard","before":{"self":[2,1],"echo":[2,6]},"after":{"self":[1,1],"echo":[3,6]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[2,3.5],"midpoint_after":[2,3.5],"history_depth":56,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":143156,"room":"open_chamber","room_index":3,"room_elapsed_ms":41770,"room_moves":56,"type":"command_animation_finished","positions":{"self":[1,1],"echo":[3,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":143416,"room":"open_chamber","room_index":3,"room_elapsed_ms":42030,"room_moves":57,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,1],"echo":[3,6]},"after":{"self":[1,2],"echo":[3,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[0,1],"echo_displacement":[0,-1],"midpoint_before":[2,3.5],"midpoint_after":[2,3.5],"history_depth":57,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":143573,"room":"open_chamber","room_index":3,"room_elapsed_ms":42187,"room_moves":57,"type":"command_animation_finished","positions":{"self":[1,2],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":143864,"room":"open_chamber","room_index":3,"room_elapsed_ms":42478,"room_moves":58,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,2],"echo":[3,5]},"after":{"self":[1,3],"echo":[3,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[2,3.5],"midpoint_after":[2,4],"history_depth":58,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144023,"room":"open_chamber","room_index":3,"room_elapsed_ms":42637,"room_moves":58,"type":"command_animation_finished","positions":{"self":[1,3],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144178,"room":"open_chamber","room_index":3,"room_elapsed_ms":42792,"room_moves":59,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,3],"echo":[3,5]},"after":{"self":[1,4],"echo":[3,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[2,4],"midpoint_after":[2,4.5],"history_depth":59,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144340,"room":"open_chamber","room_index":3,"room_elapsed_ms":42954,"room_moves":59,"type":"command_animation_finished","positions":{"self":[1,4],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144494,"room":"open_chamber","room_index":3,"room_elapsed_ms":43108,"room_moves":60,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,4],"echo":[3,5]},"after":{"self":[1,5],"echo":[3,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[2,4.5],"midpoint_after":[2,5],"history_depth":60,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144640,"room":"open_chamber","room_index":3,"room_elapsed_ms":43254,"room_moves":60,"type":"command_animation_finished","positions":{"self":[1,5],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144802,"room":"open_chamber","room_index":3,"room_elapsed_ms":43416,"room_moves":61,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,5],"echo":[3,5]},"after":{"self":[1,6],"echo":[3,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[2,5],"midpoint_after":[2,5.5],"history_depth":61,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":144957,"room":"open_chamber","room_index":3,"room_elapsed_ms":43571,"room_moves":61,"type":"command_animation_finished","positions":{"self":[1,6],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145093,"room":"open_chamber","room_index":3,"room_elapsed_ms":43707,"room_moves":62,"type":"command","direction":"down","source":"keyboard","before":{"self":[1,6],"echo":[3,5]},"after":{"self":[1,7],"echo":[3,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[0,1],"echo_displacement":[0,0],"midpoint_before":[2,5.5],"midpoint_after":[2,6],"history_depth":62,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145240,"room":"open_chamber","room_index":3,"room_elapsed_ms":43854,"room_moves":62,"type":"command_animation_finished","positions":{"self":[1,7],"echo":[3,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145410,"room":"open_chamber","room_index":3,"room_elapsed_ms":44024,"room_moves":63,"type":"command","direction":"right","source":"keyboard","before":{"self":[1,7],"echo":[3,5]},"after":{"self":[2,7],"echo":[2,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[2,6],"midpoint_after":[2,6],"history_depth":63,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145556,"room":"open_chamber","room_index":3,"room_elapsed_ms":44170,"room_moves":63,"type":"command_animation_finished","positions":{"self":[2,7],"echo":[2,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145708,"room":"open_chamber","room_index":3,"room_elapsed_ms":44322,"room_moves":64,"type":"command","direction":"right","source":"keyboard","before":{"self":[2,7],"echo":[2,5]},"after":{"self":[3,7],"echo":[1,5]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[2,6],"midpoint_after":[2,6],"history_depth":64,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145856,"room":"open_chamber","room_index":3,"room_elapsed_ms":44470,"room_moves":64,"type":"command_animation_finished","positions":{"self":[3,7],"echo":[1,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":145979,"room":"open_chamber","room_index":3,"room_elapsed_ms":44593,"room_moves":65,"type":"command","direction":"right","source":"keyboard","before":{"self":[3,7],"echo":[1,5]},"after":{"self":[4,7],"echo":[1,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[2,6],"midpoint_after":[2.5,6],"history_depth":65,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146140,"room":"open_chamber","room_index":3,"room_elapsed_ms":44754,"room_moves":65,"type":"command_animation_finished","positions":{"self":[4,7],"echo":[1,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146250,"room":"open_chamber","room_index":3,"room_elapsed_ms":44864,"room_moves":66,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,7],"echo":[1,5]},"after":{"self":[5,7],"echo":[1,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[2.5,6],"midpoint_after":[3,6],"history_depth":66,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146406,"room":"open_chamber","room_index":3,"room_elapsed_ms":45020,"room_moves":66,"type":"command_animation_finished","positions":{"self":[5,7],"echo":[1,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146511,"room":"open_chamber","room_index":3,"room_elapsed_ms":45125,"room_moves":67,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,7],"echo":[1,5]},"after":{"self":[6,7],"echo":[1,5]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[3,6],"midpoint_after":[3.5,6],"history_depth":67,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146673,"room":"open_chamber","room_index":3,"room_elapsed_ms":45287,"room_moves":67,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[1,5]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146744,"room":"open_chamber","room_index":3,"room_elapsed_ms":45358,"room_moves":68,"type":"command","direction":"up","source":"keyboard","before":{"self":[6,7],"echo":[1,5]},"after":{"self":[6,7],"echo":[1,6]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[3.5,6],"midpoint_after":[3.5,6.5],"history_depth":68,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":146890,"room":"open_chamber","room_index":3,"room_elapsed_ms":45504,"room_moves":68,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[1,6]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147045,"room":"open_chamber","room_index":3,"room_elapsed_ms":45659,"room_moves":69,"type":"command","direction":"up","source":"keyboard","before":{"self":[6,7],"echo":[1,6]},"after":{"self":[6,7],"echo":[1,7]},"self_blocked":true,"echo_blocked":false,"self_displacement":[0,0],"echo_displacement":[0,1],"midpoint_before":[3.5,6.5],"midpoint_after":[3.5,7],"history_depth":69,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147207,"room":"open_chamber","room_index":3,"room_elapsed_ms":45821,"room_moves":69,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147425,"room":"open_chamber","room_index":3,"room_elapsed_ms":46039,"room_moves":70,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,7],"echo":[1,7]},"after":{"self":[7,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[3.5,7],"midpoint_after":[4,7],"history_depth":70,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147573,"room":"open_chamber","room_index":3,"room_elapsed_ms":46187,"room_moves":70,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147780,"room":"open_chamber","room_index":3,"room_elapsed_ms":46394,"room_moves":71,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,7],"echo":[1,7]},"after":{"self":[6,7],"echo":[2,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":71,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":147940,"room":"open_chamber","room_index":3,"room_elapsed_ms":46554,"room_moves":71,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[2,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":148098,"room":"open_chamber","room_index":3,"room_elapsed_ms":46712,"room_moves":72,"type":"command","direction":"left","source":"keyboard","before":{"self":[6,7],"echo":[2,7]},"after":{"self":[5,7],"echo":[3,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":72,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":148256,"room":"open_chamber","room_index":3,"room_elapsed_ms":46870,"room_moves":72,"type":"command_animation_finished","positions":{"self":[5,7],"echo":[3,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":148706,"room":"open_chamber","room_index":3,"room_elapsed_ms":47320,"room_moves":73,"type":"command","direction":"left","source":"keyboard","before":{"self":[5,7],"echo":[3,7]},"after":{"self":[4,7],"echo":[4,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":73,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":148856,"room":"open_chamber","room_index":3,"room_elapsed_ms":47470,"room_moves":73,"type":"command_animation_finished","positions":{"self":[4,7],"echo":[4,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":148922,"room":"open_chamber","room_index":3,"room_elapsed_ms":47536,"room_moves":74,"type":"command","direction":"right","source":"keyboard","before":{"self":[4,7],"echo":[4,7]},"after":{"self":[5,7],"echo":[3,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":74,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149073,"room":"open_chamber","room_index":3,"room_elapsed_ms":47687,"room_moves":74,"type":"command_animation_finished","positions":{"self":[5,7],"echo":[3,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149163,"room":"open_chamber","room_index":3,"room_elapsed_ms":47777,"room_moves":75,"type":"command","direction":"right","source":"keyboard","before":{"self":[5,7],"echo":[3,7]},"after":{"self":[6,7],"echo":[2,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":75,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149323,"room":"open_chamber","room_index":3,"room_elapsed_ms":47937,"room_moves":75,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[2,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149429,"room":"open_chamber","room_index":3,"room_elapsed_ms":48043,"room_moves":76,"type":"command","direction":"right","source":"keyboard","before":{"self":[6,7],"echo":[2,7]},"after":{"self":[7,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[1,0],"echo_displacement":[-1,0],"midpoint_before":[4,7],"midpoint_after":[4,7],"history_depth":76,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149590,"room":"open_chamber","room_index":3,"room_elapsed_ms":48204,"room_moves":76,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":149901,"room":"open_chamber","room_index":3,"room_elapsed_ms":48515,"room_moves":77,"type":"command","direction":"right","source":"keyboard","before":{"self":[7,7],"echo":[1,7]},"after":{"self":[8,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[4,7],"midpoint_after":[4.5,7],"history_depth":77,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":150056,"room":"open_chamber","room_index":3,"room_elapsed_ms":48670,"room_moves":77,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":150282,"room":"open_chamber","room_index":3,"room_elapsed_ms":48896,"room_moves":78,"type":"command","direction":"right","source":"keyboard","before":{"self":[8,7],"echo":[1,7]},"after":{"self":[9,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[4.5,7],"midpoint_after":[5,7],"history_depth":78,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":150441,"room":"open_chamber","room_index":3,"room_elapsed_ms":49055,"room_moves":78,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":150659,"room":"open_chamber","room_index":3,"room_elapsed_ms":49273,"room_moves":79,"type":"command","direction":"right","source":"keyboard","before":{"self":[9,7],"echo":[1,7]},"after":{"self":[10,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5,7],"midpoint_after":[5.5,7],"history_depth":79,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":150806,"room":"open_chamber","room_index":3,"room_elapsed_ms":49420,"room_moves":79,"type":"command_animation_finished","positions":{"self":[10,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":151054,"room":"open_chamber","room_index":3,"room_elapsed_ms":49668,"room_moves":80,"type":"command","direction":"right","source":"keyboard","before":{"self":[10,7],"echo":[1,7]},"after":{"self":[11,7],"echo":[1,7]},"self_blocked":false,"echo_blocked":true,"self_displacement":[1,0],"echo_displacement":[0,0],"midpoint_before":[5.5,7],"midpoint_after":[6,7],"history_depth":80,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":151206,"room":"open_chamber","room_index":3,"room_elapsed_ms":49820,"room_moves":80,"type":"command_animation_finished","positions":{"self":[11,7],"echo":[1,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":151498,"room":"open_chamber","room_index":3,"room_elapsed_ms":50112,"room_moves":81,"type":"command","direction":"left","source":"keyboard","before":{"self":[11,7],"echo":[1,7]},"after":{"self":[10,7],"echo":[2,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,7],"midpoint_after":[6,7],"history_depth":81,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":151657,"room":"open_chamber","room_index":3,"room_elapsed_ms":50271,"room_moves":81,"type":"command_animation_finished","positions":{"self":[10,7],"echo":[2,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152343,"room":"open_chamber","room_index":3,"room_elapsed_ms":50957,"room_moves":82,"type":"command","direction":"left","source":"keyboard","before":{"self":[10,7],"echo":[2,7]},"after":{"self":[9,7],"echo":[3,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,7],"midpoint_after":[6,7],"history_depth":82,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152490,"room":"open_chamber","room_index":3,"room_elapsed_ms":51104,"room_moves":82,"type":"command_animation_finished","positions":{"self":[9,7],"echo":[3,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152524,"room":"open_chamber","room_index":3,"room_elapsed_ms":51138,"room_moves":83,"type":"command","direction":"left","source":"keyboard","before":{"self":[9,7],"echo":[3,7]},"after":{"self":[8,7],"echo":[4,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,7],"midpoint_after":[6,7],"history_depth":83,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152673,"room":"open_chamber","room_index":3,"room_elapsed_ms":51287,"room_moves":83,"type":"command_animation_finished","positions":{"self":[8,7],"echo":[4,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152715,"room":"open_chamber","room_index":3,"room_elapsed_ms":51329,"room_moves":84,"type":"command","direction":"left","source":"keyboard","before":{"self":[8,7],"echo":[4,7]},"after":{"self":[7,7],"echo":[5,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,7],"midpoint_after":[6,7],"history_depth":84,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152873,"room":"open_chamber","room_index":3,"room_elapsed_ms":51487,"room_moves":84,"type":"command_animation_finished","positions":{"self":[7,7],"echo":[5,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":152936,"room":"open_chamber","room_index":3,"room_elapsed_ms":51550,"room_moves":85,"type":"command","direction":"left","source":"keyboard","before":{"self":[7,7],"echo":[5,7]},"after":{"self":[6,7],"echo":[6,7]},"self_blocked":false,"echo_blocked":false,"self_displacement":[-1,0],"echo_displacement":[1,0],"midpoint_before":[6,7],"midpoint_after":[6,7],"history_depth":85,"open_chamber":true}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":153090,"room":"open_chamber","room_index":3,"room_elapsed_ms":51704,"room_moves":85,"type":"command_animation_finished","positions":{"self":[6,7],"echo":[6,7]},"completed":false}
{"schema":1,"experiment":"004_invariant_rooms","prototype_revision":1,"session_id":"a7ad2d34-cba4-43a5-942e-1f7dc1ad0295","elapsed_ms":161153,"room":"open_chamber","room_index":3,"room_elapsed_ms":59767,"room_moves":85,"type":"save_requested","event_count_before_save":382}

View file

@ -0,0 +1,653 @@
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":1,"trial":"haul","attempt":0,"type":"session_started","viewport":{"width":1672,"height":976},"starter_build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":1,"trial":"haul","attempt":1,"type":"attempt_started","reason":"session_start","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":14617,"trial":"haul","attempt":1,"type":"attempt_engaged","source":"start_overlay","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":14617,"trial":"haul","attempt":1,"type":"start_overlay_dismissed"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":15074,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":15232,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":158}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":15755,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":15920,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":165}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":16159,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":16342,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":183}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":16623,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":16894,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":271}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":17314,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":17554,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":240}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":17994,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":19613,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.304,"y":0.474,"vx":0.159,"vy":-0.004,"heat":28.303,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":21512,"trial":"haul","attempt":1,"type":"obstacle_collision","x":0.646,"y":0.471,"speed":0.054}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":21640,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":3646}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":22031,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":24611,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.286,"y":0.471,"vx":-0.185,"vy":0,"heat":37.022,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":24886,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":2855}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":24949,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":25185,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":236}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":25353,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":27898,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":2545}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":28126,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":28490,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":364}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":29044,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":29148,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":104}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":29612,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.503,"y":0.471,"vx":0,"vy":0,"heat":42.329,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":30787,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":30837,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":31243,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":406}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":31844,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":32235,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":1448}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":33548,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":33548,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":1704}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":33647,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":34125,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":478}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":34629,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.818,"y":0.279,"vx":0.015,"vy":0.115,"heat":52.684,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":34736,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":1188}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":34836,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":35021,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":185}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":35749,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":36490,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":36640,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":150}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":38020,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":2271}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":38282,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":39645,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.862,"y":0.746,"vx":0.002,"vy":-0.109,"heat":59.677,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":39812,"trial":"haul","attempt":1,"type":"heat_band_changed","before":"normal","after":"high","heat":60.01}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":41100,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":2819}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":43245,"trial":"haul","attempt":1,"type":"heat_band_changed","before":"high","after":"normal","heat":59.995}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":44661,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.864,"y":0.35,"vx":0,"vy":-0.006,"heat":58.295,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":49662,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.864,"y":0.344,"vx":0,"vy":0,"heat":52.294,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":54678,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.864,"y":0.344,"vx":0,"vy":0,"heat":46.275,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":54707,"trial":"haul","attempt":1,"type":"placement_facing_changed","before":"up","after":"right"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":55274,"trial":"haul","attempt":1,"type":"placement_facing_changed","before":"right","after":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":56265,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":56485,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":56776,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":291}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":57081,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":816}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":57737,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":57811,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":57889,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":78}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":57898,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":161}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":58338,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":58441,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":103}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":58905,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":58985,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":80}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":59325,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":59382,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":57}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":59678,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.824,"y":0.227,"vx":0,"vy":0.004,"heat":45.394,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":64695,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.824,"y":0.231,"vx":0,"vy":0,"heat":39.374,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":66938,"trial":"haul","attempt":1,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.011,"rig_mass":11.4}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":67653,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":67739,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":68121,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":382}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":69695,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.853,"y":0.344,"vx":0.004,"vy":0.079,"heat":41.159,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":74609,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":6956}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":74711,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.856,"y":0.766,"vx":0,"vy":0.073,"heat":50.818,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":75107,"trial":"haul","attempt":1,"type":"cargo_released","cargo_id":"fragment-a","x":0.856,"y":0.788}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":75932,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":76060,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":128}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":76352,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":76683,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":77076,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":724}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":77094,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":411}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":77515,"trial":"haul","attempt":1,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.005,"rig_mass":11.4}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":77731,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":78289,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":78466,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":177}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":78794,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":78991,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":1260}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":79669,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":79729,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.667,"y":0.744,"vx":-0.037,"vy":-0.059,"heat":56.592,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":80055,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":386}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":80062,"trial":"haul","attempt":1,"type":"obstacle_collision","x":0.653,"y":0.723,"speed":0.024}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":80445,"trial":"haul","attempt":1,"type":"obstacle_collision","x":0.657,"y":0.722,"speed":0.006}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":80599,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":1805}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":80737,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":81000,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":81061,"trial":"haul","attempt":1,"type":"heat_band_changed","before":"normal","after":"high","heat":60.056}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":81265,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":528}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":81515,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":81683,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":683}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":84053,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":84744,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.578,"y":0.557,"vx":-0.051,"vy":-0.083,"heat":70.838,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":85231,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":3716}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":86159,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":86428,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":269}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":89261,"trial":"haul","attempt":1,"type":"cargo_delivered","cargo_id":"fragment-a","delivered":1,"elapsed_in_attempt_ms":74644}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":89270,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":5217}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":89762,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.186,"y":0.474,"vx":-0.052,"vy":0,"heat":81.672,"attached":null},"progress":1,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":90003,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":91471,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":91595,"trial":"haul","attempt":1,"type":"heat_band_changed","before":"high","after":"critical","heat":85.02}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":91651,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":180}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":92269,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":92543,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":2540}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":92841,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":93153,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":312}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":93903,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":94302,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyS","duration_ms":2033}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":94779,"trial":"haul","attempt":1,"type":"field_snapshot","rig":{"x":0.626,"y":0.806,"vx":0.125,"vy":0.109,"heat":94.692,"attached":null},"progress":1,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":94901,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":95193,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":292}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":95437,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":95703,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":1800}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":95915,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":478}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":96344,"trial":"haul","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":96402,"trial":"haul","attempt":1,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.018,"rig_mass":11.4}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":97228,"trial":"haul","attempt":1,"type":"attempt_failed","reason":"thermal_collapse","x":0.858,"y":0.785,"heat":100.02,"build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":97229,"trial":"haul","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":885,"reason":"thermal_collapse"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":97229,"trial":"haul","attempt":2,"type":"attempt_started","reason":"thermal_collapse","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":98061,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":99011,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":950}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":99392,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":99720,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":328}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":99984,"trial":"haul","attempt":2,"type":"attempt_engaged","source":"canvas","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":100153,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":102369,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":102545,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.435,"y":0.497,"vx":0.181,"vy":-0.033,"heat":30.385,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":102708,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":2556}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":103021,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":103231,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":210}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":103821,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":104136,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":1767}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":104788,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":105237,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":1416}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":105281,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyS","duration_ms":493}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":105719,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":105865,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":146}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":105877,"trial":"haul","attempt":2,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.021,"rig_mass":11.4}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":106021,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":107545,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.788,"y":0.221,"vx":-0.063,"vy":-0.002,"heat":42.15,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":108225,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":109106,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":3085}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":110028,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":110349,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":321}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":111042,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":111251,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyS","duration_ms":3026}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":111736,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":111756,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyS","duration_ms":20}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":112545,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.508,"y":0.467,"vx":-0.076,"vy":0.017,"heat":56.791,"attached":"fragment-a"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":114161,"trial":"haul","attempt":2,"type":"heat_band_changed","before":"normal","after":"high","heat":60.023}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":115556,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":4514}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":115770,"trial":"haul","attempt":2,"type":"cargo_released","cargo_id":"fragment-a","x":0.243,"y":0.479}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":116061,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":117561,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.335,"y":0.479,"vx":0.143,"vy":0,"heat":65.225,"attached":null},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":117992,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":118532,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":2471}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":118852,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":119435,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":583}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":119653,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":120024,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyS","duration_ms":2032}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":120777,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":121095,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":318}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":121342,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":121531,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":189}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":121556,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":1903}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":121856,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122006,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":150}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122114,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122248,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":134}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122352,"trial":"haul","attempt":2,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.017,"rig_mass":11.4}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122525,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":122561,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.851,"y":0.795,"vx":0.022,"vy":-0.01,"heat":79.494,"attached":"fragment-b"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":124848,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":124933,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":85}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":124964,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":125062,"trial":"haul","attempt":2,"type":"heat_band_changed","before":"high","after":"critical","heat":85.083}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":125690,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":3165}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":126944,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":127352,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":2388}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":127562,"trial":"haul","attempt":2,"type":"field_snapshot","rig":{"x":0.591,"y":0.625,"vx":-0.055,"vy":-0.062,"heat":93.386,"attached":"fragment-b"},"progress":0,"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":127676,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":128355,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":679}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":128719,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":128778,"trial":"haul","attempt":2,"type":"obstacle_collision","x":0.508,"y":0.551,"speed":0.026}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":128890,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":171}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129161,"trial":"haul","attempt":2,"type":"obstacle_collision","x":0.508,"y":0.552,"speed":0.005}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129258,"trial":"haul","attempt":2,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129258,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyA","duration_ms":2314}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129511,"trial":"haul","attempt":2,"type":"attempt_failed","reason":"thermal_collapse","x":0.507,"y":0.551,"heat":100.003,"build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129512,"trial":"haul","attempt":2,"type":"drive_input_ended","key":"KeyW","duration_ms":254,"reason":"thermal_collapse"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129512,"trial":"haul","attempt":3,"type":"attempt_started","reason":"thermal_collapse","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129549,"trial":"haul","attempt":3,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":129831,"trial":"haul","attempt":3,"type":"drive_input_ended","key":"KeyW","duration_ms":282}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":130207,"trial":"haul","attempt":3,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":130726,"trial":"haul","attempt":3,"type":"drive_input_ended","key":"KeyD","duration_ms":519}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":131389,"trial":"haul","attempt":3,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":132034,"trial":"haul","attempt":3,"type":"drive_input_ended","key":"KeyA","duration_ms":645}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":132204,"trial":"haul","attempt":3,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":132566,"trial":"haul","attempt":3,"type":"drive_input_ended","key":"KeyD","duration_ms":362}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":137328,"trial":"haul","attempt":3,"type":"palette_selected","part":"tractor","facing":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":137932,"trial":"haul","attempt":3,"type":"part_placed","x":3,"y":3,"part":"tractor","facing":"down","replaced":null,"build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":137934,"trial":"haul","attempt":4,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":143726,"trial":"haul","attempt":4,"type":"attempt_engaged","source":"canvas","build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":144021,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":146422,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":146778,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.472,"y":0.489,"vx":0.17,"vy":-0.055,"heat":31.708,"attached":null},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":146853,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":2832}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":147189,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":147374,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":185}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":147649,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":147892,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":243}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":148132,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":148377,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":1955}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":148826,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":149171,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":345}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":149253,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":1121}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":149886,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":150108,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":150426,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":318}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":150709,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":150852,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":143}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":150885,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":151028,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":143}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":151242,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":151356,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":114}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":151794,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.842,"y":0.231,"vx":0.002,"vy":-0.012,"heat":39.286,"attached":null},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":151949,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.025,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":152349,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":156795,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.844,"y":0.519,"vx":0,"vy":0.081,"heat":47.525,"attached":"fragment-a"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":157725,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":157886,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":161}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":159992,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":7643}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":160483,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":160620,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":137}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":160922,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-a","x":0.858,"y":0.817}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":161458,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":161642,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":184}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":161811,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.858,"y":0.818,"vx":0,"vy":-0.018,"heat":53.24,"attached":null},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":161844,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.028,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":162277,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":162792,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":515}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":163578,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":164205,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":627}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":164315,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-b","x":0.799,"y":0.804}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":164616,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.016,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":165106,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":165606,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":500}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":165815,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-a","x":0.749,"y":0.804}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":166089,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":166391,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":166498,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":107}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":166681,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":166828,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.719,"y":0.804,"vx":-0.028,"vy":0,"heat":52.878,"attached":null},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":167170,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":167649,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":479}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":167849,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":167935,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":86}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":168194,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":168289,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":95}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":168516,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":168976,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":169359,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":169525,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":166}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":169684,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":169905,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":170304,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":399}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":170498,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":170712,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":170890,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":171227,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":337}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":171599,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":171845,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.681,"y":0.804,"vx":-0.037,"vy":0,"heat":51.871,"attached":null},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":171938,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":172173,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":172395,"trial":"haul","attempt":4,"type":"tractor_toggled","result":"no_aligned_cargo"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":173153,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174105,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":952}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174176,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174448,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174491,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":315}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174723,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174810,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":362}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":174888,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":165}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":175339,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":175436,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":97}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":175567,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.039,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":176087,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":176571,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":176845,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.714,"y":0.835,"vx":-0.029,"vy":-0.046,"heat":55.259,"attached":"fragment-b"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":176940,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":369}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":177337,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":177498,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":161}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":177625,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":1538}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":177943,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":177957,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":178048,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":105}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":178082,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":125}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":178382,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":178507,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":125}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":178767,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-b","x":0.673,"y":0.756}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":179531,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":179622,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":91}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":179772,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.056,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":180070,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":180411,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":341}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":180488,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":180756,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":180786,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":298}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181072,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":316}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181267,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181568,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":301}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181598,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181743,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181861,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.637,"y":0.716,"vx":-0.037,"vy":-0.037,"heat":59.207,"attached":"fragment-a"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181968,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":225}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":181994,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":396}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":182465,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-a","x":0.617,"y":0.696}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":182708,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":182764,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":56}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":182873,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":183023,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":150}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":183192,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.062,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":183514,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":183860,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":346}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":183906,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":184266,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":184278,"trial":"haul","attempt":4,"type":"heat_band_changed","before":"normal","after":"high","heat":60.04}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":184405,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":499}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":184699,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":184724,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":458}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185240,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":541}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185302,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185505,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185522,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":220}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185621,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":116}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":185832,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":186536,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":186862,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.542,"y":0.574,"vx":-0.034,"vy":-0.064,"heat":65.847,"attached":"fragment-b"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":186902,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":1070}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":187153,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":617}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":187176,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":187544,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":368}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":187574,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-b","x":0.515,"y":0.55}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":187816,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":188136,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":188306,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":490}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":188665,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":188804,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":139}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":189024,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":888}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":189231,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":189332,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.066,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":190404,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":190565,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":161}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":191082,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":191701,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":2470}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":191878,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.512,"y":0.534,"vx":-0.054,"vy":-0.059,"heat":77.796,"attached":"fragment-a"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":192231,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":1148}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":192297,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":192458,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":161}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":192648,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":192764,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":116}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":193042,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":193174,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":132}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":193243,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":193385,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":142}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":193988,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-a","x":0.447,"y":0.509}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":194298,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.042,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":194535,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":195214,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":679}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":195516,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":195586,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":195906,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":390}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196026,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":440}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196190,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196316,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":126}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196390,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196782,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":392}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196894,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.369,"y":0.474,"vx":-0.009,"vy":-0.039,"heat":81.163,"attached":"fragment-b"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":196924,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-b","x":0.369,"y":0.473}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":197205,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":197315,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":110}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":197471,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.053,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":197769,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":197926,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":198109,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":340}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":198474,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":198843,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":369}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":198845,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":919}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":199134,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":199236,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":102}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":200158,"trial":"haul","attempt":4,"type":"cargo_released","cargo_id":"fragment-a","x":0.296,"y":0.513}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":200423,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":200590,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":167}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":200818,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":200910,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":92}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201091,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201337,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":246}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201338,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201468,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":130}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201769,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-b","distance":0.04,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":201911,"trial":"haul","attempt":4,"type":"field_snapshot","rig":{"x":0.296,"y":0.513,"vx":0.012,"vy":0.011,"heat":83.035,"attached":"fragment-b"},"progress":0,"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":202000,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":202557,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":202745,"trial":"haul","attempt":4,"type":"heat_band_changed","before":"high","after":"critical","heat":85.077}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":202889,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":332}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":203191,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":203193,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":1193}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":203443,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":252}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":203832,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":203928,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyD","duration_ms":96}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":204358,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":204435,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyW","duration_ms":77}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":204912,"trial":"haul","attempt":4,"type":"cargo_delivered","cargo_id":"fragment-b","delivered":1,"elapsed_in_attempt_ms":61186}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":205390,"trial":"haul","attempt":4,"type":"cargo_attached","cargo_id":"fragment-a","distance":0.046,"rig_mass":12}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":205395,"trial":"haul","attempt":4,"type":"cargo_delivered","cargo_id":"fragment-a","delivered":2,"elapsed_in_attempt_ms":61669}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":205396,"trial":"haul","attempt":4,"type":"trial_completed","elapsed_in_attempt_ms":61669,"build":["7:drive:down","8:tractor:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":1.2,"ward":0,"ballast":0,"tractors":2,"invalid":0},"completions":["haul"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":205675,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":206086,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":411}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":206653,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":206898,"trial":"haul","attempt":4,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":207139,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyS","duration_ms":486}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":207165,"trial":"haul","attempt":4,"type":"drive_input_ended","key":"KeyA","duration_ms":267}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":221932,"trial":"haul","attempt":4,"type":"viewport_changed","viewport":{"width":1254,"height":732},"canvas":{"width":953.05,"height":595}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":222345,"trial":"haul","attempt":4,"type":"viewport_changed","viewport":{"width":836,"height":488},"canvas":{"width":551,"height":351}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":223228,"trial":"haul","attempt":4,"type":"viewport_changed","viewport":{"width":1045,"height":610},"canvas":{"width":745,"height":473}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":263643,"trial":"haul","attempt":4,"type":"palette_selected","part":"sink","facing":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":264611,"trial":"haul","attempt":4,"type":"part_rejected","reason":"budget","x":1,"y":1,"part":"sink","projected_cost":14}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":268860,"trial":"haul","attempt":4,"type":"part_placed","x":3,"y":1,"part":"sink","facing":"up","replaced":"tractor","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":268862,"trial":"haul","attempt":5,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":281291,"trial":"furnace","attempt":1,"type":"attempt_started","reason":"trial_switch","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":281293,"trial":"furnace","attempt":1,"type":"trial_changed","before":"haul","after":"furnace","build_preserved":true}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":286465,"trial":"furnace","attempt":1,"type":"viewport_changed","viewport":{"width":1588,"height":927},"canvas":{"width":1228.4,"height":790.2}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":287897,"trial":"furnace","attempt":1,"type":"viewport_changed","viewport":{"width":2508,"height":1464},"canvas":{"width":2148,"height":1327}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":288192,"trial":"furnace","attempt":1,"type":"attempt_engaged","source":"canvas","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":289333,"trial":"furnace","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":289711,"trial":"furnace","attempt":1,"type":"field_snapshot","rig":{"x":0.151,"y":0.5,"vx":0.054,"vy":0,"heat":25.077,"attached":null},"progress":0.151,"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":292345,"trial":"furnace","attempt":1,"type":"heat_band_changed","before":"normal","after":"high","heat":60.397}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293027,"trial":"furnace","attempt":1,"type":"heat_band_changed","before":"high","after":"critical","heat":85.112}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293446,"trial":"furnace","attempt":1,"type":"attempt_failed","reason":"thermal_collapse","x":0.676,"y":0.5,"heat":100.224,"build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293447,"trial":"furnace","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":4114,"reason":"thermal_collapse"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293447,"trial":"furnace","attempt":2,"type":"attempt_started","reason":"thermal_collapse","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:tractor:down"],"stats":{"cost":12,"mass":7.4,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":1,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293466,"trial":"furnace","attempt":2,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":293733,"trial":"furnace","attempt":2,"type":"drive_input_ended","key":"KeyD","duration_ms":267}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":297054,"trial":"furnace","attempt":2,"type":"part_placed","x":3,"y":3,"part":"sink","facing":"up","replaced":"tractor","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":297056,"trial":"furnace","attempt":3,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":299752,"trial":"furnace","attempt":3,"type":"part_placed","x":2,"y":1,"part":"sink","facing":"up","replaced":"drive","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":299754,"trial":"furnace","attempt":4,"type":"attempt_started","reason":"build_changed","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":8.1,"drives":{"up":1,"right":1,"down":0,"left":1},"cooling":9.9,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":300254,"trial":"furnace","attempt":4,"type":"part_placed","x":3,"y":2,"part":"sink","facing":"up","replaced":"drive","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:drive:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":300255,"trial":"furnace","attempt":5,"type":"attempt_started","reason":"build_changed","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":8.4,"drives":{"up":1,"right":1,"down":0,"left":0},"cooling":14.1,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":300815,"trial":"furnace","attempt":5,"type":"part_placed","x":2,"y":3,"part":"sink","facing":"up","replaced":"drive","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":300817,"trial":"furnace","attempt":6,"type":"attempt_started","reason":"build_changed","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"],"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":305770,"trial":"furnace","attempt":6,"type":"part_rejected","reason":"budget","x":1,"y":1,"part":"sink","projected_cost":14}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":307485,"trial":"furnace","attempt":6,"type":"attempt_engaged","source":"canvas","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"],"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":307738,"trial":"furnace","attempt":6,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":308761,"trial":"furnace","attempt":6,"type":"field_snapshot","rig":{"x":0.197,"y":0.5,"vx":0.093,"vy":0,"heat":25,"attached":null},"progress":0.197,"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":311778,"trial":"furnace","attempt":6,"type":"heat_band_changed","before":"normal","after":"high","heat":60.332}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":312975,"trial":"furnace","attempt":6,"type":"drive_input_ended","key":"KeyD","duration_ms":5237}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":313029,"trial":"furnace","attempt":6,"type":"heat_band_changed","before":"high","after":"critical","heat":85.118}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":313223,"trial":"furnace","attempt":6,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":313361,"trial":"furnace","attempt":6,"type":"heat_band_changed","before":"critical","after":"high","heat":84.719}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":313550,"trial":"furnace","attempt":6,"type":"drive_input_ended","key":"KeyA","duration_ms":327}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":313761,"trial":"furnace","attempt":6,"type":"field_snapshot","rig":{"x":0.842,"y":0.5,"vx":0.067,"vy":0,"heat":77.029,"attached":null},"progress":0.842,"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":314661,"trial":"furnace","attempt":6,"type":"heat_band_changed","before":"high","after":"normal","heat":59.754}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":315222,"trial":"furnace","attempt":6,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":315429,"trial":"furnace","attempt":6,"type":"drive_input_ended","key":"KeyD","duration_ms":207,"reason":"trial_completed"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":315429,"trial":"furnace","attempt":6,"type":"trial_completed","elapsed_in_attempt_ms":7944,"build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"],"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0},"completions":["haul","furnace"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":315471,"trial":"furnace","attempt":6,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":315583,"trial":"furnace","attempt":6,"type":"drive_input_ended","key":"KeyD","duration_ms":112}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":321654,"trial":"gale","attempt":1,"type":"attempt_started","reason":"trial_switch","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"],"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":321656,"trial":"gale","attempt":1,"type":"trial_changed","before":"furnace","after":"gale","build_preserved":true}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":323434,"trial":"gale","attempt":1,"type":"attempt_engaged","source":"canvas","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","17:sink:up","18:sink:up"],"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":323586,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":324171,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":324333,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":162}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":325027,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":1441}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":326177,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":326426,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":249}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":326720,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":326779,"trial":"gale","attempt":1,"type":"field_snapshot","rig":{"x":0.176,"y":0.579,"vx":0.039,"vy":-0.082,"heat":25,"attached":null},"progress":0,"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":327969,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":1249}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":328660,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":328970,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":310}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":329294,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.257,"y":0.37,"speed":0.027}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":329436,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":329571,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":135}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":330611,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0.015}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":330995,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0.001}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331089,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331161,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":73}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331344,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331481,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331711,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0.001}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331783,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyD","duration_ms":302}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331795,"trial":"gale","attempt":1,"type":"field_snapshot","rig":{"x":0.258,"y":0.34,"vx":0,"vy":-0.001,"heat":25,"attached":null},"progress":0,"stats":{"cost":12,"mass":8.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":19.2,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":331908,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332061,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332238,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyA","duration_ms":330}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332304,"trial":"gale","attempt":1,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332428,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332619,"trial":"gale","attempt":1,"type":"drive_input_ended","key":"KeyW","duration_ms":315}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":332794,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":333161,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":333528,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":333878,"trial":"gale","attempt":1,"type":"obstacle_collision","x":0.258,"y":0.34,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":334166,"trial":"gale","attempt":1,"type":"part_removed","x":2,"y":3,"part":"sink","facing":"up","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":334168,"trial":"gale","attempt":2,"type":"attempt_started","reason":"part_removed","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","13:sink:up","18:sink:up"],"stats":{"cost":10,"mass":7.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":15,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":335005,"trial":"gale","attempt":2,"type":"part_removed","x":3,"y":2,"part":"sink","facing":"up","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":335006,"trial":"gale","attempt":3,"type":"attempt_started","reason":"part_removed","build":["7:sink:up","8:sink:up","11:drive:right","12:core:up","18:sink:up"],"stats":{"cost":8,"mass":6.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":9,"ward":0,"ballast":0,"tractors":0,"invalid":1}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":335491,"trial":"gale","attempt":3,"type":"part_removed","x":2,"y":1,"part":"sink","facing":"up","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":335492,"trial":"gale","attempt":4,"type":"attempt_started","reason":"part_removed","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"],"stats":{"cost":6,"mass":5.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":1.2,"ward":0,"ballast":0,"tractors":0,"invalid":2}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":338289,"trial":"gale","attempt":4,"type":"palette_selected","part":"tractor","facing":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":338728,"trial":"gale","attempt":4,"type":"palette_selected","part":"drive","facing":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":339879,"trial":"gale","attempt":4,"type":"part_placed","x":3,"y":2,"part":"drive","facing":"down","replaced":null,"build":["8:sink:up","11:drive:right","12:core:up","13:drive:down","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":339880,"trial":"gale","attempt":5,"type":"attempt_started","reason":"build_changed","build":["8:sink:up","11:drive:right","12:core:up","13:drive:down","18:sink:up"],"stats":{"cost":8,"mass":6.4,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":6.6,"ward":0,"ballast":0,"tractors":0,"invalid":1}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":340653,"trial":"gale","attempt":5,"type":"part_removed","x":3,"y":2,"part":"drive","facing":"down","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":340654,"trial":"gale","attempt":6,"type":"attempt_started","reason":"part_removed","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"],"stats":{"cost":6,"mass":5.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":1.2,"ward":0,"ballast":0,"tractors":0,"invalid":2}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":343972,"trial":"gale","attempt":6,"type":"placement_facing_changed","before":"down","after":"right"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":344641,"trial":"gale","attempt":6,"type":"part_placed","x":3,"y":2,"part":"drive","facing":"right","replaced":null,"build":["8:sink:up","11:drive:right","12:core:up","13:drive:right","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":344643,"trial":"gale","attempt":7,"type":"attempt_started","reason":"build_changed","build":["8:sink:up","11:drive:right","12:core:up","13:drive:right","18:sink:up"],"stats":{"cost":8,"mass":6.4,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":6.6,"ward":0,"ballast":0,"tractors":0,"invalid":1}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":346093,"trial":"gale","attempt":7,"type":"placement_facing_changed","before":"right","after":"left"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":346543,"trial":"gale","attempt":7,"type":"part_rotated","x":3,"y":2,"part":"drive","before":"right","after":"down","build":["8:sink:up","11:drive:right","12:core:up","13:drive:down","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":346546,"trial":"gale","attempt":8,"type":"attempt_started","reason":"build_changed","build":["8:sink:up","11:drive:right","12:core:up","13:drive:down","18:sink:up"],"stats":{"cost":8,"mass":6.4,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":6.6,"ward":0,"ballast":0,"tractors":0,"invalid":1}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":347365,"trial":"gale","attempt":8,"type":"part_removed","x":3,"y":2,"part":"drive","facing":"down","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":347367,"trial":"gale","attempt":9,"type":"attempt_started","reason":"part_removed","build":["8:sink:up","11:drive:right","12:core:up","18:sink:up"],"stats":{"cost":6,"mass":5.7,"drives":{"up":0,"right":1,"down":0,"left":0},"cooling":1.2,"ward":0,"ballast":0,"tractors":0,"invalid":2}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":347786,"trial":"gale","attempt":9,"type":"part_placed","x":3,"y":2,"part":"drive","facing":"left","replaced":null,"build":["8:sink:up","11:drive:right","12:core:up","13:drive:left","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":347788,"trial":"gale","attempt":10,"type":"attempt_started","reason":"build_changed","build":["8:sink:up","11:drive:right","12:core:up","13:drive:left","18:sink:up"],"stats":{"cost":8,"mass":6.4,"drives":{"up":0,"right":1,"down":0,"left":1},"cooling":6.6,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":350125,"trial":"gale","attempt":10,"type":"placement_facing_changed","before":"left","after":"down"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":350800,"trial":"gale","attempt":10,"type":"part_placed","x":2,"y":1,"part":"drive","facing":"down","replaced":null,"build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":350802,"trial":"gale","attempt":11,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","18:sink:up"],"stats":{"cost":10,"mass":7.1,"drives":{"up":0,"right":1,"down":1,"left":1},"cooling":5.7,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":352320,"trial":"gale","attempt":11,"type":"placement_facing_changed","before":"down","after":"up"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":352769,"trial":"gale","attempt":11,"type":"part_placed","x":2,"y":3,"part":"drive","facing":"up","replaced":null,"build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":352772,"trial":"gale","attempt":12,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":355662,"trial":"gale","attempt":12,"type":"attempt_engaged","source":"canvas","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":355965,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":356890,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":357394,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":504}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":357786,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":1821}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":358005,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":358199,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":194}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":358279,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.214,"y":0.408,"vx":0.071,"vy":-0.171,"heat":25,"attached":null},"progress":0,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":358724,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":358889,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":165}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":359165,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":359266,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":359362,"trial":"gale","attempt":12,"type":"gale_beacon_stabilized","beacon":1,"heat":25.16,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":359757,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":491}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":360340,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":360741,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":1576}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":361212,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.331,"y":0.338,"speed":0.021}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":361570,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":362023,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":453}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":362332,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":362703,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":2363}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":363033,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":363279,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.372,"y":0.334,"vx":0.012,"vy":0.09,"heat":25.669,"attached":null},"progress":1,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":363328,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.373,"y":0.337,"speed":0.027}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":363733,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":1401}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":363745,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.374,"y":0.338,"speed":0.002}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":364161,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.376,"y":0.338,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":364511,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.376,"y":0.338,"speed":0}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":364573,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":364632,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":1599}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":365049,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":476}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":365669,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":368294,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.369,"y":0.297,"vx":0.047,"vy":0.01,"heat":25,"attached":null},"progress":1,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":368682,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":369641,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":959}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":371665,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":372273,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":6604}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":372484,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":819}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":372485,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373294,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.621,"y":0.605,"vx":0.042,"vy":0.057,"heat":25,"attached":null},"progress":1,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373345,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.622,"y":0.607,"speed":0.019}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373482,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":997}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373619,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373667,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373672,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":53}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":373936,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":374425,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":489}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":374619,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":374909,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":1242}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":375359,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":375420,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":801}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":375844,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":376115,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":271}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":376123,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":764}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":376402,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":376644,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":376828,"trial":"gale","attempt":12,"type":"obstacle_collision","x":0.633,"y":0.706,"speed":0.03}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377094,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":692}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377323,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":679}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377412,"trial":"gale","attempt":12,"type":"gale_beacon_stabilized","beacon":2,"heat":25,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377471,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyS"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377760,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":377806,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyS","duration_ms":335}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":378295,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.682,"y":0.772,"vx":0.099,"vy":0.088,"heat":25,"attached":null},"progress":2,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":378436,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":378686,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":926}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":378867,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":431}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":378920,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":379368,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyW"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":379369,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":449}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":380602,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":380944,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":342}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":381375,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyD"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":381437,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyD","duration_ms":62}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":381937,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":382124,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":187}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":382585,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":382739,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":154}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":383132,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":383288,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":156}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":383311,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.88,"y":0.686,"vx":0,"vy":-0.067,"heat":25.187,"attached":null},"progress":2,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":384311,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":384561,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":250}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":384990,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":385184,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":194}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":385526,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":385716,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":190}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":386203,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":386391,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":188}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":386954,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":387224,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":269}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":387551,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":387770,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":219}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388311,"trial":"gale","attempt":12,"type":"field_snapshot","rig":{"x":0.889,"y":0.34,"vx":0.006,"vy":-0.07,"heat":25,"attached":null},"progress":2,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388325,"trial":"gale","attempt":12,"type":"drive_input_started","key":"KeyA"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388452,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyA","duration_ms":127}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388544,"trial":"gale","attempt":12,"type":"gale_beacon_stabilized","beacon":3,"heat":25.053,"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388546,"trial":"gale","attempt":12,"type":"drive_input_ended","key":"KeyW","duration_ms":9178,"reason":"trial_completed"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":388547,"trial":"gale","attempt":12,"type":"trial_completed","elapsed_in_attempt_ms":32885,"build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0},"completions":["haul","furnace","gale"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":395833,"trial":"gale","attempt":12,"type":"viewport_changed","viewport":{"width":1379,"height":805},"canvas":{"width":1048.35,"height":668.2}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":397214,"trial":"gale","attempt":12,"type":"part_removed","x":3,"y":3,"part":"sink","facing":"up","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":397218,"trial":"gale","attempt":13,"type":"attempt_started","reason":"part_removed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up"],"stats":{"cost":10,"mass":6.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":3,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":399254,"trial":"gale","attempt":13,"type":"palette_selected","part":"sink","facing":"up"}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":399808,"trial":"gale","attempt":13,"type":"part_placed","x":3,"y":3,"part":"sink","facing":"up","replaced":null,"build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"]}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":399811,"trial":"gale","attempt":14,"type":"attempt_started","reason":"build_changed","build":["7:drive:down","8:sink:up","11:drive:right","12:core:up","13:drive:left","17:drive:up","18:sink:up"],"stats":{"cost":12,"mass":7.8,"drives":{"up":1,"right":1,"down":1,"left":1},"cooling":4.8,"ward":0,"ballast":0,"tractors":0,"invalid":0}}
{"schema":1,"experiment":"003_rig_trials","prototype_revision":1,"session_id":"e1fa0f4d-9828-431e-8158-ddf826a41770","elapsed_ms":407058,"trial":"gale","attempt":14,"type":"log_exported","events_before_export":652}

File diff suppressed because it is too large Load diff

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# Find My Fun
This repository is a sequence of small game-design experiments. It is not a single game yet.
The current experiment is **008 — Catalyst Ecology**. See [its README](experiments/008_catalyst_ecology/README.md) for the one-command launch instructions and playtest prompt. Play it before reading its private hypothesis notes.
Earlier experiments are preserved in [`experiments/`](experiments/).
Research notes live in [`research/`](research/). They should be updated after each playtest, before another experiment is built.
Launch prototypes with their `run.sh` script. Their **Save JSONL** button posts the validated log to the local server and writes it directly into [`JSONL/`](JSONL/). If the custom server is unavailable, the button falls back to a normal browser download.

View file

@ -0,0 +1,982 @@
# Codex Plan: Experimental Game-Design Research Program
## Goal
Do **not** try to design the final game immediately.
The goal is to experimentally discover what combination of mechanics, constraints, feedback, pacing, and system structure causes the player to become deeply curious and want to continue experimenting.
Treat this as a game-design research program where each playable prototype is an experiment.
The player already knows many game mechanics they enjoy, but attempts to combine those mechanics directly have not reliably produced something fun. Therefore, do not assume the stated preferences are the actual causal ingredients of fun. Maintain hypotheses, design controlled experiments, collect playtest evidence, and iteratively update the theory of what the player enjoys.
The long-term aspiration is a game that feels like:
> **An imaginary branch of engineering whose physics happen to be magic.**
It should feel technically deep, closer to programming, electronics, or engineering than to a conventional spell-crafting inventory system.
---
# 1. Do Not Build a General Harness Yet
For the first phase, **Codex itself is the harness**.
Do not spend time building a generic automated game-generation platform, experiment scheduler, preference-learning service, or large framework.
Instead:
1. Maintain lightweight research files in the repository.
2. Form explicit hypotheses.
3. Build a small playable prototype to test one or a few hypotheses.
4. Instrument it enough to understand what happened.
5. Have the player play it.
6. Collect concise feedback.
7. Update the hypotheses.
8. Choose and build the next experiment.
Only automate parts of this workflow after repeated use demonstrates exactly what is worth automating.
Prematurely building a harness risks encoding incorrect assumptions about what should be measured or generated.
---
# 2. Known Starting Preferences
Treat these as **prior evidence**, not immutable requirements.
The player enjoys games including:
- Antichamber
- Helldivers 2
- Lethal Company
- Risk of Rain 2
- Minecraft
- 100% Orange Juice
- Crab Champions
- Vindictus
- Factorio
- Paper Mario: The Thousand-Year Door
- Robocraft
- Natural Selection 2
- Super Smash Bros.
- Oddworld: Munch's Oddysee
- Ori and the Blind Forest
- Portal 1 and 2
- Warframe
- RollerCoaster Tycoon 2
Observed preference threads include:
- Co-op players-vs-world rather than ladder-style competitive PvP.
- Systems and emergence over large amounts of authored content.
- Building and engineering as self-expression.
- Multiple valid ways to construct a competent solution.
- Asymmetric roles in co-op are interesting.
- Roguelite/run structures are attractive.
- Discovering and exploiting systemic interactions is enjoyable.
- Becoming extremely powerful through understanding is acceptable or desirable.
- Competitive balance is not inherently valuable.
- Recoverable chaos and improvisation are enjoyable.
- Optional friend-vs-friend duels can be fun, but PvP should not be the core.
A particularly useful detail:
The player prefers Risk of Rain 2 with **Artifact of Command**.
The interesting randomness is therefore not:
> "This item dropped; make the best of the item."
It is closer to:
> "A resource/opportunity of this class became available; choose the best use for it."
Prefer randomness that defines the **problem, budget, or opportunity** while leaving the actual solution under player control.
---
# 3. Central Design Hypothesis
The most promising current hypothesis is **epistemic progression**.
Progression should come primarily from learning how the world works.
A knowledgeable player starting a new game should be able to do things immediately that a new player does not know are possible.
Avoid fake knowledge progression such as:
- researching a node that enables a spell,
- finding a recipe that grants permission to perform an operation,
- collecting a spell block before a concept becomes usable,
- arbitrary level gates.
If a physical/magical operation is possible under the world's laws, the player should ideally be capable of performing it from the beginning if they understand how.
A notebook, codex, or visualization system may record discoveries, but it should generally **describe knowledge rather than grant permission**.
---
# 4. The Main Failure Mode to Study: Strategy Crystallization
A system can initially be deep and fun but later collapse into a routine.
Factorio is an important example.
At first there are many logistics problems to solve. Eventually much of the game can collapse into patterns such as:
- make a large main bus,
- leave enough space to tap resources later,
- use a known modular layout,
- repeat previously solved infrastructure patterns.
The simulation remains complicated, but the player has mentally compressed it.
The game becomes implementation work rather than reasoning.
Call this:
**strategy crystallization**
The research program should specifically attempt to discover designs where:
- knowledge continues to transfer,
- reusable abstractions are valuable,
- but complete solutions do not become universal templates.
A useful principle is:
> **Knowledge should transfer between problems; complete solutions should not.**
Another useful principle is:
> **Reuse components, not answers.**
---
# 5. Deep Systems, Narrow Interfaces
Minecraft modpacks provide another useful clue.
Large modpacks can contain many individually deep systems that interact at specific interfaces while otherwise remaining somewhat separate.
This may create better long-term complexity than one globally entangled system.
Prefer experiments involving:
- deep subsystems,
- narrow and understandable interfaces,
- reusable modules,
- explicit conversions between domains,
- emergent behavior at subsystem boundaries.
Think more like software architecture than a single giant web of dependencies.
Avoid assuming that "more cross-system interaction" is always better.
The desired property may instead be:
> **Complexity through composition rather than entanglement.**
This is a hypothesis to test.
---
# 6. Magic as an Engineering Discipline
The current best thematic and mechanical substrate is a technical magic system.
Do not treat magic as:
- recipes,
- collectible spell cards,
- a list of predefined spells,
- socketing modifiers into a projectile,
- inventory-based spell assembly.
Treat magic as an executable set of natural laws.
Possible underlying domains include:
- energy
- matter
- momentum
- temperature
- phase
- fields
- space
- information
- resonance
- oscillation
- delay
- feedback
- sensing
- transformation
- storage
- routing
These are examples, not a required final list.
The player should construct magical systems from operations that behave consistently.
For example, something conceptually like:
sense(target)
-> measure(temperature)
-> extract(thermal energy)
-> accumulate
-> transform into kinetic energy
-> release toward target
The important part is not this exact syntax.
The important part is that the system has enough consistency that the player can:
1. form a hypothesis,
2. build an experiment,
3. observe the result,
4. update their mental model,
5. exploit the discovery elsewhere.
The game should repeatedly produce:
> "What if I try X?"
>
> -> try X
>
> -> "Oh, THAT happens."
>
> -> "Then could I use that with Y?"
>
> -> try Y
>
> -> "Wait, that means Z might be possible..."
This curiosity chain is one of the strongest current candidates for the thing the final game must sustain.
---
# 7. The Real Research Target: What Causes Fun?
Do not define a fixed `fun_score`.
Do not ask an LLM to rate a prototype from 1 to 10.
Do not optimize against a static checklist of known preferences.
The actual causal mix is unknown.
Maintain a file such as:
`research/hypotheses.md`
with hypotheses like:
- Fun comes primarily from discovering non-obvious interactions.
- Optimization remains fun only while constraints create meaningful tradeoffs.
- A general-purpose solution becomes boring once it covers too much of the problem space.
- Reusable abstractions are satisfying only when they eliminate repetition without eliminating reasoning.
- External pressure may be required to make engineering decisions emotionally meaningful.
- External pressure may instead harm the engineering experience.
- The engineered system may need to affect a world the player cares about.
- Discovery may require surprise rather than pure optimization.
- The player may enjoy uncertain future requirements more than strict resource scarcity.
- Interface constraints may be more interesting than raw resource constraints.
- The player may prefer modifying an existing system over repeatedly rebuilding from nothing.
- The player may enjoy a mix where old modules remain useful but old architectures do not.
- Some systems may require a minimum complexity before they become enjoyable.
Every hypothesis should have:
- current confidence,
- evidence for,
- evidence against,
- experiments that tested it,
- unresolved questions.
Do not overfit after one playtest.
---
# 8. Prototype Philosophy
Each prototype is an **experiment**, not a product demo.
The target playtime for an initial test is roughly:
**5-30 minutes**
Thirty minutes is a maximum useful first-test window, not a required duration.
If the player becomes bored after 7 minutes, that is valuable evidence.
Never force a prototype to contain thirty minutes of content.
A prototype should be long enough for the relevant system to reveal itself.
Promising prototypes can later receive longer versions.
The prototype should be:
- immediately runnable,
- visually legible,
- responsive,
- instrumented,
- easy to modify,
- intentionally disposable.
Do not build:
- account systems,
- generic plugin architectures,
- production networking,
- final save formats,
- elaborate menus,
- extensive art pipelines,
- content authoring tools,
- generalized ECS abstractions unless clearly necessary,
- a reusable "game engine" as a side project.
Use simple technology that allows fast iteration.
A 2D desktop prototype is preferred when visualization matters.
Minimal art is acceptable, but cause and effect must be easy to read.
Do not assume that a terminal-only prototype is sufficient if the missing game feel may depend on spatial reasoning, motion, timing, or visual feedback.
---
# 9. Controlled Variants Are More Valuable Than Unrelated Games
Whenever practical, test hypotheses with **closely related variants** instead of entirely different prototypes.
Example:
Variant A:
- freely build a magical system to solve a static problem.
Variant B:
- identical system, but the environment changes after construction begins.
Variant C:
- identical system, but the machine affects an ecosystem or settlement.
Variant D:
- identical system, but the player must divide a constrained power budget.
Variant E:
- identical system, but future requirements are partly unknown.
This makes feedback much more informative because the changed variable is identifiable.
Pairwise comparison is especially useful:
> Which version would you rather keep playing?
Prefer this over asking the player to assign numerical scores.
---
# 10. Feedback Collection
Keep feedback lightweight.
After a playtest, ask only questions that materially distinguish competing hypotheses.
Useful prompts include:
- When did you first feel bored?
- Did you stop because you had solved the system, because implementation became tedious, or simply because you were done playing?
- Did the prototype cause you to try anything that was not required?
- Did you voluntarily experiment after already having a working solution?
- Did you form a new question about how the system worked?
- What did you want to try next?
- Which part felt like reasoning?
- Which part felt like work?
- Did you discover anything surprising?
- Was the surprise understandable in hindsight?
- Did you wish you had more control or less?
- Would you rather keep playing this prototype or the previous one?
Do not ask all of these every time.
Choose a few based on what the experiment was testing.
Free-form player comments are high-value evidence.
---
# 11. Instrumentation
Each prototype should generate lightweight logs.
Do not build a generalized telemetry platform yet.
A simple JSONL event log is enough.
Useful events may include:
- actions performed,
- components placed,
- connections made,
- configuration changes,
- experiments attempted,
- failures,
- successful outputs,
- state transitions,
- module reuse,
- undo/rebuild events,
- timestamps,
- completion time,
- optional player annotations.
Instrumentation should help answer questions such as:
- Did the player keep changing the system after it already worked?
- Did they repeatedly use the same pattern?
- How much of an old solution was copied?
- How much was recomposed?
- When did experimentation stop?
- Did behavior become repetitive?
Do not let analytics work delay the playable prototype.
---
# 12. Important Candidate Metrics
These are **research heuristics**, not definitions of fun.
## Solution convergence
Do successful solutions eventually become nearly identical?
High convergence may indicate strategy crystallization.
## Whole-solution reuse
Can a previous solution solve most new problems with trivial modification?
Too much may be bad.
## Component reuse
Do useful modules recur while the overall architecture changes?
This is currently considered promising.
## Knowledge leverage
Can an experienced player perform dramatically better than a naive player with the same in-game resources?
High knowledge leverage is desirable.
## Novel problem rate
After mastering previous scenarios, does the next scenario still demand new reasoning?
## Revision radius
When requirements change, how much of the existing construction must be redesigned?
Both extremes may be undesirable:
- rebuilding everything can become tedious,
- changing almost nothing can become rote.
## Curiosity generation
Does one discovery create another meaningful question?
This is especially important.
## Voluntary experimentation
Does the player test ideas that are not required to finish the stated objective?
This may be a stronger signal than explicit ratings.
## Strategy half-life
How long does a useful strategy remain valuable before becoming an automatic answer?
The desired game may maximize:
> **transferability of knowledge while limiting the lifetime of complete strategies.**
---
# 13. Randomness
Use randomness carefully.
Prefer:
**randomness defines the problem**
over:
**randomness chooses the player's solution**
Good examples of randomness:
- environmental conditions,
- topology,
- available energy,
- enemy properties,
- goals,
- constraints,
- timing,
- system disturbances,
- resource budgets,
- opportunity classes,
- partially unknown future requirements.
The player should retain meaningful agency over how those conditions are solved.
A useful analogy is Artifact of Command in Risk of Rain 2:
- a drop establishes a tier/opportunity,
- the player chooses the exact investment.
Avoid making the technical depth depend primarily on lucking into a particular spell piece.
---
# 14. Persistent Knowledge, Transient Problems
A promising structure is:
Persistent:
- player understanding,
- discovered principles,
- reusable mental models,
- saved modules or abstractions,
- instrumentation techniques,
- perhaps a notebook of observations.
Transient:
- environment,
- problem constraints,
- available energy,
- topology,
- enemies,
- goals,
- resource budgets,
- world conditions,
- run-specific opportunities.
The player should feel that starting over does not erase progress because **they themselves are better at the system**.
Saved modules may also persist, but they should ideally require recomposition rather than acting as universal answers.
---
# 15. Initial Experiment Sequence
Do not implement all of these before the first playtest.
Build one experiment, test it, update the research model, then decide whether the next experiment still makes sense.
## Experiment 0 — Is the magical engineering itself interesting?
Build the smallest system that can support real experimentation.
Possible ingredients:
- sources,
- sinks,
- routing,
- sensing,
- storage,
- transformation,
- thresholds,
- oscillation,
- feedback,
- delay.
Give the player a few open technical objectives.
Do not gate operations behind unlocks.
Goal:
Determine whether constructing and debugging a consistent magical system is intrinsically interesting.
Important observation:
Does the player start experimenting with things unrelated to the objective?
If not, do not simply add more components. Determine why.
---
## Experiment 1 — Static problem vs changing requirements
Reuse Experiment 0.
Variant A:
- objective and environment remain stable.
Variant B:
- requirements change after the player has a working system.
Examples:
- output target changes,
- energy supply becomes intermittent,
- a load develops a new property,
- an interface moves,
- one transport mechanism becomes dangerous.
Goal:
Determine whether adapting existing designs creates useful continuing reasoning or merely annoyance.
Measure revision radius and module reuse.
---
## Experiment 2 — Does the output need to matter to a world?
Reuse the same core system.
Compare:
A:
- solve abstract technical objectives.
B:
- the constructed magical machine affects an observable world.
Examples:
- powers a settlement,
- alters weather,
- protects organisms,
- influences creature migration,
- changes terrain,
- stabilizes a dangerous phenomenon.
Goal:
Test whether engineering becomes more compelling when consequences are externally meaningful.
---
## Experiment 3 — Constraint selection rather than random parts
Create run-specific constraints.
Do not randomly withhold the core language.
Instead provide things like:
- one high-quality power allocation,
- two medium-grade stabilization budgets,
- limited stable anchors,
- unusual environmental energy sources,
- constrained routing interfaces.
Let the player decide exactly where to invest them.
Goal:
Test the Artifact-of-Command-like structure:
> random opportunity, chosen solution.
---
## Experiment 4 — Library building
Allow the player to save a useful subsystem as a reusable module.
Then present several new scenarios.
Goal:
Determine whether reusing abstractions is satisfying while complete solutions remain non-transferable.
Look for the desired pattern:
- old knowledge: highly useful,
- old components: useful,
- old whole design: insufficient.
---
## Experiment 5 — Pressure vs no pressure
Only test this after the base engineering loop shows promise.
Compare:
A:
- no time or external pressure.
B:
- soft pressure.
C:
- dynamic external pressure such as creatures, instability, or changing demand.
Goal:
Discover whether pressure turns design decisions into exciting improvisation or merely interrupts the interesting work.
Do not assume combat is necessary.
---
# 16. Adversarial Design Review
Once a prototype is promising, try to destroy it intellectually.
Ask:
- What is the universal solution?
- What can the player template?
- What can be reduced to rote execution?
- Which subsystem dominates every other option?
- Can one abstraction eliminate an entire category of decisions?
- Is there an obvious "main bus" equivalent?
- What would an expert do every single run?
- What becomes busywork after the tenth repetition?
- Can the best strategy be summarized in one sentence?
- Is complexity coming from reasoning or merely bookkeeping?
Then create a test specifically targeting the suspected failure.
Do not automatically nerf powerful discoveries.
Distinguish:
**boring dominant strategy**
from:
**clever exploitative synergy**
The latter may be desirable.
---
# 17. Explore vs Exploit
Do not endlessly polish the current best idea.
Maintain both:
**Exploitation**
- deepen promising discoveries.
**Exploration**
- test alternate explanations for what is fun.
A reasonable informal balance is approximately:
- 2 experiments extending promising findings,
- then 1 experiment probing a substantially different hypothesis.
This is not a rigid schedule.
The point is to avoid converging too early on an incorrect theory.
---
# 18. Repository Structure
Keep it simple.
Suggested structure:
README.md
research/
current_model.md
hypotheses.md
experiment_index.md
experiments/
000_magic_language/
README.md
hypothesis.md
prototype/
results/
001_dynamic_constraints/
...
002_world_consequences/
...
shared/
only code that has genuinely become common across multiple experiments
`research/current_model.md` should contain the latest concise theory of what appears to create and destroy enjoyment.
`research/hypotheses.md` should preserve competing explanations.
`research/experiment_index.md` should summarize:
- experiment,
- hypothesis,
- result,
- confidence change,
- next question.
Do not prematurely refactor experiments into a shared framework.
Duplication is acceptable during research.
---
# 19. Experiment README Template
Each experiment should record:
## Question
What are we trying to learn?
## Hypotheses
What competing explanations does this distinguish?
## Difference From Previous Experiment
What intentionally changed?
## Expected Result
What evidence would support each hypothesis?
## Controls
What was deliberately kept the same?
## How To Run
One short command if possible.
## What To Pay Attention To
Only the few observations relevant to this test.
## Result
Filled in after playtesting.
## Interpretation
What changed in the preference model?
## Next Best Experiment
What uncertainty is most valuable to reduce next?
---
# 20. Codex Operating Rules
Codex should follow these rules throughout the project.
1. **Playable evidence beats design discussion.**
If a question can reasonably be answered by a small prototype, build the prototype.
2. **Do not build the final game yet.**
We are discovering what the final game needs to be.
3. **Do not treat user-stated preferences as ground truth.**
They are hypotheses with prior evidence.
4. **Change as few variables as practical between comparative experiments.**
5. **Do not add content to hide a weak system.**
6. **Do not assume a prototype failed because it was insufficiently fleshed out.**
Consider that possibility, but also consider that the underlying interaction may simply be uninteresting.
7. **Conversely, do not reject an idea merely because a crude prototype lacks presentation.**
Identify whether missing feel, feedback, readability, timing, or embodiment could plausibly be masking the system.
8. **Separate implementation difficulty from conceptual value.**
9. **Do not optimize away complexity merely because conventional game-design advice prefers simplicity.**
10. **Prefer complexity that creates new reasoning over complexity that creates bookkeeping.**
11. **Avoid arbitrary progression gates.**
12. **Prefer stable laws and changing constraints.**
13. **Prefer reusable abstractions over repeated manual work.**
14. **Watch aggressively for strategy crystallization.**
15. **Treat boredom as data.**
16. **Treat voluntary experimentation as strong positive evidence.**
17. **Treat "I immediately want to try one more thing" as one of the strongest positive signals available.**
18. **After each playtest, update the research files before building the next prototype.**
19. **Choose the next experiment based on information gain, not on what is easiest to implement.**
20. **Do not build a generalized harness until repeated experiments make its required shape obvious.**
---
# 21. When to Build the Harness
Only consider formalizing the process into an automated harness after several experiments have revealed stable repeated operations.
Good reasons to automate include:
- every prototype needs the same event logger,
- experimental variants share a stable declarative rules format,
- the same scenario runner is repeatedly rewritten,
- pairwise playtest results need structured storage,
- common mutation operators have emerged,
- automated agents can meaningfully test strategy convergence,
- the preference model has stable dimensions,
- Codex is spending more time repeating research administration than building experiments.
At that point, extract the proven workflow.
Do not design the harness spec from imagination.
Let the research process generate the spec.
---
# 22. Immediate Task
Start with **Experiment 0**.
Before writing substantial code:
1. Create the lightweight research directory and files.
2. Record the initial hypotheses from this plan.
3. Design the smallest magical-engineering system that can create genuine hypotheses and surprising interactions.
4. Keep all core operations available from the beginning.
5. Make the system visually legible.
6. Add lightweight JSONL instrumentation.
7. Build a runnable prototype.
8. Provide a short explanation of what this specific experiment is testing.
9. Stop and let the player test it before implementing Experiment 1.
The first prototype does not need to prove the entire game concept.
Its job is to answer one question:
> **Does interacting with a consistent, programmable-feeling magical system create enough curiosity that the player voluntarily starts asking and testing their own questions?**
If yes, determine *why*.
If no, determine *why not*.
That is the beginning of the research program.

View file

@ -0,0 +1,70 @@
# Experiment 000 — Resonance Bench
## Question
Does interacting with a consistent, programmable-feeling magical system create enough curiosity that the player voluntarily asks and tests their own questions?
## Hypotheses
The primary hypothesis is that phase, timing, conservation, storage, and feedback can form a legible curiosity chain. Competing explanations are that abstract engineering needs world consequences, that circuit-like manipulation is not intrinsically compelling, or that interface friction masks the idea. See [`hypothesis.md`](hypothesis.md).
## Difference From Previous Experiment
This is the baseline experiment.
## Expected Result
Strong support is not merely finishing the three observations. It is changing a working network to answer a new question, especially after discovering resonance, cancellation, or timing behavior. A clean stop after prescribed objectives, boredom after understanding the laws, and interface frustration imply different next experiments.
## Controls
- No time pressure, enemies, unlocks, randomized parts, progression, or world-fiction consequences.
- Every operation is available from the beginning.
- The three fixed wells provide the same signals on every run.
- Objectives observe behavior and do not award capabilities.
## How To Run
From this directory:
```bash
./run.sh
```
Then open <http://localhost:8000>. The prototype also works by opening `prototype/index.html` directly, although serving it avoids browser-specific local-file restrictions.
## What To Pay Attention To
- After you satisfy an observation, do you want to alter the network anyway?
- Did any result make you ask a new question about the laws?
- Which moments felt like reasoning, and which felt like wiring work?
When you are done—even if boredom arrives quickly—use **Export JSONL**, then report what happened. The most useful first feedback is: when you wanted to stop, one thing you tried that was not required (if any), and what you wanted to try next (if anything).
## Result
Playtested on 2026-08-16. The player completed all three observations in roughly three minutes or less and felt no reason to continue.
- No voluntary experiment followed completion.
- The player did not notice that sources were cyclic or that flux had defined direction.
- The observation predicates were permissive enough that arbitrary timing and direction would still produce competent solutions.
- A merely working solution felt fully sufficient; there was no meaningful reason to improve, vary, or interrogate it.
No JSONL was available for this interpretation; the player's direct report is decisive for the primary behavioral question.
## Interpretation
Experiment 000 did **not** produce the desired curiosity chain. This is a failure of the experiment as built, not yet a clean rejection of programmable magic.
The observations failed to make the central laws causally relevant. They measured loose occupancy and threshold events rather than behavior whose outcome depended on phase and timing. Consequently:
- completion is not evidence that the player formed the intended mental model;
- lack of post-completion optimization is unsurprising because improvement had no consequence;
- cycles and vector direction were implementation details rather than perceived phenomena;
- the prototype's expressive space appeared exhausted as soon as its three binary predicates were satisfied.
This lowers confidence that an abstract signal bench is intrinsically compelling in its current form. It increases confidence that legibility must come from visible consequences, not explanatory text, and that objectives must discriminate between structurally different outputs if they are meant to expose a law.
## Next Best Experiment
Build a controlled successor that retains deterministic cyclic sources and transformations but maps output vectors onto visible motion in a small world. Direction becomes literal direction; timing changes trajectories; cycles create observable repeated behavior. This tests whether embodied consequence makes the same knowledge interesting, versus the competing explanation that the signal substrate itself is too limited.

View file

@ -0,0 +1,26 @@
# Experiment 000 Hypothesis
## Primary hypothesis
A small set of consistent signal laws—vector phase, conservation through splitting, propagation loss, delay, accumulation, thresholds, and feedback—will create legible interactions that prompt the player to form and test an unrequired question.
## Competing explanations
1. The interaction is intrinsically interesting, even with abstract objectives.
2. The laws are interesting but require more embodied or consequential output.
3. The activity feels like ordinary circuit assembly and produces no meaningful curiosity.
4. The idea has promise, but this interface hides causality or makes construction too laborious to judge it.
## Strong evidence
- The player keeps changing a completed network to test an idea.
- A result surprises them, becomes understandable, and suggests another experiment.
- They exploit feedback, cancellation, timing, or accumulation in an approach not dictated by the objectives.
## Weak or negative evidence
- They wire only the minimum path described by the UI and stop.
- They understand the system but feel no desire to manipulate it.
- Most time is spent fighting selection, wiring, deletion, or readability.
- Success comes from unexplained trial and error rather than a revised mental model.

View file

@ -0,0 +1,696 @@
(() => {
"use strict";
const workspace = document.querySelector("#workspace");
const nodesLayer = document.querySelector("#nodes");
const wiresLayer = document.querySelector("#wires");
const pendingWire = document.querySelector("#pending-wire");
const palette = document.querySelector("#palette");
const objectivesEl = document.querySelector("#objectives");
const scopesEl = document.querySelector("#scopes");
const runButton = document.querySelector("#run-button");
const stepButton = document.querySelector("#step-button");
const speedInput = document.querySelector("#speed");
const tickReadout = document.querySelector("#tick-readout");
const resetButton = document.querySelector("#reset-button");
const exportButton = document.querySelector("#export-button");
const selectionReadout = document.querySelector("#selection-readout");
const toastEl = document.querySelector("#toast");
const MACHINE_DEFS = {
junction: {
name: "Confluence", glyph: "+", short: "vector sum", description: "Combines every arrival by vector addition, then passes the result unchanged.", config: {}
},
rotor: {
name: "Turner", glyph: "↻", short: "rotate phase", description: "Rotates phase without changing strength.",
config: { turns: { label: "turn", value: 1, options: [[-1, "90°"], [1, "+90°"], [2, "180°"]] } }
},
delay: {
name: "Echo", glyph: "⋯", short: "hold in time", description: "Returns each arrival after a fixed number of ticks.",
config: { delay: { label: "ticks", value: 2, options: [[1, "1"], [2, "2"], [3, "3"], [4, "4"], [5, "5"], [6, "6"]] } }
},
vessel: {
name: "Vessel", glyph: "◇", short: "store & burst", description: "Adds flux to a leaky store. At threshold, releases the entire vector and empties.",
config: { threshold: { label: "release", value: 2.5, options: [[1.5, "1.5"], [2.5, "2.5"], [4, "4.0"], [6, "6.0"]] } }
},
valve: {
name: "Threshold", glyph: "⊣", short: "pass strong flux", description: "Passes an arrival only when its combined strength reaches the threshold.",
config: { threshold: { label: "minimum", value: 1, options: [[0.35, "0.35"], [0.75, "0.75"], [1, "1.0"], [1.5, "1.5"], [2.5, "2.5"]] } }
},
polarizer: {
name: "Polarizer", glyph: "∥", short: "project an axis", description: "Keeps only the signed component parallel to its chosen phase; the rest dissipates.",
config: { axis: { label: "axis", value: 0, options: [[0, "east / west"], [90, "north / south"], [45, "diagonal /"], [135, "diagonal backslash"]] } }
}
};
const SOURCE_DEFS = [
{ id: "well-dawn", name: "Dawn well", glyph: "☼", x: 34, y: 42, phase: 0, strength: 1, period: 2, description: "1.00 east-phase flux every 2 ticks." },
{ id: "well-tide", name: "Tide well", glyph: "≈", x: 34, y: 200, phase: 90, strength: 1.15, period: 3, description: "1.15 north-phase flux every 3 ticks." },
{ id: "well-dusk", name: "Dusk well", glyph: "◒", x: 34, y: 358, phase: 180, strength: .9, period: 5, description: "0.90 west-phase flux every 5 ticks." }
];
const SCOPE_DEFS = [
{ id: "scope-amber", name: "Continuity lens", glyph: "", color: "#f0bf68", description: "Observes continuity: strength > 0.18 on 10 of the latest 12 ticks." },
{ id: "scope-cyan", name: "Impulse lens", glyph: "Ⅱ", color: "#67d7ff", description: "Observes one arrival with strength ≥ 2.40." },
{ id: "scope-violet", name: "Polarity lens", glyph: "Ⅲ", color: "#be92ff", description: "Observes two non-trivial arrivals with nearly opposite phases within 10 ticks." }
];
const OBJECTIVES = [
{ id: "continuity", title: "Continuity", description: "Continuity lens: > 0.18 strength on 10 of 12 recent ticks." },
{ id: "impulse", title: "Impulse", description: "Impulse lens: a single arrival at strength 2.40 or greater." },
{ id: "polarity", title: "Reversal", description: "Polarity lens: opposing phases within a 10-tick window." }
];
const state = {
nodes: new Map(),
edges: new Map(),
mailbox: new Map(),
tick: 0,
running: true,
timer: null,
speed: Number(speedInput.value),
pendingFrom: null,
selectedNode: null,
selectedEdge: null,
nextNodeId: 1,
nextEdgeId: 1,
completed: new Set(),
logs: [],
sessionId: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
startedAt: Date.now()
};
const zero = () => ({ x: 0, y: 0 });
const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
const scale = (a, amount) => ({ x: a.x * amount, y: a.y * amount });
const magnitude = a => Math.hypot(a.x, a.y);
const degrees = a => magnitude(a) < .0001 ? 0 : (Math.atan2(a.y, a.x) * 180 / Math.PI + 360) % 360;
const fromPolar = (strength, phase) => ({ x: strength * Math.cos(phase * Math.PI / 180), y: strength * Math.sin(phase * Math.PI / 180) });
const rotate = (a, angle) => {
const r = angle * Math.PI / 180;
return { x: a.x * Math.cos(r) - a.y * Math.sin(r), y: a.x * Math.sin(r) + a.y * Math.cos(r) };
};
const sum = vectors => vectors.reduce(add, zero());
const round = n => Math.round(n * 1000) / 1000;
const compactVector = v => ({ x: round(v.x), y: round(v.y), strength: round(magnitude(v)), phase: round(degrees(v)) });
const phaseColor = v => magnitude(v) < .015 ? "#718096" : `hsl(${Math.round((degrees(v) + 165) % 360)} 82% 70%)`;
const phaseName = v => {
if (magnitude(v) < .015) return "quiet";
const names = ["east", "north-east", "north", "north-west", "west", "south-west", "south", "south-east"];
return names[Math.round(degrees(v) / 45) % 8];
};
function logEvent(type, data = {}) {
const event = {
schema: 1,
session_id: state.sessionId,
elapsed_ms: Date.now() - state.startedAt,
tick: state.tick,
type,
...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("resonance-bench-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) { /* Export still works. */ }
}
function makeConfig(type) {
const result = {};
for (const [key, spec] of Object.entries(MACHINE_DEFS[type]?.config || {})) result[key] = spec.value;
return result;
}
function addNode(type, x, y, extras = {}, shouldLog = true) {
const id = extras.id || `machine-${state.nextNodeId++}`;
const def = MACHINE_DEFS[type] || extras;
const node = {
id, type, x, y,
name: extras.name || def.name,
glyph: extras.glyph || def.glyph,
description: extras.description || def.description,
fixed: Boolean(extras.fixed),
color: extras.color || null,
config: extras.config || makeConfig(type),
source: extras.source || null,
history: [],
internal: {},
live: zero(),
received: zero()
};
state.nodes.set(id, node);
renderNode(node);
if (shouldLog) logEvent("node_added", { node_id: id, operation: type, x: round(x), y: round(y), config: node.config });
updateWorkspaceHint();
return node;
}
function renderNode(node) {
const el = document.createElement("article");
el.className = `node ${node.fixed ? "fixed" : ""} ${node.type === "scope" ? "scope-node" : ""}`;
el.dataset.nodeId = node.id;
el.style.left = `${node.x}px`;
el.style.top = `${node.y}px`;
if (node.color) el.style.setProperty("--node-accent", node.color);
const hasInput = node.type !== "source";
const hasOutput = node.type !== "scope";
const configHtml = Object.entries(MACHINE_DEFS[node.type]?.config || {}).map(([key, spec]) => {
const options = spec.options.map(([value, label]) => `<option value="${value}" ${String(value) === String(node.config[key]) ? "selected" : ""}>${label}</option>`).join("");
return `<label for="${node.id}-${key}">${spec.label}</label><select id="${node.id}-${key}" data-config="${key}">${options}</select>`;
}).join("");
el.innerHTML = `
<header class="node-header">
<span class="node-glyph">${node.glyph}</span>
<span class="node-kind">${node.name}</span>
${node.fixed ? "" : '<button class="node-delete" title="Remove node" aria-label="Remove node">×</button>'}
</header>
${hasInput ? '<button class="port input" title="Flux input" aria-label="Flux input"></button>' : ""}
${hasOutput ? '<button class="port output" title="Flux output" aria-label="Flux output"></button>' : ""}
<div class="node-body">
<div class="node-description">${node.description}</div>
<div class="node-live">
<div class="flux-orb"></div>
<div class="flux-numbers"><strong>quiet</strong><span>x 0.00 · y 0.00</span></div>
</div>
${configHtml ? `<div class="node-config">${configHtml}</div>` : ""}
</div>`;
nodesLayer.append(el);
node.el = el;
el.addEventListener("pointerdown", event => {
if (event.target.closest(".port, button, select, input")) return;
selectNode(node.id);
});
el.querySelector(".node-delete")?.addEventListener("click", () => removeNode(node.id));
el.querySelector(".port.output")?.addEventListener("click", event => beginWire(event, node.id));
el.querySelector(".port.input")?.addEventListener("click", event => finishWire(event, node.id));
el.querySelectorAll("[data-config]").forEach(control => control.addEventListener("change", event => {
const key = event.target.dataset.config;
const before = node.config[key];
node.config[key] = Number(event.target.value);
node.internal = {};
logEvent("node_configured", { node_id: node.id, operation: node.type, parameter: key, before, after: node.config[key] });
}));
installDrag(node, el.querySelector(".node-header"));
}
function installDrag(node, handle) {
handle.addEventListener("pointerdown", event => {
if (event.button !== 0 || event.target.closest("button")) return;
event.preventDefault();
handle.setPointerCapture(event.pointerId);
selectNode(node.id);
const start = { px: event.clientX, py: event.clientY, x: node.x, y: node.y };
let moved = false;
const onMove = moveEvent => {
const rect = workspace.getBoundingClientRect();
const maxX = Math.max(0, rect.width - node.el.offsetWidth);
const maxY = Math.max(0, rect.height - node.el.offsetHeight);
node.x = Math.max(0, Math.min(maxX, start.x + moveEvent.clientX - start.px));
node.y = Math.max(0, Math.min(maxY, start.y + moveEvent.clientY - start.py));
moved ||= Math.abs(moveEvent.clientX - start.px) + Math.abs(moveEvent.clientY - start.py) > 3;
node.el.style.left = `${node.x}px`;
node.el.style.top = `${node.y}px`;
renderWires();
};
const onUp = () => {
handle.removeEventListener("pointermove", onMove);
handle.removeEventListener("pointerup", onUp);
if (moved) logEvent("node_moved", { node_id: node.id, x: round(node.x), y: round(node.y) });
};
handle.addEventListener("pointermove", onMove);
handle.addEventListener("pointerup", onUp);
});
}
function removeNode(id) {
const node = state.nodes.get(id);
if (!node || node.fixed) return;
const connected = [...state.edges.values()].filter(edge => edge.from === id || edge.to === id);
connected.forEach(edge => removeEdge(edge.id, false));
node.el.remove();
state.nodes.delete(id);
if (state.selectedNode === id) clearSelection();
logEvent("node_removed", { node_id: id, operation: node.type, removed_connections: connected.length });
updateWorkspaceHint();
}
function beginWire(event, nodeId) {
event.stopPropagation();
cancelPendingWire();
state.pendingFrom = nodeId;
state.nodes.get(nodeId).el.querySelector(".port.output").classList.add("pending");
pendingWire.hidden = false;
const move = moveEvent => drawPending(moveEvent.clientX, moveEvent.clientY);
const cancel = cancelEvent => {
if (cancelEvent.target.closest?.(".port.input")) return;
cancelPendingWire();
};
state.pendingMove = move;
state.pendingCancel = cancel;
window.addEventListener("pointermove", move);
window.addEventListener("click", cancel, { capture: true, once: true });
}
function finishWire(event, toId) {
if (!state.pendingFrom) return;
event.stopPropagation();
event.preventDefault();
const fromId = state.pendingFrom;
cancelPendingWire();
if (state.edges.values().some(edge => edge.from === fromId && edge.to === toId)) {
showToast("That link already exists.");
return;
}
addEdge(fromId, toId);
}
function cancelPendingWire() {
if (state.pendingFrom) state.nodes.get(state.pendingFrom)?.el.querySelector(".port.output")?.classList.remove("pending");
if (state.pendingMove) window.removeEventListener("pointermove", state.pendingMove);
state.pendingFrom = null;
state.pendingMove = null;
state.pendingCancel = null;
pendingWire.hidden = true;
pendingWire.setAttribute("d", "");
}
function portPoint(node, output) {
return { x: node.x + (output ? node.el.offsetWidth : 0), y: node.y + 51 + 7.5 };
}
function curvePath(a, b) {
const distance = Math.abs(b.x - a.x);
const bend = Math.max(52, Math.min(210, distance * .48));
const direction = b.x >= a.x ? 1 : -1;
return `M ${a.x} ${a.y} C ${a.x + bend * direction} ${a.y}, ${b.x - bend * direction} ${b.y}, ${b.x} ${b.y}`;
}
function drawPending(clientX, clientY) {
const node = state.nodes.get(state.pendingFrom);
if (!node) return;
const rect = workspace.getBoundingClientRect();
pendingWire.setAttribute("d", curvePath(portPoint(node, true), { x: clientX - rect.left, y: clientY - rect.top }));
}
function addEdge(from, to, shouldLog = true) {
const id = `link-${state.nextEdgeId++}`;
const edge = { id, from, to, signal: zero(), el: null };
state.edges.set(id, edge);
renderWires();
if (shouldLog) logEvent("link_added", { link_id: id, from, to, closes_cycle: pathExists(to, from) });
return edge;
}
function pathExists(start, target) {
const seen = new Set();
const stack = [start];
while (stack.length) {
const current = stack.pop();
if (current === target) return true;
if (seen.has(current)) continue;
seen.add(current);
for (const edge of state.edges.values()) if (edge.from === current) stack.push(edge.to);
}
return false;
}
function removeEdge(id, shouldLog = true) {
const edge = state.edges.get(id);
if (!edge) return;
edge.el?.remove();
state.edges.delete(id);
if (state.selectedEdge === id) clearSelection();
if (shouldLog) logEvent("link_removed", { link_id: id, from: edge.from, to: edge.to });
}
function renderWires() {
for (const edge of state.edges.values()) {
if (!edge.el) {
const ns = "http://www.w3.org/2000/svg";
const group = document.createElementNS(ns, "g");
group.classList.add("wire");
group.dataset.edgeId = edge.id;
const hit = document.createElementNS(ns, "path");
hit.classList.add("wire-hit");
const visible = document.createElementNS(ns, "path");
visible.classList.add("wire-visible");
const flow = document.createElementNS(ns, "path");
flow.classList.add("wire-flow");
group.append(hit, visible, flow);
hit.addEventListener("click", event => { event.stopPropagation(); selectEdge(edge.id); });
hit.addEventListener("contextmenu", event => { event.preventDefault(); removeEdge(edge.id); });
wiresLayer.append(group);
edge.el = group;
}
const from = state.nodes.get(edge.from);
const to = state.nodes.get(edge.to);
if (!from || !to) continue;
const d = curvePath(portPoint(from, true), portPoint(to, false));
edge.el.querySelectorAll("path").forEach(path => path.setAttribute("d", d));
updateEdgeVisual(edge);
}
}
function updateEdgeVisual(edge) {
if (!edge.el) return;
const power = magnitude(edge.signal);
edge.el.classList.toggle("active", power > .015);
edge.el.classList.toggle("selected", state.selectedEdge === edge.id);
edge.el.style.setProperty("--flow-color", phaseColor(edge.signal));
edge.el.style.setProperty("--flow-opacity", String(Math.min(.95, .35 + power * .25)));
edge.el.style.setProperty("--flow-width", `${Math.min(7, 1.5 + power * 1.2)}px`);
}
function selectNode(id) {
clearSelection();
state.selectedNode = id;
state.nodes.get(id)?.el.classList.add("selected");
const node = state.nodes.get(id);
selectionReadout.textContent = node ? `${node.name} · ${node.id}` : "Nothing selected";
}
function selectEdge(id) {
clearSelection();
state.selectedEdge = id;
const edge = state.edges.get(id);
edge?.el.classList.add("selected");
selectionReadout.textContent = edge ? `link · ${state.nodes.get(edge.from)?.name}${state.nodes.get(edge.to)?.name}` : "Nothing selected";
}
function clearSelection() {
if (state.selectedNode) state.nodes.get(state.selectedNode)?.el.classList.remove("selected");
if (state.selectedEdge) state.edges.get(state.selectedEdge)?.el.classList.remove("selected");
state.selectedNode = null;
state.selectedEdge = null;
selectionReadout.textContent = "Nothing selected";
}
function machineOutput(node, input) {
switch (node.type) {
case "junction": return input;
case "rotor": return rotate(input, node.config.turns * 90);
case "delay": {
node.internal.queue ||= [];
node.internal.queue.push({ ...input });
return node.internal.queue.length > node.config.delay ? node.internal.queue.shift() : zero();
}
case "vessel": {
node.internal.stored ||= zero();
node.internal.stored = scale(add(node.internal.stored, input), .99);
if (magnitude(node.internal.stored) >= node.config.threshold) {
const released = node.internal.stored;
node.internal.stored = zero();
logEvent("vessel_released", { node_id: node.id, flux: compactVector(released) });
return released;
}
return zero();
}
case "valve": return magnitude(input) >= node.config.threshold ? input : zero();
case "polarizer": {
const axis = fromPolar(1, node.config.axis);
const projection = input.x * axis.x + input.y * axis.y;
return scale(axis, projection);
}
default: return zero();
}
}
function simulationStep() {
state.tick += 1;
const nextMailbox = new Map();
const outputs = new Map();
for (const node of state.nodes.values()) {
const arrivals = state.mailbox.get(node.id) || [];
const input = sum(arrivals);
node.received = input;
let output = zero();
if (node.type === "source") {
if (state.tick % node.source.period === 0) output = fromPolar(node.source.strength, node.source.phase);
} else if (node.type === "scope") {
node.history.push({ tick: state.tick, ...input });
if (node.history.length > 24) node.history.shift();
} else {
output = machineOutput(node, input);
}
node.live = node.type === "scope" ? input : output;
outputs.set(node.id, output);
updateNodeVisual(node);
}
for (const node of state.nodes.values()) {
const outgoing = [...state.edges.values()].filter(edge => edge.from === node.id);
const output = outputs.get(node.id) || zero();
const share = outgoing.length ? scale(output, .92 / outgoing.length) : zero();
for (const edge of outgoing) {
edge.signal = share;
if (!nextMailbox.has(edge.to)) nextMailbox.set(edge.to, []);
nextMailbox.get(edge.to).push({ ...share });
updateEdgeVisual(edge);
}
}
state.mailbox = nextMailbox;
tickReadout.textContent = `tick ${state.tick}`;
evaluateObjectives();
renderScopes();
if (state.tick % 20 === 0) {
logEvent("network_snapshot", {
nodes: state.nodes.size,
links: state.edges.size,
completed_observations: [...state.completed],
active_links: [...state.edges.values()].filter(edge => magnitude(edge.signal) > .015).length
});
}
}
function updateNodeVisual(node) {
if (!node.el) return;
const vector = node.live;
const power = magnitude(vector);
const orb = node.el.querySelector(".flux-orb");
const color = phaseColor(vector);
orb.style.setProperty("--phase", `${-degrees(vector)}deg`);
orb.style.setProperty("--power", String(Math.min(1, power / 2)));
orb.style.setProperty("--flux-color", color);
const label = node.el.querySelector(".flux-numbers strong");
const detail = node.el.querySelector(".flux-numbers span");
if (node.type === "vessel" && power < .015 && magnitude(node.internal.stored || zero()) > .015) {
const stored = node.internal.stored;
label.textContent = `holding ${magnitude(stored).toFixed(2)}`;
detail.textContent = `${phaseName(stored)} · not released`;
} else {
label.textContent = power < .015 ? "quiet" : `${power.toFixed(2)} · ${phaseName(vector)}`;
detail.textContent = `x ${vector.x.toFixed(2)} · y ${vector.y.toFixed(2)}`;
}
}
function evaluateObjectives() {
const amber = state.nodes.get("scope-amber")?.history || [];
const cyan = state.nodes.get("scope-cyan")?.history || [];
const violet = state.nodes.get("scope-violet")?.history || [];
const recentAmber = amber.slice(-12);
if (recentAmber.length === 12 && recentAmber.filter(v => magnitude(v) > .18).length >= 10) completeObjective("continuity");
if (cyan.some(v => magnitude(v) >= 2.4)) completeObjective("impulse");
const recentViolet = violet.slice(-10).filter(v => magnitude(v) > .2);
let opposite = false;
for (let i = 0; i < recentViolet.length; i++) {
for (let j = i + 1; j < recentViolet.length; j++) {
const a = recentViolet[i], b = recentViolet[j];
const dot = (a.x * b.x + a.y * b.y) / (magnitude(a) * magnitude(b));
if (dot < -.86) opposite = true;
}
}
if (opposite) completeObjective("polarity");
}
function completeObjective(id) {
if (state.completed.has(id)) return;
state.completed.add(id);
document.querySelector(`[data-objective="${id}"]`)?.classList.add("complete");
const objective = OBJECTIVES.find(item => item.id === id);
logEvent("observation_completed", { observation: id, nodes: state.nodes.size, links: state.edges.size });
showToast(`${objective.title} observed. What does that make you want to try?`);
if (state.completed.size === OBJECTIVES.length) {
setTimeout(() => showToast("All three are observed. The experiment starts now: alter a working network to answer your own question."), 1700);
logEvent("all_observations_completed", { nodes: state.nodes.size, links: state.edges.size });
}
}
function renderScopes() {
for (const def of SCOPE_DEFS) {
const node = state.nodes.get(def.id);
const card = scopesEl.querySelector(`[data-scope="${def.id}"]`);
if (!node || !card) continue;
const history = node.history;
const latest = history.at(-1) || zero();
card.querySelector("header span").textContent = magnitude(latest) < .015 ? "quiet" : `${magnitude(latest).toFixed(2)} @ ${phaseName(latest)}`;
const padded = [...Array(Math.max(0, 24 - history.length)).fill(zero()), ...history];
const points = component => padded.map((v, index) => `${index * (240 / 23)},${44 - Math.max(-4, Math.min(4, v[component])) * 10}`).join(" ");
const traceX = card.querySelector(".trace-x");
const traceY = card.querySelector(".trace-y");
traceX.setAttribute("points", points("x"));
traceY.setAttribute("points", points("y"));
traceX.style.opacity = padded.some(v => Math.abs(v.x) > .015) ? "1" : "0";
traceY.style.opacity = padded.some(v => Math.abs(v.y) > .015) ? ".55" : "0";
}
}
function startTimer() {
clearInterval(state.timer);
if (state.running) state.timer = setInterval(simulationStep, state.speed);
}
function toggleRunning() {
state.running = !state.running;
runButton.textContent = state.running ? "Pause" : "Run";
runButton.classList.toggle("primary", state.running);
logEvent(state.running ? "simulation_resumed" : "simulation_paused");
startTimer();
}
function resetBench() {
const removedNodes = [...state.nodes.values()].filter(node => !node.fixed).length;
const removedLinks = state.edges.size;
for (const node of [...state.nodes.values()]) if (!node.fixed) removeNode(node.id);
for (const edge of [...state.edges.values()]) removeEdge(edge.id, false);
state.tick = 0;
state.mailbox.clear();
state.completed.clear();
for (const node of state.nodes.values()) {
node.history = [];
node.internal = {};
node.live = zero();
node.received = zero();
updateNodeVisual(node);
}
document.querySelectorAll(".objective").forEach(el => el.classList.remove("complete"));
tickReadout.textContent = "tick 0";
renderScopes();
logEvent("bench_reset", { removed_nodes: removedNodes, removed_links: removedLinks });
showToast("Bench reset. The event log was kept.");
}
async function exportLog() {
const button = $("export-button"), filename = `resonance-bench-${state.sessionId}.jsonl`;
logEvent("log_exported", { events_before_export: state.logs.length, completed_observations: [...state.completed], destination: "repository" }); button.disabled = true; button.textContent = "Saving…";
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: state.logs.join("\n") + "\n" });
if (!response.ok) throw new Error((await response.json().catch(() => ({}))).error || `HTTP ${response.status}`);
const result = await response.json(); logEvent("log_saved", { path: result.path, events: result.events }); showToast(`Saved directly to ${result.path}.`);
} catch (error) {
logEvent("log_save_failed", { error: String(error) }); const blob = new Blob([state.logs.join("\n") + "\n"], { type: "application/x-ndjson" }), link = document.createElement("a");
link.href = URL.createObjectURL(blob); link.download = filename; link.click(); setTimeout(() => URL.revokeObjectURL(link.href), 500); showToast("Direct save failed; downloaded the JSONL instead.");
} finally { button.disabled = false; button.textContent = "Save JSONL"; }
}
let toastTimer;
function showToast(message) {
toastEl.textContent = message;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("show"), 3400);
}
function updateWorkspaceHint() {
workspace.classList.toggle("no-machines", [...state.nodes.values()].every(node => node.fixed));
}
function placeMachine(type) {
const rect = workspace.getBoundingClientRect();
const machineCount = [...state.nodes.values()].filter(node => !node.fixed).length;
const x = Math.max(210, Math.min(rect.width - 390, rect.width * .42 + (machineCount % 3) * 28));
const y = Math.max(24, Math.min(rect.height - 150, 55 + (machineCount * 93) % Math.max(110, rect.height - 180)));
const node = addNode(type, x, y);
selectNode(node.id);
}
function buildStaticUI() {
for (const [type, def] of Object.entries(MACHINE_DEFS)) {
const button = document.createElement("button");
button.title = def.description;
button.innerHTML = `<span class="glyph">${def.glyph}</span><strong>${def.name}</strong><span>${def.short}</span>`;
button.addEventListener("click", () => placeMachine(type));
palette.append(button);
}
for (const objective of OBJECTIVES) {
const el = document.createElement("div");
el.className = "objective";
el.dataset.objective = objective.id;
el.innerHTML = `<strong>${objective.title}</strong><span>${objective.description}</span>`;
objectivesEl.append(el);
}
for (const def of SCOPE_DEFS) {
const card = document.createElement("article");
card.className = "scope-card";
card.dataset.scope = def.id;
card.style.setProperty("--scope-color", def.color);
card.innerHTML = `
<header>${def.name}<span>quiet</span></header>
<svg viewBox="0 0 240 88" preserveAspectRatio="none" aria-label="x and y flux component history">
<path class="scope-grid" d="M0 4H240 M0 24H240 M0 44H240 M0 64H240 M0 84H240"></path>
<path class="scope-zero" d="M0 44H240"></path>
<polyline class="scope-trace trace-y" opacity=".38" stroke-dasharray="3 3"></polyline>
<polyline class="scope-trace trace-x"></polyline>
</svg>`;
scopesEl.append(card);
}
}
function initializeBench() {
const rect = workspace.getBoundingClientRect();
const top = 34;
const bottom = Math.max(top, rect.height - 122);
const verticalSpacing = Math.min(158, (bottom - top) / 2);
SOURCE_DEFS.forEach((source, index) => {
addNode("source", source.x, top + index * verticalSpacing, {
...source, fixed: true, source: { phase: source.phase, strength: source.strength, period: source.period }
}, false);
});
SCOPE_DEFS.forEach((scope, index) => addNode("scope", Math.max(205, rect.width - 205), top + index * verticalSpacing, { ...scope, fixed: true }, false));
renderScopes();
renderWires();
updateWorkspaceHint();
}
runButton.addEventListener("click", toggleRunning);
stepButton.addEventListener("click", () => {
if (state.running) toggleRunning();
simulationStep();
logEvent("simulation_stepped");
});
speedInput.addEventListener("input", () => {
const before = state.speed;
state.speed = Number(speedInput.value);
startTimer();
logEvent("tempo_changed", { before_ms: before, after_ms: state.speed });
});
resetButton.addEventListener("click", resetBench);
exportButton.addEventListener("click", exportLog);
workspace.addEventListener("click", event => {
if (event.target === workspace || event.target.classList.contains("grid-glow") || event.target === nodesLayer) clearSelection();
});
workspace.addEventListener("pointermove", event => {
if (state.pendingFrom) drawPending(event.clientX, event.clientY);
});
window.addEventListener("keydown", event => {
if (!["Delete", "Backspace"].includes(event.key) || event.target.matches("input, select")) return;
if (state.selectedEdge) removeEdge(state.selectedEdge);
else if (state.selectedNode) removeNode(state.selectedNode);
});
window.addEventListener("resize", renderWires);
document.addEventListener("visibilitychange", () => logEvent(document.hidden ? "page_hidden" : "page_visible"));
window.addEventListener("beforeunload", () => logEvent("session_unloaded", { completed_observations: [...state.completed] }));
buildStaticUI();
initializeBench();
logEvent("session_started", {
experiment: "000_magic_language",
viewport: { width: window.innerWidth, height: window.innerHeight },
fixed_sources: SOURCE_DEFS.map(source => ({ id: source.id, phase: source.phase, strength: source.strength, period: source.period }))
});
startTimer();
})();

View file

@ -0,0 +1,101 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Resonance Bench — Experiment 000</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="topbar">
<div>
<div class="eyebrow">EXPERIMENT 000</div>
<h1>Resonance Bench</h1>
</div>
<div class="sim-controls" aria-label="Simulation controls">
<button id="run-button" class="primary">Pause</button>
<button id="step-button">Step</button>
<label class="speed-label">Tempo
<input id="speed" type="range" min="120" max="850" step="10" value="420">
</label>
<span id="tick-readout" class="tick-readout">tick 0</span>
<button id="reset-button">Reset bench</button>
<button id="export-button">Save JSONL</button>
</div>
</header>
<div class="app-shell">
<aside class="sidebar">
<section class="intro-card">
<h2>Research prompt</h2>
<p>Make the three instruments observe their named phenomena. Then—more importantly—change something just to answer a question of your own.</p>
<p class="small">There are no unlocks or rewards. Finishing is not the point.</p>
</section>
<section>
<div class="section-heading">
<h2>Operations</h2>
<span>click to place</span>
</div>
<div id="palette" class="palette"></div>
</section>
<details class="lawbook" open>
<summary>The five flux laws</summary>
<ol>
<li>Flux is a vector: direction is <strong>phase</strong>; length is <strong>strength</strong>.</li>
<li>A node combines arrivals by vector addition. Aligned flux reinforces; opposed flux cancels.</li>
<li>Every link takes one tick, loses 8%, and divides output evenly among outgoing links.</li>
<li>A closed path returns old flux. Its timing and rotation decide whether it reinforces new flux.</li>
<li>Operations transform flux consistently. Nothing requires discovery or permission.</li>
</ol>
</details>
<section>
<div class="section-heading">
<h2>Observations</h2>
<span>not quests</span>
</div>
<div id="objectives" class="objectives"></div>
</section>
<section class="interaction-help">
<h2>Bench controls</h2>
<p>Drag node headers. Click an output port, then an input port, to weave a link. Click a link to select it; press <kbd>Delete</kbd> to remove selected links or nodes. Right-click also removes a link. Hover anything for detail.</p>
</section>
</aside>
<main class="workspace-wrap">
<div id="workspace" class="workspace" tabindex="0" aria-label="Flux engineering workspace">
<div class="grid-glow"></div>
<svg id="wire-layer" class="wire-layer" aria-hidden="true">
<defs>
<filter id="wire-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur"></feGaussianBlur>
<feMerge><feMergeNode in="blur"></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge>
</filter>
</defs>
<g id="wires"></g>
<path id="pending-wire" class="pending-wire" d="" hidden></path>
</svg>
<div id="nodes" class="nodes"></div>
<div id="empty-hint" class="empty-hint">Choose an operation from the left, or connect a well directly to observe its raw flux.</div>
</div>
<section class="scope-rack">
<div class="scope-title">
<div>
<span class="eyebrow">LIVE INSTRUMENTS</span>
<h2>Last 24 ticks · solid x / dotted y</h2>
</div>
<div id="selection-readout" class="selection-readout">Nothing selected</div>
</div>
<div id="scopes" class="scopes"></div>
</section>
</main>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,186 @@
:root {
color-scheme: dark;
--bg: #080b12;
--panel: #101622;
--panel-2: #151e2d;
--line: #28354a;
--muted: #8d9bb0;
--text: #e7edf6;
--accent: #77e4d4;
--gold: #f0bf68;
--cyan: #67d7ff;
--violet: #be92ff;
--danger: #ff6f7d;
--node-width: 164px;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); }
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
overflow: hidden;
}
button, input, select { font: inherit; }
button {
border: 1px solid var(--line);
background: #172131;
color: var(--text);
border-radius: 7px;
padding: 7px 11px;
cursor: pointer;
}
button:hover { border-color: #52667f; background: #1e2b3e; }
button.primary { color: #07100f; background: var(--accent); border-color: var(--accent); font-weight: 700; }
button.primary:hover { background: #9af1e5; }
h1, h2, p { margin-top: 0; }
h1 { margin-bottom: 0; font-size: 21px; letter-spacing: .01em; font-weight: 670; }
h2 { margin-bottom: 8px; font-size: 13px; text-transform: uppercase; letter-spacing: .09em; }
p { line-height: 1.47; }
.eyebrow { color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .16em; }
.small { color: var(--muted); font-size: 12px; }
.topbar {
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 8px 18px;
border-bottom: 1px solid var(--line);
background: #0c111a;
}
.sim-controls { display: flex; align-items: center; gap: 8px; }
.speed-label { display: flex; gap: 7px; align-items: center; color: var(--muted); font-size: 12px; }
.speed-label input { direction: rtl; width: 100px; accent-color: var(--accent); }
.tick-readout { min-width: 70px; color: var(--muted); font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }
.app-shell { height: calc(100vh - 64px); display: grid; grid-template-columns: 286px minmax(0, 1fr); }
.sidebar {
overflow-y: auto;
padding: 15px;
border-right: 1px solid var(--line);
background: #0c111a;
}
.sidebar section, .lawbook { margin-bottom: 18px; }
.intro-card { padding: 13px; border: 1px solid #34575a; border-radius: 9px; background: linear-gradient(145deg, #102026, #111723); }
.intro-card p { margin-bottom: 8px; font-size: 13px; }
.intro-card p:last-child { margin-bottom: 0; }
.section-heading { display: flex; justify-content: space-between; align-items: baseline; }
.section-heading span { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
.palette { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.palette button { min-height: 60px; text-align: left; padding: 9px; }
.palette button strong { display: block; color: #f4f7fb; font-size: 12px; }
.palette button span { display: block; margin-top: 4px; color: var(--muted); font-size: 10px; line-height: 1.25; }
.palette .glyph { float: right; color: var(--accent); font: 18px ui-monospace, monospace; }
.lawbook { border: 1px solid var(--line); border-radius: 8px; background: var(--panel); }
.lawbook summary { padding: 10px 12px; cursor: pointer; font-size: 12px; font-weight: 700; }
.lawbook ol { margin: 0; padding: 0 13px 11px 30px; color: var(--muted); font-size: 11px; line-height: 1.42; }
.lawbook li + li { margin-top: 6px; }
.lawbook strong { color: var(--text); }
.objectives { display: grid; gap: 7px; }
.objective { position: relative; padding: 9px 10px 9px 28px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.objective::before { content: "○"; position: absolute; left: 10px; top: 9px; color: var(--muted); }
.objective.complete { border-color: #3a776d; background: #10231f; }
.objective.complete::before { content: "●"; color: var(--accent); }
.objective strong { display: block; font-size: 11px; }
.objective span { display: block; margin-top: 3px; color: var(--muted); font-size: 10px; line-height: 1.3; }
.interaction-help p { color: var(--muted); font-size: 11px; }
kbd { border: 1px solid #46566d; border-bottom-width: 2px; border-radius: 4px; padding: 1px 4px; color: var(--text); background: #161e2a; }
.workspace-wrap { min-width: 0; display: grid; grid-template-rows: minmax(390px, 1fr) 190px; }
.workspace { position: relative; overflow: hidden; outline: none; background-color: #090e17; }
.workspace:focus-visible { box-shadow: inset 0 0 0 2px #385f68; }
.grid-glow { position: absolute; inset: 0; pointer-events: none; background-image: linear-gradient(rgba(108,139,171,.07) 1px, transparent 1px), linear-gradient(90deg, rgba(108,139,171,.07) 1px, transparent 1px), radial-gradient(circle at 50% 45%, rgba(40,84,94,.15), transparent 60%); background-size: 24px 24px, 24px 24px, 100% 100%; }
.wire-layer, .nodes { position: absolute; inset: 0; width: 100%; height: 100%; }
.wire-layer { overflow: visible; pointer-events: none; }
.nodes { pointer-events: none; }
.empty-hint { position: absolute; left: 50%; top: 48%; translate: -50% -50%; width: 340px; text-align: center; color: #536175; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity .2s; }
.workspace.no-machines .empty-hint { opacity: 1; }
.wire-hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; cursor: pointer; }
.wire-visible { fill: none; stroke: #344359; stroke-width: 2; pointer-events: none; transition: stroke .12s, stroke-width .12s, opacity .12s; }
.wire-flow { fill: none; stroke: var(--accent); stroke-width: 3; stroke-dasharray: 4 12; opacity: 0; pointer-events: none; animation: flow .6s linear infinite; filter: url(#wire-glow); }
.wire.active .wire-flow { opacity: var(--flow-opacity, .7); stroke: var(--flow-color, var(--accent)); stroke-width: var(--flow-width, 3); }
.wire.selected .wire-visible { stroke: #f1f5fb; stroke-width: 4; }
.pending-wire { fill: none; stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 5; pointer-events: none; }
@keyframes flow { to { stroke-dashoffset: -16; } }
.node {
position: absolute;
width: var(--node-width);
min-height: 96px;
pointer-events: auto;
border: 1px solid #344258;
border-radius: 9px;
background: linear-gradient(155deg, #182231, #111824);
box-shadow: 0 8px 24px rgba(0,0,0,.28);
user-select: none;
}
.node.selected { border-color: #ecf4ff; box-shadow: 0 0 0 1px #ecf4ff, 0 9px 28px rgba(0,0,0,.4); }
.node.fixed { background: linear-gradient(155deg, #172934, #111923); }
.node.scope-node { border-color: var(--node-accent, #53667e); background: linear-gradient(155deg, #202031, #111824); }
.node-header { height: 31px; display: flex; align-items: center; gap: 7px; padding: 0 9px; border-bottom: 1px solid #2b394c; cursor: grab; font-size: 11px; font-weight: 750; letter-spacing: .035em; }
.node-header:active { cursor: grabbing; }
.node-glyph { color: var(--accent); font: 16px ui-monospace, monospace; }
.node-kind { flex: 1; }
.node-delete { width: 19px; height: 19px; padding: 0; border: 0; background: transparent; color: #78879b; }
.node-delete:hover { background: #39212a; color: var(--danger); }
.node-body { padding: 8px 10px 9px; }
.node-description { min-height: 25px; margin-bottom: 6px; color: var(--muted); font-size: 9px; line-height: 1.3; }
.node-live { display: grid; grid-template-columns: 29px 1fr; gap: 7px; align-items: center; }
.flux-orb { --phase: 0deg; --power: 0; position: relative; width: 26px; height: 26px; border: 1px solid #3c4b60; border-radius: 50%; background: radial-gradient(circle, rgba(119,228,212, calc(var(--power) * .6)), transparent 65%); }
.flux-orb::after { content: ""; position: absolute; width: 10px; height: 2px; top: 11px; left: 12px; transform-origin: 1px 1px; rotate: var(--phase); background: var(--flux-color, #78879b); box-shadow: 0 0 5px var(--flux-color, transparent); }
.flux-orb::before { content: ""; position: absolute; left: 3px; top: 3px; width: 18px; height: 18px; border: 1px solid rgba(255,255,255,.08); border-radius: 50%; }
.flux-numbers { min-width: 0; }
.flux-numbers strong { display: block; overflow: hidden; color: #e9f4f3; font: 10px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
.flux-numbers span { display: block; margin-top: 2px; color: #78879b; font: 9px ui-monospace, monospace; }
.node-config { display: grid; grid-template-columns: auto 1fr; gap: 4px 6px; align-items: center; margin-top: 7px; border-top: 1px solid #293648; padding-top: 6px; }
.node-config label { color: var(--muted); font-size: 9px; }
.node-config select, .node-config input { min-width: 0; width: 100%; height: 22px; border: 1px solid #334258; border-radius: 4px; background: #0d141f; color: var(--text); padding: 1px 4px; font-size: 9px; }
.port { position: absolute; top: 51px; width: 15px; height: 15px; border: 2px solid #8796aa; border-radius: 50%; background: #111925; cursor: crosshair; z-index: 3; }
.port:hover, .port.pending { scale: 1.25; border-color: #fff; background: var(--accent); box-shadow: 0 0 9px var(--accent); }
.port.input { left: -8px; }
.port.output { right: -8px; }
.port::after { position: absolute; top: 16px; color: #68778b; font-size: 8px; pointer-events: none; }
.port.input::after { content: "IN"; left: 0; }
.port.output::after { content: "OUT"; right: -4px; }
.scope-rack { overflow: hidden; border-top: 1px solid var(--line); background: #0d121c; padding: 11px 16px; }
.scope-title { display: flex; align-items: end; justify-content: space-between; margin-bottom: 8px; }
.scope-title h2 { margin: 2px 0 0; font-size: 12px; }
.selection-readout { color: var(--muted); font: 10px ui-monospace, monospace; }
.scopes { height: 125px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.scope-card { position: relative; overflow: hidden; border: 1px solid var(--line); border-radius: 7px; background: #080c13; padding: 7px 8px; }
.scope-card header { display: flex; justify-content: space-between; color: var(--scope-color); font-size: 10px; font-weight: 750; }
.scope-card header span { color: var(--muted); font: 9px ui-monospace, monospace; }
.scope-card svg { display: block; width: 100%; height: 88px; margin-top: 3px; }
.scope-grid { stroke: #1e2a3b; stroke-width: 1; }
.scope-zero { stroke: #334157; stroke-width: 1; }
.scope-trace { fill: none; stroke: var(--scope-color); stroke-width: 2; vector-effect: non-scaling-stroke; filter: drop-shadow(0 0 3px var(--scope-color)); }
.toast { position: fixed; z-index: 20; left: 50%; bottom: 20px; translate: -50% 12px; max-width: 440px; padding: 10px 15px; border: 1px solid #4e6b68; border-radius: 8px; background: #12231f; box-shadow: 0 10px 30px rgba(0,0,0,.45); opacity: 0; pointer-events: none; transition: opacity .2s, translate .2s; font-size: 12px; }
.toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 900px) {
body { overflow: auto; }
.topbar { height: auto; align-items: flex-start; flex-direction: column; }
.sim-controls { flex-wrap: wrap; }
.app-shell { height: auto; grid-template-columns: 1fr; }
.sidebar { border-right: 0; border-bottom: 1px solid var(--line); }
.workspace-wrap { height: 760px; }
.scopes { grid-template-columns: 1fr; overflow-y: auto; }
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" --directory "$EXPERIMENT_DIR/prototype" --log-directory "$REPO_ROOT/JSONL" --port 8000

View file

@ -0,0 +1,72 @@
# Experiment 001 — Flux Familiar
## Question
Does mapping constructed signals onto visible physical motion make phase and timing perceptible, consequential, and interesting enough to invite refinement or further experiments?
## Hypotheses
The leading hypothesis is that Experiment 000 failed partly because its abstract, tolerant predicates did not make its laws matter. The competing hypothesis is that the signal substrate remains too limited even when embodied. See [`hypothesis.md`](hypothesis.md).
## Difference From Previous Experiment
- Flux now applies physical impulses to a visible familiar instead of satisfying abstract lenses.
- Vector phase is literal movement direction and cyclic timing creates a visible trajectory.
- The output space is continuous: routes, impacts, approach angles, speed, and docking stability vary.
- A constellation asks the familiar to wake two stars and settle in a dock; changing constellations alters world geometry while retaining the network.
- The core source, link, phase, splitting, rotation, delay, accumulation, and threshold laws are retained.
## Expected Result
Support for embodiment is continued network revision driven by observed motion: anticipating a turn, correcting an overshoot, deliberately creating a repeated route, or trying a behavior beyond completion. If the player understands the motion but still sees only a shallow routing puzzle, confidence in the underlying signal substrate should fall further.
## Controls
- No time pressure, unlocks, part randomness, enemies, or resource economy.
- Deterministic source cycles and deterministic physics.
- All operations are available immediately.
- Resetting the familiar or changing constellation retains the constructed network.
- Every constellation is selectable from the beginning; none is a progression unlock.
## How To Run
From this directory:
```bash
./run.sh
```
Then open <http://localhost:8000>.
## What To Pay Attention To
- Did watching motion make you predict or care about the next signal?
- Did you revise a working route to change how it behaved?
- After docking, did a new constellation or an idea of your own sound appealing?
Use **Export JSONL** when finished.
## Result
Playtested on 2026-08-16. The player wanted to stop almost immediately.
- Construction felt like awkward steering rather than programming or engineering.
- Inputs and operations had no information about the field, so wiring amounted to brute-force directional movement.
- The route was too simple to offer meaningful refinement.
- The player predicted that the obvious extension—adding field-aware sensors—would turn the task into manually programming an AI and remain tedious. This is a report about a hypothetical variant, not observed playtest behavior.
- Completion supplied no incentive to improve an already adequate solution.
- No voluntary refinement, constellation change, or unrelated experiment occurred.
## Interpretation
Experiment 001 failed and narrows the model substantially.
The failure was not demonstrably only insufficient sensing or output legibility. Directing this autonomous agent toward this static goal was unpromising. More sophisticated feedback control is currently a low-information next step because it would add implementation work without first establishing a reason to value elegance, efficiency, or alternate behavior.
Together, Experiments 000 and 001 suggest that “construct a shallow system, satisfy an arbitrary finite specification, optionally optimize it” is currently the wrong tested frame. The next experiment should not merely add sensors, more nodes, longer routes, or stricter scoring, because that would not distinguish missing depth, missing stakes, missing leverage, and indirect-control friction.
This does not reject systemic magic, automation, or indirect design generally. It is negative evidence about two very small puzzle-like implementations. The next useful exploration probe changes the relationship between player and system: direct action, immediate consequences, and an ongoing situation where understanding changes what the player can accomplish.
## Next Best Experiment
Test direct systemic manipulation in a small action sandbox, with closely related calm and pressured variants. Reuse broad engineering principles—transfer, storage, conversion, momentum, temperature—but remove graph programming and autonomous-agent control. Determine whether external stakes make experimentation instrumentally meaningful or merely conceal another shallow system.

View file

@ -0,0 +1,28 @@
# Experiment 001 Hypothesis
## Primary hypothesis
The same deterministic signal distinctions that were irrelevant in Experiment 000 will become understandable and generative when they directly determine a visible object's trajectory.
## Competing explanations
1. Embodied consequence makes timing, direction, accumulation, and cycles worth reasoning about.
2. Motion improves legibility but produces only a conventional finite routing puzzle.
3. The signal language remains too limited to sustain curiosity regardless of presentation.
4. Direct physical manipulation would be enjoyable, but graph construction adds too much indirection or work.
5. A cared-about world is still missing; a puck-like familiar is only an animated oscilloscope.
## Strong evidence
- The player changes a route after it already completes, because another trajectory seems worth creating.
- Visible motion causes a prediction, surprise, revised mental model, and another test.
- The player deliberately exploits periodicity, phase, storage, or recirculation.
- The player chooses another constellation without being prompted by progression rewards.
## Weak or negative evidence
- Completion is fast and followed by the same sense that nothing remains.
- The familiar is treated as a cursor and the network as awkward controls.
- Random or noisy output eventually succeeds without understanding.
- A single universal controller handles every constellation with trivial changes.

View file

@ -0,0 +1,407 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const networkEl = $("#network");
const nodesEl = $("#nodes");
const wiresEl = $("#wires");
const pendingEl = $("#pending");
const canvas = $("#arena");
const ctx = canvas.getContext("2d");
const OPERATIONS = {
junction: { name: "Confluence", glyph: "+", short: "add arrivals", description: "Adds simultaneous arrivals as vectors.", config: {} },
rotor: { name: "Turner", glyph: "↻", short: "rotate phase", description: "Rotates direction without changing strength.", config: { turns: { label: "turn", value: 1, options: [[-1, "90°"], [1, "+90°"], [2, "180°"]] } } },
delay: { name: "Echo", glyph: "⋯", short: "delay pulses", description: "Returns each arrival after a fixed delay.", config: { delay: { label: "ticks", value: 2, options: [[1, "1"], [2, "2"], [3, "3"], [4, "4"], [6, "6"], [8, "8"]] } } },
vessel: { name: "Vessel", glyph: "◇", short: "store & burst", description: "Stores a leaky vector, then releases it all.", config: { threshold: { label: "release", value: 2.5, options: [[1.5, "1.5"], [2.5, "2.5"], [4, "4.0"], [6, "6.0"]] } } },
valve: { name: "Threshold", glyph: "⊣", short: "reject weak flux", description: "Passes only arrivals above a minimum strength.", config: { threshold: { label: "minimum", value: .75, options: [[.3, "0.30"], [.75, "0.75"], [1, "1.0"], [1.5, "1.5"], [2.5, "2.5"]] } } },
polarizer: { name: "Polarizer", glyph: "∥", short: "keep one axis", description: "Keeps only the signed component on one axis.", config: { axis: { label: "axis", value: 0, options: [[0, "east / west"], [90, "north / south"], [45, "diagonal /"], [135, "other diagonal"]] } } }
};
const SOURCES = [
{ id: "dawn", name: "Dawn well", glyph: "☼", phase: 0, strength: 1, period: 3, description: "East impulse every 3 ticks." },
{ id: "tide", name: "Tide well", glyph: "≈", phase: 90, strength: 1.1, period: 5, description: "North impulse every 5 ticks." },
{ id: "dusk", name: "Dusk well", glyph: "◒", phase: 180, strength: .9, period: 7, description: "West impulse every 7 ticks." }
];
const CONSTELLATIONS = [
{
name: "perimeter",
start: [.16, .79], starA: [.76, .79], starB: [.76, .22], dock: [.22, .22],
obstacles: [{ x: .42, y: .40, w: .16, h: .22 }]
},
{
name: "crosswind",
start: [.18, .50], starA: [.54, .18], starB: [.82, .67], dock: [.25, .82],
obstacles: [{ x: .38, y: .35, w: .09, h: .43 }, { x: .63, y: .18, w: .08, h: .37 }]
},
{
name: "split channel",
start: [.12, .82], starA: [.83, .18], starB: [.18, .31], dock: [.79, .78],
obstacles: [{ x: .32, y: .20, w: .1, h: .60 }, { x: .60, y: .08, w: .1, h: .62 }]
}
];
const state = {
nodes: new Map(), edges: new Map(), mailbox: new Map(),
tick: 0, running: true, speed: Number($("#tempo").value), timer: null,
nextNode: 1, nextEdge: 1, pendingFrom: null, selectedNode: null, selectedEdge: null,
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
started: Date.now(), logs: [], constellation: 0,
world: null, completedTick: null
};
const zero = () => ({ x: 0, y: 0 });
const magnitude = v => Math.hypot(v.x, v.y);
const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
const scale = (v, n) => ({ x: v.x * n, y: v.y * n });
const sum = values => values.reduce(add, zero());
const phase = v => magnitude(v) < .0001 ? 0 : (Math.atan2(v.y, v.x) * 180 / Math.PI + 360) % 360;
const polar = (power, angle) => ({ x: power * Math.cos(angle * Math.PI / 180), y: power * Math.sin(angle * Math.PI / 180) });
const rotate = (v, angle) => {
const r = angle * Math.PI / 180;
return { x: v.x * Math.cos(r) - v.y * Math.sin(r), y: v.x * Math.sin(r) + v.y * Math.cos(r) };
};
const round = n => Math.round(n * 1000) / 1000;
const vecLog = v => ({ x: round(v.x), y: round(v.y), strength: round(magnitude(v)), phase: round(phase(v)) });
const fluxColor = v => magnitude(v) < .01 ? "#718096" : `hsl(${Math.round((phase(v) + 165) % 360)} 84% 70%)`;
const phaseName = v => {
if (magnitude(v) < .01) return "quiet";
return ["east", "north-east", "north", "north-west", "west", "south-west", "south", "south-east"][Math.round(phase(v) / 45) % 8];
};
function log(type, data = {}) {
const row = { schema: 1, experiment: "001_flux_familiar", session_id: state.session, elapsed_ms: Date.now() - state.started, tick: state.tick, type, ...data };
state.logs.push(JSON.stringify(row));
try { localStorage.setItem("flux-familiar-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function defaultConfig(type) {
return Object.fromEntries(Object.entries(OPERATIONS[type]?.config || {}).map(([key, spec]) => [key, spec.value]));
}
function addNode(type, x, y, extra = {}, shouldLog = true) {
const def = OPERATIONS[type] || extra;
const node = {
id: extra.id || `machine-${state.nextNode++}`, type, x, y,
name: extra.name || def.name, glyph: extra.glyph || def.glyph,
description: extra.description || def.description, fixed: Boolean(extra.fixed),
source: extra.source || null, config: extra.config || defaultConfig(type),
internal: {}, live: zero(), received: zero(), el: null
};
state.nodes.set(node.id, node);
renderNode(node);
if (shouldLog) log("node_added", { node_id: node.id, operation: type, x: round(x), y: round(y), config: node.config, after_completion: state.completedTick !== null });
updateHint();
return node;
}
function renderNode(node) {
const el = document.createElement("article");
el.className = `node ${node.fixed ? "fixed" : ""} ${node.type === "bond" ? "bond" : ""}`;
el.dataset.nodeId = node.id;
el.style.left = `${node.x}px`; el.style.top = `${node.y}px`;
const hasInput = node.type !== "source";
const hasOutput = node.type !== "bond";
const config = Object.entries(OPERATIONS[node.type]?.config || {}).map(([key, spec]) => {
const options = spec.options.map(([value, label]) => `<option value="${value}" ${String(value) === String(node.config[key]) ? "selected" : ""}>${label}</option>`).join("");
return `<label>${spec.label}</label><select data-config="${key}">${options}</select>`;
}).join("");
el.innerHTML = `
<header><i>${node.glyph}</i><span>${node.name}</span>${node.fixed ? "" : '<button class="delete" aria-label="Remove">×</button>'}</header>
${hasInput ? '<button class="port input" aria-label="Flux input"></button>' : ""}
${hasOutput ? '<button class="port output" aria-label="Flux output"></button>' : ""}
<div class="body"><div class="description">${node.description}</div>
<div class="live"><div class="orb"></div><div class="numbers"><b>quiet</b><span>x 0.00 · y 0.00</span></div></div>
${config ? `<div class="config">${config}</div>` : ""}
</div>`;
nodesEl.append(el); node.el = el;
el.addEventListener("pointerdown", event => { if (!event.target.closest("button,select")) selectNode(node.id); });
el.querySelector(".delete")?.addEventListener("click", () => removeNode(node.id));
el.querySelector(".port.output")?.addEventListener("click", event => beginWire(event, node.id));
el.querySelector(".port.input")?.addEventListener("click", event => finishWire(event, node.id));
el.querySelectorAll("select").forEach(select => select.addEventListener("change", event => {
const key = event.target.dataset.config, before = node.config[key];
node.config[key] = Number(event.target.value); node.internal = {};
log("node_configured", { node_id: node.id, parameter: key, before, after: node.config[key], after_completion: state.completedTick !== null });
}));
installDrag(node, el.querySelector("header"));
}
function installDrag(node, handle) {
handle.addEventListener("pointerdown", event => {
if (event.button !== 0 || event.target.closest("button")) return;
event.preventDefault(); handle.setPointerCapture(event.pointerId); selectNode(node.id);
const start = { mx: event.clientX, my: event.clientY, x: node.x, y: node.y }; let moved = false;
const move = e => {
const rect = networkEl.getBoundingClientRect();
node.x = Math.max(0, Math.min(rect.width - node.el.offsetWidth, start.x + e.clientX - start.mx));
node.y = Math.max(0, Math.min(rect.height - node.el.offsetHeight, start.y + e.clientY - start.my));
moved ||= Math.abs(e.clientX - start.mx) + Math.abs(e.clientY - start.my) > 3;
node.el.style.left = `${node.x}px`; node.el.style.top = `${node.y}px`; renderWires();
};
const up = () => { handle.removeEventListener("pointermove", move); handle.removeEventListener("pointerup", up); if (moved) log("node_moved", { node_id: node.id, x: round(node.x), y: round(node.y) }); };
handle.addEventListener("pointermove", move); handle.addEventListener("pointerup", up);
});
}
function removeNode(id) {
const node = state.nodes.get(id); if (!node || node.fixed) return;
const connected = [...state.edges.values()].filter(edge => edge.from === id || edge.to === id);
connected.forEach(edge => removeEdge(edge.id, false));
node.el.remove(); state.nodes.delete(id); clearSelection(); updateHint();
log("node_removed", { node_id: id, operation: node.type, removed_links: connected.length, after_completion: state.completedTick !== null });
}
function beginWire(event, id) {
event.stopPropagation(); cancelWire(); state.pendingFrom = id;
state.nodes.get(id).el.querySelector(".output").classList.add("pending"); pendingEl.hidden = false;
const cancel = e => { if (!e.target.closest?.(".port.input")) cancelWire(); };
state.cancelWireListener = cancel; window.addEventListener("click", cancel, { capture: true, once: true });
}
function finishWire(event, to) {
if (!state.pendingFrom) return;
event.stopPropagation(); const from = state.pendingFrom; cancelWire();
if (state.edges.values().some(edge => edge.from === from && edge.to === to)) return toast("That link already exists.");
addEdge(from, to);
}
function cancelWire() {
state.nodes.get(state.pendingFrom)?.el.querySelector(".output")?.classList.remove("pending");
state.pendingFrom = null; pendingEl.hidden = true; pendingEl.setAttribute("d", "");
}
function portPoint(node, output) { return { x: node.x + (output ? node.el.offsetWidth : 0), y: node.y + 52 }; }
function curve(a, b) {
const bend = Math.max(42, Math.min(170, Math.abs(b.x - a.x) * .48)); const sign = b.x >= a.x ? 1 : -1;
return `M ${a.x} ${a.y} C ${a.x + bend * sign} ${a.y}, ${b.x - bend * sign} ${b.y}, ${b.x} ${b.y}`;
}
function addEdge(from, to, shouldLog = true) {
const edge = { id: `link-${state.nextEdge++}`, from, to, signal: zero(), el: null };
const closesCycle = hasPath(to, from); state.edges.set(edge.id, edge); renderWires();
if (shouldLog) log("link_added", { link_id: edge.id, from, to, closes_cycle: closesCycle, after_completion: state.completedTick !== null });
}
function hasPath(start, target) {
const seen = new Set(), stack = [start];
while (stack.length) { const here = stack.pop(); if (here === target) return true; if (seen.has(here)) continue; seen.add(here); for (const e of state.edges.values()) if (e.from === here) stack.push(e.to); }
return false;
}
function removeEdge(id, shouldLog = true) {
const edge = state.edges.get(id); if (!edge) return;
edge.el?.remove(); state.edges.delete(id); clearSelection();
if (shouldLog) log("link_removed", { link_id: id, from: edge.from, to: edge.to, after_completion: state.completedTick !== null });
}
function renderWires() {
for (const edge of state.edges.values()) {
if (!edge.el) {
const ns = "http://www.w3.org/2000/svg", group = document.createElementNS(ns, "g"); group.classList.add("wire"); group.dataset.edgeId = edge.id;
for (const cls of ["wire-hit", "wire-base", "wire-flow"]) { const path = document.createElementNS(ns, "path"); path.classList.add(cls); group.append(path); }
group.querySelector(".wire-hit").addEventListener("click", e => { e.stopPropagation(); selectEdge(edge.id); });
group.querySelector(".wire-hit").addEventListener("contextmenu", e => { e.preventDefault(); removeEdge(edge.id); });
wiresEl.append(group); edge.el = group;
}
const from = state.nodes.get(edge.from), to = state.nodes.get(edge.to); if (!from || !to) continue;
const d = curve(portPoint(from, true), portPoint(to, false)); edge.el.querySelectorAll("path").forEach(p => p.setAttribute("d", d)); updateEdge(edge);
}
}
function updateEdge(edge) {
if (!edge.el) return; const power = magnitude(edge.signal);
edge.el.classList.toggle("active", power > .01); edge.el.classList.toggle("selected", state.selectedEdge === edge.id);
edge.el.style.setProperty("--color", fluxColor(edge.signal)); edge.el.style.setProperty("--opacity", String(Math.min(.95, .35 + power * .25))); edge.el.style.setProperty("--width", `${Math.min(7, 1.5 + power)}px`);
}
function selectNode(id) { clearSelection(); state.selectedNode = id; state.nodes.get(id)?.el.classList.add("selected"); $("#selection").textContent = `${state.nodes.get(id)?.name} · ${id}`; }
function selectEdge(id) { clearSelection(); state.selectedEdge = id; state.edges.get(id)?.el.classList.add("selected"); const e = state.edges.get(id); $("#selection").textContent = `${state.nodes.get(e.from).name}${state.nodes.get(e.to).name}`; }
function clearSelection() { state.nodes.get(state.selectedNode)?.el.classList.remove("selected"); state.edges.get(state.selectedEdge)?.el.classList.remove("selected"); state.selectedNode = null; state.selectedEdge = null; $("#selection").textContent = "nothing selected"; }
function operationOutput(node, input) {
switch (node.type) {
case "junction": return input;
case "rotor": return rotate(input, node.config.turns * 90);
case "delay": node.internal.queue ||= []; node.internal.queue.push({ ...input }); return node.internal.queue.length > node.config.delay ? node.internal.queue.shift() : zero();
case "vessel": {
node.internal.stored = scale(add(node.internal.stored || zero(), input), .99);
if (magnitude(node.internal.stored) >= node.config.threshold) { const out = node.internal.stored; node.internal.stored = zero(); log("vessel_released", { node_id: node.id, flux: vecLog(out) }); return out; }
return zero();
}
case "valve": return magnitude(input) >= node.config.threshold ? input : zero();
case "polarizer": { const axis = polar(1, node.config.axis); return scale(axis, input.x * axis.x + input.y * axis.y); }
default: return zero();
}
}
function step() {
state.tick++;
const next = new Map(), outputs = new Map();
for (const node of state.nodes.values()) {
const input = sum(state.mailbox.get(node.id) || []); node.received = input; let output = zero();
if (node.type === "source") { if (state.tick % node.source.period === 0) output = polar(node.source.strength, node.source.phase); }
else if (node.type === "bond") output = zero();
else output = operationOutput(node, input);
node.live = node.type === "bond" ? input : output; outputs.set(node.id, output); updateNode(node);
}
for (const node of state.nodes.values()) {
const outgoing = [...state.edges.values()].filter(edge => edge.from === node.id), share = outgoing.length ? scale(outputs.get(node.id) || zero(), .92 / outgoing.length) : zero();
for (const edge of outgoing) { edge.signal = share; if (!next.has(edge.to)) next.set(edge.to, []); next.get(edge.to).push({ ...share }); updateEdge(edge); }
}
state.mailbox = next;
const control = state.nodes.get("bond").received;
worldStep(control); drawArena(); $("#tick").textContent = `tick ${state.tick}`;
if (state.tick % 20 === 0) log("trajectory_snapshot", { position: { x: round(state.world.x), y: round(state.world.y) }, velocity: { x: round(state.world.vx), y: round(state.world.vy) }, incoming: vecLog(control), stars: [state.world.starA, state.world.starB], dock_stability: state.world.dockTicks, nodes: state.nodes.size, links: state.edges.size });
}
function updateNode(node) {
const v = node.live, power = magnitude(v), orb = node.el.querySelector(".orb");
orb.style.setProperty("--angle", `${-phase(v)}deg`); orb.style.setProperty("--flux", fluxColor(v));
node.el.querySelector(".numbers b").textContent = power < .01 ? "quiet" : `${power.toFixed(2)} · ${phaseName(v)}`;
node.el.querySelector(".numbers span").textContent = `x ${v.x.toFixed(2)} · y ${v.y.toFixed(2)}`;
}
function resetWorld(reason = "manual") {
const c = CONSTELLATIONS[state.constellation], previous = state.world;
state.world = { x: c.start[0], y: c.start[1], vx: 0, vy: 0, trail: [], starA: false, starB: false, dockTicks: 0, impacts: 0 };
state.completedTick = null; updateWorldStatus(); drawArena();
log("familiar_reset", { reason, previous: previous ? { x: round(previous.x), y: round(previous.y), stars: [previous.starA, previous.starB], dock_ticks: previous.dockTicks } : null, constellation: state.constellation });
}
function worldStep(control) {
const w = state.world, c = CONSTELLATIONS[state.constellation];
w.vx += control.x * .014; w.vy -= control.y * .014;
const speed = Math.hypot(w.vx, w.vy), cap = .045;
if (speed > cap) { w.vx *= cap / speed; w.vy *= cap / speed; }
w.vx *= .88; w.vy *= .88;
const old = { x: w.x, y: w.y }; w.x += w.vx; w.y += w.vy;
let impact = false;
if (w.x < .035 || w.x > .965) { w.x = Math.max(.035, Math.min(.965, w.x)); w.vx *= -.52; impact = true; }
if (w.y < .045 || w.y > .955) { w.y = Math.max(.045, Math.min(.955, w.y)); w.vy *= -.52; impact = true; }
for (const obstacle of c.obstacles) {
const pad = .022;
if (w.x > obstacle.x - pad && w.x < obstacle.x + obstacle.w + pad && w.y > obstacle.y - pad && w.y < obstacle.y + obstacle.h + pad) {
const enteredX = old.x <= obstacle.x - pad || old.x >= obstacle.x + obstacle.w + pad;
if (enteredX) { w.x = old.x; w.vx *= -.5; } else { w.y = old.y; w.vy *= -.5; }
impact = true;
}
}
if (impact) { w.impacts++; log("familiar_impact", { x: round(w.x), y: round(w.y), speed: round(Math.hypot(w.vx, w.vy)), impacts: w.impacts }); }
w.trail.push({ x: w.x, y: w.y, color: fluxColor(control), active: magnitude(control) > .01 }); if (w.trail.length > 520) w.trail.shift();
if (!w.starA && distance(w, c.starA) < .052) { w.starA = true; log("star_woken", { star: "amber", position: { x: round(w.x), y: round(w.y) } }); toast("The amber star remembers your path."); }
if (!w.starB && distance(w, c.starB) < .052) { w.starB = true; log("star_woken", { star: "violet", position: { x: round(w.x), y: round(w.y) } }); toast("The violet star is awake."); }
const inDock = distance(w, c.dock) < .065, slow = Math.hypot(w.vx, w.vy) < .0045;
if (w.starA && w.starB && inDock && slow) w.dockTicks++; else if (!inDock || !slow) w.dockTicks = 0;
if (w.dockTicks === 14 && state.completedTick === null) {
state.completedTick = state.tick; log("constellation_completed", { constellation: state.constellation, ticks: state.tick, impacts: w.impacts, nodes: state.nodes.size, links: state.edges.size });
toast("The familiar is settled. Refine this route, try another constellation, or test an idea of your own.");
}
updateWorldStatus(control);
}
const distance = (w, point) => Math.hypot(w.x - point[0], w.y - point[1]);
function updateWorldStatus(control = zero()) {
const w = state.world;
$("#star-a-status").classList.toggle("done", w.starA); $("#star-a-status").textContent = `${w.starA ? "●" : "○"} ${w.starA ? "Amber star awake" : "Wake the amber star"}`;
$("#star-b-status").classList.toggle("done", w.starB); $("#star-b-status").textContent = `${w.starB ? "●" : "○"} ${w.starB ? "Violet star awake" : "Wake the violet star"}`;
const docked = state.completedTick !== null; $("#dock-status").classList.toggle("done", docked); $("#dock-status").textContent = `${docked ? "● Familiar settled" : "○ Settle in the cradle"}`;
$("#motion-readout").textContent = `position ${w.x.toFixed(2)}, ${w.y.toFixed(2)} · speed ${Math.hypot(w.vx, w.vy).toFixed(3)} · impacts ${w.impacts}`;
$("#flux-readout").textContent = magnitude(control) < .01 ? "incoming: quiet" : `incoming: ${magnitude(control).toFixed(2)} ${phaseName(control)}`;
$("#dock-readout").textContent = `cradle stability ${w.dockTicks} / 14`;
}
function resizeCanvas() {
const rect = canvas.getBoundingClientRect(), ratio = Math.min(2, devicePixelRatio || 1);
canvas.width = Math.max(1, Math.round(rect.width * ratio)); canvas.height = Math.max(1, Math.round(rect.height * ratio)); ctx.setTransform(ratio, 0, 0, ratio, 0, 0); drawArena();
}
function drawArena() {
const rect = canvas.getBoundingClientRect(), W = rect.width, H = rect.height; if (!state.world || !W || !H) return;
const w = state.world, c = CONSTELLATIONS[state.constellation]; ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#080c13"; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = "rgba(103,140,171,.1)"; ctx.lineWidth = 1;
for (let x = 20; x < W; x += 28) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
for (let y = 20; y < H; y += 28) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
for (const obstacle of c.obstacles) {
const x = obstacle.x * W, y = obstacle.y * H, width = obstacle.w * W, height = obstacle.h * H;
ctx.fillStyle = "#182333"; ctx.strokeStyle = "#40516a"; ctx.lineWidth = 2; ctx.fillRect(x, y, width, height); ctx.strokeRect(x, y, width, height);
ctx.strokeStyle = "rgba(112,234,214,.12)"; for (let d = -height; d < width; d += 12) { ctx.beginPath(); ctx.moveTo(x + Math.max(0, d), y + Math.max(0, -d)); ctx.lineTo(x + Math.min(width, d + height), y + Math.min(height, height + d)); ctx.stroke(); }
}
drawDock(c.dock, W, H, w.dockTicks);
drawStar(c.starA, W, H, "#f5bf64", w.starA, "A"); drawStar(c.starB, W, H, "#c39aff", w.starB, "B");
if (w.trail.length > 1) {
ctx.lineWidth = 2; ctx.lineCap = "round"; ctx.lineJoin = "round";
for (let i = 1; i < w.trail.length; i++) { const a = w.trail[i - 1], b = w.trail[i]; ctx.globalAlpha = .12 + .55 * i / w.trail.length; ctx.strokeStyle = b.active ? b.color : "#5d7188"; ctx.beginPath(); ctx.moveTo(a.x * W, a.y * H); ctx.lineTo(b.x * W, b.y * H); ctx.stroke(); }
ctx.globalAlpha = 1;
}
const x = w.x * W, y = w.y * H, speed = Math.hypot(w.vx, w.vy);
const glow = ctx.createRadialGradient(x, y, 2, x, y, 23); glow.addColorStop(0, "rgba(112,234,214,.8)"); glow.addColorStop(1, "rgba(112,234,214,0)"); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(x, y, 23, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = "#e9fff9"; ctx.strokeStyle = "#70ead6"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x, y - 10); ctx.lineTo(x + 9, y + 7); ctx.lineTo(x - 9, y + 7); ctx.closePath(); ctx.fill(); ctx.stroke();
if (speed > .0005) { ctx.strokeStyle = "rgba(112,234,214,.8)"; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + w.vx * W * 6, y + w.vy * H * 6); ctx.stroke(); }
}
function drawStar(point, W, H, color, awake, label) {
const x = point[0] * W, y = point[1] * H; ctx.save(); ctx.translate(x, y); ctx.strokeStyle = color; ctx.fillStyle = awake ? color : "#111823"; ctx.shadowColor = color; ctx.shadowBlur = awake ? 18 : 4; ctx.lineWidth = 2; ctx.beginPath();
for (let i = 0; i < 10; i++) { const r = i % 2 ? 7 : 14, a = -Math.PI / 2 + i * Math.PI / 5; const px = Math.cos(a) * r, py = Math.sin(a) * r; i ? ctx.lineTo(px, py) : ctx.moveTo(px, py); } ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.shadowBlur = 0; ctx.fillStyle = awake ? "#07100f" : color; ctx.font = "bold 8px monospace"; ctx.textAlign = "center"; ctx.fillText(label, 0, 3); ctx.restore();
}
function drawDock(point, W, H, ticks) {
const x = point[0] * W, y = point[1] * H; ctx.strokeStyle = ticks ? "#70ead6" : "#60748c"; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.arc(x, y, Math.min(W, H) * .065, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = `rgba(112,234,214,${.03 + ticks / 70})`; ctx.fill(); ctx.fillStyle = "#8fa0b6"; ctx.font = "9px monospace"; ctx.textAlign = "center"; ctx.fillText("CRADLE", x, y + 3);
}
function place(type) {
const rect = networkEl.getBoundingClientRect(), count = [...state.nodes.values()].filter(n => !n.fixed).length;
const x = Math.max(190, Math.min(rect.width - 330, rect.width * .4 + (count % 2) * 25)); const y = 30 + (count * 100) % Math.max(120, rect.height - 140);
selectNode(addNode(type, x, y).id);
}
function initializeUI() {
for (const [type, def] of Object.entries(OPERATIONS)) { const button = document.createElement("button"); button.innerHTML = `<em>${def.glyph}</em><strong>${def.name}</strong><span>${def.short}</span>`; button.title = def.description; button.addEventListener("click", () => place(type)); $("#palette").append(button); }
const rect = networkEl.getBoundingClientRect(), top = 32, bottom = Math.max(top, rect.height - 112), gap = Math.min(137, (bottom - top) / 2);
SOURCES.forEach((source, i) => addNode("source", 24, top + gap * i, { ...source, fixed: true, source: { phase: source.phase, strength: source.strength, period: source.period } }, false));
addNode("bond", Math.max(210, rect.width - 178), top + gap, { id: "bond", name: "Familiar bond", glyph: "△", description: "Every arrival becomes an impulse in the physical field.", fixed: true }, false);
renderWires(); resetWorld("session_start"); resizeCanvas();
}
function updateHint() { networkEl.classList.toggle("has-machine", [...state.nodes.values()].some(n => !n.fixed)); }
function clearNetwork() {
const nodeCount = [...state.nodes.values()].filter(n => !n.fixed).length, linkCount = state.edges.size;
for (const n of [...state.nodes.values()]) if (!n.fixed) removeNode(n.id);
for (const e of [...state.edges.values()]) removeEdge(e.id, false);
state.mailbox.clear(); log("network_cleared", { nodes: nodeCount, links: linkCount }); toast("Network cleared. The familiar and event log were kept.");
}
function setRunning(value) { state.running = value; $("#run").textContent = value ? "Pause" : "Run"; $("#run").classList.toggle("primary", value); clearInterval(state.timer); if (value) state.timer = setInterval(step, state.speed); }
function toast(message) { const el = $("#toast"); el.textContent = message; el.classList.add("show"); clearTimeout(state.toastTimer); state.toastTimer = setTimeout(() => el.classList.remove("show"), 3600); }
async function exportLog() {
const button = $("#export"), filename = `flux-familiar-${state.session}.jsonl`;
log("log_exported", { events_before_export: state.logs.length, completed: state.completedTick !== null, destination: "repository" }); button.disabled = true; button.textContent = "Saving…";
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: state.logs.join("\n") + "\n" });
if (!response.ok) throw new Error((await response.json().catch(() => ({}))).error || `HTTP ${response.status}`);
const result = await response.json(); log("log_saved", { path: result.path, events: result.events }); toast(`Saved directly to ${result.path}.`);
} catch (error) {
log("log_save_failed", { error: String(error) }); const blob = new Blob([state.logs.join("\n") + "\n"], { type: "application/x-ndjson" }), anchor = document.createElement("a");
anchor.href = URL.createObjectURL(blob); anchor.download = filename; anchor.click(); setTimeout(() => URL.revokeObjectURL(anchor.href), 500); toast("Direct save failed; downloaded the JSONL instead.");
} finally { button.disabled = false; button.textContent = "Save JSONL"; }
}
$("#run").addEventListener("click", () => { setRunning(!state.running); log(state.running ? "simulation_resumed" : "simulation_paused"); });
$("#step").addEventListener("click", () => { if (state.running) setRunning(false); step(); log("simulation_stepped"); });
$("#tempo").addEventListener("input", e => { const before = state.speed; state.speed = Number(e.target.value); setRunning(state.running); log("tempo_changed", { before_ms: before, after_ms: state.speed }); });
$("#reset-familiar").addEventListener("click", () => resetWorld("manual"));
$("#clear-network").addEventListener("click", clearNetwork);
$("#export").addEventListener("click", exportLog);
$("#new-constellation").addEventListener("click", () => { state.constellation = (state.constellation + 1) % CONSTELLATIONS.length; $("#constellation-name").textContent = CONSTELLATIONS[state.constellation].name; resetWorld("new_constellation"); log("constellation_changed", { constellation: state.constellation }); });
networkEl.addEventListener("pointermove", e => { if (!state.pendingFrom) return; const rect = networkEl.getBoundingClientRect(); pendingEl.setAttribute("d", curve(portPoint(state.nodes.get(state.pendingFrom), true), { x: e.clientX - rect.left, y: e.clientY - rect.top })); });
networkEl.addEventListener("click", e => { if (e.target === networkEl || e.target.classList.contains("grid")) clearSelection(); });
window.addEventListener("keydown", e => { if (!["Delete", "Backspace"].includes(e.key) || e.target.matches("select,input")) return; if (state.selectedEdge) removeEdge(state.selectedEdge); else if (state.selectedNode) removeNode(state.selectedNode); });
new ResizeObserver(() => { renderWires(); resizeCanvas(); }).observe(canvas);
document.addEventListener("visibilitychange", () => log(document.hidden ? "page_hidden" : "page_visible"));
log("session_started", { viewport: { width: innerWidth, height: innerHeight }, constellation: 0 });
initializeUI(); $("#constellation-name").textContent = CONSTELLATIONS[0].name; setRunning(true);
})();

View file

@ -0,0 +1,95 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flux Familiar — Experiment 001</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="topbar">
<div class="title"><span>EXPERIMENT 001</span><h1>Flux Familiar</h1></div>
<div class="controls">
<button id="run" class="primary">Pause</button>
<button id="step">Step</button>
<label>Tempo <input id="tempo" type="range" min="100" max="650" value="260" step="10"></label>
<span id="tick">tick 0</span>
<button id="reset-familiar">Reset familiar</button>
<button id="clear-network">Clear network</button>
<button id="export">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="prompt">
<h2>Teach it a route</h2>
<p>Wake both stars, then settle inside the cradle nearly motionless.</p>
<p>The bond turns every arriving flux vector into a physical impulse. The trail is its memory.</p>
</section>
<section>
<div class="heading"><h2>Operations</h2><span>click to place</span></div>
<div id="palette" class="palette"></div>
</section>
<details open>
<summary>Flux laws</summary>
<ul>
<li>Phase is literal direction in the arena.</li>
<li>Strength is impulse magnitude.</li>
<li>Every link takes one tick, keeps 92%, and divides at forks.</li>
<li>Arrivals add as vectors. Opposites cancel.</li>
<li>The familiar keeps momentum, drags, and rebounds from walls.</li>
</ul>
</details>
<section class="status-panel">
<div class="heading"><h2>Constellation</h2><span id="constellation-name">one</span></div>
<div id="star-a-status" class="status">○ Wake the amber star</div>
<div id="star-b-status" class="status">○ Wake the violet star</div>
<div id="dock-status" class="status">○ Settle in the cradle</div>
<button id="new-constellation">Change constellation</button>
<p class="fine">Changing constellation keeps your network but changes the physical problem.</p>
</section>
<section class="help">
<h2>Bench controls</h2>
<p>Drag headers. Click an output port, then an input port, to weave. Click a link or node and press <kbd>Delete</kbd>. Right-click removes a link.</p>
</section>
</aside>
<div class="workarea">
<section class="network-panel">
<div class="panel-label"><span>SIGNAL BENCH</span><b id="selection">nothing selected</b></div>
<div id="network" tabindex="0">
<div class="grid"></div>
<svg id="wire-layer">
<defs><filter id="glow"><feGaussianBlur stdDeviation="3" result="b"></feGaussianBlur><feMerge><feMergeNode in="b"></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter></defs>
<g id="wires"></g>
<path id="pending" hidden></path>
</svg>
<div id="nodes"></div>
<div class="network-hint">Connect a well through operations to the familiar bond.</div>
</div>
</section>
<section class="arena-panel">
<div class="panel-label">
<span>PHYSICAL FIELD</span>
<b><i class="solid"></i> x flux <i class="dotted"></i> y flux</b>
</div>
<canvas id="arena"></canvas>
<div class="readouts">
<span id="motion-readout">speed 0.00</span>
<span id="flux-readout">incoming: quiet</span>
<span id="dock-readout">cradle stability 0 / 14</span>
</div>
</section>
</div>
</main>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,117 @@
:root {
color-scheme: dark;
--bg: #070a10;
--panel: #0e1520;
--panel2: #151e2c;
--line: #29374b;
--text: #e7edf6;
--muted: #8c9aaf;
--aqua: #70ead6;
--amber: #f5bf64;
--violet: #c39aff;
--danger: #ff7281;
--node-w: 154px;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
button, select, input { font: inherit; }
button { border: 1px solid var(--line); border-radius: 7px; padding: 7px 10px; color: var(--text); background: #162131; cursor: pointer; }
button:hover:not(:disabled) { border-color: #566b84; background: #1d2b3e; }
button.primary { border-color: var(--aqua); background: var(--aqua); color: #07110f; font-weight: 800; }
button:disabled { opacity: .38; cursor: not-allowed; }
h1, h2, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin-bottom: 8px; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; }
p { font-size: 12px; line-height: 1.46; }
.topbar { height: 62px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 8px 17px; border-bottom: 1px solid var(--line); background: #0b1018; }
.title span, .panel-label span { display: block; color: var(--aqua); font-size: 9px; font-weight: 850; letter-spacing: .16em; }
.controls { display: flex; align-items: center; gap: 7px; }
.controls label { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 10px; }
.controls input { direction: rtl; width: 85px; accent-color: var(--aqua); }
#tick { min-width: 57px; color: var(--muted); font: 10px ui-monospace, monospace; }
main { height: calc(100vh - 62px); display: grid; grid-template-columns: 260px minmax(0, 1fr); }
aside { overflow-y: auto; padding: 14px; border-right: 1px solid var(--line); background: #0b1018; }
aside section, aside details { margin-bottom: 17px; }
.prompt { padding: 12px; border: 1px solid #34635d; border-radius: 9px; background: linear-gradient(145deg, #10241f, #101722); }
.prompt p:last-child { margin-bottom: 0; color: var(--muted); }
.heading { display: flex; align-items: baseline; justify-content: space-between; }
.heading span { color: var(--muted); font-size: 9px; letter-spacing: .09em; text-transform: uppercase; }
.palette { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.palette button { min-height: 55px; padding: 8px; text-align: left; }
.palette strong { display: block; font-size: 11px; }
.palette em { float: right; color: var(--aqua); font: normal 17px ui-monospace, monospace; }
.palette span { display: block; margin-top: 3px; color: var(--muted); font-size: 9px; }
details { border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
summary { padding: 9px 10px; cursor: pointer; font-size: 11px; font-weight: 750; }
details ul { margin: 0; padding: 0 12px 10px 27px; color: var(--muted); font-size: 10px; line-height: 1.4; }
details li + li { margin-top: 4px; }
.status { margin-bottom: 6px; padding: 7px 9px; border: 1px solid var(--line); border-radius: 6px; color: var(--muted); background: var(--panel); font-size: 10px; }
.status.done { color: var(--aqua); border-color: #39736a; background: #10231f; }
.status-panel button { width: 100%; margin-top: 3px; }
.fine, .help p { margin-top: 7px; color: var(--muted); font-size: 10px; }
kbd { padding: 1px 4px; border: 1px solid #46556a; border-bottom-width: 2px; border-radius: 4px; color: var(--text); }
.workarea { min-width: 0; display: grid; grid-template-columns: minmax(460px, 54%) minmax(360px, 46%); }
.network-panel, .arena-panel { min-width: 0; display: grid; grid-template-rows: 34px minmax(0, 1fr); background: #080d15; }
.network-panel { border-right: 1px solid var(--line); }
.panel-label { display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border-bottom: 1px solid var(--line); background: #0d131d; }
.panel-label b { color: var(--muted); font: 9px ui-monospace, monospace; font-weight: 400; }
.solid, .dotted { display: inline-block; width: 12px; margin: 0 3px 2px 7px; border-top: 2px solid var(--aqua); }
.dotted { border-top-style: dotted; }
#network { position: relative; min-height: 0; overflow: hidden; outline: none; }
.grid { position: absolute; inset: 0; background-image: linear-gradient(rgba(102,139,168,.07) 1px, transparent 1px), linear-gradient(90deg, rgba(102,139,168,.07) 1px, transparent 1px), radial-gradient(circle, rgba(38,85,92,.12), transparent 65%); background-size: 22px 22px, 22px 22px, 100% 100%; }
#wire-layer, #nodes { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
#wire-layer { overflow: visible; }
.network-hint { position: absolute; left: 50%; top: 52%; translate: -50% -50%; width: 230px; color: #536277; text-align: center; font-size: 10px; pointer-events: none; }
#pending { fill: none; stroke: var(--aqua); stroke-width: 2; stroke-dasharray: 5 5; }
.wire-hit { fill: none; stroke: transparent; stroke-width: 15; pointer-events: stroke; cursor: pointer; }
.wire-base { fill: none; stroke: #334359; stroke-width: 2; }
.wire-flow { fill: none; stroke: var(--aqua); stroke-width: 3; stroke-dasharray: 3 11; opacity: 0; filter: url(#glow); animation: flow .55s linear infinite; }
.wire.active .wire-flow { opacity: var(--opacity); stroke: var(--color); stroke-width: var(--width); }
.wire.selected .wire-base { stroke: white; stroke-width: 4; }
@keyframes flow { to { stroke-dashoffset: -14; } }
.node { position: absolute; width: var(--node-w); min-height: 91px; border: 1px solid #34445a; border-radius: 8px; background: linear-gradient(145deg, #182332, #111824); box-shadow: 0 8px 22px rgba(0,0,0,.3); pointer-events: auto; user-select: none; }
.node.fixed { background: linear-gradient(145deg, #152932, #111923); }
.node.bond { border-color: var(--violet); background: linear-gradient(145deg, #251f34, #121824); }
.node.selected { border-color: white; box-shadow: 0 0 0 1px white, 0 8px 25px #0008; }
.node header { height: 29px; display: flex; align-items: center; gap: 6px; padding: 0 8px; border-bottom: 1px solid #2c3a4d; cursor: grab; font-size: 10px; font-weight: 750; }
.node header i { color: var(--aqua); font: normal 14px ui-monospace, monospace; }
.node header span { flex: 1; }
.delete { width: 18px; height: 18px; padding: 0; border: 0; background: transparent; color: var(--muted); }
.delete:hover { color: var(--danger); background: #3a2028 !important; }
.body { padding: 7px 9px 8px; }
.description { min-height: 23px; color: var(--muted); font-size: 8px; line-height: 1.3; }
.live { display: flex; gap: 6px; align-items: center; margin-top: 4px; }
.orb { --angle: 0deg; width: 24px; height: 24px; position: relative; border: 1px solid #405168; border-radius: 50%; background: radial-gradient(circle, color-mix(in srgb, var(--flux) 45%, transparent), transparent 65%); }
.orb::after { content: ""; position: absolute; left: 11px; top: 10px; width: 9px; height: 2px; transform-origin: 1px 1px; rotate: var(--angle); background: var(--flux); box-shadow: 0 0 5px var(--flux); }
.numbers b, .numbers span { display: block; font: 8px ui-monospace, monospace; }
.numbers span { margin-top: 2px; color: var(--muted); }
.config { display: grid; grid-template-columns: auto 1fr; gap: 3px 5px; align-items: center; margin-top: 5px; padding-top: 5px; border-top: 1px solid #293648; }
.config label { color: var(--muted); font-size: 8px; }
.config select { min-width: 0; height: 20px; border: 1px solid #34445a; border-radius: 4px; background: #0c131d; color: var(--text); font-size: 8px; }
.port { position: absolute; top: 45px; width: 14px; height: 14px; padding: 0; border: 2px solid #8999ad; border-radius: 50%; background: #101823; z-index: 3; cursor: crosshair; }
.port.input { left: -8px; }
.port.output { right: -8px; }
.port:hover, .port.pending { scale: 1.25; border-color: white; background: var(--aqua); box-shadow: 0 0 8px var(--aqua); }
.arena-panel { position: relative; grid-template-rows: 34px minmax(0, 1fr) 32px; }
#arena { width: 100%; height: 100%; display: block; background: #080c13; }
.readouts { display: flex; align-items: center; justify-content: space-between; padding: 0 10px; border-top: 1px solid var(--line); background: #0d131d; color: var(--muted); font: 9px ui-monospace, monospace; }
#toast { position: fixed; z-index: 20; left: 50%; bottom: 18px; translate: -50% 12px; max-width: 430px; padding: 10px 14px; border: 1px solid #47726b; border-radius: 8px; background: #10231f; opacity: 0; pointer-events: none; transition: .2s; font-size: 11px; box-shadow: 0 10px 30px #0009; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 980px) {
body { overflow: auto; }
.topbar { height: auto; align-items: flex-start; flex-direction: column; }
.controls { flex-wrap: wrap; }
main { height: auto; grid-template-columns: 1fr; }
aside { max-height: none; }
.workarea { height: 850px; grid-template-columns: 1fr; grid-template-rows: 480px 520px; }
.network-panel { border-right: 0; border-bottom: 1px solid var(--line); }
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" --directory "$EXPERIMENT_DIR/prototype" --log-directory "$REPO_ROOT/JSONL" --port 8000

View file

@ -0,0 +1,73 @@
# Experiment 002 — Conversion Breach
## Question
Are stable magical/physical laws interesting when the player manipulates them directly during an ongoing activity, and does external pressure make understanding instrumentally valuable?
## Hypotheses
The leading hypotheses are that direct agency is preferable to autonomous programming and that optimization needs practical stakes. The competing explanation is that action merely masks another shallow interaction. See [`hypothesis.md`](hypothesis.md).
## Difference From Previous Experiment
- No graph, wiring, autonomous controller, or static route specification.
- The player directly extracts, stores, injects, and converts energy while moving and aiming.
- Temperature consistently affects speed, brittleness, and instability; momentum, mass, walls, and collisions determine damage.
- Calm and breach variants use the same verbs, entities, and laws. Only threat pressure changes.
- Periodic calibration opportunities offer chosen improvements, not random verbs or permission unlocks.
## Expected Result
Support for the new model would look like deliberate exploitation: freezing before a wall impact, overheating a construct before driving it into a group, using one enemy as fuel or a projectile, or choosing upgrades to deepen a discovered tactic. The pressure hypothesis is supported only if breach mode makes these decisions more meaningful without eliminating experimentation.
## Controls
- All manipulation verbs exist from the start in both variants.
- The initial field layout and construct properties are deterministic.
- Calm and breach modes share physics; constructs only pursue and injure the player in breach mode.
- Switching variants starts a fresh baseline so calibration upgrades cannot contaminate the comparison; resetting within one variant preserves its calibration.
- No authored spell recipes, equipment drops, or part unlocks.
## How To Run
From this directory:
```bash
./run.sh
```
Then open <http://localhost:8000>.
## What To Pay Attention To
- Try both **Calm** and **Breach**; which one would you rather continue?
- Did you discover or deliberately exploit an interaction?
- Did improvement choices create a tactic you cared about, or only numbers?
Use **Export JSONL** when finished.
## Result
The first playtest produced the first sustained engagement in the program: about 16 logged minutes, approximately 14 in Breach, ten waves cleared, 74 defeats, tactical adaptation, and strongly directed upgrade choices.
The player changed from indiscriminate heat extraction to conditional cooling based on target position and remaining fuel. They voluntarily tested player ramming. They chose conversion 19 of 24 times, capacity four times, and transfer once.
The test also contained major contamination: an input-device conflict during the first run, viewport failure, through-wall extraction, crowded wall phasing, an unreadable resource deadlock, exponential conversion scaling, and an invalid Calm comparison. The player now has a mouse, so the first issue is historical rather than a priority for the next run. See [`results/0732452b-analysis.md`](results/0732452b-analysis.md).
## Interpretation
This is positive evidence that a coupled property/resource system with practical stakes can provoke learning and tactical revision. It is not sufficient evidence that action, pressure, or direct control is the root cause.
The most promising observed interaction was not simply “freeze enemies.” Extracting heat simultaneously created ammunition, altered pursuit speed, enabled brittleness, and depleted the remaining world resource. That coupling caused the player to revise when and where they extracted.
However, the dominant outcome was wall impact, conversion upgrades became exponentially overwhelming, and Calm did not share wave goals. The result could be driven by progression, increasing power, conventional action feedback, a safe wall exploit, or the property tradeoff. These remain competing explanations.
## Next Best Experiment
Corrective revision 2 gives Calm the same waves, populations, layouts, and calibration as Breach; only pursuit and contact damage differ. It also fixes full-screen containment and sidebar scrolling, blocks transfer and discharge through obstacles, strengthens obstacle collision resolution, clarifies brittle/unstable impact feedback, adds click-to-move and alternate pointer controls, and marks new logs with `prototype_revision: 2`.
Revision 2 passed JavaScript syntax checking and real-browser layout checks at 1920×1080 and 1365×768. Browser input validation confirmed click-to-move and logged displacement, both pointer transfer modes, variant switching, inspection of an unobstructed target, rejection of a wall-occluded target, and revision-tagged telemetry.
The revision-2 playtest lasted about 19:39: roughly 2:55 in Calm and 16:44 in Breach. Calm received a genuine attempt, while Breach reached wave 29. Input was easier with a mouse and upgrade selection broadened substantially. Nevertheless, wall-directed impulse remained the durable answer, escalating statistics eventually let the default heat supply clear enemies, and the player reported little new to say. See [`results/bb30ddfc-analysis.md`](results/bb30ddfc-analysis.md).
Move to an exploratory Experiment 003 rather than further tuning 002. Test a directly piloted constructed artifact under qualitatively changing physical requirements, without numerical power escalation.

View file

@ -0,0 +1,34 @@
# Experiment 002 Hypothesis
## Primary hypothesis
Direct control plus practical stakes will turn systems knowledge from implementation work into personal agency. The player will care about understanding because it produces survival, power, improvisation, or recoverable chaos.
## Controlled comparison
- **Calm:** constructs have the same properties and physics but do not pursue or damage the player.
- **Breach:** constructs pursue, collide with, and damage the player; waves continue.
## Competing explanations
1. Direct systemic manipulation is interesting in both modes; pressure changes pacing but is not necessary.
2. The system becomes meaningful only under pressure.
3. Pressure interrupts experimentation; calm is more promising.
4. Action feedback is momentarily stimulating but the laws remain shallow.
5. The mechanics feel like ordinary combat abilities rather than magical engineering.
## Strong evidence
- A discovery produces a second voluntary test.
- The player deliberately engineers a state before acting: brittle, unstable, massive, aligned, or grouped.
- A chosen calibration supports a self-selected tactic.
- The player recovers from a bad state using the system rather than merely kiting.
- The calm/breach preference has a clear reason tied to reasoning or emotional stakes.
## Weak or negative evidence
- The player spams one verb without tracking properties.
- Conventional movement and aiming do all the enjoyable work.
- Temperature states are merely status effects with obvious prescribed combos.
- Calibration is generic numerical progression without changing decisions.

View file

@ -0,0 +1,479 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const CLASS = {
ember: { name: "Ember construct", mass: .72, heat: 92, hp: 52, color: "#ff8b59", radius: .021, chase: .16 },
ballast: { name: "Ballast construct", mass: 2.25, heat: 52, hp: 105, color: "#d6c7a7", radius: .031, chase: .085 },
rime: { name: "Rime construct", mass: .95, heat: 22, hp: 62, color: "#75c9ff", radius: .023, chase: .12 }
};
const BASE_SPAWNS = [
["ember", .77, .22], ["ballast", .75, .74], ["rime", .39, .55],
["ember", .61, .40], ["rime", .85, .49], ["ballast", .31, .22]
];
const OBSTACLES = [
{ x: .47, y: .15, w: .065, h: .27 },
{ x: .47, y: .59, w: .065, h: .27 }
];
const state = {
mode: "calm", paused: false, upgrading: false, dead: false,
entities: [], nextEntity: 1, keys: new Set(),
mouse: { x: .7, y: .5, inside: false, left: false, shift: false },
thermalMode: "extract", moveTarget: null,
player: { x: .16, y: .5, health: 100, maxHealth: 100, reservoir: 40, capacity: 100, transfer: 27, conversion: 1, moveSpeed: .31 },
wave: 1, kills: 0, nextCalibration: 3, target: null,
effects: [], lastFrame: performance.now(), snapshotClock: 0,
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
started: Date.now(), logs: [], tethering: null
};
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const round = value => Math.round(value * 1000) / 1000;
function log(type, data = {}) {
const event = { schema: 1, experiment: "002_conversion_breach", prototype_revision: 2, session_id: state.session, elapsed_ms: Date.now() - state.started, mode: state.mode, wave: state.wave, type, ...data };
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("conversion-breach-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function spawn(kind, x, y) {
const def = CLASS[kind];
return {
id: `construct-${state.nextEntity++}`, kind, x, y, vx: 0, vy: 0,
heat: def.heat, hp: def.hp, maxHp: def.hp, mass: def.mass, radius: def.radius,
alive: true, state: thermalState(def.heat), lastThermalState: thermalState(def.heat),
impactCooldown: 0, contactCooldown: 0, flash: 0
};
}
function resetField(reason = "manual") {
state.entities = BASE_SPAWNS.map(([kind, x, y], index) => {
const spread = state.wave > 1 ? Math.min(.08, (state.wave - 1) * .012) : 0;
return spawn(kind, clamp(x + (index % 2 ? spread : -spread), .08, .92), y);
});
if (state.wave >= 2) state.entities.push(spawn(state.wave % 2 ? "ember" : "rime", .91, .14));
state.player.x = .16; state.player.y = .5;
state.player.health = reason === "wave_cleared" ? Math.min(state.player.maxHealth, state.player.health + 20) : state.player.maxHealth;
state.player.reservoir = reason === "wave_cleared" ? Math.min(state.player.capacity, state.player.reservoir + 20) : Math.min(state.player.capacity, 40);
state.dead = false; state.target = null; state.effects = []; state.moveTarget = null;
$("#pause").textContent = state.paused ? "Resume" : "Pause";
updateHUD(); log("field_reset", { reason, constructs: state.entities.map(e => ({ id: e.id, kind: e.kind, heat: e.heat, mass: e.mass })) });
}
function thermalState(heat) {
if (heat < 15) return "brittle";
if (heat > 105) return "unstable";
if (heat < 35) return "cold";
if (heat > 75) return "hot";
return "stable";
}
function setMode(mode) {
if (state.mode === mode) return;
const before = state.mode; state.mode = mode; state.wave = 1; state.kills = 0; state.nextCalibration = 3;
state.player.capacity = 100; state.player.transfer = 27; state.player.conversion = 1;
$("#calm-mode").classList.toggle("selected", mode === "calm");
$("#breach-mode").classList.toggle("selected", mode === "breach");
$("#variant-title").textContent = mode === "calm" ? "Calm field" : "Breach field";
$("#variant-copy").textContent = mode === "calm" ? "Full waves and calibration, but constructs do not pursue or injure you." : "The same waves and calibration, with pursuit and player damage added.";
resetField("mode_switch"); log("mode_changed", { before, after: mode, calibration_reset: true }); toast(mode === "calm" ? "Calm field: the laws remain, the threat does not." : "Breach field: same laws, active threat.");
}
function canvasMetrics() {
const rect = canvas.getBoundingClientRect(); return { rect, width: rect.width, height: rect.height, scale: Math.min(rect.width, rect.height) };
}
function resizeCanvas() {
const { width, height } = canvasMetrics(), ratio = Math.min(2, devicePixelRatio || 1);
canvas.width = Math.max(1, Math.round(width * ratio)); canvas.height = Math.max(1, Math.round(height * ratio));
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); draw();
}
function selectTarget() {
if (!state.mouse.inside) return null;
const { width, height } = canvasMetrics(); let best = null, bestPixels = Infinity;
for (const entity of state.entities) {
if (!entity.alive || dist(entity, state.player) > .36 || lineBlocked(state.player, entity)) continue;
const pixels = Math.hypot((entity.x - state.mouse.x) * width, (entity.y - state.mouse.y) * height);
if (pixels < bestPixels && pixels < 62) { best = entity; bestPixels = pixels; }
}
return best;
}
function update(dt) {
if (state.paused || state.upgrading || state.dead) return;
movePlayer(dt);
state.target = selectTarget();
applyTether(dt);
updateEntities(dt);
collideEntities(dt);
updateEffects(dt);
state.snapshotClock += dt;
if (state.snapshotClock >= 5) {
state.snapshotClock = 0;
log("field_snapshot", {
player: { x: round(state.player.x), y: round(state.player.y), health: round(state.player.health), reservoir: round(state.player.reservoir) },
alive: state.entities.filter(e => e.alive).map(e => ({ id: e.id, kind: e.kind, heat: round(e.heat), hp: round(e.hp), state: e.state }))
});
}
}
function movePlayer(dt) {
let dx = 0, dy = 0;
if (state.keys.has("KeyW")) dy--;
if (state.keys.has("KeyS")) dy++;
if (state.keys.has("KeyA")) dx--;
if (state.keys.has("KeyD")) dx++;
if (dx || dy) state.moveTarget = null;
if (!dx && !dy && state.moveTarget) {
dx = state.moveTarget.x - state.player.x; dy = state.moveTarget.y - state.player.y;
const remaining = Math.hypot(dx, dy);
if (remaining < .012) { state.moveTarget = null; return; }
dx /= remaining; dy /= remaining;
}
if (!dx && !dy) return;
const length = Math.hypot(dx, dy); dx /= length; dy /= length;
const old = { x: state.player.x, y: state.player.y };
state.player.x = clamp(state.player.x + dx * state.player.moveSpeed * dt, .025, .975);
state.player.y = clamp(state.player.y + dy * state.player.moveSpeed * dt, .035, .965);
if (insideObstacle(state.player, .018)) { state.player.x = old.x; state.player.y = old.y; }
}
function applyTether(dt) {
const target = state.target;
if (!state.mouse.left || !target) {
if (state.tethering) { log("tether_ended", state.tethering); state.tethering = null; }
return;
}
const inject = state.thermalMode === "inject" || state.keys.has("ShiftLeft") || state.keys.has("ShiftRight");
const direction = inject ? "inject" : "extract";
if (!state.tethering || state.tethering.target_id !== target.id || state.tethering.direction !== direction) {
if (state.tethering) log("tether_ended", state.tethering);
state.tethering = { target_id: target.id, direction, start_heat: round(target.heat), start_reservoir: round(state.player.reservoir) };
log("tether_started", state.tethering);
}
const amount = state.player.transfer * dt;
if (inject) {
const moved = Math.min(amount, state.player.reservoir, 130 - target.heat);
state.player.reservoir -= moved; target.heat += moved;
} else {
const moved = Math.min(amount, target.heat, state.player.capacity - state.player.reservoir);
target.heat -= moved; state.player.reservoir += moved;
}
target.flash = .08;
}
function updateEntities(dt) {
for (const entity of state.entities) {
if (!entity.alive) continue;
entity.impactCooldown = Math.max(0, entity.impactCooldown - dt); entity.contactCooldown = Math.max(0, entity.contactCooldown - dt); entity.flash = Math.max(0, entity.flash - dt);
entity.heat += (50 - entity.heat) * .004 * dt;
entity.state = thermalState(entity.heat);
if (entity.state !== entity.lastThermalState) {
log("thermal_state_changed", { target_id: entity.id, kind: entity.kind, before: entity.lastThermalState, after: entity.state, heat: round(entity.heat) });
if (["brittle", "unstable"].includes(entity.state)) toast(`${CLASS[entity.kind].name} became ${entity.state}.`);
entity.lastThermalState = entity.state;
}
if (state.mode === "breach") {
const dx = state.player.x - entity.x, dy = state.player.y - entity.y, length = Math.max(.001, Math.hypot(dx, dy));
const thermalMobility = entity.heat < 15 ? .16 : clamp((entity.heat + 12) / 72, .3, 1.45);
const acceleration = CLASS[entity.kind].chase * thermalMobility / Math.sqrt(entity.mass);
entity.vx += dx / length * acceleration * dt; entity.vy += dy / length * acceleration * dt;
}
const drag = Math.pow(.72, dt); entity.vx *= drag; entity.vy *= drag;
const speed = Math.hypot(entity.vx, entity.vy), cap = entity.state === "brittle" ? .13 : .48;
if (speed > cap) { entity.vx *= cap / speed; entity.vy *= cap / speed; }
const impactSpeed = Math.hypot(entity.vx, entity.vy);
entity.x += entity.vx * dt; entity.y += entity.vy * dt;
let hit = false;
if (entity.x < entity.radius || entity.x > 1 - entity.radius) { entity.x = clamp(entity.x, entity.radius, 1 - entity.radius); entity.vx *= -.56; hit = true; }
if (entity.y < entity.radius || entity.y > 1 - entity.radius) { entity.y = clamp(entity.y, entity.radius, 1 - entity.radius); entity.vy *= -.56; hit = true; }
if (resolveObstacle(entity)) hit = true;
if (hit) applyImpact(entity, impactSpeed, "wall");
if (state.mode === "breach" && dist(entity, state.player) < entity.radius + .022 && entity.contactCooldown <= 0) {
const damage = entity.kind === "ballast" ? 9 : 6; state.player.health = Math.max(0, state.player.health - damage); entity.contactCooldown = .8;
const awayX = entity.x - state.player.x, awayY = entity.y - state.player.y, len = Math.max(.001, Math.hypot(awayX, awayY)); entity.vx += awayX / len * .18 / entity.mass; entity.vy += awayY / len * .18 / entity.mass;
log("player_damaged", { source_id: entity.id, amount: damage, health: round(state.player.health) });
if (state.player.health <= 0) collapseField();
}
}
}
function insideObstacle(point, radius) {
return OBSTACLES.some(o => point.x > o.x - radius && point.x < o.x + o.w + radius && point.y > o.y - radius && point.y < o.y + o.h + radius);
}
function lineBlocked(a, b) {
const samples = Math.max(8, Math.ceil(dist(a, b) * 80));
for (let i = 1; i < samples; i++) {
const t = i / samples;
if (insideObstacle({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }, .003)) return true;
}
return false;
}
function resolveObstacle(entity) {
let collided = false;
for (const o of OBSTACLES) {
const left = o.x - entity.radius, right = o.x + o.w + entity.radius;
const top = o.y - entity.radius, bottom = o.y + o.h + entity.radius;
if (entity.x <= left || entity.x >= right || entity.y <= top || entity.y >= bottom) continue;
collided = true;
const sides = [
{ distance: entity.x - left, side: "left" },
{ distance: right - entity.x, side: "right" },
{ distance: entity.y - top, side: "top" },
{ distance: bottom - entity.y, side: "bottom" }
].sort((a, b) => a.distance - b.distance);
if (sides[0].side === "left") { entity.x = left - .0001; if (entity.vx > 0) entity.vx *= -.56; }
if (sides[0].side === "right") { entity.x = right + .0001; if (entity.vx < 0) entity.vx *= -.56; }
if (sides[0].side === "top") { entity.y = top - .0001; if (entity.vy > 0) entity.vy *= -.56; }
if (sides[0].side === "bottom") { entity.y = bottom + .0001; if (entity.vy < 0) entity.vy *= -.56; }
}
return collided;
}
function collideEntities(dt) {
const alive = state.entities.filter(e => e.alive);
for (let i = 0; i < alive.length; i++) for (let j = i + 1; j < alive.length; j++) {
const a = alive[i], b = alive[j], dx = b.x - a.x, dy = b.y - a.y, distance = Math.max(.0001, Math.hypot(dx, dy)), min = a.radius + b.radius;
if (distance >= min) continue;
const nx = dx / distance, ny = dy / distance, overlap = min - distance, total = a.mass + b.mass;
a.x -= nx * overlap * b.mass / total; a.y -= ny * overlap * b.mass / total; b.x += nx * overlap * a.mass / total; b.y += ny * overlap * a.mass / total;
resolveObstacle(a); resolveObstacle(b);
const relative = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;
if (relative < 0) {
const impulse = -(1.35 * relative) / (1 / a.mass + 1 / b.mass);
a.vx -= impulse * nx / a.mass; a.vy -= impulse * ny / a.mass; b.vx += impulse * nx / b.mass; b.vy += impulse * ny / b.mass;
const impact = Math.abs(relative); applyImpact(a, impact * b.mass / total, b.id); applyImpact(b, impact * a.mass / total, a.id);
}
const heatFlow = (a.heat - b.heat) * Math.min(.25, dt * .7); a.heat -= heatFlow; b.heat += heatFlow;
}
}
function applyImpact(entity, speed, source) {
if (!entity.alive || entity.impactCooldown > 0 || speed < .075) return;
let damage = Math.max(0, speed - .06) * entity.mass * 115;
if (entity.state === "brittle") damage *= 13;
if (damage > 1) {
entity.hp -= damage; entity.flash = .13; entity.impactCooldown = .08;
log("construct_impact", { target_id: entity.id, source, speed: round(speed), state: entity.state, damage: round(damage), hp: round(entity.hp) });
}
if (entity.state === "unstable" && speed > .105) explode(entity);
if (entity.hp <= 0) destroy(entity, source);
}
function explode(entity) {
if (!entity.alive || entity.heat < 105) return;
entity.heat = 62; entity.hp -= 18;
state.effects.push({ type: "ring", x: entity.x, y: entity.y, radius: 0, life: .5, maxLife: .5, color: "#ff8b59" });
let affected = 0;
for (const other of state.entities) {
if (!other.alive || other === entity) continue;
const dx = other.x - entity.x, dy = other.y - entity.y, d = Math.hypot(dx, dy);
if (d > .22 || d < .001) continue;
const power = (1 - d / .22); other.vx += dx / d * .32 * power / other.mass; other.vy += dy / d * .32 * power / other.mass; other.heat = Math.min(130, other.heat + 22 * power); other.hp -= 8 * power; affected++;
if (other.hp <= 0) destroy(other, `explosion:${entity.id}`);
}
log("unstable_release", { source_id: entity.id, affected }); toast(`Unstable release propagated to ${affected} construct${affected === 1 ? "" : "s"}.`);
if (entity.hp <= 0) destroy(entity, "unstable_release");
}
function destroy(entity, source) {
if (!entity.alive) return; entity.alive = false; state.kills++; state.player.reservoir = Math.min(state.player.capacity, state.player.reservoir + 6);
state.effects.push({ type: "burst", x: entity.x, y: entity.y, radius: 0, life: .42, maxLife: .42, color: CLASS[entity.kind].color });
log("construct_destroyed", { target_id: entity.id, kind: entity.kind, source, state: entity.state, heat: round(entity.heat), total: state.kills });
if (state.kills >= state.nextCalibration) { state.nextCalibration += 3; openCalibration(); }
if (!state.entities.some(e => e.alive)) {
state.wave++; resetField("wave_cleared");
toast(`${state.mode === "breach" ? "Breach" : "Calm"} wave ${state.wave}: calibration persists, composition changes.`);
}
}
function discharge() {
if (state.paused || state.upgrading || state.dead || state.player.reservoir < 5) { if (state.player.reservoir < 5) toast("Not enough stored heat to convert."); return; }
const dx = state.mouse.x - state.player.x, dy = state.mouse.y - state.player.y, length = Math.max(.001, Math.hypot(dx, dy)), nx = dx / length, ny = dy / length;
const spend = Math.min(28, state.player.reservoir); state.player.reservoir -= spend;
let affected = 0;
for (const entity of state.entities) {
if (!entity.alive || lineBlocked(state.player, entity)) continue;
const ex = entity.x - state.player.x, ey = entity.y - state.player.y, d = Math.hypot(ex, ey); if (d > .34 || d < .001) continue;
const alignment = (ex / d) * nx + (ey / d) * ny; if (alignment < .72) continue;
const falloff = .45 + .55 * (1 - d / .34), impulse = .43 * (spend / 28) * state.player.conversion * falloff / entity.mass;
entity.vx += nx * impulse; entity.vy += ny * impulse; affected++;
}
state.effects.push({ type: "cone", x: state.player.x, y: state.player.y, angle: Math.atan2(ny, nx), radius: 0, life: .18, maxLife: .18, color: "#f5bf64" });
log("kinetic_discharge", { spend: round(spend), direction: { x: round(nx), y: round(ny) }, affected, conversion: round(state.player.conversion) });
}
function openCalibration() {
state.upgrading = true;
const choices = [
{ id: "capacity", name: "Deeper reservoir", description: "+35 maximum storage. Existing heat is retained.", apply: () => { state.player.capacity += 35; } },
{ id: "transfer", name: "Wider conduit", description: "+30% extraction and injection rate.", apply: () => { state.player.transfer *= 1.3; } },
{ id: "conversion", name: "Sharper conversion", description: "+28% kinetic impulse per unit of heat.", apply: () => { state.player.conversion *= 1.28; } }
];
const holder = $("#upgrade-choices"); holder.innerHTML = "";
choices.forEach(choice => { const button = document.createElement("button"); button.innerHTML = `<b>${choice.name}</b><span>${choice.description}</span>`; button.addEventListener("click", () => { choice.apply(); state.upgrading = false; $("#upgrade-modal").classList.add("hidden"); log("calibration_chosen", { choice: choice.id, capacity: state.player.capacity, transfer: round(state.player.transfer), conversion: round(state.player.conversion) }); toast(`${choice.name} calibrated.`); }); holder.append(button); });
$("#upgrade-modal").classList.remove("hidden"); log("calibration_offered", { opportunity: Math.floor(state.kills / 3) });
}
function collapseField() {
state.dead = true; log("field_collapsed", { wave: state.wave, kills: state.kills }); toast("Field integrity collapsed. Reset or compare the calm variant.");
}
function updateEffects(dt) {
for (const effect of state.effects) { effect.life -= dt; effect.radius += dt * (effect.type === "cone" ? .9 : .45); }
state.effects = state.effects.filter(effect => effect.life > 0);
}
function heatColor(heat) {
if (heat <= 50) { const t = clamp(heat / 50, 0, 1); return `rgb(${Math.round(90 + 150 * t)},${Math.round(174 + 54 * t)},${Math.round(255 - 25 * t)})`; }
const t = clamp((heat - 50) / 80, 0, 1); return `rgb(${Math.round(240 + 15 * t)},${Math.round(228 - 105 * t)},${Math.round(230 - 150 * t)})`;
}
function draw() {
const { width: W, height: H, scale: S } = canvasMetrics(); if (!W || !H) return;
ctx.clearRect(0, 0, W, H); ctx.fillStyle = "#080c12"; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = "rgba(104,139,170,.09)"; ctx.lineWidth = 1;
for (let x = 0; x < W; x += 28) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
for (let y = 0; y < H; y += 28) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
drawObstacles(W, H);
if (state.mouse.left && state.target) drawTether(W, H);
for (const effect of state.effects) drawEffect(effect, W, H, S);
for (const entity of state.entities) if (entity.alive) drawEntity(entity, W, H, S);
drawPlayer(W, H, S); updateTargetCard(); updateHUD();
}
function drawObstacles(W, H) {
for (const o of OBSTACLES) {
ctx.fillStyle = "#172231"; ctx.strokeStyle = "#40516a"; ctx.lineWidth = 2; ctx.fillRect(o.x * W, o.y * H, o.w * W, o.h * H); ctx.strokeRect(o.x * W, o.y * H, o.w * W, o.h * H);
ctx.strokeStyle = "rgba(113,234,215,.13)"; for (let y = o.y * H + 7; y < (o.y + o.h) * H; y += 12) { ctx.beginPath(); ctx.moveTo(o.x * W, y); ctx.lineTo((o.x + o.w) * W, y + 7); ctx.stroke(); }
}
}
function drawEntity(entity, W, H, S) {
const x = entity.x * W, y = entity.y * H, r = entity.radius * S, color = heatColor(entity.heat);
if (entity === state.target) { ctx.strokeStyle = "#fff"; ctx.lineWidth = 1.5; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.arc(x, y, r + 9, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]); }
ctx.save(); ctx.translate(x, y); ctx.shadowColor = color; ctx.shadowBlur = entity.state === "unstable" ? 18 : entity.state === "brittle" ? 10 : 5; ctx.fillStyle = entity.flash ? "#fff" : color; ctx.strokeStyle = entity.state === "brittle" ? "#e8f8ff" : "#15202d"; ctx.lineWidth = 2;
ctx.beginPath();
if (entity.kind === "ember") ctx.arc(0, 0, r, 0, Math.PI * 2);
else if (entity.kind === "ballast") ctx.rect(-r, -r, r * 2, r * 2);
else { ctx.moveTo(0, -r * 1.2); ctx.lineTo(r, r); ctx.lineTo(-r, r); ctx.closePath(); }
ctx.fill(); ctx.stroke(); ctx.shadowBlur = 0;
if (entity.state === "brittle") { ctx.strokeStyle = "#315b78"; ctx.beginPath(); ctx.moveTo(-r * .6, -r * .5); ctx.lineTo(r * .2, .1); ctx.lineTo(-r * .15, r * .7); ctx.stroke(); }
if (entity.state === "unstable") { ctx.strokeStyle = "#fff0b7"; ctx.beginPath(); ctx.arc(0, 0, r * .58, 0, Math.PI * 2); ctx.stroke(); }
ctx.restore();
ctx.fillStyle = "#1b2736"; ctx.fillRect(x - r, y - r - 9, r * 2, 3); ctx.fillStyle = color; ctx.fillRect(x - r, y - r - 9, r * 2 * clamp(entity.heat / 130, 0, 1), 3);
ctx.fillStyle = "#1b2736"; ctx.fillRect(x - r, y + r + 6, r * 2, 3); ctx.fillStyle = "#70ead6"; ctx.fillRect(x - r, y + r + 6, r * 2 * clamp(entity.hp / entity.maxHp, 0, 1), 3);
}
function drawPlayer(W, H, S) {
const x = state.player.x * W, y = state.player.y * H, r = .019 * S;
const dx = state.mouse.x - state.player.x, dy = state.mouse.y - state.player.y, length = Math.max(.001, Math.hypot(dx, dy));
ctx.strokeStyle = "rgba(113,234,215,.35)"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + dx / length * 42, y + dy / length * 42); ctx.stroke();
const glow = ctx.createRadialGradient(x, y, 2, x, y, r * 2.2); glow.addColorStop(0, "rgba(113,234,215,.8)"); glow.addColorStop(1, "rgba(113,234,215,0)"); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(x, y, r * 2.2, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = state.dead ? "#ff6f7d" : "#eafffb"; ctx.strokeStyle = "#70ead6"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x + dx / length * r, y + dy / length * r); ctx.lineTo(x - dy / length * r * .72 - dx / length * r * .65, y + dx / length * r * .72 - dy / length * r * .65); ctx.lineTo(x + dy / length * r * .72 - dx / length * r * .65, y - dx / length * r * .72 - dy / length * r * .65); ctx.closePath(); ctx.fill(); ctx.stroke();
if (state.moveTarget) {
ctx.strokeStyle = "rgba(113,234,215,.65)"; ctx.lineWidth = 1.5; ctx.setLineDash([3, 4]); ctx.beginPath(); ctx.arc(state.moveTarget.x * W, state.moveTarget.y * H, 8, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]);
}
}
function drawTether(W, H) {
const inject = state.thermalMode === "inject" || state.keys.has("ShiftLeft") || state.keys.has("ShiftRight"), target = state.target;
ctx.strokeStyle = inject ? "#ff9a60" : "#72cbff"; ctx.lineWidth = 3; ctx.setLineDash([4, 7]); ctx.lineDashOffset = -(performance.now() / 35) % 11; ctx.beginPath(); ctx.moveTo(state.player.x * W, state.player.y * H); ctx.lineTo(target.x * W, target.y * H); ctx.stroke(); ctx.setLineDash([]);
}
function drawEffect(effect, W, H, S) {
const alpha = clamp(effect.life / effect.maxLife, 0, 1); ctx.save(); ctx.globalAlpha = alpha; ctx.strokeStyle = effect.color; ctx.lineWidth = 3;
if (effect.type === "cone") { ctx.translate(effect.x * W, effect.y * H); ctx.rotate(effect.angle); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, effect.radius * S, -.55, .55); ctx.closePath(); ctx.stroke(); }
else { ctx.beginPath(); ctx.arc(effect.x * W, effect.y * H, effect.radius * S, 0, Math.PI * 2); ctx.stroke(); }
ctx.restore();
}
function updateTargetCard() {
const entity = state.target, card = $("#target-card"); card.classList.toggle("hidden", !entity); if (!entity) return;
const stateLabel = entity.state === "brittle" ? "brittle · impact ×13" : entity.state === "unstable" ? "unstable · impact releases" : entity.state;
$("#target-name").textContent = CLASS[entity.kind].name; $("#target-state").textContent = stateLabel; $("#target-state").style.color = heatColor(entity.heat);
$("#target-temp").style.width = `${clamp(entity.heat / 130 * 100, 0, 100)}%`; $("#target-temp-label").textContent = `${entity.heat.toFixed(0)}°`;
$("#target-hp").style.width = `${clamp(entity.hp / entity.maxHp * 100, 0, 100)}%`; $("#target-hp-label").textContent = `${Math.max(0, entity.hp).toFixed(0)}`;
$("#target-detail").textContent = `mass ${entity.mass.toFixed(2)} · speed ${Math.hypot(entity.vx, entity.vy).toFixed(2)}`;
}
function updateHUD() {
const p = state.player;
$("#health-fill").style.width = `${clamp(p.health / p.maxHealth * 100, 0, 100)}%`; $("#health-label").textContent = `${p.health.toFixed(0)} / ${p.maxHealth}`;
$("#reservoir-fill").style.width = `${clamp(p.reservoir / p.capacity * 100, 0, 100)}%`; $("#reservoir-label").textContent = `${p.reservoir.toFixed(0)} / ${p.capacity}`;
$("#wave-label").textContent = `${state.mode} wave ${state.wave}`; $("#kill-label").textContent = `${state.kills} conversion${state.kills === 1 ? "" : "s"}`;
}
function frame(now) {
const dt = Math.min(.033, Math.max(0, (now - state.lastFrame) / 1000)); state.lastFrame = now; update(dt); draw(); requestAnimationFrame(frame);
}
function pointerPosition(event) {
const rect = canvas.getBoundingClientRect(); state.mouse.x = clamp((event.clientX - rect.left) / rect.width, 0, 1); state.mouse.y = clamp((event.clientY - rect.top) / rect.height, 0, 1);
}
canvas.addEventListener("pointermove", event => { pointerPosition(event); state.mouse.inside = true; $(".aim-hint").style.opacity = "0"; });
canvas.addEventListener("pointerenter", event => { pointerPosition(event); state.mouse.inside = true; });
canvas.addEventListener("pointerleave", () => { state.mouse.inside = false; state.mouse.left = false; });
canvas.addEventListener("pointerdown", event => {
event.preventDefault(); canvas.focus(); pointerPosition(event); state.mouse.inside = true; $("#start-overlay").classList.add("hidden");
if (event.button === 0) {
const aimed = selectTarget();
if (aimed) { state.target = aimed; state.mouse.left = true; state.moveTarget = null; }
else { state.mouse.left = false; state.moveTarget = { x: state.mouse.x, y: state.mouse.y }; log("move_target_set", { x: round(state.mouse.x), y: round(state.mouse.y) }); }
}
if (event.button === 2) discharge();
});
window.addEventListener("pointerup", event => { if (event.button === 0) state.mouse.left = false; });
canvas.addEventListener("contextmenu", event => event.preventDefault());
window.addEventListener("keydown", event => {
if (["KeyW", "KeyA", "KeyS", "KeyD", "ShiftLeft", "ShiftRight"].includes(event.code)) { state.keys.add(event.code); event.preventDefault(); }
if (event.code === "Space" && !event.repeat) { event.preventDefault(); discharge(); }
});
window.addEventListener("keyup", event => state.keys.delete(event.code));
window.addEventListener("blur", () => { state.keys.clear(); state.mouse.left = false; });
$("#start-overlay").addEventListener("click", () => { $("#start-overlay").classList.add("hidden"); canvas.focus(); });
$("#calm-mode").addEventListener("click", () => setMode("calm"));
$("#breach-mode").addEventListener("click", () => setMode("breach"));
$("#extract-tool").addEventListener("click", () => setThermalMode("extract"));
$("#inject-tool").addEventListener("click", () => setThermalMode("inject"));
$("#stop-move").addEventListener("click", () => { state.moveTarget = null; log("move_target_cleared"); });
$("#pause").addEventListener("click", () => { state.paused = !state.paused; $("#pause").textContent = state.paused ? "Resume" : "Pause"; log(state.paused ? "paused" : "resumed"); });
$("#reset").addEventListener("click", () => resetField("manual"));
$("#export").addEventListener("click", async () => {
const button = $("#export"), filename = `conversion-breach-${state.session}.jsonl`;
log("log_exported", { events_before_export: state.logs.length, destination: "repository" }); button.disabled = true; button.textContent = "Saving…";
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: state.logs.join("\n") + "\n" });
if (!response.ok) throw new Error((await response.json().catch(() => ({}))).error || `HTTP ${response.status}`);
const result = await response.json(); log("log_saved", { path: result.path, events: result.events }); toast(`Saved directly to ${result.path}.`);
} catch (error) {
log("log_save_failed", { error: String(error) }); const blob = new Blob([state.logs.join("\n") + "\n"], { type: "application/x-ndjson" }), anchor = document.createElement("a");
anchor.href = URL.createObjectURL(blob); anchor.download = filename; anchor.click(); setTimeout(() => URL.revokeObjectURL(anchor.href), 500); toast("Direct save failed; downloaded the JSONL instead.");
} finally { button.disabled = false; button.textContent = "Save JSONL"; }
});
new ResizeObserver(resizeCanvas).observe(canvas);
let resizeLogTimer;
window.addEventListener("resize", () => { clearTimeout(resizeLogTimer); resizeLogTimer = setTimeout(() => log("viewport_changed", { viewport: { width: innerWidth, height: innerHeight }, canvas: { width: round(canvas.getBoundingClientRect().width), height: round(canvas.getBoundingClientRect().height) } }), 250); });
let toastTimer;
function toast(message) { const el = $("#toast"); el.textContent = message; el.classList.add("show"); clearTimeout(toastTimer); toastTimer = setTimeout(() => el.classList.remove("show"), 3400); }
function setThermalMode(mode) {
const before = state.thermalMode; state.thermalMode = mode;
$("#extract-tool").classList.toggle("selected", mode === "extract"); $("#inject-tool").classList.toggle("selected", mode === "inject");
if (before !== mode) log("thermal_mode_changed", { before, after: mode });
}
log("session_started", { viewport: { width: innerWidth, height: innerHeight } }); resetField("session_start"); resizeCanvas(); requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,102 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Conversion Breach — Experiment 002</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 002</span><h1>Conversion Breach</h1></div>
<div class="mode-switch" aria-label="Experiment variant">
<button id="calm-mode" class="selected">Calm field</button>
<button id="breach-mode">Breach field</button>
</div>
<div class="top-actions">
<button id="pause">Pause</button>
<button id="reset">Reset field</button>
<button id="export">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="prompt">
<h2>Manipulate, dont assemble</h2>
<p>Constructs carry temperature, mass, momentum, and integrity. Use the same laws in a calm field and under breach pressure.</p>
<p class="comparison"><b>Both mode buttons start complete experimental runs</b> with waves and calibration. Calm removes pursuit and player damage; Breach adds them.</p>
</section>
<section class="verbs">
<h2>Direct operations</h2>
<div><kbd>WASD</kbd><span class="or">or</span><kbd>Click ground</kbd><p><b>Move</b><span>Position yourself and line up impacts.</span></p></div>
<div><kbd>LMB</kbd><p><b>Use selected transfer</b><span>Choose Extract or Inject beside the field.</span></p></div>
<div><kbd>Shift</kbd>+<kbd>LMB</kbd><p><b>Inject heat</b><span>Return stored heat to the aimed construct.</span></p></div>
<div><kbd>RMB</kbd><span class="or">or</span><kbd>Space</kbd><p><b>Convert to momentum</b><span>Spend stored heat toward the cursor.</span></p></div>
</section>
<details open>
<summary>Observed laws</summary>
<ul>
<li>Cold constructs slow down. Below 15°, they become <b>brittle</b>.</li>
<li>Above 105°, constructs become <b>unstable</b>.</li>
<li>Wall and construct impacts convert momentum into damage.</li>
<li>Mass resists acceleration but increases impact energy.</li>
<li>Brittle impacts amplify damage. Unstable impacts release heat and momentum nearby.</li>
</ul>
</details>
<section class="legend">
<h2>Construct classes</h2>
<div><i class="ember"></i><p><b>Ember</b><span>hot · light · quick</span></p></div>
<div><i class="ballast"></i><p><b>Ballast</b><span>temperate · heavy</span></p></div>
<div><i class="rime"></i><p><b>Rime</b><span>cold · light</span></p></div>
</section>
<section class="variant-note">
<h2 id="variant-title">Calm field</h2>
<p id="variant-copy">Full waves and calibration, but constructs do not pursue or injure you.</p>
<small>Scroll this panel for every instruction and law.</small>
</section>
</aside>
<section class="playfield">
<div class="hud">
<div class="meter-block"><span>INTEGRITY</span><div class="meter"><i id="health-fill"></i></div><b id="health-label">100 / 100</b></div>
<div class="meter-block reservoir"><span>THERMAL RESERVOIR</span><div class="meter"><i id="reservoir-fill"></i></div><b id="reservoir-label">40 / 100</b></div>
<div class="run-state"><span id="wave-label">calm field</span><b id="kill-label">0 conversions</b></div>
</div>
<canvas id="field" tabindex="0"></canvas>
<div class="pointer-tools" aria-label="Pointer controls">
<span>LEFT CLICK TRANSFER</span>
<button id="extract-tool" class="selected">Extract</button>
<button id="inject-tool">Inject</button>
<button id="stop-move">Stop moving</button>
</div>
<div id="target-card" class="target-card hidden">
<b id="target-name">Ember construct</b>
<span id="target-state">stable</span>
<div><label>temperature</label><i class="temp-track"><em id="target-temp"></em></i><strong id="target-temp-label">50°</strong></div>
<div><label>integrity</label><i class="hp-track"><em id="target-hp"></em></i><strong id="target-hp-label">60</strong></div>
<small id="target-detail">mass 1.0 · speed 0.00</small>
</div>
<div class="aim-hint">Move the cursor near a construct to inspect and tether it.</div>
</section>
</main>
<div id="upgrade-modal" class="modal hidden">
<div class="modal-card">
<span>CALIBRATION OPPORTUNITY</span>
<h2>Choose where the recovered pattern goes.</h2>
<p>The opportunity is fixed; its exact use is yours.</p>
<div id="upgrade-choices"></div>
</div>
</div>
<div id="start-overlay" class="start-overlay">
<div><span>TWO COMPLETE VARIANTS</span><b>Calm and Breach both have waves, calibration, and the same physical laws.</b><p>Calm removes pursuit and contact damage. Breach adds them. Click to begin either run.</p></div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,123 @@
:root {
color-scheme: dark;
--bg: #070a0f;
--panel: #0d141e;
--panel2: #151f2d;
--line: #29384b;
--text: #e9eef6;
--muted: #8b9aaf;
--aqua: #71ead7;
--hot: #ff8b59;
--cold: #75c9ff;
--violet: #c29aff;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
button { border: 1px solid var(--line); border-radius: 7px; padding: 8px 11px; background: #162130; color: var(--text); font: inherit; cursor: pointer; }
button:hover { border-color: #566a82; background: #1e2c3e; }
button.selected { border-color: var(--aqua); background: #15332e; color: var(--aqua); }
h1, h2, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin-bottom: 8px; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
p { line-height: 1.43; }
header { height: 62px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding: 7px 16px; border-bottom: 1px solid var(--line); background: #0a1018; }
.title span, .modal-card > span, .start-overlay span { display: block; color: var(--aqua); font-size: 9px; font-weight: 850; letter-spacing: .16em; }
.mode-switch { display: flex; gap: 6px; }
.top-actions { display: flex; justify-content: flex-end; gap: 6px; }
main { height: calc(100vh - 62px); min-height: 0; overflow: hidden; display: grid; grid-template-columns: clamp(230px, 19vw, 270px) minmax(0, 1fr); }
aside { min-height: 0; overflow-y: scroll; scrollbar-gutter: stable; scrollbar-color: #58677a #0a1018; padding: 14px; border-right: 1px solid var(--line); background: #0a1018; }
aside section, aside details { margin-bottom: 16px; }
.prompt { padding: 12px; border: 1px solid #34655e; border-radius: 9px; background: linear-gradient(145deg, #10241f, #101722); }
.prompt p { margin-bottom: 0; color: #c6d1df; font-size: 11px; }
.prompt .comparison { margin-top: 9px; padding-top: 8px; border-top: 1px solid #31524f; color: var(--aqua); font-size: 9px; }
.verbs > div, .legend > div { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; padding: 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
kbd { flex: 0 0 auto; padding: 3px 5px; border: 1px solid #506177; border-bottom-width: 2px; border-radius: 4px; background: #182231; font: 9px ui-monospace, monospace; }
.verbs p, .legend p { margin: 0; }
.verbs b, .verbs span, .legend b, .legend span { display: block; }
.verbs .or { flex: 0 0 auto; color: #607087; font-size: 8px; }
.verbs b, .legend b { font-size: 10px; }
.verbs span, .legend span { margin-top: 2px; color: var(--muted); font-size: 9px; }
details { border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
summary { padding: 9px 10px; cursor: pointer; font-size: 10px; font-weight: 750; }
details ul { margin: 0; padding: 0 12px 10px 27px; color: var(--muted); font-size: 9px; line-height: 1.42; }
details li + li { margin-top: 5px; }
.legend i { width: 17px; height: 17px; border: 2px solid currentColor; rotate: 45deg; }
.legend .ember { color: var(--hot); border-radius: 50%; }
.legend .ballast { color: #d1c3a4; }
.legend .rime { color: var(--cold); clip-path: polygon(50% 0,100% 100%,0 100%); }
.variant-note { padding: 10px; border-left: 2px solid var(--aqua); background: #101821; }
.variant-note p { margin-bottom: 0; color: var(--muted); font-size: 10px; }
.variant-note small { display: block; margin-top: 8px; color: #718198; font-size: 8px; }
.playfield { position: relative; min-width: 0; min-height: 0; height: 100%; overflow: hidden; display: grid; grid-template-rows: 52px minmax(0, 1fr); }
.hud { display: grid; grid-template-columns: minmax(190px, 1fr) minmax(220px, 1.35fr) auto; align-items: center; gap: 18px; padding: 7px 13px; border-bottom: 1px solid var(--line); background: #0d141e; }
.meter-block { display: grid; grid-template-columns: auto minmax(70px, 1fr) auto; align-items: center; gap: 8px; }
.meter-block > span, .run-state span { color: var(--muted); font-size: 8px; letter-spacing: .11em; }
.meter-block > b, .run-state b { font: 9px ui-monospace, monospace; }
.meter { overflow: hidden; height: 8px; border: 1px solid #35455a; border-radius: 6px; background: #080c12; }
.meter i { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, #eb6371, #78e5d2); transition: width .1s; }
.reservoir .meter i { width: 40%; background: linear-gradient(90deg, #6cc8ff, #f5bc62); }
.run-state { min-width: 120px; text-align: right; }
.run-state span, .run-state b { display: block; }
.run-state b { margin-top: 3px; }
#field { display: block; min-width: 0; min-height: 0; width: 100%; height: 100%; background: #080c12; outline: none; cursor: crosshair; }
.pointer-tools { position: absolute; z-index: 5; left: 11px; bottom: 10px; display: flex; align-items: center; gap: 5px; padding: 6px; border: 1px solid #33455a; border-radius: 8px; background: #0c131ddd; backdrop-filter: blur(4px); }
.pointer-tools span { margin: 0 3px; color: var(--muted); font-size: 7px; letter-spacing: .1em; }
.pointer-tools button { padding: 5px 8px; font-size: 9px; }
.pointer-tools button.selected { border-color: var(--aqua); background: #15332e; color: var(--aqua); }
.target-card { position: absolute; right: 12px; top: 64px; width: 220px; padding: 10px; border: 1px solid #3b4c62; border-radius: 8px; background: rgba(12,18,27,.91); backdrop-filter: blur(5px); pointer-events: none; transition: opacity .15s; }
.target-card.hidden { opacity: 0; }
.target-card > b { font-size: 11px; }
.target-card > span { float: right; color: var(--muted); font-size: 9px; text-transform: uppercase; }
.target-card > div { display: grid; grid-template-columns: 62px 1fr 28px; align-items: center; gap: 5px; margin-top: 8px; }
.target-card label, .target-card strong, .target-card small { color: var(--muted); font: 8px ui-monospace, monospace; }
.target-card strong { text-align: right; }
.target-card small { display: block; margin-top: 8px; }
.temp-track, .hp-track { overflow: hidden; height: 6px; border-radius: 5px; background: #253246; }
.temp-track em, .hp-track em { display: block; width: 40%; height: 100%; background: linear-gradient(90deg, var(--cold), #d9e5eb, var(--hot)); }
.hp-track em { width: 100%; background: var(--aqua); }
.aim-hint { position: absolute; left: 50%; bottom: 57px; translate: -50%; padding: 6px 10px; border-radius: 6px; background: #0b111bbb; color: var(--muted); font-size: 9px; pointer-events: none; transition: opacity .3s; }
.modal, .start-overlay { position: fixed; z-index: 20; inset: 0; display: grid; place-items: center; background: rgba(3,6,10,.76); }
.modal.hidden, .start-overlay.hidden { display: none; }
.modal-card { width: min(620px, 90vw); padding: 20px; border: 1px solid #3b615e; border-radius: 10px; background: #101923; box-shadow: 0 24px 70px #000c; }
.modal-card h2 { margin: 6px 0; font-size: 17px; text-transform: none; letter-spacing: 0; }
.modal-card p { color: var(--muted); font-size: 11px; }
#upgrade-choices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
#upgrade-choices button { min-height: 105px; padding: 12px; text-align: left; }
#upgrade-choices b, #upgrade-choices span { display: block; }
#upgrade-choices b { color: var(--aqua); font-size: 11px; }
#upgrade-choices span { margin-top: 7px; color: var(--muted); font-size: 9px; line-height: 1.4; }
.start-overlay { position: absolute; top: 52px; }
.start-overlay > div { padding: 18px 23px; border: 1px solid #41645f; border-radius: 9px; background: #101923ee; text-align: center; }
.start-overlay b, .start-overlay p { display: block; }
.start-overlay b { margin-top: 5px; font-size: 13px; }
.start-overlay p { margin: 7px 0 0; color: var(--muted); font-size: 10px; }
#toast { position: fixed; z-index: 30; left: 50%; bottom: 17px; translate: -50% 12px; max-width: 450px; padding: 10px 14px; border: 1px solid #4b706a; border-radius: 8px; background: #10231f; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 11px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 850px) {
header { grid-template-columns: auto 1fr auto; gap: 8px; padding-inline: 8px; }
.title h1 { font-size: 15px; }
.title span { font-size: 7px; }
header button { padding: 6px 7px; font-size: 10px; }
main { grid-template-columns: 215px minmax(0, 1fr); }
aside { padding: 10px; }
.hud { grid-template-columns: 1fr 1fr; gap: 6px; height: auto; }
.run-state { display: none; }
.pointer-tools > span { display: none; }
}
@media (max-width: 560px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
.top-actions { justify-content: flex-start; }
main { height: auto; overflow: visible; grid-template-columns: 1fr; }
aside { max-height: 430px; }
.playfield { height: 620px; }
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,70 @@
# Playtest Analysis — Session 0732452b
Date: 2026-08-16
Source log: `JSONL/conversion-breach-0732452b-3ce4-4329-aa69-b121180c61a8.jsonl`
## Observed Behavior
- Total logged duration: 970 seconds (about 16 minutes 10 seconds).
- Breach events span about 14 minutes 23 seconds across two breach segments.
- The player reached wave 11 and cleared ten complete waves.
- 74 constructs were destroyed in Breach.
- 67 kinetic discharges affected an average of 1.66 constructs; none affected zero targets.
- 184 tether sessions: 139 extraction and 45 injection.
- 25 tether sessions lasted under 150 ms, consistent with at least some control interruption or accidental taps; the log cannot prove the cause.
- 80 thermal-state transitions, including 33 entries into brittle and only two into unstable.
- Only one unstable release occurred.
- 69 of 74 destroys came from wall impacts. Five came from construct collisions.
- 19 player-damage events occurred. Thirteen were in the first logged Breach wave; only six occurred across later waves. Minimum recorded health was 22.
- Reservoir was below 5 in 42 of 150 Breach snapshots (28%). Mean snapshot reservoir was 47.5, but later capacity upgrades inflate that mean.
- 24 calibration choices: 19 conversion, four capacity, one transfer.
- Final calibration values were capacity 240, transfer 35.1, conversion 85.071. Multiplicative conversion scaling became extreme.
## Player Report
- Calm was perceived as a sandbox/tutorial rather than a test and was left quickly for Breach.
- Breach remained interesting for an estimated 510 minutes; the log shows longer engagement.
- Trackpad palm rejection made keyboard-plus-pointer controls poor during this run. The player subsequently obtained a mouse, so this should not be treated as a central cause in the next comparison.
- Initial tactic: extract as much heat as possible, then blast.
- Revised tactic: avoid leaving the final enemy brittle and depleted unless already positioned for an immediate wall blast.
- Capacity felt unhelpful; transfer already felt fast; most upgrades went to conversion.
- The player tried to ram constructs after perceiving possible collision damage, but could not understand or safely reproduce it.
- Extracting through a wall felt too safe.
- Crowded constructs appeared to phase through walls.
- Full-screen layout hid instructions and/or part of the field; the player could not confidently scroll. A narrower window appeared to fit better.
## Important Model Mismatch
The implementation multiplies brittle collision damage by 13. Brittle targets do not take less collision damage. The reported difficulty killing a final brittle target is more plausibly a resource-state problem: extraction removed the target's heat, the player reservoir was empty, and no other enemy remained as fuel. Cooling also reduced pursuit speed, which could make arranging an unpowered collision harder.
Do not “correct” the player report as if their experience were invalid. The mismatch is evidence that the causal feedback did not clearly distinguish toughness from lack of available impulse.
Player ramming did not deal construct damage in the implementation. The player nevertheless formed and tested that hypothesis from observed collision behavior. This is positive voluntary-experiment evidence, while the inability to read the result is a feedback/affordance failure.
## What This Supports
- A coupled tradeoff produced real adaptation: extracting heat both funded attacks and changed the remaining target/resource state.
- Practical stakes and/or wave progression provided incentive absent from Experiments 000 and 001.
- The player chose upgrades instrumentally around the perceived bottleneck rather than distributing them evenly.
- Knowledge and power both increased: later waves generally produced few player hits and many were cleared much faster.
- Visible physical laws generated at least one unrequired hypothesis (ramming).
## What This Does Not Establish
- Pressure was not isolated. Calm lacked continuing waves/progression and looked like a testing room.
- Direct control was not isolated from action feedback, wave goals, upgrades, escalating power, and enemies.
- The property system may still be shallow. Wall blasts dominated, brittle was common, unstable behavior was nearly absent, and conversion upgrades overwhelmed other choices.
- Later safety may reflect learning, exponential power, through-wall extraction, collision bugs, or their combination.
- Continued play may have been driven by wave progression/power accumulation rather than curiosity about laws.
## Corrective Work Before Further Comparison
- Make Calm a complete wave/calibration variant differing only in pursuit and player damage.
- Clearly label Calm and Breach as two full experimental variants.
- Add pointer-only movement and thermal-mode controls; add Space as an alternate discharge input. Retain these as general accessibility/readability improvements, but do not center the next test on trackpad friction because the player now has a mouse.
- Make the entire field and instructions fit a full-screen viewport, with unmistakable scrolling where needed and resize telemetry.
- Block tethering through obstacles.
- Strengthen obstacle collision resolution to prevent clustered phasing.
- Improve feedback that separates low target heat, low player reservoir, target integrity, and actual impact damage.
- Preserve the current playtest build's result before using corrected behavior as new evidence.

View file

@ -0,0 +1,50 @@
# Playtest Analysis — Session bb30ddfc
Date: 2026-08-16
Source log: `JSONL/conversion-breach-bb30ddfc-8e4f-4e18-bb7e-22235266d9ed.jsonl`
Prototype revision: 2
## Observed Behavior
- Total logged duration was 1,179 seconds, about 19 minutes 39 seconds.
- Calm lasted about 2 minutes 55 seconds. The player destroyed 12 constructs, cleared wave 1, and defeated six of the seven constructs in wave 2 before switching.
- Breach then lasted about 16 minutes 44 seconds. The player reached wave 29, cleared 28 waves, and destroyed 195 constructs.
- The player made 208 kinetic discharges: 25 in Calm and 183 in Breach. Four Breach discharges affected no target.
- There were 431 tether sessions: 343 extraction and 88 injection. Injection was more common than in the first session.
- The pointer transfer buttons themselves were toggled only once in each direction; most injection continued to use Shift.
- There were 69 forced calibration choices. In Breach these were capacity 33, conversion 24, and transfer 8. This is substantially broader than the first session's conversion-heavy 19/24 distribution.
- Final Breach values were capacity 1,255, transfer 220.247, and conversion 374.144. All three upgrade dimensions became extreme.
- 176 of 207 total defeats came from wall impacts: all 12 Calm defeats and 164 of 195 Breach defeats. Construct collisions accounted for most of the remaining non-release defeats.
- There were 16 unstable releases, all in Breach. Twelve occurred in the middle portion of the run, but they did not displace wall impact as the dominant answer.
- Breach produced 49 player-damage events but no collapse. Damage remained intermittent into late waves.
- Later Breach waves often took only roughly 1025 seconds. From wave 24 onward, conversion climbed from 40.565 to 374.144 within about 70 seconds.
## Player Report
- The player could not say much beyond the first playtest and is ready for a different experiment.
- A mouse made control easier.
- Easier input led them to use wider conduit and reservoir upgrades more than before.
- Eventually the default amount of supplied heat was sufficient to blast everything away.
- Accidental Shift plus right-click could still open the browser context menu and disrupt play.
- Clicking interface text outside the canvas selected text. The player considers these browser-medium problems rather than central game problems.
## Interpretation
The mouse had a measurable behavioral effect, so the original input complaint was not noise. Upgrade selection diversified and injection was used more often. However, this did not generate a materially new reported experience or a new strategic layer.
The strongest result is not merely that conversion was overtuned. The activity offered one durable answer—acquire any heat and turn it into wall-directed impulse—and every upgrade increased the throughput or reserve of that same answer. Capacity and transfer therefore broadened the numerical build without broadening the decision structure.
The long session should not be read as equivalent to deep interest. Wave progression, repeated calibration interruptions, rapid power accumulation, and a desire to see the system's limit can all sustain behavior after strategic curiosity has ended. The player's explicit “can't really say much more” and readiness to move on are important counterevidence to duration as a fun proxy.
The matched comparison gives weak evidence that threat pressure increases continuation: Calm received almost three minutes and Breach nearly seventeen. It does not show that pressure creates deeper reasoning or greater enjoyment. Familiarity with Breach, action intensity, wave escalation, or simple challenge could explain the difference.
The coupled heat tradeoff mattered early in Experiment 002, but numerical escalation eventually erased it. Once a small amount of heat created overwhelming impulse, target temperature, remaining fuel, mass, position, and reservoir scarcity stopped constraining the answer. This is direct evidence of strategy crystallization inside a short run.
## Design Consequence
Do not make Experiment 003 by adding enemy types, nerfing conversion, extending waves, or offering more numerical upgrades. Those changes would tune the lifetime of the same dominant answer without testing whether another activity is fun.
The next experiment should remove escalating numerical power and test whether the player enjoys adapting a directly controlled constructed artifact to qualitatively changing physical requirements. This preserves instrumental building and direct agency while replacing repeated extermination waves with different problems that should demand different layouts. It also probes whether construction felt tedious in Experiment 001 because it programmed an autonomous agent, or because construction itself lacked useful leverage.

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" --directory "$EXPERIMENT_DIR/prototype" --log-directory "$REPO_ROOT/JSONL" --port 8000

View file

@ -0,0 +1,72 @@
# Experiment 003 — Rig Trials
## Question
Is modifying a physical construction enjoyable when it directly changes the body the player pilots, and do qualitatively changing requirements preserve reasoning better than escalating statistics?
## Hypotheses
The leading possibility is that Experiments 000 and 001 made construction feel like low-leverage programming, while enjoyed construction games let a design become the player's practical capability. A competing explanation is that modular construction will still collapse into obvious loadout work or a universal rig. See [`hypothesis.md`](hypothesis.md).
## Difference From Previous Experiment
- The player modifies a persistent rig rather than upgrading numeric character statistics.
- The constructed object is controlled directly; it is not an autonomous agent.
- Three complete trials impose different physical requirements without unlocking parts.
- Existing construction persists when switching trials and edits are cheap.
- Physical placement affects connectivity, thruster exhaust, tool exposure, cooling, shielding, mass, and wind response.
- There are no enemies, defeat waves, random drops, or multiplicative progression.
## Expected Result
Support would look like revising the rig after observing a physical weakness, retaining a useful subassembly while changing the overall layout, or testing a build idea beyond the minimum trial requirement. Completion by itself is weak evidence.
A universal starter layout, obvious one-part substitutions, or course execution dominating build decisions would count against the intended mechanism.
## Controls
- Every part and every trial is available immediately.
- Part behavior is deterministic and displayed.
- The same rig and direct controls are used across all trials.
- Trial completion grants no statistical power.
- Switching trials preserves the build but resets only the field state.
- After a switch, reset, or edit, the field waits until the arena is clicked so construction is not performed under accidental trial pressure.
## How To Run
From this directory:
```bash
./run.sh
```
Then open <http://localhost:8000>.
## What To Pay Attention To
- Use the starter rig or change it whenever you want.
- Try any or all trials; switching does not erase the rig.
- Use **Save JSONL** when finished. When launched through `run.sh`, it writes directly into the repository-level `JSONL/` directory; a browser download remains the fallback if the local save server is unavailable.
## Result
The first playtest completed all three trials in about 6:47 but was reported as “pretty boring.” Haul consumed most of the session and involved repeated tractor/cargo manipulation plus two unexpected thermal collapses. Furnace was answered by stacking five sinks around one remaining right drive, then completed in under eight seconds. Gale was completed with four cardinal drives and no ballast. See [`results/e1fa0f4d-analysis.md`](results/e1fa0f4d-analysis.md).
## Validation
- JavaScript syntax checking passes.
- A 1672×976 Chromium screenshot—the viewport used in the preceding playtest log—kept the whole arena visible and all essential builder controls on screen.
- Real browser input placed and removed modules, rotated placement, switched trials, piloted the rig, produced completion/failure events, and emitted revision-tagged JSONL without runtime exceptions.
- The untouched starter rig thermally collapsed around 69% of Furnace. Adding a parallel right-facing drive completed it. Placing a drive directly behind another correctly blocked the inner exhaust instead of increasing thrust.
- Haul rejected a misaligned tractor approach and accepted a corrected approach; the attached fragment immediately increased effective mass. The open-loop validator did not complete the return route because it cannot visually correct momentum around walls.
- Trial fields remain stationary while the player edits. Gale begins only after the arena is clicked.
## Interpretation
Controls were not the primary problem. Haul prompted an unintended two-tractor juggling workaround only because the player missed Sinks and misread heat as a movement budget; it was still boring. Furnace reduced to one forward Drive plus five Sinks, Gale reduced to restoring the default rig, and lattice placement felt inconsequential except for fiddly Drive orientation.
The prototype behaved like a spatially slower equipment menu. Changing requirements produced prescribed full loadout swaps rather than meaningful recomposition. This is negative evidence for H06/H17 as implemented, not a general rejection of physical construction.
## Next Best Experiment
Pivot to a genuine legible-discovery experiment. Test whether learning and transferring a surprising stable rule has pull independent of combat power, numerical progression, or a cosmetic construction lattice.

View file

@ -0,0 +1,47 @@
# Experiment 003 Hypothesis
## Primary Comparison
One persistent rig is exposed to three qualitative requirements:
- **Haul:** tool exposure, cargo mass, and navigation;
- **Furnace:** drive heat, ambient heat, ward orientation, and cooling layout;
- **Gale:** changing wind, ballast anchoring, directional thrust, and maneuvering.
The comparison is not which trial is best. It is whether the player changes a physical construction for reasons learned by piloting it, and whether knowledge about parts transfers without one whole layout solving everything.
## Competing Explanations
1. Construction is valuable when it becomes the player's directly controlled capability.
2. Changing requirements create useful adaptation even if any single trial is shallow.
3. Direct piloting adds surface action but construction remains obvious loadout selection.
4. Physical placement is too weak or too opaque, so only part counts matter.
5. A universal rig crystallizes immediately.
6. Cheap revision still feels like repetitive implementation rather than reasoning.
## Evidence Priorities
Strong positive evidence:
- rebuild after a concrete observed failure;
- preserve a useful subassembly while changing other structure;
- deliberately exploit exposure, adjacency, mass, or directional behavior;
- voluntary trial or layout experiment not needed for completion;
- desire to improve or test the rig after a pass.
Weak evidence:
- completing all trials;
- spending a long time driving;
- repeatedly retrying the same build;
- placing the part named by a trial description.
## Confounds
- poor driving feel;
- an impossible or trivial tuning threshold;
- unclear placement/rotation controls;
- trial geometry requiring dexterity rather than construction;
- the starter rig being too competent;
- checklist-like descriptions prescribing the solution.

View file

@ -0,0 +1,609 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const $$ = selector => [...document.querySelectorAll(selector)];
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const GRID = 5;
const CORE = { x: 2, y: 2 };
const BUDGET = 12;
const PARTS = {
drive: { name: "Drive", glyph: "▲", cost: 2, mass: .7, color: "#f3b65d", oriented: true },
tractor: { name: "Tractor", glyph: "◇", cost: 2, mass: .6, color: "#71bceb", oriented: true },
sink: { name: "Sink", glyph: "❄", cost: 2, mass: 1, color: "#8bd9ff" },
ward: { name: "Ward", glyph: "▰", cost: 2, mass: 1.4, color: "#d4a6ff", oriented: true },
ballast: { name: "Ballast", glyph: "⬢", cost: 1, mass: 3.2, color: "#c4b69d" },
frame: { name: "Frame", glyph: "+", cost: 1, mass: 1.1, color: "#91a7a2" }
};
const DIR = {
up: { x: 0, y: -1, arrow: "↑", key: "KeyW", angle: -Math.PI / 2 },
right: { x: 1, y: 0, arrow: "→", key: "KeyD", angle: 0 },
down: { x: 0, y: 1, arrow: "↓", key: "KeyS", angle: Math.PI / 2 },
left: { x: -1, y: 0, arrow: "←", key: "KeyA", angle: Math.PI }
};
const FACING_ORDER = ["up", "right", "down", "left"];
const TRIALS = {
haul: {
kicker: "TRIAL 01 · RECOVERY", name: "Haul",
objective: "Recover both loose fragments. Engage an exposed tractor with E and return each fragment to the home bay."
},
furnace: {
kicker: "TRIAL 02 · THERMAL CROSSING", name: "Furnace",
objective: "Reach the receiver on the far side. The curtain heats the core and firing drives adds heat; collapse occurs at 100°."
},
gale: {
kicker: "TRIAL 03 · VARIABLE LOAD", name: "Gale",
objective: "Stabilize the three beacons in order. The wind changes after each beacon; touching the outer boundary restarts the attempt."
}
};
const OBSTACLES = {
haul: [
{ x: .42, y: .08, w: .055, h: .34 }, { x: .42, y: .58, w: .055, h: .34 },
{ x: .68, y: .31, w: .055, h: .38 }
],
furnace: [
{ x: .44, y: .05, w: .07, h: .27 }, { x: .44, y: .68, w: .07, h: .27 },
{ x: .66, y: .20, w: .07, h: .25 }, { x: .66, y: .57, w: .07, h: .23 }
],
gale: [
{ x: .29, y: .37, w: .07, h: .31 }, { x: .53, y: .08, w: .07, h: .30 },
{ x: .53, y: .64, w: .07, h: .28 }, { x: .77, y: .31, w: .06, h: .37 }
]
};
const STARTER = [
{ x: 2, y: 3, type: "drive", facing: "up" },
{ x: 2, y: 1, type: "drive", facing: "down" },
{ x: 3, y: 2, type: "drive", facing: "left" },
{ x: 1, y: 2, type: "drive", facing: "right" },
{ x: 3, y: 1, type: "tractor", facing: "up" }
];
const state = {
trial: "haul", selectedPart: "drive", facing: "up", paused: false, engaged: false, started: Date.now(),
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`, logs: [],
build: Array(GRID * GRID).fill(null), keys: new Set(), complete: new Set(), trialDone: false,
rig: { x: .14, y: .5, vx: 0, vy: 0, heat: 25, attached: null },
cargo: [], delivered: 0, checkpoint: 0, attempt: 0, attemptStarted: Date.now(),
lastFrame: performance.now(), snapshotClock: 0, collisionCooldown: 0, heatBand: "normal",
toastTimer: null, inputStarts: new Map()
};
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const round = value => Math.round(value * 1000) / 1000;
const index = (x, y) => y * GRID + x;
const inGrid = (x, y) => x >= 0 && y >= 0 && x < GRID && y < GRID;
const partAt = (x, y) => inGrid(x, y) ? state.build[index(x, y)] : null;
function log(type, data = {}) {
const event = {
schema: 1, experiment: "003_rig_trials", prototype_revision: 1,
session_id: state.session, elapsed_ms: Date.now() - state.started,
trial: state.trial, attempt: state.attempt, type, ...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("rig-trials-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function installStarter() {
state.build.fill(null);
state.build[index(CORE.x, CORE.y)] = { type: "core", facing: "up" };
STARTER.forEach(part => { state.build[index(part.x, part.y)] = { type: part.type, facing: part.facing }; });
}
function buildCost() {
return state.build.reduce((sum, part) => sum + (part && PARTS[part.type] ? PARTS[part.type].cost : 0), 0);
}
function connectedCells() {
const connected = new Set([index(CORE.x, CORE.y)]), queue = [{ ...CORE }];
while (queue.length) {
const cell = queue.shift();
for (const direction of Object.values(DIR)) {
const x = cell.x + direction.x, y = cell.y + direction.y, key = index(x, y);
if (!inGrid(x, y) || !partAt(x, y) || connected.has(key)) continue;
connected.add(key); queue.push({ x, y });
}
}
return connected;
}
function exposedSideCount(x, y) {
return Object.values(DIR).filter(direction => !partAt(x + direction.x, y + direction.y)).length;
}
function leadingFace(x, y, facing) {
const direction = DIR[facing];
for (let nx = x + direction.x, ny = y + direction.y; inGrid(nx, ny); nx += direction.x, ny += direction.y) {
if (partAt(nx, ny)) return false;
}
return true;
}
function workingPart(x, y, part, connected = connectedCells()) {
if (!part || part.type === "core" || !connected.has(index(x, y))) return part?.type === "core";
if (part.type === "drive") {
const direction = DIR[part.facing];
return !partAt(x - direction.x, y - direction.y);
}
if (part.type === "tractor") {
const direction = DIR[part.facing];
return !partAt(x + direction.x, y + direction.y);
}
return true;
}
function rigStats() {
const connected = connectedCells();
const drives = { up: 0, right: 0, down: 0, left: 0 };
let mass = 3, sinks = 0, ward = 0, ballast = 0, tractors = 0, invalid = 0;
state.build.forEach((part, key) => {
if (!part || part.type === "core") return;
const x = key % GRID, y = Math.floor(key / GRID), working = workingPart(x, y, part, connected);
mass += PARTS[part.type].mass;
if (!working) invalid++;
if (part.type === "drive" && working) drives[part.facing]++;
if (part.type === "tractor" && working) tractors++;
if (part.type === "ballast" && connected.has(key)) ballast++;
if (part.type === "sink" && connected.has(key)) {
const adjacentCore = Math.abs(x - CORE.x) + Math.abs(y - CORE.y) === 1;
sinks += exposedSideCount(x, y) * .9 + (adjacentCore ? 3.3 : 0);
}
if (part.type === "ward" && connected.has(key) && part.facing === "up" && leadingFace(x, y, "up")) ward++;
});
return { connected, drives, mass, cooling: 1.2 + sinks, ward, ballast, tractors, invalid, cost: buildCost() };
}
function buildSignature() {
return state.build.map((part, key) => part ? `${key}:${part.type}:${part.facing || "-"}` : null).filter(Boolean);
}
function renderBuilder() {
const grid = $("#build-grid"); grid.innerHTML = "";
const stats = rigStats();
state.build.forEach((part, key) => {
const x = key % GRID, y = Math.floor(key / GRID), cell = document.createElement("button");
cell.className = "cell"; cell.dataset.x = x; cell.dataset.y = y;
if (part) {
cell.classList.add(part.type === "core" ? "core" : "occupied"); cell.dataset.type = part.type;
const working = workingPart(x, y, part, stats.connected);
if (!working && part.type !== "core") cell.classList.add("invalid");
const definition = part.type === "core" ? { glyph: "◆", name: "Core" } : PARTS[part.type];
cell.innerHTML = `${definition.oriented ? `<span class="facing">${DIR[part.facing].arrow}</span>` : ""}<i>${definition.glyph}</i><small>${part.type === "core" ? "CORE" : definition.cost}</small>`;
cell.title = `${definition.name}${working ? "" : " — blocked or disconnected"}`;
} else cell.title = `Place ${PARTS[state.selectedPart].name}`;
cell.addEventListener("pointerdown", onBuildCell);
cell.addEventListener("contextmenu", event => event.preventDefault());
grid.append(cell);
});
$("#budget").textContent = `${stats.cost} / ${BUDGET}`;
$("#budget").style.color = stats.cost > BUDGET ? "var(--red)" : "var(--amber)";
const driveText = `${stats.drives.up}${stats.drives.right}${stats.drives.down}${stats.drives.left}`;
$("#rig-metrics").innerHTML = `
<div><span>WORKING DRIVE</span><b>${driveText}</b></div>
<div><span>MASS</span><b>${stats.mass.toFixed(1)}</b></div>
<div><span>COOLING</span><b>${stats.cooling.toFixed(1)}/s</b></div>
<div><span>TRACTORS</span><b>${stats.tractors}</b></div>
<div><span>FURNACE WARD</span><b>${Math.min(72, stats.ward * 28)}%</b></div>
<div><span>INVALID</span><b>${stats.invalid}</b></div>`;
updateStatus(stats);
}
function onBuildCell(event) {
event.preventDefault();
const x = Number(event.currentTarget.dataset.x), y = Number(event.currentTarget.dataset.y), key = index(x, y);
const existing = state.build[key];
if (existing?.type === "core") { toast("The core is the fixed structural root."); return; }
if (event.button === 2) {
if (!existing) return;
state.build[key] = null;
log("part_removed", { x, y, part: existing.type, facing: existing.facing, build: buildSignature() });
afterBuildEdit("part_removed"); return;
}
if (event.button !== 0) return;
const selected = PARTS[state.selectedPart];
const nextCost = buildCost() - (existing && PARTS[existing.type] ? PARTS[existing.type].cost : 0) + selected.cost;
if (nextCost > BUDGET) { toast(`Budget ${nextCost}/${BUDGET}. Remove or replace a module first.`); log("part_rejected", { reason: "budget", x, y, part: state.selectedPart, projected_cost: nextCost }); return; }
if (existing?.type === state.selectedPart && selected.oriented) {
const nextFacing = FACING_ORDER[(FACING_ORDER.indexOf(existing.facing) + 1) % FACING_ORDER.length];
state.build[key] = { ...existing, facing: nextFacing };
log("part_rotated", { x, y, part: existing.type, before: existing.facing, after: nextFacing, build: buildSignature() });
} else {
state.build[key] = { type: state.selectedPart, facing: selected.oriented ? state.facing : "up" };
log("part_placed", { x, y, part: state.selectedPart, facing: state.build[key].facing, replaced: existing?.type || null, build: buildSignature() });
}
afterBuildEdit("build_changed");
}
function afterBuildEdit(reason) {
renderBuilder(); resetAttempt(reason);
}
function setSelectedPart(type) {
state.selectedPart = type;
$$(".palette button").forEach(button => button.classList.toggle("selected", button.dataset.part === type));
log("palette_selected", { part: type, facing: state.facing });
}
function setFacing(facing) {
const before = state.facing; state.facing = facing;
$$(".orientation-buttons button").forEach(button => button.classList.toggle("selected", button.dataset.facing === facing));
if (before !== facing) log("placement_facing_changed", { before, after: facing });
}
function rotateFacing() {
setFacing(FACING_ORDER[(FACING_ORDER.indexOf(state.facing) + 1) % FACING_ORDER.length]);
}
function setTrial(trial) {
if (trial === state.trial) return;
const before = state.trial; state.trial = trial; state.attempt = 0;
$$(".trial-switch button").forEach(button => button.classList.toggle("selected", button.dataset.trial === trial));
updateTrialBrief(); resetAttempt("trial_switch"); log("trial_changed", { before, after: trial, build_preserved: true });
}
function updateTrialBrief() {
const trial = TRIALS[state.trial];
$("#trial-kicker").textContent = trial.kicker; $("#trial-name").textContent = trial.name; $("#trial-objective").textContent = trial.objective;
updateHUD();
}
function resetAttempt(reason = "manual") {
releaseDriveInputs(reason); state.attempt++; state.attemptStarted = Date.now(); state.trialDone = false; state.engaged = false;
Object.assign(state.rig, { x: state.trial === "gale" ? .12 : .14, y: state.trial === "gale" ? .78 : .5, vx: 0, vy: 0, heat: 25, attached: null });
state.delivered = 0; state.checkpoint = 0; state.heatBand = "normal"; state.collisionCooldown = 0;
state.cargo = state.trial === "haul" ? [
{ id: "fragment-a", x: .82, y: .22, delivered: false, attached: false },
{ id: "fragment-b", x: .86, y: .79, delivered: false, attached: false }
] : [];
log("attempt_started", { reason, build: buildSignature(), stats: serialStats(rigStats()) });
updateHUD(); updateStatus(rigStats());
}
function serialStats(stats) {
return { cost: stats.cost, mass: round(stats.mass), drives: stats.drives, cooling: round(stats.cooling), ward: stats.ward, ballast: stats.ballast, tractors: stats.tractors, invalid: stats.invalid };
}
function update(dt) {
if (state.paused || state.trialDone || !state.engaged) return;
const stats = rigStats();
const cargoMass = state.rig.attached ? 5 : 0, mass = stats.mass + cargoMass;
let activeDrives = 0;
for (const facing of FACING_ORDER) {
if (!state.keys.has(DIR[facing].key)) continue;
const count = stats.drives[facing]; activeDrives += count;
state.rig.vx += DIR[facing].x * count * 1.25 / mass * dt;
state.rig.vy += DIR[facing].y * count * 1.25 / mass * dt;
}
if (state.trial === "gale") applyGale(stats, mass, dt);
state.rig.heat += activeDrives * 3.2 * dt;
if (state.trial === "furnace" && state.rig.x > .34 && state.rig.x < .79) {
const protection = Math.min(.72, stats.ward * .28);
state.rig.heat += 36 * (1 - protection) * dt;
}
if (state.rig.heat > 25) state.rig.heat = Math.max(25, state.rig.heat - stats.cooling * dt);
updateHeatBand();
if (state.rig.heat >= 100) { failAttempt("thermal_collapse", "Core reached 100°. The attempt restarted with your build intact."); return; }
const drag = Math.pow(state.rig.attached ? .28 : .38, dt);
state.rig.vx *= drag; state.rig.vy *= drag;
const speed = Math.hypot(state.rig.vx, state.rig.vy), cap = state.rig.attached ? .30 : .46;
if (speed > cap) { state.rig.vx *= cap / speed; state.rig.vy *= cap / speed; }
const old = { x: state.rig.x, y: state.rig.y };
state.rig.x += state.rig.vx * dt; state.rig.y += state.rig.vy * dt;
resolveArena(old);
state.collisionCooldown = Math.max(0, state.collisionCooldown - dt);
updateAttachedCargo(); checkTrialProgress();
state.snapshotClock += dt;
if (state.snapshotClock >= 5) {
state.snapshotClock = 0;
log("field_snapshot", {
rig: { x: round(state.rig.x), y: round(state.rig.y), vx: round(state.rig.vx), vy: round(state.rig.vy), heat: round(state.rig.heat), attached: state.rig.attached },
progress: state.trial === "haul" ? state.delivered : state.trial === "gale" ? state.checkpoint : round(state.rig.x),
stats: serialStats(stats)
});
}
}
function applyGale(stats, mass, dt) {
const winds = [
{ x: .15, y: -.72, label: "NORTHWARD" },
{ x: -.82, y: .10, label: "WESTWARD" },
{ x: .35, y: .72, label: "SOUTHEAST" }
];
const wind = winds[Math.min(state.checkpoint, winds.length - 1)], anchor = 1 + stats.ballast * 1.15;
state.rig.vx += wind.x / (mass * anchor) * dt; state.rig.vy += wind.y / (mass * anchor) * dt;
}
function rigRadius() {
let extent = 1;
state.build.forEach((part, key) => { if (part) extent = Math.max(extent, Math.hypot(key % GRID - CORE.x, Math.floor(key / GRID) - CORE.y)); });
return .021 + extent * .008;
}
function insideRect(point, radius, rect) {
return point.x > rect.x - radius && point.x < rect.x + rect.w + radius && point.y > rect.y - radius && point.y < rect.y + rect.h + radius;
}
function resolveArena(old) {
const radius = rigRadius();
let hit = false;
for (const obstacle of OBSTACLES[state.trial]) if (insideRect(state.rig, radius, obstacle)) { hit = true; break; }
if (hit) {
state.rig.x = old.x; state.rig.y = old.y; state.rig.vx *= -.28; state.rig.vy *= -.28;
if (state.collisionCooldown <= 0) { state.collisionCooldown = .35; log("obstacle_collision", { x: round(old.x), y: round(old.y), speed: round(Math.hypot(state.rig.vx, state.rig.vy)) }); }
}
const outside = state.rig.x < radius || state.rig.x > 1 - radius || state.rig.y < radius || state.rig.y > 1 - radius;
if (outside && state.trial === "gale") { failAttempt("blown_out", "The gale drove the rig across the boundary. Build preserved; attempt restarted."); return; }
state.rig.x = clamp(state.rig.x, radius, 1 - radius); state.rig.y = clamp(state.rig.y, radius, 1 - radius);
}
function updateAttachedCargo() {
if (!state.rig.attached) return;
const cargo = state.cargo.find(item => item.id === state.rig.attached);
if (!cargo) return;
cargo.x = state.rig.x - .055; cargo.y = state.rig.y;
if (state.rig.x < .22 && state.rig.y > .36 && state.rig.y < .64) {
cargo.attached = false; cargo.delivered = true; state.rig.attached = null; state.delivered++;
log("cargo_delivered", { cargo_id: cargo.id, delivered: state.delivered, elapsed_in_attempt_ms: Date.now() - state.attemptStarted });
toast(`Fragment recovered: ${state.delivered} / 2.`);
}
}
function toggleTractor() {
if (state.trial !== "haul") { toast("Tractors are needed for loose fragments in the Haul trial."); log("tractor_toggled", { result: "no_cargo_in_trial" }); return; }
if (state.rig.attached) {
const cargo = state.cargo.find(item => item.id === state.rig.attached);
if (cargo) { cargo.attached = false; cargo.x = state.rig.x - .06; cargo.y = state.rig.y; }
log("cargo_released", { cargo_id: state.rig.attached, x: round(state.rig.x), y: round(state.rig.y) }); state.rig.attached = null; toast("Fragment released."); return;
}
const tractors = workingTractors();
if (!tractors.length) { toast("No exposed, connected tractor is working."); log("tractor_toggled", { result: "no_working_tractor" }); return; }
let best = null, bestDistance = Infinity;
for (const cargo of state.cargo) {
if (cargo.delivered || cargo.attached) continue;
const d = distance(state.rig, cargo); if (d >= bestDistance || d > .105) continue;
const dx = cargo.x - state.rig.x, dy = cargo.y - state.rig.y, length = Math.max(.001, Math.hypot(dx, dy));
const aligned = tractors.some(tractor => (dx / length) * DIR[tractor.facing].x + (dy / length) * DIR[tractor.facing].y > .35);
if (aligned) { best = cargo; bestDistance = d; }
}
if (!best) { toast("No fragment is close to an exposed tractor face."); log("tractor_toggled", { result: "no_aligned_cargo" }); return; }
best.attached = true; state.rig.attached = best.id;
log("cargo_attached", { cargo_id: best.id, distance: round(bestDistance), rig_mass: round(rigStats().mass + 5) }); toast("Fragment attached. Its mass now changes the rig.");
}
function workingTractors() {
const stats = rigStats(), result = [];
state.build.forEach((part, key) => {
if (!part || part.type !== "tractor") return;
const x = key % GRID, y = Math.floor(key / GRID);
if (workingPart(x, y, part, stats.connected)) result.push({ x, y, facing: part.facing });
});
return result;
}
function checkTrialProgress() {
if (state.trial === "haul" && state.delivered >= 2) completeTrial();
if (state.trial === "furnace" && state.rig.x > .90 && state.rig.y > .36 && state.rig.y < .64) completeTrial();
if (state.trial === "gale") {
const beacons = [{ x: .32, y: .22 }, { x: .62, y: .76 }, { x: .89, y: .27 }], target = beacons[state.checkpoint];
if (target && distance(state.rig, target) < .055) {
state.checkpoint++; log("gale_beacon_stabilized", { beacon: state.checkpoint, heat: round(state.rig.heat), stats: serialStats(rigStats()) });
if (state.checkpoint >= beacons.length) completeTrial();
else toast(`Beacon ${state.checkpoint}/3 stable. The wind changed.`);
}
}
}
function completeTrial() {
if (state.trialDone) return;
state.trialDone = true; state.complete.add(state.trial); releaseDriveInputs("trial_completed");
log("trial_completed", { trial: state.trial, elapsed_in_attempt_ms: Date.now() - state.attemptStarted, build: buildSignature(), stats: serialStats(rigStats()), completions: [...state.complete] });
const button = $(`.trial-switch button[data-trial="${state.trial}"]`); button.classList.add("complete");
toast(`${TRIALS[state.trial].name} complete. No power unlocked; revise, retry, or choose another trial.`);
updateStatus(rigStats());
}
function failAttempt(reason, message) {
log("attempt_failed", { reason, x: round(state.rig.x), y: round(state.rig.y), heat: round(state.rig.heat), build: buildSignature(), stats: serialStats(rigStats()) });
toast(message); resetAttempt(reason);
}
function updateHeatBand() {
const next = state.rig.heat >= 85 ? "critical" : state.rig.heat >= 60 ? "high" : "normal";
if (next !== state.heatBand) { log("heat_band_changed", { before: state.heatBand, after: next, heat: round(state.rig.heat) }); state.heatBand = next; }
}
function updateStatus(stats = rigStats()) {
if (state.trialDone) {
$("#status-line").textContent = `${TRIALS[state.trial].name} complete`;
$("#status-detail").textContent = "Your build did not gain power. You can alter it, retry, or switch trials."; return;
}
if (!state.engaged) {
$("#status-line").textContent = "Attempt ready · click arena to launch";
$("#status-detail").textContent = "The field waits while you inspect or edit the lattice."; return;
}
if (stats.invalid) {
$("#status-line").textContent = `${stats.invalid} inactive module${stats.invalid === 1 ? "" : "s"}`;
$("#status-detail").textContent = "A module is disconnected or its working face is blocked. Dim lattice cells are inactive."; return;
}
$("#status-line").textContent = state.rig.attached ? "Fragment attached · +5 mass" : "All installed modules connected";
$("#status-detail").textContent = "Working drives are shown bright; firing drives also raises core heat.";
}
function updateHUD() {
$("#heat-readout").textContent = `${state.rig.heat.toFixed(0)}°`;
$("#heat-readout").style.color = state.rig.heat >= 85 ? "var(--red)" : state.rig.heat >= 60 ? "var(--amber)" : "var(--text)";
if (state.trial === "haul") {
$("#progress-label").innerHTML = `FRAGMENTS <b>${state.delivered} / 2</b>`; $("#environment-label").innerHTML = "FIELD <b>CALM</b>";
} else if (state.trial === "furnace") {
$("#progress-label").innerHTML = `CROSSING <b>${Math.round(clamp((state.rig.x - .14) / .76, 0, 1) * 100)}%</b>`; $("#environment-label").innerHTML = "CURTAIN <b>36°/s</b>";
} else {
const labels = ["NORTHWARD", "WESTWARD", "SOUTHEAST", "STABLE"];
$("#progress-label").innerHTML = `BEACONS <b>${state.checkpoint} / 3</b>`; $("#environment-label").innerHTML = `WIND <b>${labels[state.checkpoint]}</b>`;
}
updateStatus(rigStats());
}
function canvasMetrics() {
const rect = canvas.getBoundingClientRect(); return { rect, width: rect.width, height: rect.height, scale: Math.min(rect.width, rect.height) };
}
function resizeCanvas() {
const { width, height } = canvasMetrics(), ratio = Math.min(2, devicePixelRatio || 1);
canvas.width = Math.max(1, Math.round(width * ratio)); canvas.height = Math.max(1, Math.round(height * ratio));
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); draw();
}
function draw() {
const { width: W, height: H, scale: S } = canvasMetrics(); if (!W || !H) return;
ctx.clearRect(0, 0, W, H); ctx.fillStyle = "#071013"; ctx.fillRect(0, 0, W, H);
drawGrid(W, H); drawTrial(W, H, S); drawObstacles(W, H); drawRig(W, H, S); updateHUD();
}
function drawGrid(W, H) {
ctx.strokeStyle = "rgba(108,149,139,.09)"; ctx.lineWidth = 1;
for (let x = 0; x < W; x += 30) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
for (let y = 0; y < H; y += 30) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
}
function drawTrial(W, H, S) {
if (state.trial === "haul") {
ctx.fillStyle = "rgba(114,230,189,.09)"; ctx.strokeStyle = "#39705f"; ctx.lineWidth = 2;
ctx.fillRect(.045 * W, .36 * H, .18 * W, .28 * H); ctx.strokeRect(.045 * W, .36 * H, .18 * W, .28 * H);
ctx.fillStyle = "#72e6bd"; ctx.font = "700 9px ui-monospace"; ctx.fillText("HOME BAY", .065 * W, .40 * H);
for (const cargo of state.cargo) if (!cargo.delivered) drawCargo(cargo, W, H, S);
} else if (state.trial === "furnace") {
const gradient = ctx.createLinearGradient(.34 * W, 0, .79 * W, 0);
gradient.addColorStop(0, "rgba(242,111,104,.08)"); gradient.addColorStop(.5, "rgba(243,182,93,.25)"); gradient.addColorStop(1, "rgba(242,111,104,.08)");
ctx.fillStyle = gradient; ctx.fillRect(.34 * W, 0, .45 * W, H);
ctx.strokeStyle = "rgba(243,182,93,.36)"; ctx.setLineDash([7, 8]); ctx.strokeRect(.34 * W, 0, .45 * W, H); ctx.setLineDash([]);
ctx.fillStyle = "rgba(113,188,235,.12)"; ctx.strokeStyle = "#5089aa"; ctx.fillRect(.90 * W, .36 * H, .075 * W, .28 * H); ctx.strokeRect(.90 * W, .36 * H, .075 * W, .28 * H);
ctx.fillStyle = "#8bd9ff"; ctx.font = "700 9px ui-monospace"; ctx.fillText("RECEIVER", .902 * W, .40 * H);
} else {
const beacons = [{ x: .32, y: .22 }, { x: .62, y: .76 }, { x: .89, y: .27 }];
beacons.forEach((beacon, i) => {
ctx.strokeStyle = i < state.checkpoint ? "#72e6bd" : i === state.checkpoint ? "#f3b65d" : "#405054";
ctx.lineWidth = i === state.checkpoint ? 3 : 1.5; ctx.beginPath(); ctx.arc(beacon.x * W, beacon.y * H, .045 * S, 0, Math.PI * 2); ctx.stroke();
ctx.fillStyle = ctx.strokeStyle; ctx.font = "700 9px ui-monospace"; ctx.fillText(String(i + 1), beacon.x * W - 3, beacon.y * H + 3);
});
const winds = [{ x: .15, y: -.72 }, { x: -.82, y: .10 }, { x: .35, y: .72 }], wind = winds[Math.min(state.checkpoint, 2)];
ctx.strokeStyle = "rgba(113,188,235,.24)"; ctx.fillStyle = ctx.strokeStyle; ctx.lineWidth = 2;
for (let i = 0; i < 8; i++) {
const x = ((i * .137 + performance.now() / 15000) % 1) * W, y = ((i * .293 + .1) % 1) * H;
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + wind.x * 42, y + wind.y * 42); ctx.stroke();
}
}
}
function drawCargo(cargo, W, H, S) {
const x = cargo.x * W, y = cargo.y * H, r = .018 * S;
ctx.save(); ctx.translate(x, y); ctx.rotate(performance.now() / 1500); ctx.fillStyle = "#80c8ef"; ctx.strokeStyle = "#d4efff"; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(0, -r); ctx.lineTo(r, 0); ctx.lineTo(0, r); ctx.lineTo(-r, 0); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore();
}
function drawObstacles(W, H) {
for (const obstacle of OBSTACLES[state.trial]) {
ctx.fillStyle = "#172226"; ctx.strokeStyle = "#435659"; ctx.lineWidth = 2;
ctx.fillRect(obstacle.x * W, obstacle.y * H, obstacle.w * W, obstacle.h * H); ctx.strokeRect(obstacle.x * W, obstacle.y * H, obstacle.w * W, obstacle.h * H);
ctx.strokeStyle = "rgba(114,230,189,.11)";
for (let y = obstacle.y * H + 7; y < (obstacle.y + obstacle.h) * H; y += 12) { ctx.beginPath(); ctx.moveTo(obstacle.x * W, y); ctx.lineTo((obstacle.x + obstacle.w) * W, y + 7); ctx.stroke(); }
}
}
function drawRig(W, H, S) {
const x = state.rig.x * W, y = state.rig.y * H, cell = clamp(S * .024, 10, 18), stats = rigStats();
const glow = ctx.createRadialGradient(x, y, 2, x, y, cell * 3.3); glow.addColorStop(0, "rgba(114,230,189,.26)"); glow.addColorStop(1, "rgba(114,230,189,0)");
ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(x, y, cell * 3.3, 0, Math.PI * 2); ctx.fill();
state.build.forEach((part, key) => {
if (!part) return;
const gx = key % GRID, gy = Math.floor(key / GRID), px = x + (gx - CORE.x) * cell, py = y + (gy - CORE.y) * cell;
const working = workingPart(gx, gy, part, stats.connected), definition = part.type === "core" ? { color: "#72e6bd", glyph: "◆" } : PARTS[part.type];
ctx.save(); ctx.globalAlpha = working ? 1 : .28; ctx.fillStyle = "#142126"; ctx.strokeStyle = definition.color; ctx.lineWidth = part.type === "core" ? 2.5 : 1.5;
ctx.fillRect(px - cell * .43, py - cell * .43, cell * .86, cell * .86); ctx.strokeRect(px - cell * .43, py - cell * .43, cell * .86, cell * .86);
ctx.fillStyle = definition.color; ctx.font = `${Math.max(8, cell * .62)}px ui-sans-serif`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(definition.glyph, px, py + .5);
if (PARTS[part.type]?.oriented) {
const direction = DIR[part.facing]; ctx.strokeStyle = definition.color; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(px + direction.x * cell * .72, py + direction.y * cell * .72); ctx.stroke();
}
if (part.type === "drive" && working && state.keys.has(DIR[part.facing].key)) {
const direction = DIR[part.facing]; ctx.strokeStyle = "#fff0bd"; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(px - direction.x * cell * .45, py - direction.y * cell * .45); ctx.lineTo(px - direction.x * cell * 1.05, py - direction.y * cell * 1.05); ctx.stroke();
}
ctx.restore();
});
ctx.textAlign = "left"; ctx.textBaseline = "alphabetic";
const heatRatio = clamp((state.rig.heat - 25) / 75, 0, 1); ctx.fillStyle = "#1a272a"; ctx.fillRect(x - cell * 2.3, y + cell * 2.85, cell * 4.6, 4); ctx.fillStyle = heatRatio > .8 ? "#f26f68" : "#f3b65d"; ctx.fillRect(x - cell * 2.3, y + cell * 2.85, cell * 4.6 * heatRatio, 4);
}
function frame(now) {
const dt = Math.min(.033, Math.max(0, (now - state.lastFrame) / 1000)); state.lastFrame = now; update(dt); draw(); requestAnimationFrame(frame);
}
function toast(message) {
const element = $("#toast"); element.textContent = message; element.classList.add("show"); clearTimeout(state.toastTimer); state.toastTimer = setTimeout(() => element.classList.remove("show"), 3600);
}
function releaseDriveInputs(reason) {
for (const code of state.keys) {
const started = state.inputStarts.get(code);
log("drive_input_ended", { key: code, duration_ms: started ? Date.now() - started : null, reason });
}
state.keys.clear(); state.inputStarts.clear();
}
$$(".trial-switch button").forEach(button => button.addEventListener("click", () => setTrial(button.dataset.trial)));
$$(".palette button").forEach(button => button.addEventListener("click", () => setSelectedPart(button.dataset.part)));
$$(".orientation-buttons button").forEach(button => button.addEventListener("click", () => setFacing(button.dataset.facing)));
$("#reset").addEventListener("click", () => resetAttempt("manual"));
$("#pause").addEventListener("click", () => { state.paused = !state.paused; $("#pause").textContent = state.paused ? "Resume" : "Pause"; log(state.paused ? "paused" : "resumed"); });
$("#export").addEventListener("click", async () => {
const button = $("#export"), filename = `rig-trials-${state.session}.jsonl`;
log("log_exported", { events_before_export: state.logs.length, destination: "repository" });
button.disabled = true; button.textContent = "Saving…";
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: state.logs.join("\n") + "\n" });
if (!response.ok) throw new Error((await response.json().catch(() => ({}))).error || `HTTP ${response.status}`);
const result = await response.json(); log("log_saved", { path: result.path, events: result.events }); toast(`Saved directly to ${result.path}.`);
} catch (error) {
log("log_save_failed", { error: String(error) });
const blob = new Blob([state.logs.join("\n") + "\n"], { type: "application/x-ndjson" }), anchor = document.createElement("a");
anchor.href = URL.createObjectURL(blob); anchor.download = filename; anchor.click(); setTimeout(() => URL.revokeObjectURL(anchor.href), 500); toast("Direct save failed; downloaded the JSONL instead.");
} finally { button.disabled = false; button.textContent = "Save JSONL"; }
});
function engageAttempt(source) {
if (!state.engaged) { state.engaged = true; state.attemptStarted = Date.now(); log("attempt_engaged", { source, build: buildSignature(), stats: serialStats(rigStats()) }); }
}
$("#start-overlay").addEventListener("click", () => { $("#start-overlay").classList.add("hidden"); canvas.focus(); engageAttempt("start_overlay"); log("start_overlay_dismissed"); });
canvas.addEventListener("pointerdown", event => { event.preventDefault(); canvas.focus(); $("#start-overlay").classList.add("hidden"); engageAttempt("canvas"); });
document.body.addEventListener("contextmenu", event => event.preventDefault());
window.addEventListener("keydown", event => {
if (["KeyW", "KeyA", "KeyS", "KeyD"].includes(event.code)) {
event.preventDefault();
if (!state.keys.has(event.code)) { state.inputStarts.set(event.code, Date.now()); log("drive_input_started", { key: event.code }); }
state.keys.add(event.code);
}
if (event.code === "KeyE" && !event.repeat) { event.preventDefault(); toggleTractor(); }
if (event.code === "KeyR" && !event.repeat && !state.keys.has("KeyW") && !state.keys.has("KeyA") && !state.keys.has("KeyS") && !state.keys.has("KeyD")) { event.preventDefault(); rotateFacing(); }
});
window.addEventListener("keyup", event => {
if (state.keys.delete(event.code)) {
const started = state.inputStarts.get(event.code); state.inputStarts.delete(event.code);
log("drive_input_ended", { key: event.code, duration_ms: started ? Date.now() - started : null });
}
});
window.addEventListener("blur", () => releaseDriveInputs("window_blur"));
new ResizeObserver(resizeCanvas).observe(canvas);
let resizeTimer;
window.addEventListener("resize", () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => log("viewport_changed", { viewport: { width: innerWidth, height: innerHeight }, canvas: { width: round(canvas.getBoundingClientRect().width), height: round(canvas.getBoundingClientRect().height) } }), 250); });
installStarter(); renderBuilder(); updateTrialBrief();
log("session_started", { viewport: { width: innerWidth, height: innerHeight }, starter_build: buildSignature() });
resetAttempt("session_start"); resizeCanvas(); requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,108 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Rig Trials — Experiment 003</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 003</span><h1>Rig Trials</h1></div>
<nav class="trial-switch" aria-label="Complete trials">
<button data-trial="haul" class="selected"><span>01</span> Haul</button>
<button data-trial="furnace"><span>02</span> Furnace</button>
<button data-trial="gale"><span>03</span> Gale</button>
</nav>
<div class="top-actions">
<button id="pause">Pause</button>
<button id="reset">Reset attempt</button>
<button id="export">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="brief">
<span>ONE RIG · THREE COMPLETE TRIALS</span>
<p>Change the body you directly pilot. Every part and trial is available now. Your rig persists when you switch trials; no completion grants power.</p>
</section>
<section class="builder-heading">
<div><h2>Rig lattice</h2><small>Left place/replace · right remove</small></div>
<b id="budget">0 / 12</b>
</section>
<div id="build-grid" class="build-grid" aria-label="Five by five rig construction grid"></div>
<section class="orientation">
<div><h2>Placement facing</h2><small>Thruster arrow = force. Tool/ward arrow = exposed face.</small></div>
<div class="orientation-buttons">
<button data-facing="up" class="selected" title="Face up"></button>
<button data-facing="right" title="Face right"></button>
<button data-facing="down" title="Face down"></button>
<button data-facing="left" title="Face left"></button>
</div>
</section>
<section class="palette" aria-label="Rig parts">
<button data-part="drive" class="selected"><i></i><span><b>Drive</b><small>2 budget · thrust + heat</small></span></button>
<button data-part="tractor"><i></i><span><b>Tractor</b><small>2 · exposed face grabs</small></span></button>
<button data-part="sink"><i></i><span><b>Sink</b><small>2 · exposure cools rig</small></span></button>
<button data-part="ward"><i></i><span><b>Ward</b><small>2 · leading face screens heat</small></span></button>
<button data-part="ballast"><i></i><span><b>Ballast</b><small>1 · mass anchors against wind</small></span></button>
<button data-part="frame"><i>+</i><span><b>Frame</b><small>1 · cheap structural reach</small></span></button>
</section>
<section id="rig-metrics" class="rig-metrics"></section>
<details>
<summary>Construction laws</summary>
<ul>
<li>Every module must connect orthogonally to the core through other modules.</li>
<li>A drive works only when the cell behind its arrow is empty; that is its exhaust.</li>
<li>A tractor works only when the cell in front of its arrow is empty.</li>
<li>Sinks cool more through exposed sides and cool best beside the core.</li>
<li>A ward screens furnace heat only when it is the leading module facing upward.</li>
<li>Ballast adds mass and disproportionately reduces wind acceleration.</li>
</ul>
</details>
<section class="controls">
<h2>Pilot controls</h2>
<p><kbd>WASD</kbd><span>Fire working drives in that world direction.</span></p>
<p><kbd>E</kbd><span>Toggle an exposed tractor near cargo.</span></p>
<p><kbd>R</kbd><span>Rotate placement facing.</span></p>
</section>
</aside>
<section class="playfield">
<div class="trial-briefing">
<div><span id="trial-kicker">TRIAL 01 · RECOVERY</span><h2 id="trial-name">Haul</h2></div>
<p id="trial-objective">Recover both loose fragments. Engage an exposed tractor with E and return each fragment to the home bay.</p>
<div class="readouts">
<span>CORE HEAT <b id="heat-readout">25°</b></span>
<span id="progress-label">FRAGMENTS <b>0 / 2</b></span>
<span id="environment-label">FIELD <b>CALM</b></span>
</div>
</div>
<canvas id="field" tabindex="0"></canvas>
<div class="canvas-help">Click the arena, then pilot with WASD. Edit the lattice at any time; an edit restarts only the current attempt.</div>
<div id="status-card" class="status-card">
<span>RIG STATUS</span>
<b id="status-line">Starter rig ready</b>
<small id="status-detail">Working drives are shown bright; blocked or disconnected modules are dim.</small>
</div>
</section>
</main>
<div id="start-overlay" class="start-overlay">
<div>
<span>YOUR BUILD IS YOUR BODY</span>
<b>A working starter rig is already installed.</b>
<p>Pilot it, alter it, or switch among the three complete trials whenever you want. Click to begin.</p>
</div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,127 @@
:root {
color-scheme: dark;
--bg: #070a0c;
--panel: #0d1417;
--panel2: #131d21;
--line: #2b3a3f;
--text: #e9f0ed;
--muted: #8d9d9a;
--mint: #72e6bd;
--amber: #f3b65d;
--red: #f26f68;
--blue: #71bceb;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; user-select: none; }
button { border: 1px solid var(--line); border-radius: 7px; background: #142025; color: var(--text); font: inherit; cursor: pointer; }
button:hover { border-color: #59716f; background: #1b2a30; }
button.selected { border-color: var(--mint); background: #153129; color: var(--mint); }
h1, h2, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 0; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
header { height: 62px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; padding: 7px 14px; border-bottom: 1px solid var(--line); background: #0a1012; }
.title > span, .brief > span, .start-overlay span, .status-card > span { display: block; color: var(--mint); font-size: 8px; font-weight: 850; letter-spacing: .17em; }
.trial-switch { display: flex; gap: 6px; }
.trial-switch button { min-width: 92px; padding: 8px 11px; }
.trial-switch button span { margin-right: 5px; color: #687976; font: 9px ui-monospace, monospace; }
.trial-switch button.complete::after { content: " ✓"; color: var(--mint); }
.top-actions { display: flex; justify-content: flex-end; gap: 6px; }
.top-actions button { padding: 8px 10px; }
main { height: calc(100vh - 62px); min-height: 0; overflow: hidden; display: grid; grid-template-columns: clamp(300px, 24vw, 360px) minmax(0, 1fr); }
aside { min-height: 0; overflow-y: scroll; scrollbar-gutter: stable; padding: 13px; border-right: 1px solid var(--line); background: #0a1012; }
aside section, aside details { margin-bottom: 13px; }
.brief { padding: 11px; border: 1px solid #2e6252; border-radius: 8px; background: linear-gradient(145deg, #10231d, #11181c); }
.brief p { margin: 7px 0 0; color: #bdcbc6; font-size: 10px; line-height: 1.42; }
.builder-heading, .orientation { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.builder-heading small, .orientation small { display: block; margin-top: 3px; color: var(--muted); font-size: 8px; }
#budget { color: var(--amber); font: 11px ui-monospace, monospace; }
.build-grid { width: min(100%, 310px); aspect-ratio: 1; display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; margin-inline: auto; padding: 7px; border: 1px solid #34484b; border-radius: 9px; background: #081013; }
.cell { position: relative; display: grid; place-items: center; min-width: 0; border: 1px dashed #26363a; border-radius: 5px; background: #0d171a; color: #a9bbb6; }
.cell:hover { border-color: #5d7773; background: #152328; }
.cell.core { border-style: solid; border-color: var(--mint); background: #19332c; color: var(--mint); }
.cell.occupied { border-style: solid; border-color: #536765; background: #172327; }
.cell.invalid { opacity: .35; border-color: var(--red); }
.cell i { font-style: normal; font-size: clamp(15px, 2vw, 23px); line-height: 1; }
.cell small { position: absolute; right: 3px; bottom: 2px; color: #758682; font: 7px ui-monospace, monospace; }
.cell .facing { position: absolute; left: 3px; top: 1px; color: var(--amber); font-size: 10px; }
.cell[data-type="drive"] { color: var(--amber); }
.cell[data-type="tractor"] { color: var(--blue); }
.cell[data-type="sink"] { color: #8bd9ff; }
.cell[data-type="ward"] { color: #d4a6ff; }
.cell[data-type="ballast"] { color: #c4b69d; }
.orientation { align-items: center; }
.orientation-buttons { display: flex; gap: 4px; }
.orientation-buttons button { width: 31px; height: 29px; padding: 0; }
.palette { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.palette button { min-width: 0; display: grid; grid-template-columns: 24px 1fr; align-items: center; gap: 6px; padding: 7px; text-align: left; }
.palette i { color: var(--amber); font-style: normal; font-size: 16px; text-align: center; }
.palette b, .palette small { display: block; }
.palette b { font-size: 9px; }
.palette small { margin-top: 2px; overflow: hidden; color: var(--muted); font-size: 7px; text-overflow: ellipsis; white-space: nowrap; }
.rig-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.rig-metrics div { padding: 7px; border: 1px solid var(--line); border-radius: 5px; background: var(--panel); }
.rig-metrics span, .rig-metrics b { display: block; }
.rig-metrics span { color: var(--muted); font-size: 7px; letter-spacing: .08em; }
.rig-metrics b { margin-top: 3px; color: #cfdbd7; font: 9px ui-monospace, monospace; }
details { border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
summary { padding: 8px 9px; cursor: pointer; font-size: 9px; font-weight: 750; }
details ul { margin: 0; padding: 0 12px 10px 25px; color: var(--muted); font-size: 8px; line-height: 1.42; }
details li + li { margin-top: 4px; }
.controls p { display: grid; grid-template-columns: 52px 1fr; align-items: center; gap: 7px; margin: 6px 0 0; color: var(--muted); font-size: 8px; }
kbd { padding: 3px 5px; border: 1px solid #526461; border-bottom-width: 2px; border-radius: 4px; background: #172226; color: var(--text); font: 8px ui-monospace, monospace; text-align: center; }
.playfield { position: relative; min-width: 0; min-height: 0; overflow: hidden; display: grid; grid-template-rows: 75px minmax(0, 1fr); }
.trial-briefing { display: grid; grid-template-columns: 155px minmax(240px, 1fr) auto; align-items: center; gap: 15px; padding: 8px 13px; border-bottom: 1px solid var(--line); background: #0d1417; }
.trial-briefing span { color: var(--muted); font-size: 7px; letter-spacing: .1em; }
.trial-briefing h2 { margin-top: 3px; color: var(--mint); font-size: 16px; letter-spacing: 0; text-transform: none; }
.trial-briefing p { max-width: 650px; margin: 0; color: #b5c3bf; font-size: 10px; line-height: 1.35; }
.readouts { display: flex; gap: 12px; }
.readouts span, .readouts b { display: block; }
.readouts b { margin-top: 3px; color: var(--text); font: 9px ui-monospace, monospace; letter-spacing: 0; }
#field { display: block; width: 100%; height: 100%; min-width: 0; min-height: 0; outline: none; background: #071013; }
.canvas-help { position: absolute; z-index: 3; left: 50%; bottom: 12px; translate: -50%; padding: 6px 10px; border-radius: 6px; background: #081114d8; color: #82938f; font-size: 8px; pointer-events: none; }
.status-card { position: absolute; z-index: 4; right: 12px; top: 87px; width: 235px; padding: 10px; border: 1px solid #3d5353; border-radius: 8px; background: #0c1518e8; backdrop-filter: blur(4px); pointer-events: none; }
.status-card b, .status-card small { display: block; }
.status-card b { margin-top: 4px; font-size: 10px; }
.status-card small { margin-top: 4px; color: var(--muted); font-size: 8px; line-height: 1.35; }
.start-overlay { position: fixed; z-index: 20; inset: 62px 0 0 clamp(300px, 24vw, 360px); display: grid; place-items: center; background: rgba(3,7,8,.76); }
.start-overlay.hidden { display: none; }
.start-overlay > div { width: min(570px, 86%); padding: 20px 24px; border: 1px solid #416b5f; border-radius: 10px; background: #101a1dee; text-align: center; }
.start-overlay b, .start-overlay p { display: block; }
.start-overlay b { margin-top: 6px; font-size: 14px; }
.start-overlay p { margin: 8px 0 0; color: var(--muted); font-size: 10px; line-height: 1.4; }
#toast { position: fixed; z-index: 30; left: calc(50% + 150px); bottom: 18px; translate: -50% 12px; max-width: 480px; padding: 10px 14px; border: 1px solid #4c7368; border-radius: 8px; background: #10231e; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 10px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 1000px) {
header { grid-template-columns: auto 1fr auto; gap: 7px; padding-inline: 8px; }
.title h1 { font-size: 15px; }
.title > span { font-size: 6px; }
.trial-switch { justify-content: center; }
.trial-switch button { min-width: auto; padding: 7px; font-size: 9px; }
.trial-switch button span { display: none; }
.top-actions button { padding: 7px; font-size: 9px; }
main { grid-template-columns: 285px minmax(0, 1fr); }
.start-overlay { left: 285px; }
.trial-briefing { grid-template-columns: 110px minmax(180px, 1fr); }
.readouts { display: none; }
}
@media (max-width: 650px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
.trial-switch { justify-content: flex-start; }
.top-actions { justify-content: flex-start; }
main { height: auto; grid-template-columns: 1fr; overflow: visible; }
aside { max-height: 720px; }
.playfield { height: 650px; }
.start-overlay { display: none; }
#toast { left: 50%; }
}

View file

@ -0,0 +1,4 @@
# Results
Playtest analyses for Experiment 003 belong here. Raw player exports remain in the repository-level `JSONL/` directory.

View file

@ -0,0 +1,61 @@
# Playtest Analysis — Session e1fa0f4d
Date: 2026-08-16
Source log: `JSONL/rig-trials-e1fa0f4d-9828-431e-8158-ddf826a41770.jsonl`
## Observed Behavior
- Total logged duration was 407 seconds, about 6 minutes 47 seconds.
- All three trials were completed in the presented order.
- Haul occupied about 4 minutes 29 seconds. It produced 16 cargo attachments, 11 manual releases, 12 unsuccessful tractor activations, three deliveries, five obstacle collisions, and two thermal collapses.
- Haul was completed on attempt 4 with the four starter drives and two oppositely facing tractors. The successful attempt took about 61.7 seconds.
- Furnace occupied about 34 seconds. The first engaged attempt thermally collapsed at x≈0.676.
- After that collapse, the player replaced the tractor, three drives, and an added tractor with sinks in quick succession. The successful Furnace rig contained one right-facing drive and five sinks, with displayed cooling 19.2/s. It completed in about 7.9 seconds.
- Gale occupied about 85 seconds. Before the successful run, the player removed three sinks and tried/reoriented several drive placements over eleven cheap build-reset attempts.
- The successful Gale rig had the four cardinal starter drive directions plus two sinks. It had no ballast and completed in about 32.9 seconds.
- After completing Gale, the player removed and then restored one sink before exporting.
- Across the whole session there were 12 placements, six removals, one in-place rotation, two budget rejections, 23 obstacle collisions, and 222 paired drive-input sessions.
## Player Report
- The overall experience was “pretty boring.”
- Controls and tractor alignment were not awkward. The task was obvious rather than physically difficult.
- The player initially interpreted the heat bar as a limited movement budget and did not notice Sinks.
- Under that mistaken model, they added a second oppositely facing tractor and repeatedly juggled the two fragments back to the home bay.
- After learning that Sinks existed, they expected Haul would become even easier.
- Furnace was solved by replacing everything except the core and forward drive with Sinks, then driving straight through.
- Gale was solved by restoring the default rig.
- Placement did not feel important. Re-adding and orienting Drives was only mildly annoying.
## Important Confounds
Heat accumulation and 100° collapse applied in Haul even though the Haul briefing focused on tractor exposure, cargo mass, and recovery. The two Haul thermal failures may have felt like an unexplained cross-trial tax rather than meaningful coupled physics.
Haul telemetry suggests a large share of activity was execution/debugging rather than construction reasoning: repeated attach/release operations, failed alignment checks, and long direct piloting. This could mask or constitute the failure.
Furnace admitted an obvious extreme answer: remove almost every movement capability except the required rightward drive and fill the rest of the budget with sinks. Completion then took under eight seconds. This is qualitative part swapping, but it may be a prescribed checklist rather than fertile adaptation.
Gale did not require its nominally distinctive ballast part. Reconstructing four cardinal drives was sufficient. The placement sequence may reflect learning the builder's rotation semantics rather than reasoning about wind-responsive architecture.
## Questions Needed Before Interpretation
1. Did Haul feel primarily like awkward driving/tractor alignment, or was the underlying task already uninteresting even when controls behaved?
2. Did rebuilding for Furnace and Gale involve a decision you cared about, or did each trial merely announce which parts to stack?
3. Was there any moment when placement itself seemed likely to matter, or did the lattice feel like a slower equipment menu because counts dominated geometry?
## Final Interpretation
The answers rule out awkward piloting as the primary cause. They support the weaker-construction explanations:
- modules behaved mainly as scalar counters or required verbs;
- each trial advertised an obvious dominant composition;
- lattice geometry rarely changed capability;
- switching requirements caused near-total loadout replacement rather than knowledge-rich recomposition;
- orientation added interface work without producing a valued spatial decision.
The two-tractor solution is important counterevidence to treating voluntary experimentation as a sufficient fun signal. It was an unintended, self-devised workaround caused by a mistaken heat model, and it involved repeated execution. The player still found the experience boring. Voluntary deviation is strong evidence of agency or problem solving, but not necessarily curiosity, delight, or a fertile consequence space.
This experiment does not establish that physical construction is uninteresting. It establishes that construction whose geometry is largely cosmetic and whose parts are one-dimensional counters feels like a slower loadout menu. A richer craft prototype could test physical placement, but immediately building one risks polishing a favored prior after several shallow systems.
Experiment 004 should make an exploratory pivot to **legible discovery**. The first four prototypes either documented their laws or made the useful response obvious. None cleanly tested whether encountering a surprising, stable rule, forming a model, and transferring that knowledge to a new situation generates interest.

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" --directory "$EXPERIMENT_DIR/prototype" --log-directory "$REPO_ROOT/JSONL" --port 8000

View file

@ -0,0 +1,15 @@
# Experiment 004 — Invariant Rooms
This experiment is awaiting its first playtest.
Run:
```bash
./experiments/004_invariant_rooms/run.sh
```
Then open <http://127.0.0.1:8000>. The **Save JSONL** button writes the run directly to the repository `JSONL/` directory when the supplied server is running. If the endpoint is unavailable, it falls back to a browser download.
Do not read `hypothesis.md` before playing if you are the playtester. It contains the experiment's hidden purpose and interpretation criteria.
After playing, report the felt experience before reading the research notes. Useful behavioral detail includes where you paused, reset, formed a prediction, or kept interacting after the required rooms.

View file

@ -0,0 +1,76 @@
# Experiment 004 Hypothesis — Private Until After Play
## Why this follows Experiment 003
Experiment 003 removed indirect automation but still reduced to obvious counter-loadouts. Its lattice rarely changed outcomes. The player nevertheless invented a two-tractor workaround while misunderstanding the heat system and still found the experience boring. This is important: deviation and problem solving are not equivalent to curiosity or fun.
Experiment 004 removes construction, upgrades, combat pressure, continuous steering, and numerical optimization. It isolates whether an unfamiliar but stable law can produce a valued cycle of observation, inference, prediction, and transfer.
## Mechanic
One command affects two bodies:
- Self moves in the requested cardinal direction.
- Echo moves in the exact opposite direction.
- A wall blocks each body independently.
- Therefore ordinary moves preserve their midpoint, while an asymmetric collision changes it. Walls can absorb one half of a command and let the player ratchet the pair into configurations impossible in open space.
The interface does not state this law. It makes it legible through simultaneous tweened motion, trails, collision flashes, a connecting line, a midpoint marker, and a terse record of the last displacement. The player can undo or reset instantly.
## Room Sequence
1. **First Pair:** an open horizontal arrangement. Two left commands solve it and expose opposed motion.
2. **One Holds:** one body begins against a wall while the other must move three cells. This isolates independent blocking.
3. **Transfer:** asymmetric internal walls require combining ordinary opposed moves with two different wall absorptions. The shortest unordered solution is `UUUUURRDR` (9 commands), verified by breadth-first search.
4. **Open Chamber:** no sockets and no completion condition. It exists only to observe whether the player has a question or prediction they want to test after the authored sequence.
## Competing Interpretations
1. Inferring a stable unfamiliar law is intrinsically rewarding and produces a second question.
2. The reveal is momentarily interesting but becomes a short authored puzzle with no continuing possibility space.
3. The player enjoys spatial puzzle solving but not open experimentation.
4. The law is learned but applying it feels like laborious state bookkeeping.
5. The visualization is insufficient, so success comes from input search rather than a usable mental model.
6. The tasks are too easy or too short to expose the difference between satisfaction and compliance.
## Evidence Priorities
Strong evidence for the target loop:
- a prediction stated or behaviorally tested before a required move;
- deliberate use of a wall after first observing asymmetric blocking;
- low-search transfer in the final room after exploratory earlier rooms;
- movement in the open chamber aimed at a self-chosen configuration;
- a concrete question about a consequence not required by sockets;
- wanting another situation because of the law rather than merely another puzzle.
Ambiguous evidence:
- completion;
- resets or many moves;
- finding an unintended route;
- spending time in a room;
- any claim that the mechanic would or would not be fun with more content.
Negative evidence:
- immediate mechanical input search without model formation;
- stopping as soon as sockets are filled with no desire to predict anything else;
- describing the law as understood but exhausted;
- finding state planning tedious even after the behavior is clear.
## Instrumentation
Log every command with before/after coordinates, requested direction, each body's displacement and blocked state, midpoint before/after, move count, undo depth, and completion state. Also log resets, undos, room entry/completion, time and move totals, open-chamber moves, visibility changes, and saves.
Telemetry cannot distinguish thoughtful prediction from trial-and-error by itself. Pair it with the player's report and inspect pauses, undo patterns, and whether wall interactions become more intentional over time.
## Questions After Play
Ask only after receiving the JSONL:
1. At what point, if any, did you feel you understood what the paired movement and walls would do before pressing a key?
2. In the last required room, were you executing a plan, testing local guesses, or searching inputs until something worked?
3. Once the required sequence ended, did you have any result you wanted to produce or any question you wanted answered?
Do not ask whether “discovering rules” is fun; that wording would encourage agreement with the hypothesis.

View file

@ -0,0 +1,467 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const $$ = selector => [...document.querySelectorAll(selector)];
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const W = 13;
const H = 9;
const TWEEN_MS = 145;
const DIRECTIONS = {
up: { x: 0, y: -1, glyph: "↑" },
right: { x: 1, y: 0, glyph: "→" },
down: { x: 0, y: 1, glyph: "↓" },
left: { x: -1, y: 0, glyph: "←" }
};
const KEY_DIRECTIONS = {
KeyW: "up", ArrowUp: "up", KeyD: "right", ArrowRight: "right",
KeyS: "down", ArrowDown: "down", KeyA: "left", ArrowLeft: "left"
};
function boundaryWalls() {
const walls = [];
for (let x = 0; x < W; x++) walls.push([x, 0], [x, H - 1]);
for (let y = 1; y < H - 1; y++) walls.push([0, y], [W - 1, y]);
return walls;
}
const ROOMS = [
{
id: "first_pair", kicker: "ROOM 01 · FIRST PAIR", name: "First Pair",
objective: "Place both occupants on the two sockets. Either occupant may use either socket.",
start: { self: [4, 4], echo: [8, 4] }, sockets: [[2, 4], [10, 4]], walls: boundaryWalls()
},
{
id: "one_holds", kicker: "ROOM 02 · ONE HOLDS", name: "One Holds",
objective: "Place both occupants on the sockets again.",
start: { self: [3, 4], echo: [9, 4] }, sockets: [[3, 4], [6, 4]],
walls: [...boundaryWalls(), [4, 4]]
},
{
id: "transfer", kicker: "ROOM 03 · TRANSFER", name: "Transfer",
objective: "Place both occupants on the sockets.",
start: { self: [3, 6], echo: [9, 2] }, sockets: [[6, 2], [7, 4]],
walls: [...boundaryWalls(), [4, 4], [4, 5], [4, 6], [8, 2], [8, 3], [6, 3], [6, 4], [2, 5], [9, 6]]
},
{
id: "open_chamber", kicker: "OPEN CHAMBER", name: "After the Sequence",
objective: "There is no required arrangement in this chamber.",
start: { self: [4, 5], echo: [8, 3] }, sockets: [],
walls: [...boundaryWalls(), [3, 2], [3, 3], [3, 4], [5, 6], [6, 6], [7, 6], [9, 3], [9, 4], [9, 5], [6, 2]]
}
];
const state = {
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
started: Date.now(), logs: [], roomIndex: 0, roomStarted: Date.now(), roomMoves: 0,
self: { x: ROOMS[0].start.self[0], y: ROOMS[0].start.self[1] },
echo: { x: ROOMS[0].start.echo[0], y: ROOMS[0].start.echo[1] }, history: [], completed: new Set(),
active: false, animating: false, animation: null, bannerTimer: null, toastTimer: null,
measurement: null, render: { scale: 1, ox: 0, oy: 0, width: 0, height: 0 }
};
const room = () => ROOMS[state.roomIndex];
const pair = () => ({ self: [state.self.x, state.self.y], echo: [state.echo.x, state.echo.y] });
const midpoint = value => [
Math.round(((value.self[0] + value.echo[0]) / 2) * 10) / 10,
Math.round(((value.self[1] + value.echo[1]) / 2) * 10) / 10
];
const wallSet = () => new Set(room().walls.map(([x, y]) => `${x},${y}`));
const samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
const formatTime = milliseconds => {
const seconds = Math.floor(milliseconds / 1000);
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
};
function log(type, data = {}) {
const event = {
schema: 1, experiment: "004_invariant_rooms", prototype_revision: 1,
session_id: state.session, elapsed_ms: Date.now() - state.started,
room: room().id, room_index: state.roomIndex, room_elapsed_ms: Date.now() - state.roomStarted,
room_moves: state.roomMoves, type, ...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("invariant-rooms-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function enterRoom(index, reason) {
state.roomIndex = index;
const current = room();
state.self = { x: current.start.self[0], y: current.start.self[1] };
state.echo = { x: current.start.echo[0], y: current.start.echo[1] };
state.roomStarted = Date.now();
state.roomMoves = 0;
state.history = [];
state.animating = false;
state.animation = null;
state.measurement = null;
updateRoomUI();
log("room_entered", { reason, start: pair(), sockets: current.sockets, wall_count: current.walls.length });
showBanner(index === ROOMS.length - 1 ? "Sequence complete · no required result" : current.name);
canvas.focus({ preventScroll: true });
}
function updateRoomUI() {
const current = room();
$("#room-kicker").textContent = current.kicker;
$("#room-name").textContent = current.name;
$("#objective").textContent = current.objective;
$("#undo").disabled = state.history.length === 0 || state.animating;
$("#move-count").textContent = String(state.roomMoves);
if (state.roomIndex === ROOMS.length - 1) {
$("#sequence-note").textContent = "The measured sequence is complete. Stay only as long as you want to.";
$("#sequence-note").classList.add("open");
} else {
$("#sequence-note").textContent = "Complete rooms advance automatically. Undo and reset do not consume anything.";
$("#sequence-note").classList.remove("open");
}
renderDots();
updateMeasurement();
}
function renderDots() {
const holder = $("#room-dots");
holder.innerHTML = "";
ROOMS.forEach((candidate, index) => {
const dot = document.createElement("span");
dot.className = "room-dot";
if (state.completed.has(candidate.id)) dot.classList.add("complete");
if (index === state.roomIndex) dot.classList.add(index === ROOMS.length - 1 ? "open" : "current");
dot.title = index === ROOMS.length - 1 ? "Open chamber" : `Room ${index + 1}`;
holder.append(dot);
});
}
function updateMeasurement() {
const measurement = state.measurement;
if (!measurement) {
$("#measure-command").textContent = "—";
$("#measure-self").textContent = "—";
$("#measure-echo").textContent = "—";
$("#measure-center").textContent = "—";
return;
}
const movementText = value => value.blocked ? "HELD" : `${value.dx > 0 ? "+" : ""}${value.dx}, ${value.dy > 0 ? "+" : ""}${value.dy}`;
$("#measure-command").textContent = DIRECTIONS[measurement.direction].glyph;
$("#measure-self").textContent = movementText(measurement.self);
$("#measure-echo").textContent = movementText(measurement.echo);
const [bx, by] = measurement.midpointBefore, [ax, ay] = measurement.midpointAfter;
$("#measure-center").textContent = bx === ax && by === ay ? `${ax}, ${ay} · HELD` : `${bx},${by}${ax},${ay}`;
}
function issueCommand(direction, source = "keyboard") {
if (!state.active || state.animating || !DIRECTIONS[direction]) return;
const vector = DIRECTIONS[direction];
const before = pair();
const walls = wallSet();
const selfCandidate = [state.self.x + vector.x, state.self.y + vector.y];
const echoCandidate = [state.echo.x - vector.x, state.echo.y - vector.y];
const selfBlocked = walls.has(selfCandidate.join(","));
const echoBlocked = walls.has(echoCandidate.join(","));
const after = {
self: selfBlocked ? [...before.self] : selfCandidate,
echo: echoBlocked ? [...before.echo] : echoCandidate
};
if (samePoint(before.self, after.self) && samePoint(before.echo, after.echo)) {
log("command_no_effect", { direction, source, before, self_blocked: true, echo_blocked: true });
flashMeasurement(direction, before, after, selfBlocked, echoBlocked);
return;
}
state.history.push({ before, measurement: state.measurement });
state.roomMoves++;
const measurement = makeMeasurement(direction, before, after, selfBlocked, echoBlocked);
state.measurement = measurement;
state.animation = { started: performance.now(), before, after, measurement };
state.animating = true;
state.self = { x: after.self[0], y: after.self[1] };
state.echo = { x: after.echo[0], y: after.echo[1] };
updateRoomUI();
log("command", {
direction, source, before, after, self_blocked: selfBlocked, echo_blocked: echoBlocked,
self_displacement: [measurement.self.dx, measurement.self.dy],
echo_displacement: [measurement.echo.dx, measurement.echo.dy],
midpoint_before: measurement.midpointBefore, midpoint_after: measurement.midpointAfter,
history_depth: state.history.length, open_chamber: state.roomIndex === ROOMS.length - 1
});
}
function makeMeasurement(direction, before, after, selfBlocked, echoBlocked) {
return {
direction, before, after,
self: { dx: after.self[0] - before.self[0], dy: after.self[1] - before.self[1], blocked: selfBlocked },
echo: { dx: after.echo[0] - before.echo[0], dy: after.echo[1] - before.echo[1], blocked: echoBlocked },
midpointBefore: midpoint(before), midpointAfter: midpoint(after)
};
}
function flashMeasurement(direction, before, after, selfBlocked, echoBlocked) {
state.measurement = makeMeasurement(direction, before, after, selfBlocked, echoBlocked);
updateMeasurement();
}
function finishAnimation() {
state.animating = false;
const completed = roomComplete();
log("command_animation_finished", { positions: pair(), completed });
if (completed) completeRoom();
}
function roomComplete() {
const sockets = room().sockets;
if (sockets.length !== 2) return false;
const current = pair();
return (samePoint(current.self, sockets[0]) && samePoint(current.echo, sockets[1])) ||
(samePoint(current.self, sockets[1]) && samePoint(current.echo, sockets[0]));
}
function completeRoom() {
const current = room();
if (state.completed.has(current.id)) return;
state.completed.add(current.id);
log("room_completed", {
moves: state.roomMoves, duration_ms: Date.now() - state.roomStarted,
resets: Number(sessionStorage.getItem(`invariant-resets-${state.session}-${current.id}`) || 0)
});
renderDots();
showBanner("Both sockets occupied");
setTimeout(() => {
if (state.roomIndex < ROOMS.length - 1) enterRoom(state.roomIndex + 1, "previous_completed");
}, 780);
}
function undo() {
if (!state.active || state.animating || state.history.length === 0) return;
const current = pair();
const previous = state.history.pop();
state.self = { x: previous.before.self[0], y: previous.before.self[1] };
state.echo = { x: previous.before.echo[0], y: previous.before.echo[1] };
state.measurement = previous.measurement;
log("undo", { before: current, after: pair(), history_depth: state.history.length });
updateRoomUI();
}
function resetRoom(reason = "button") {
if (!state.active || state.animating) return;
const before = pair();
const current = room();
const key = `invariant-resets-${state.session}-${current.id}`;
const resets = Number(sessionStorage.getItem(key) || 0) + 1;
sessionStorage.setItem(key, String(resets));
state.self = { x: current.start.self[0], y: current.start.self[1] };
state.echo = { x: current.start.echo[0], y: current.start.echo[1] };
state.history = [];
state.roomMoves = 0;
state.measurement = null;
log("room_reset", { reason, before, after: pair(), reset_count: resets });
updateRoomUI();
}
async function saveLog() {
log("save_requested", { event_count_before_save: state.logs.length });
const filename = `invariant-rooms-${state.session}.jsonl`;
const payload = state.logs.join("\n") + "\n";
try {
const response = await fetch("/api/playtest-log", {
method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: payload
});
if (!response.ok) throw new Error(`server returned ${response.status}`);
const result = await response.json();
log("log_saved", { path: result.path, saved_events: result.events });
showToast(`Saved ${result.events} events to ${result.path}`);
} catch (error) {
const blob = new Blob([payload], { type: "application/x-ndjson" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob); link.download = filename; link.click();
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
log("save_fallback_download", { message: String(error) });
showToast("Server save unavailable; downloaded the JSONL instead.");
}
}
function showBanner(message) {
const banner = $("#room-banner");
banner.textContent = message;
banner.classList.add("show");
clearTimeout(state.bannerTimer);
state.bannerTimer = setTimeout(() => banner.classList.remove("show"), 1350);
}
function showToast(message) {
const toast = $("#toast");
toast.textContent = message;
toast.classList.add("show");
clearTimeout(state.toastTimer);
state.toastTimer = setTimeout(() => toast.classList.remove("show"), 2800);
}
function resize() {
const rect = canvas.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.max(1, Math.round(rect.width * dpr));
canvas.height = Math.max(1, Math.round(rect.height * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const margin = Math.max(24, Math.min(rect.width, rect.height) * .06);
const scale = Math.min((rect.width - margin * 2) / W, (rect.height - margin * 2) / H);
state.render = { scale, ox: (rect.width - W * scale) / 2, oy: (rect.height - H * scale) / 2, width: rect.width, height: rect.height };
}
function cellCenter(x, y) {
const { scale, ox, oy } = state.render;
return { x: ox + (x + .5) * scale, y: oy + (y + .5) * scale };
}
function draw(time) {
const { width, height, scale, ox, oy } = state.render;
ctx.clearRect(0, 0, width, height);
drawGrid(scale, ox, oy);
drawSockets();
drawWalls(scale, ox, oy);
let selfPosition = [state.self.x, state.self.y], echoPosition = [state.echo.x, state.echo.y], progress = 1;
if (state.animating && state.animation) {
progress = Math.min(1, (time - state.animation.started) / TWEEN_MS);
const eased = 1 - Math.pow(1 - progress, 3);
selfPosition = interpolate(state.animation.before.self, state.animation.after.self, eased);
echoPosition = interpolate(state.animation.before.echo, state.animation.after.echo, eased);
drawTrails(state.animation, eased);
if (progress >= 1) finishAnimation();
} else if (state.measurement) {
drawTrails({ ...state.measurement, measurement: state.measurement }, 1, .22);
}
drawPairLine(selfPosition, echoPosition);
drawOccupant(selfPosition, "self", state.animating && state.animation?.measurement.self.blocked, progress);
drawOccupant(echoPosition, "echo", state.animating && state.animation?.measurement.echo.blocked, progress);
requestAnimationFrame(draw);
}
function interpolate(from, to, amount) {
return [from[0] + (to[0] - from[0]) * amount, from[1] + (to[1] - from[1]) * amount];
}
function drawGrid(scale, ox, oy) {
ctx.save();
ctx.strokeStyle = "rgba(89, 121, 130, .16)";
ctx.lineWidth = 1;
for (let x = 0; x <= W; x++) { ctx.beginPath(); ctx.moveTo(ox + x * scale, oy); ctx.lineTo(ox + x * scale, oy + H * scale); ctx.stroke(); }
for (let y = 0; y <= H; y++) { ctx.beginPath(); ctx.moveTo(ox, oy + y * scale); ctx.lineTo(ox + W * scale, oy + y * scale); ctx.stroke(); }
ctx.restore();
}
function drawWalls(scale, ox, oy) {
for (const [x, y] of room().walls) {
const inset = Math.max(2, scale * .055);
const left = ox + x * scale + inset, top = oy + y * scale + inset, size = scale - inset * 2;
const gradient = ctx.createLinearGradient(left, top, left + size, top + size);
gradient.addColorStop(0, "#263843"); gradient.addColorStop(1, "#14212a");
ctx.fillStyle = gradient; ctx.fillRect(left, top, size, size);
ctx.strokeStyle = "#425963"; ctx.lineWidth = 1; ctx.strokeRect(left + .5, top + .5, size - 1, size - 1);
ctx.strokeStyle = "rgba(122, 151, 160, .14)";
for (let offset = -size; offset < size * 2; offset += Math.max(9, scale * .2)) {
ctx.beginPath(); ctx.moveTo(left + offset, top + size); ctx.lineTo(left + offset + size, top); ctx.stroke();
}
}
}
function drawSockets() {
room().sockets.forEach(([x, y]) => {
const center = cellCenter(x, y), radius = state.render.scale * .31;
ctx.save();
ctx.strokeStyle = "#f1b85c"; ctx.lineWidth = Math.max(2, state.render.scale * .045);
ctx.setLineDash([state.render.scale * .1, state.render.scale * .075]);
ctx.beginPath(); ctx.arc(center.x, center.y, radius, 0, Math.PI * 2); ctx.stroke();
ctx.fillStyle = "rgba(241, 184, 92, .08)"; ctx.fill();
ctx.setLineDash([]); ctx.fillStyle = "#f1b85c"; ctx.beginPath(); ctx.arc(center.x, center.y, 2.2, 0, Math.PI * 2); ctx.fill();
ctx.restore();
});
}
function drawPairLine(selfPosition, echoPosition) {
const a = cellCenter(selfPosition[0], selfPosition[1]), b = cellCenter(echoPosition[0], echoPosition[1]);
const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;
ctx.save();
ctx.strokeStyle = "rgba(181, 164, 220, .25)"; ctx.lineWidth = 1.5; ctx.setLineDash([5, 7]);
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); ctx.setLineDash([]);
ctx.strokeStyle = "#f1b85c"; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(mx - 6, my); ctx.lineTo(mx + 6, my); ctx.moveTo(mx, my - 6); ctx.lineTo(mx, my + 6); ctx.stroke();
ctx.restore();
}
function drawTrails(animation, amount, alpha = .6) {
const measurement = animation.measurement;
for (const identity of ["self", "echo"]) {
const from = animation.before[identity], to = animation.after[identity];
if (samePoint(from, to)) continue;
const a = cellCenter(from[0], from[1]), b = cellCenter(to[0], to[1]);
ctx.save(); ctx.globalAlpha = alpha * amount;
ctx.strokeStyle = identity === "self" ? "#69e2bb" : "#bc91ff"; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); ctx.restore();
}
}
function drawOccupant(position, identity, blocked, progress) {
const center = cellCenter(position[0], position[1]);
const radius = state.render.scale * .27;
const color = identity === "self" ? "#69e2bb" : "#bc91ff";
ctx.save();
if (blocked && progress < 1) { ctx.shadowColor = "#ef716c"; ctx.shadowBlur = 18 * (1 - progress); }
else { ctx.shadowColor = color; ctx.shadowBlur = 14; }
ctx.fillStyle = identity === "self" ? "#173c34" : "#302448";
ctx.strokeStyle = color; ctx.lineWidth = Math.max(2, state.render.scale * .045);
ctx.beginPath();
if (identity === "self") {
ctx.moveTo(center.x, center.y - radius); ctx.lineTo(center.x + radius * .88, center.y + radius * .78);
ctx.lineTo(center.x - radius * .88, center.y + radius * .78); ctx.closePath();
} else {
ctx.moveTo(center.x, center.y - radius); ctx.lineTo(center.x + radius, center.y);
ctx.lineTo(center.x, center.y + radius); ctx.lineTo(center.x - radius, center.y); ctx.closePath();
}
ctx.fill(); ctx.stroke();
ctx.shadowBlur = 0; ctx.fillStyle = color; ctx.font = `700 ${Math.max(7, state.render.scale * .12)}px ui-monospace, monospace`;
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText(identity === "self" ? "S" : "E", center.x, center.y + (identity === "self" ? radius * .18 : 0));
if (blocked && progress < 1) {
ctx.strokeStyle = "#ef716c"; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(center.x, center.y, radius * (1.15 + progress * .35), 0, Math.PI * 2); ctx.stroke();
}
ctx.restore();
}
function begin(source) {
if (state.active) return;
state.active = true;
$("#start-overlay").classList.add("hidden");
canvas.focus({ preventScroll: true });
log("session_started", { source, viewport: [window.innerWidth, window.innerHeight], room_count: ROOMS.length });
enterRoom(0, "session_started");
}
document.addEventListener("keydown", event => {
if (!state.active) return;
if (KEY_DIRECTIONS[event.code]) {
event.preventDefault();
if (!event.repeat) issueCommand(KEY_DIRECTIONS[event.code], "keyboard");
} else if (event.code === "KeyZ" || event.code === "KeyU") {
event.preventDefault(); if (!event.repeat) undo();
} else if (event.code === "KeyR") {
event.preventDefault(); if (!event.repeat) resetRoom("keyboard");
}
});
document.addEventListener("contextmenu", event => event.preventDefault());
document.addEventListener("selectstart", event => event.preventDefault());
$("#start-overlay").addEventListener("pointerdown", () => begin("overlay"));
canvas.addEventListener("pointerdown", () => { if (!state.active) begin("canvas"); else canvas.focus({ preventScroll: true }); });
$$("[data-direction]").forEach(button => button.addEventListener("click", () => issueCommand(button.dataset.direction, "button")));
$("#undo").addEventListener("click", undo);
$("#reset").addEventListener("click", () => resetRoom("button"));
$("#save").addEventListener("click", saveLog);
document.addEventListener("visibilitychange", () => log("visibility_changed", { visibility: document.visibilityState, positions: pair() }));
window.addEventListener("beforeunload", () => log("session_unload", { positions: pair(), completed_rooms: [...state.completed] }));
window.addEventListener("resize", resize);
setInterval(() => { $("#room-time").textContent = formatTime(Date.now() - state.roomStarted); }, 250);
resize();
updateRoomUI();
requestAnimationFrame(draw);
})();

View file

@ -0,0 +1,80 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Paired Rooms — Experiment 004</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 004</span><h1>Paired Rooms</h1></div>
<div id="room-dots" class="room-dots" aria-label="Room progress"></div>
<div class="top-actions">
<button id="undo" type="button">Undo</button>
<button id="reset" type="button">Reset room</button>
<button id="save" type="button">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="brief">
<span id="room-kicker">ROOM 01 · FIRST PAIR</span>
<h2 id="room-name">First Pair</h2>
<p id="objective">Place both occupants on the two sockets. Either occupant may use either socket.</p>
</section>
<section class="identity-card">
<div><i class="self-symbol"></i><span><b>SELF</b><small>Mint occupant</small></span></div>
<div><i class="echo-symbol"></i><span><b>ECHO</b><small>Violet occupant</small></span></div>
</section>
<section class="controls">
<h3>Shared command</h3>
<div class="key-grid" aria-label="Movement controls">
<button data-direction="up" type="button">W<span></span></button>
<button data-direction="left" type="button">A<span></span></button>
<button data-direction="down" type="button">S<span></span></button>
<button data-direction="right" type="button">D<span></span></button>
</div>
<p><kbd>WASD</kbd> or <kbd>arrows</kbd> issue one command.</p>
<p><kbd>Z</kbd> undoes. <kbd>R</kbd> restores the room.</p>
</section>
<section class="measurements">
<h3>Last measurement</h3>
<div><span>COMMAND</span><b id="measure-command"></b></div>
<div><span>SELF</span><b id="measure-self"></b></div>
<div><span>ECHO</span><b id="measure-echo"></b></div>
<div><span>CENTER</span><b id="measure-center"></b></div>
</section>
<section class="room-stats">
<div><span>ROOM MOVES</span><b id="move-count">0</b></div>
<div><span>ROOM TIME</span><b id="room-time">0:00</b></div>
</section>
<section id="sequence-note" class="sequence-note">
Complete rooms advance automatically. Undo and reset do not consume anything.
</section>
</aside>
<section class="playfield">
<canvas id="field" tabindex="0" aria-label="Paired room playfield"></canvas>
<div id="room-banner" class="room-banner" aria-live="polite"></div>
<div class="canvas-help">Click the field, then use WASD or arrow keys.</div>
</section>
</main>
<div id="start-overlay" class="start-overlay">
<div>
<span>TWO OCCUPANTS · ONE COMMAND</span>
<b>Place both occupants on the sockets.</b>
<p>The command's effect is not described. Watch both occupants. Click anywhere to begin.</p>
</div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,106 @@
:root {
color-scheme: dark;
--bg: #06090d;
--panel: #0c1319;
--panel2: #111b23;
--line: #293943;
--text: #e8f1ef;
--muted: #879a9f;
--mint: #69e2bb;
--violet: #bc91ff;
--amber: #f1b85c;
--red: #ef716c;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; user-select: none; }
button { border: 1px solid var(--line); border-radius: 7px; background: #132029; color: var(--text); font: inherit; cursor: pointer; touch-action: manipulation; }
button:hover { border-color: #56717a; background: #192a34; }
button:disabled { opacity: .38; cursor: default; }
h1, h2, h3, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 4px 0 0; font-size: 23px; }
h3 { margin: 0 0 9px; color: #a9bbb9; font-size: 9px; letter-spacing: .14em; text-transform: uppercase; }
header { height: 64px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 15px; padding: 8px 14px; border-bottom: 1px solid var(--line); background: #091015; }
.title > span, .brief > span, .start-overlay span { display: block; color: var(--mint); font-size: 8px; font-weight: 850; letter-spacing: .18em; }
.room-dots { display: flex; align-items: center; gap: 8px; }
.room-dot { width: 34px; height: 5px; border: 0; border-radius: 5px; background: #27353e; }
.room-dot.current { background: var(--amber); box-shadow: 0 0 12px #f1b85c66; }
.room-dot.complete { background: var(--mint); }
.room-dot.open { background: linear-gradient(90deg, var(--violet), var(--mint)); }
.top-actions { display: flex; justify-content: flex-end; gap: 7px; }
.top-actions button { padding: 8px 11px; }
main { height: calc(100vh - 64px); min-height: 0; display: grid; grid-template-columns: clamp(260px, 21vw, 330px) minmax(0, 1fr); overflow: hidden; }
aside { min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 16px; border-right: 1px solid var(--line); background: #091015; }
aside section { margin-bottom: 15px; }
.brief { padding: 14px; border: 1px solid #345449; border-radius: 9px; background: linear-gradient(145deg, #10231d, #101820); }
.brief p { margin: 9px 0 0; color: #b7c6c4; font-size: 11px; line-height: 1.45; }
.identity-card { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.identity-card > div { display: flex; align-items: center; gap: 9px; padding: 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.identity-card i { width: 25px; font-style: normal; font-size: 22px; text-align: center; }
.identity-card b, .identity-card small { display: block; }
.identity-card b { font-size: 9px; letter-spacing: .1em; }
.identity-card small { margin-top: 2px; color: var(--muted); font-size: 8px; }
.self-symbol { color: var(--mint); }
.echo-symbol { color: var(--violet); }
.controls { padding: 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); }
.key-grid { width: 142px; display: grid; grid-template-columns: repeat(3, 42px); grid-template-rows: repeat(2, 42px); gap: 5px; margin: 0 auto 10px; }
.key-grid button { padding: 0; color: #b8c9c8; font: 700 12px ui-monospace, monospace; }
.key-grid button span { display: block; color: var(--amber); font-size: 10px; }
.key-grid button[data-direction="up"] { grid-column: 2; }
.key-grid button[data-direction="left"] { grid-row: 2; grid-column: 1; }
.key-grid button[data-direction="down"] { grid-row: 2; grid-column: 2; }
.key-grid button[data-direction="right"] { grid-row: 2; grid-column: 3; }
.controls p { margin: 5px 0 0; color: var(--muted); font-size: 9px; text-align: center; }
kbd { padding: 2px 5px; border: 1px solid #53666c; border-bottom-width: 2px; border-radius: 4px; background: #17242c; color: var(--text); font: 8px ui-monospace, monospace; }
.measurements { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.measurements h3 { grid-column: 1 / -1; margin-bottom: 2px; }
.measurements div, .room-stats div { min-height: 50px; padding: 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.measurements span, .measurements b, .room-stats span, .room-stats b { display: block; }
.measurements span, .room-stats span { color: var(--muted); font-size: 7px; letter-spacing: .11em; }
.measurements b, .room-stats b { margin-top: 5px; color: #d7e3e0; font: 10px ui-monospace, monospace; }
.room-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.sequence-note { padding: 10px; border-left: 2px solid #41545a; color: var(--muted); font-size: 9px; line-height: 1.45; }
.sequence-note.open { border-color: var(--violet); color: #c4b4dc; }
.playfield { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: radial-gradient(circle at 50% 45%, #101b23, #05090c 75%); }
#field { display: block; width: 100%; height: 100%; outline: none; touch-action: none; }
.canvas-help { position: absolute; left: 50%; bottom: 13px; translate: -50%; padding: 7px 11px; border-radius: 6px; background: #071015dc; color: #7d9195; font-size: 9px; pointer-events: none; }
.room-banner { position: absolute; z-index: 5; left: 50%; top: 20px; translate: -50% -8px; min-width: 230px; padding: 10px 16px; border: 1px solid #487367; border-radius: 8px; background: #10221dda; color: var(--mint); opacity: 0; text-align: center; pointer-events: none; transition: .22s; font-size: 11px; font-weight: 750; }
.room-banner.show { opacity: 1; translate: -50% 0; }
.start-overlay { position: fixed; z-index: 20; inset: 64px 0 0 clamp(260px, 21vw, 330px); display: grid; place-items: center; background: #030709d9; }
.start-overlay.hidden { display: none; }
.start-overlay > div { width: min(560px, 86%); padding: 22px 26px; border: 1px solid #436c61; border-radius: 10px; background: #101a20f2; text-align: center; }
.start-overlay b, .start-overlay p { display: block; }
.start-overlay b { margin-top: 7px; font-size: 15px; }
.start-overlay p { margin: 9px 0 0; color: var(--muted); font-size: 10px; line-height: 1.45; }
#toast { position: fixed; z-index: 30; left: calc(50% + 145px); bottom: 20px; translate: -50% 12px; max-width: 500px; padding: 10px 14px; border: 1px solid #4c7368; border-radius: 8px; background: #10231e; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 10px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 850px) {
header { grid-template-columns: auto 1fr auto; padding-inline: 8px; }
.title h1 { font-size: 15px; }
.title > span { font-size: 6px; }
.room-dot { width: 20px; }
.top-actions button { padding: 7px; font-size: 9px; }
main { grid-template-columns: 250px minmax(0, 1fr); }
.start-overlay { left: 250px; }
}
@media (max-width: 620px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
.room-dots { justify-content: flex-start; }
.top-actions { justify-content: flex-start; }
main { height: auto; grid-template-columns: 1fr; overflow: visible; }
aside { max-height: none; }
.playfield { height: min(80vh, 650px); }
.start-overlay { display: none; }
#toast { left: 50%; }
}

View file

@ -0,0 +1,3 @@
# Experiment 004 Results
No playtest has been analyzed yet. JSONL telemetry is saved in the repository-level `JSONL/` directory; qualitative interpretation belongs here after the player reports their experience.

View file

@ -0,0 +1,48 @@
# Experiment 004 Preliminary Analysis — Session a7ad2d34
Status: telemetry and player report analyzed.
Source: `JSONL/invariant-rooms-a7ad2d34-cba4-43a5-942e-1f7dc1ad0295.jsonl`
## Session Summary
- 383 saved events over 161.2 seconds.
- All three required rooms completed.
- No undo or reset was used anywhere.
- 185 effective commands and four no-effect commands were logged.
- The player remained in the explicitly non-required open chamber for 59.8 seconds and made 88 attempted / 85 effective commands there.
Room behavior:
| Room | Time | Attempted commands | Effective commands | Sequence/result |
|---|---:|---:|---:|---|
| First Pair | 6.9 s | 2 | 2 | `LL`, optimal |
| One Holds | 4.4 s | 5 | 5 | `LRRRR`; one exploratory move away, then correction and three wall absorptions |
| Transfer | 80.9 s | 94 | 93 | broad state search; completed with occupants on the two sockets in the swapped assignment |
| Open Chamber | 59.8 s | 88 | 85 | extensive interaction despite no required result; ended with both occupants exactly overlapped at `(6,7)` |
## Behavioral Reading
The first two rooms establish quick control acquisition. In One Holds, the initial left command moved away from the solution; four rights then first restored the initial relation and subsequently used the Self-blocking wall three times. This is consistent with observing and applying independent wall blocking, although telemetry cannot reveal the player's explicit model.
Transfer was not a short execution of the verified nine-move route. The player explored for 94 commands across roughly 81 seconds and encountered many asymmetric blocks. The path repeatedly shifted the midpoint and returned through several configurations. This could be productive model-building, local trial-and-error, or undirected search. Absence of undo/reset means the player treated every resulting state as recoverable rather than restarting from a failed plan.
The open chamber is the strongest novel evidence in the project so far, but its cause must not be assumed. The player paused about 14.1 seconds before its first command, then made 88 attempts in under 46 seconds. They deliberately or accidentally ended with Self and Echo occupying the exact same cell `(6,7)`. The late command structure drove the midpoint to the lower boundary, held Echo against the boundary while moving Self right, and then moved Self back left until the pair coincided. This looks goal-directed, but only the player can confirm whether overlap was a self-chosen target, a discovered possibility, input play, or confusion about whether another task existed.
If overlap was deliberate, it is stronger evidence than the two-tractor workaround in Experiment 003 because the chamber explicitly stated there was no required arrangement. Even then, it would establish a self-generated spatial goal, not automatically enjoyment or a curiosity chain. The next distinction is whether the player valued making the prediction and seeing it work, merely felt compelled to finish an obvious possibility, or was already bored while doing it.
## Player Report
The player never felt able to predict the paired system. Rooms 1 and 2 were obvious without requiring thought, while the complexity increase into Transfer was too large. In Transfer they noticed locally promising states and then mostly guessed/brute-forced. On a second look they understood that the yellow center cross could have been used as a reference, but that interpretation did not occur during the required sequence.
The open-chamber behavior was an intentional self-generated experiment, but the inferred goal from final overlap was wrong. The player was trying to move the yellow cross as close to a wall as possible. The chamber itself made them notice the cross. Answering that question was not satisfying.
## Final Interpretation
Experiment 004 did produce a genuine unrequired question, which is behavioral evidence that a stable system can provoke self-directed manipulation. It did not produce the hypothesized reward. The object of curiosity was an abstract marker state, and reaching an extreme had no meaningful consequence or new capability.
The required sequence also failed as a clean transfer test. Rooms 1 and 2 could be solved from immediate geometry without constructing the midpoint model; the third then demanded planning over that latent state without an intermediate scaffold. The marker was rendered but not functionally legible. This repeats an important finding from Experiment 000: visualization is not the same as strategic legibility.
Do not conclude that more tutorial rooms would make this fun. Better scaffolding would reduce brute force and allow a cleaner test of predictive mastery, but the player's self-chosen cross experiment already isolated the intrinsic payoff and was not satisfying. Abstract model acquisition is not currently supported as a sufficient activity.
The strongest cross-experiment model is now that questions become valuable when their answers change agency inside an activity the player already cares about. Curiosity, construction, pressure, and progression have each produced behavior without reliably producing enjoyment when their consequences were terminal, abstract, or strategically universal.

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" \
--directory "$EXPERIMENT_DIR/prototype" \
--log-directory "$REPO_ROOT/JSONL" \
--port 8000

View file

@ -0,0 +1,13 @@
# Experiment 005 — Sanctuary Wake
Run:
```bash
./experiments/005_sanctuary_wake/run.sh
```
Then open <http://127.0.0.1:8000>.
Play until the shift ends or until you genuinely want to stop. Use **End shift** if you want to stop early, then **Save JSONL**. The log is written directly into the repository-level `JSONL/` directory.
Do not read `hypothesis.md` before playing; it contains the private experiment rationale.

View file

@ -0,0 +1,72 @@
# Experiment 005 Hypothesis — Private Until After Play
## Reason for the Pivot
Experiment 004 produced a real self-directed question but no satisfaction. Better room scaffolding would improve predictive play, yet would not address the larger repeated failure: abstract answers and completed predicates have not been valuable by themselves.
Experiment 002 remains the strongest behavioral signal. Pressure sustained activity and its coupled thermal action briefly caused contextual tactics, but multiplicative upgrades collapsed the run into one wall-blast answer. Experiment 005 removes growth while retaining direct action, pressure, coupled consequences, and recoverable failure.
## Question
Does one simple physical tool become enjoyable when it supports improvised rescues, accidents, and weaponization in a changing field—without construction, hidden rules, or stat progression?
## Core System
The player directly pilots a small keeper around a sanctuary. Its field has two fully disclosed modes:
- pull every loose body in range toward the keeper;
- push every loose body in range away from the keeper.
The field does not distinguish rescue pods, raiders, or wreckage. Position and timing determine whether the same input helps or harms.
- Pods are saved on touching the sanctuary.
- Raiders capture pods and damage the sanctuary.
- Fast wreckage destroys raiders but also damages the sanctuary.
- Saved pods repair sanctuary integrity.
- Field charge and player impacts prevent continuous maximal use.
- A sanctuary breach vents the field and resets integrity rather than ending the run.
All laws are stated. There is no discovery test and no permanent build. Randomized spawns define the changing problem while the player always chooses exact actions.
## Competing Interpretations
1. Recoverable chaos plus coupled tools creates enjoyable improvisation.
2. Pressure creates input persistence but not enjoyment, repeating Experiment 002.
3. Rescue/defense supplies a valued consequence missing from prior abstractions.
4. Shepherding bodies with radial forces feels like another awkward steering task.
5. The system has one dominant positional answer, such as camping at the sanctuary and repelling.
6. The absence of power progression removes the main reason Experiment 002 lasted.
7. Three minutes is endured because it is a timer, not because the activity is wanted.
## Strong Evidence
- a deliberate save after an accident changes the situation;
- using wreckage against a raider without an explicit prompt;
- relocating because pull/push would affect mixed nearby bodies;
- changing priorities based on pod, raider, debris, and sanctuary state;
- continuing into optional overtime;
- a player-described moment whose outcome was surprising, understandable, and useful.
## Ambiguous Evidence
- finishing the timer;
- high rescue count;
- rapid input;
- survival without breaches;
- overtime entered merely to check whether anything changes.
## Failure Signals
- stationary field spam solves most states;
- activity feels like object babysitting or imprecise mouse-less herding;
- consequences are unreadable during clutter;
- the player waits out or dutifully finishes the timer;
- no event creates a memorable decision or recovery.
## Feedback to Request
1. When did they first want to stop, and did the shift timer make them continue past that point?
2. Was there one situation where they intentionally used or avoided affecting multiple object types at once?
3. Did any mistake create an interesting recovery, or did errors only add cleanup/frustration?
Do not ask whether they “like recoverable chaos”; ask about concrete moments.

View file

@ -0,0 +1,613 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const SHIFT_DURATION = 180;
const DOCK = { x: .5, y: .5, r: .073 };
const FIELD_RANGE = .235;
const KEY_VECTOR = {
KeyW: [0, -1], ArrowUp: [0, -1], KeyS: [0, 1], ArrowDown: [0, 1],
KeyA: [-1, 0], ArrowLeft: [-1, 0], KeyD: [1, 0], ArrowRight: [1, 0]
};
const STAGES = [
{ at: 0, name: "Quiet Wake" },
{ at: 25, name: "Scavengers" },
{ at: 75, name: "Debris Front" },
{ at: 130, name: "Convergence" }
];
const state = {
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
started: Date.now(), logs: [], active: false, ended: false, overtime: false,
shiftTime: 0, lastFrame: performance.now(), stage: 0, snapshotClock: 0,
sessionSeed: 0, random: null, objectId: 0, objects: [], particles: [], stars: [],
keys: new Set(), fieldMode: null, fieldStartedAt: 0, fieldSource: null,
nextPod: 3, nextRaider: 20, nextDebris: 6, bannerTimer: null, toastTimer: null,
report: [], integrity: 100, rescued: 0, lost: 0, breaches: 0, broken: 0,
player: { x: .5, y: .72, vx: 0, vy: 0, energy: 100, hitCooldown: 0 },
render: { size: 1, ox: 0, oy: 0, width: 1, height: 1 }
};
function hashSeed(text) {
let hash = 2166136261;
for (let i = 0; i < text.length; i++) { hash ^= text.charCodeAt(i); hash = Math.imul(hash, 16777619); }
return hash >>> 0;
}
function mulberry32(seed) {
return () => {
seed |= 0; seed = seed + 0x6D2B79F5 | 0;
let value = Math.imul(seed ^ seed >>> 15, 1 | seed);
value = value + Math.imul(value ^ value >>> 7, 61 | value) ^ value;
return ((value ^ value >>> 14) >>> 0) / 4294967296;
};
}
state.sessionSeed = hashSeed(state.session);
state.random = mulberry32(state.sessionSeed);
for (let i = 0; i < 90; i++) state.stars.push([state.random(), state.random(), .15 + state.random() * .55]);
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const round = value => Math.round(value * 1000) / 1000;
const objectCounts = () => state.objects.reduce((counts, object) => {
counts[object.kind] = (counts[object.kind] || 0) + 1; return counts;
}, { pod: 0, raider: 0, debris: 0 });
function log(type, data = {}) {
const event = {
schema: 1, experiment: "005_sanctuary_wake", prototype_revision: 1,
session_id: state.session, elapsed_ms: Date.now() - state.started,
shift_seconds: round(state.shiftTime), stage: state.overtime ? "overtime" : STAGES[state.stage].name,
type, ...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("sanctuary-wake-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function addReport(text) {
state.report.unshift(text);
state.report = state.report.slice(0, 4);
$("#field-report").innerHTML = state.report.map((line, index) => `<div class="${index === 0 ? "latest" : ""}">${line}</div>`).join("");
}
function begin() {
if (state.active || state.ended) return;
$("#start-overlay").classList.add("hidden");
state.active = true;
state.lastFrame = performance.now();
spawnInitial();
addReport("Recovery shift started. The wake is quiet—for now.");
log("shift_started", { seed: state.sessionSeed, viewport: [window.innerWidth, window.innerHeight], initial_objects: objectCounts() });
canvas.focus({ preventScroll: true });
updateUI();
}
function spawnInitial() {
spawn("pod", { x: .12, y: .27, vx: .018, vy: .008 });
spawn("pod", { x: .87, y: .34, vx: -.016, vy: .006 });
spawn("debris", { x: .16, y: .74, vx: .067, vy: -.02 });
spawn("debris", { x: .84, y: .77, vx: -.058, vy: -.026 });
}
function randomEdge(kind) {
const edge = Math.floor(state.random() * 4);
const margin = .035, along = .1 + state.random() * .8;
let x, y, nx, ny;
if (edge === 0) { x = along; y = margin; nx = 0; ny = 1; }
else if (edge === 1) { x = 1 - margin; y = along; nx = -1; ny = 0; }
else if (edge === 2) { x = along; y = 1 - margin; nx = 0; ny = -1; }
else { x = margin; y = along; nx = 1; ny = 0; }
const spread = (state.random() - .5) * .055;
const speed = kind === "debris" ? .055 + state.random() * .075 : .01 + state.random() * .012;
return { x, y, vx: nx * speed + (ny !== 0 ? spread : 0), vy: ny * speed + (nx !== 0 ? spread : 0) };
}
function spawn(kind, values = randomEdge(kind)) {
const definitions = {
pod: { radius: .016, mass: .72 }, raider: { radius: .019, mass: 1.05 }, debris: { radius: .021, mass: 2.15 }
};
const object = {
id: ++state.objectId, kind, radius: definitions[kind].radius, mass: definitions[kind].mass,
x: values.x, y: values.y, vx: values.vx, vy: values.vy, age: 0, dockCooldown: 0,
fieldInfluence: 0, removed: false
};
state.objects.push(object);
log("object_spawned", { id: object.id, kind, position: [round(object.x), round(object.y)], velocity: [round(object.vx), round(object.vy)], counts: objectCounts() });
return object;
}
function updateSpawns() {
const stage = state.overtime ? 4 : state.stage;
if (state.shiftTime >= state.nextPod) {
spawn("pod"); state.nextPod += Math.max(5.2, 8.4 - stage * .65) + state.random() * 2;
}
if (stage > 0 && state.shiftTime >= state.nextRaider) {
spawn("raider"); state.nextRaider += Math.max(3.4, 7.4 - stage * .9) + state.random() * 1.4;
}
if (state.shiftTime >= state.nextDebris) {
spawn("debris");
const base = stage === 2 ? 4.7 : stage >= 3 ? 5.5 : 8.5;
state.nextDebris += base + state.random() * 2.2;
}
}
function updateStage() {
if (state.overtime) return;
let next = 0;
for (let i = 0; i < STAGES.length; i++) if (state.shiftTime >= STAGES[i].at) next = i;
if (next === state.stage) return;
state.stage = next;
const stage = STAGES[next];
showBanner(stage.name);
addReport(`Conditions changed: ${stage.name}.`);
log("stage_changed", { stage_index: next, stage_name: stage.name, counts: objectCounts(), integrity: round(state.integrity) });
}
function updatePlayer(dt) {
let dx = 0, dy = 0;
for (const code of state.keys) {
const vector = KEY_VECTOR[code]; if (!vector) continue;
dx += vector[0]; dy += vector[1];
}
const magnitude = Math.hypot(dx, dy) || 1;
if (dx || dy) { state.player.vx += dx / magnitude * .82 * dt; state.player.vy += dy / magnitude * .82 * dt; }
const drag = Math.pow(.055, dt);
state.player.vx *= drag; state.player.vy *= drag;
const speed = Math.hypot(state.player.vx, state.player.vy), max = .29;
if (speed > max) { state.player.vx *= max / speed; state.player.vy *= max / speed; }
state.player.x = clamp(state.player.x + state.player.vx * dt, .035, .965);
state.player.y = clamp(state.player.y + state.player.vy * dt, .035, .965);
if (state.player.x <= .035 || state.player.x >= .965) state.player.vx *= -.25;
if (state.player.y <= .035 || state.player.y >= .965) state.player.vy *= -.25;
state.player.hitCooldown = Math.max(0, state.player.hitCooldown - dt);
if (state.fieldMode && state.player.energy > 0) {
state.player.energy = Math.max(0, state.player.energy - 25 * dt);
applyField(dt);
if (state.player.energy === 0) stopField("depleted");
} else {
state.player.energy = Math.min(100, state.player.energy + 18 * dt);
}
}
function applyField(dt) {
const sign = state.fieldMode === "pull" ? 1 : -1;
for (const object of state.objects) {
const dx = state.player.x - object.x, dy = state.player.y - object.y;
const length = Math.hypot(dx, dy);
if (length <= .001 || length > FIELD_RANGE) { object.fieldInfluence = 0; continue; }
const falloff = 1 - length / FIELD_RANGE;
const acceleration = sign * (.46 / object.mass) * (.22 + falloff * .78);
object.vx += dx / length * acceleration * dt;
object.vy += dy / length * acceleration * dt;
object.fieldInfluence = sign * falloff;
}
}
function updateObjects(dt) {
for (const object of state.objects) {
object.age += dt; object.dockCooldown = Math.max(0, object.dockCooldown - dt);
if (!state.fieldMode) object.fieldInfluence = 0;
if (object.kind === "raider") {
const pods = state.objects.filter(candidate => candidate.kind === "pod" && !candidate.removed);
const target = pods.length ? pods.reduce((best, candidate) => distance(object, candidate) < distance(object, best) ? candidate : best) : DOCK;
steerToward(object, target, .045, dt);
} else if (object.kind === "pod") {
steerToward(object, DOCK, .0065, dt);
}
const drag = Math.pow(object.kind === "debris" ? .996 : .982, dt * 60);
object.vx *= drag; object.vy *= drag;
const speed = Math.hypot(object.vx, object.vy), max = object.kind === "debris" ? .34 : .28;
if (speed > max) { object.vx *= max / speed; object.vy *= max / speed; }
object.x += object.vx * dt; object.y += object.vy * dt;
bounceBoundary(object);
}
resolveObjectCollisions();
resolvePlayerCollisions();
resolveDockContacts();
state.objects = state.objects.filter(object => !object.removed);
}
function steerToward(object, target, acceleration, dt) {
const dx = target.x - object.x, dy = target.y - object.y, length = Math.hypot(dx, dy) || 1;
object.vx += dx / length * acceleration * dt;
object.vy += dy / length * acceleration * dt;
}
function bounceBoundary(object) {
const low = object.radius, high = 1 - object.radius;
if (object.x < low) { object.x = low; object.vx = Math.abs(object.vx) * .78; }
if (object.x > high) { object.x = high; object.vx = -Math.abs(object.vx) * .78; }
if (object.y < low) { object.y = low; object.vy = Math.abs(object.vy) * .78; }
if (object.y > high) { object.y = high; object.vy = -Math.abs(object.vy) * .78; }
}
function resolveObjectCollisions() {
for (let i = 0; i < state.objects.length; i++) {
const a = state.objects[i]; if (a.removed) continue;
for (let j = i + 1; j < state.objects.length; j++) {
const b = state.objects[j]; if (b.removed) continue;
const dx = b.x - a.x, dy = b.y - a.y, length = Math.hypot(dx, dy), overlap = a.radius + b.radius - length;
if (overlap <= 0) continue;
const nx = length > .0001 ? dx / length : 1, ny = length > .0001 ? dy / length : 0;
const relative = Math.abs((b.vx - a.vx) * nx + (b.vy - a.vy) * ny);
const raider = a.kind === "raider" ? a : b.kind === "raider" ? b : null;
const debris = a.kind === "debris" ? a : b.kind === "debris" ? b : null;
if (raider && debris && relative >= .105) {
raider.removed = true; state.broken++;
burst(raider.x, raider.y, "#f07370", 12);
addReport("Fast wreckage broke a raider.");
log("raider_broken", { raider_id: raider.id, debris_id: debris.id, relative_speed: round(relative), position: [round(raider.x), round(raider.y)], field_mode: state.fieldMode });
continue;
}
const pod = a.kind === "pod" ? a : b.kind === "pod" ? b : null;
if (raider && pod) {
pod.removed = true; state.lost++;
burst(pod.x, pod.y, "#7bdcf4", 9);
addReport("A raider captured an escape pod.");
log("pod_lost", { pod_id: pod.id, reason: "captured", raider_id: raider.id, position: [round(pod.x), round(pod.y)] });
continue;
}
separateAndBounce(a, b, nx, ny, overlap);
}
}
}
function separateAndBounce(a, b, nx, ny, overlap) {
const totalMass = a.mass + b.mass;
a.x -= nx * overlap * b.mass / totalMass; a.y -= ny * overlap * b.mass / totalMass;
b.x += nx * overlap * a.mass / totalMass; b.y += ny * overlap * a.mass / totalMass;
const relative = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;
if (relative >= 0) return;
const impulse = -(1.55) * relative / (1 / a.mass + 1 / b.mass);
a.vx -= impulse * nx / a.mass; a.vy -= impulse * ny / a.mass;
b.vx += impulse * nx / b.mass; b.vy += impulse * ny / b.mass;
}
function resolvePlayerCollisions() {
for (const object of state.objects) {
if (object.removed) continue;
const dx = object.x - state.player.x, dy = object.y - state.player.y, length = Math.hypot(dx, dy), minimum = object.radius + .021;
if (length >= minimum) continue;
const nx = length > .0001 ? dx / length : 1, ny = length > .0001 ? dy / length : 0;
object.x = state.player.x + nx * minimum; object.y = state.player.y + ny * minimum;
const push = .11 + Math.hypot(state.player.vx, state.player.vy) * .35;
object.vx += nx * push; object.vy += ny * push;
state.player.vx -= nx * .035; state.player.vy -= ny * .035;
if ((object.kind === "raider" || object.kind === "debris") && state.player.hitCooldown === 0) {
state.player.energy = Math.max(0, state.player.energy - 16);
state.player.hitCooldown = .55;
burst(state.player.x, state.player.y, "#efb45a", 6);
log("keeper_impact", { object_id: object.id, object_kind: object.kind, energy_after: round(state.player.energy), position: [round(state.player.x), round(state.player.y)] });
}
}
}
function resolveDockContacts() {
for (const object of state.objects) {
if (object.removed) continue;
const dx = object.x - DOCK.x, dy = object.y - DOCK.y, length = Math.hypot(dx, dy);
if (length > DOCK.r + object.radius) continue;
if (object.kind === "pod") {
object.removed = true; state.rescued++; state.integrity = Math.min(100, state.integrity + 6);
burst(object.x, object.y, "#7bdcf4", 13);
addReport("Escape pod secured. Sanctuary integrity restored.");
log("pod_rescued", { pod_id: object.id, rescued: state.rescued, integrity_after: round(state.integrity), velocity: [round(object.vx), round(object.vy)] });
} else if (object.kind === "raider") {
object.removed = true; damageDock(14, "raider", object);
} else {
const nx = length > .0001 ? dx / length : 1, ny = length > .0001 ? dy / length : 0;
object.x = DOCK.x + nx * (DOCK.r + object.radius + .002);
const radial = object.vx * nx + object.vy * ny;
if (radial < 0) { object.vx -= 1.75 * radial * nx; object.vy -= 1.75 * radial * ny; }
if (object.dockCooldown === 0 && Math.abs(radial) > .065) {
object.dockCooldown = .8;
damageDock(clamp(Math.abs(radial) * 72, 5, 18), "wreckage", object);
}
}
}
}
function damageDock(amount, cause, object) {
state.integrity -= amount;
burst(DOCK.x, DOCK.y, cause === "raider" ? "#f07370" : "#efb45a", 15);
addReport(`${cause === "raider" ? "Raider" : "Wreckage"} struck the sanctuary: ${Math.round(amount)} integrity.`);
log("sanctuary_hit", { cause, object_id: object.id, damage: round(amount), integrity_after: round(Math.max(0, state.integrity)), velocity: [round(object.vx), round(object.vy)] });
if (state.integrity <= 0) breach();
}
function breach() {
state.breaches++; state.integrity = 45;
for (const object of state.objects) {
const dx = object.x - DOCK.x, dy = object.y - DOCK.y, length = Math.hypot(dx, dy) || 1;
object.vx += dx / length * .24; object.vy += dy / length * .24;
}
burst(DOCK.x, DOCK.y, "#f07370", 35);
showBanner("SANCTUARY BREACH · EMERGENCY VENT");
addReport("Breach contained. Emergency vent cleared the center; recovery continues.");
log("sanctuary_breach", { breaches: state.breaches, reset_integrity: state.integrity, counts: objectCounts() });
}
function burst(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = state.random() * Math.PI * 2, speed = .035 + state.random() * .16;
state.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: .35 + state.random() * .55, maxLife: .9, color });
}
}
function updateParticles(dt) {
for (const particle of state.particles) {
particle.x += particle.vx * dt; particle.y += particle.vy * dt;
particle.vx *= Math.pow(.2, dt); particle.vy *= Math.pow(.2, dt); particle.life -= dt;
}
state.particles = state.particles.filter(particle => particle.life > 0);
}
function startField(mode, source) {
if (!state.active || state.player.energy <= 0 || (mode !== "pull" && mode !== "push")) return;
if (state.fieldMode === mode) return;
if (state.fieldMode) stopField("mode_changed");
state.fieldMode = mode; state.fieldStartedAt = state.shiftTime; state.fieldSource = source;
$("#pull-button").classList.toggle("active", mode === "pull");
$("#push-button").classList.toggle("active", mode === "push");
log("field_started", { mode, source, energy: round(state.player.energy), position: [round(state.player.x), round(state.player.y)], counts: objectCounts() });
}
function stopField(reason) {
if (!state.fieldMode) return;
log("field_ended", {
mode: state.fieldMode, source: state.fieldSource, reason,
duration_seconds: round(state.shiftTime - state.fieldStartedAt), energy: round(state.player.energy),
position: [round(state.player.x), round(state.player.y)], counts: objectCounts()
});
state.fieldMode = null; state.fieldSource = null;
$("#pull-button").classList.remove("active"); $("#push-button").classList.remove("active");
}
function endShift(early) {
if (state.ended || (!state.active && !state.overtime)) return;
stopField("shift_ended"); state.active = false; state.ended = true;
log("shift_ended", { early, rescued: state.rescued, lost: state.lost, breaches: state.breaches, broken: state.broken, integrity: round(state.integrity), counts: objectCounts() });
$("#summary-title").textContent = early ? "Shift ended early" : "Measured shift complete";
$("#summary-stats").innerHTML = [
["RESCUED", state.rescued], ["LOST", state.lost], ["BREACHES", state.breaches], ["RAIDERS BROKEN", state.broken]
].map(([label, value]) => `<div><span>${label}</span><b>${value}</b></div>`).join("");
$("#summary-copy").textContent = early ? "You ended the measured shift. Continue only if you want to keep interacting." : "The measured shift is over. Continuing grants no upgrade or new tool.";
$("#summary-overlay").classList.remove("hidden");
updateUI();
}
function startOvertime() {
if (!state.ended) return;
state.ended = false; state.overtime = true; state.active = true;
$("#summary-overlay").classList.add("hidden");
state.nextPod = state.shiftTime + 2; state.nextRaider = state.shiftTime + 1.5; state.nextDebris = state.shiftTime + 3;
addReport("Overtime started. No new tools or upgrades are coming.");
showBanner("OVERTIME");
log("overtime_started", { stats: { rescued: state.rescued, lost: state.lost, breaches: state.breaches, broken: state.broken }, counts: objectCounts() });
canvas.focus({ preventScroll: true }); updateUI();
}
async function saveLog() {
log("save_requested", { event_count_before_save: state.logs.length });
const filename = `sanctuary-wake-${state.session}.jsonl`;
const payload = state.logs.join("\n") + "\n";
try {
const response = await fetch("/api/playtest-log", {
method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: payload
});
if (!response.ok) throw new Error(`server returned ${response.status}`);
const result = await response.json();
log("log_saved", { path: result.path, saved_events: result.events });
showToast(`Saved ${result.events} events to ${result.path}`);
} catch (error) {
const blob = new Blob([payload], { type: "application/x-ndjson" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob); link.download = filename; link.click();
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
log("save_fallback_download", { message: String(error) });
showToast("Server save unavailable; downloaded the JSONL instead.");
}
}
function update(dt) {
if (!state.active) return;
state.shiftTime += dt;
updateStage(); updateSpawns(); updatePlayer(dt); updateObjects(dt); updateParticles(dt);
state.snapshotClock += dt;
if (state.snapshotClock >= 2) {
state.snapshotClock -= 2;
log("field_snapshot", {
player: [round(state.player.x), round(state.player.y)], velocity: [round(state.player.vx), round(state.player.vy)],
energy: round(state.player.energy), field_mode: state.fieldMode, integrity: round(state.integrity),
rescued: state.rescued, lost: state.lost, breaches: state.breaches, broken: state.broken, counts: objectCounts()
});
}
if (!state.overtime && state.shiftTime >= SHIFT_DURATION) endShift(false);
updateUI();
}
function updateUI() {
const remaining = Math.max(0, SHIFT_DURATION - state.shiftTime);
const remainingSeconds = Math.ceil(remaining);
$("#time-left").textContent = state.overtime ? "OVERTIME" : `${Math.floor(remainingSeconds / 60)}:${String(remainingSeconds % 60).padStart(2, "0")}`;
$("#charge").textContent = `${Math.round(state.player.energy)}%`;
$("#rescued").textContent = state.rescued; $("#lost").textContent = state.lost;
$("#breaches").textContent = state.breaches; $("#broken").textContent = state.broken;
$("#integrity-value").textContent = `${Math.round(state.integrity)}%`;
$("#integrity-bar").style.transform = `scaleX(${clamp(state.integrity / 100, 0, 1)})`;
$("#stage-name").textContent = state.overtime ? "Overtime" : STAGES[state.stage].name;
$("#end-shift").disabled = !state.active;
}
function showBanner(message) {
const banner = $("#banner"); banner.textContent = message; banner.classList.add("show");
clearTimeout(state.bannerTimer); state.bannerTimer = setTimeout(() => banner.classList.remove("show"), 1500);
}
function showToast(message) {
const toast = $("#toast"); toast.textContent = message; toast.classList.add("show");
clearTimeout(state.toastTimer); state.toastTimer = setTimeout(() => toast.classList.remove("show"), 2800);
}
function resize() {
const rect = canvas.getBoundingClientRect(), dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.max(1, Math.round(rect.width * dpr)); canvas.height = Math.max(1, Math.round(rect.height * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const size = Math.min(rect.width, rect.height) * .91;
state.render = { size, ox: (rect.width - size) / 2, oy: (rect.height - size) / 2, width: rect.width, height: rect.height };
}
function screen(point) {
return { x: state.render.ox + point.x * state.render.size, y: state.render.oy + point.y * state.render.size };
}
function draw(time) {
const { width, height, size, ox, oy } = state.render;
ctx.clearRect(0, 0, width, height);
ctx.save(); ctx.beginPath(); ctx.rect(ox, oy, size, size); ctx.clip();
ctx.fillStyle = "#071016"; ctx.fillRect(ox, oy, size, size);
for (const [x, y, alpha] of state.stars) {
ctx.fillStyle = `rgba(151, 190, 199, ${alpha})`; ctx.fillRect(ox + x * size, oy + y * size, 1.2, 1.2);
}
drawGrid(); drawDock(time); drawField();
for (const object of state.objects) drawObject(object);
for (const particle of state.particles) drawParticle(particle);
drawPlayer();
ctx.restore();
ctx.strokeStyle = "#38505a"; ctx.lineWidth = 1; ctx.strokeRect(ox + .5, oy + .5, size - 1, size - 1);
}
function drawGrid() {
const { size, ox, oy } = state.render;
ctx.strokeStyle = "rgba(87, 126, 137, .09)"; ctx.lineWidth = 1;
for (let i = 1; i < 10; i++) {
ctx.beginPath(); ctx.moveTo(ox + i * size / 10, oy); ctx.lineTo(ox + i * size / 10, oy + size); ctx.stroke();
ctx.beginPath(); ctx.moveTo(ox, oy + i * size / 10); ctx.lineTo(ox + size, oy + i * size / 10); ctx.stroke();
}
}
function drawDock(time) {
const center = screen(DOCK), radius = DOCK.r * state.render.size;
ctx.save(); ctx.translate(center.x, center.y);
ctx.fillStyle = "rgba(65, 133, 115, .16)"; ctx.strokeStyle = state.integrity < 30 ? "#f07370" : "#6ee4bd";
ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
ctx.strokeStyle = "rgba(110, 228, 189, .35)"; ctx.lineWidth = 1.5; ctx.setLineDash([5, 7]);
ctx.rotate(time / 3200); ctx.beginPath(); ctx.arc(0, 0, radius * .72, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]);
for (let i = 0; i < Math.min(12, state.rescued); i++) {
const angle = i / 12 * Math.PI * 2; ctx.fillStyle = "#7bdcf4";
ctx.beginPath(); ctx.arc(Math.cos(angle) * radius * .48, Math.sin(angle) * radius * .48, 2.3, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = "#d7f4e9"; ctx.font = `800 ${Math.max(8, radius * .2)}px ui-monospace, monospace`;
ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("SANCTUARY", 0, 1);
ctx.restore();
}
function drawField() {
if (!state.fieldMode) return;
const player = screen(state.player), radius = FIELD_RANGE * state.render.size;
const color = state.fieldMode === "pull" ? "123,220,244" : "239,180,90";
const gradient = ctx.createRadialGradient(player.x, player.y, 0, player.x, player.y, radius);
gradient.addColorStop(0, `rgba(${color}, .13)`); gradient.addColorStop(.65, `rgba(${color}, .06)`); gradient.addColorStop(1, `rgba(${color}, 0)`);
ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(player.x, player.y, radius, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = `rgba(${color}, .5)`; ctx.lineWidth = 1.5; ctx.setLineDash([7, 7]);
ctx.beginPath(); ctx.arc(player.x, player.y, radius, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]);
for (const object of state.objects) {
if (!object.fieldInfluence) continue;
const point = screen(object); ctx.strokeStyle = `rgba(${color}, ${.15 + Math.abs(object.fieldInfluence) * .45})`;
ctx.beginPath(); ctx.moveTo(player.x, player.y); ctx.lineTo(point.x, point.y); ctx.stroke();
}
}
function drawObject(object) {
const point = screen(object), radius = object.radius * state.render.size;
ctx.save(); ctx.translate(point.x, point.y);
if (object.kind === "pod") {
ctx.shadowColor = "#7bdcf4"; ctx.shadowBlur = 10; ctx.fillStyle = "#173844"; ctx.strokeStyle = "#7bdcf4"; ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
ctx.fillStyle = "#d9f8ff"; ctx.beginPath(); ctx.arc(0, 0, radius * .3, 0, Math.PI * 2); ctx.fill();
} else if (object.kind === "raider") {
ctx.rotate(Math.atan2(object.vy, object.vx) + Math.PI / 4); ctx.shadowColor = "#f07370"; ctx.shadowBlur = 10;
ctx.fillStyle = "#401e24"; ctx.strokeStyle = "#f07370"; ctx.lineWidth = 2;
ctx.beginPath(); ctx.rect(-radius * .72, -radius * .72, radius * 1.44, radius * 1.44); ctx.fill(); ctx.stroke();
ctx.strokeStyle = "#ffb0a7"; ctx.beginPath(); ctx.moveTo(-radius * .45, 0); ctx.lineTo(radius * .45, 0); ctx.stroke();
} else {
ctx.rotate(object.id * 1.71 + object.age * .15); ctx.fillStyle = "#443621"; ctx.strokeStyle = "#efb45a"; ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = i / 6 * Math.PI * 2, uneven = i % 2 ? .78 : 1;
const x = Math.cos(angle) * radius * uneven, y = Math.sin(angle) * radius * uneven;
i ? ctx.lineTo(x, y) : ctx.moveTo(x, y);
}
ctx.closePath(); ctx.fill(); ctx.stroke();
}
ctx.restore();
}
function drawPlayer() {
const point = screen(state.player), radius = .022 * state.render.size;
ctx.save(); ctx.translate(point.x, point.y); ctx.rotate(Math.atan2(state.player.vy, state.player.vx) + Math.PI / 2);
ctx.shadowColor = state.player.hitCooldown ? "#f07370" : "#6ee4bd"; ctx.shadowBlur = 14;
ctx.fillStyle = "#173b33"; ctx.strokeStyle = state.player.hitCooldown ? "#f07370" : "#6ee4bd"; ctx.lineWidth = 2.5;
ctx.beginPath(); ctx.moveTo(0, -radius); ctx.lineTo(radius * .8, radius * .85); ctx.lineTo(0, radius * .56); ctx.lineTo(-radius * .8, radius * .85); ctx.closePath(); ctx.fill(); ctx.stroke();
ctx.restore();
}
function drawParticle(particle) {
const point = screen(particle); ctx.globalAlpha = clamp(particle.life / particle.maxLife, 0, 1);
ctx.fillStyle = particle.color; ctx.beginPath(); ctx.arc(point.x, point.y, 2.2, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1;
}
function frame(now) {
const dt = Math.min(.035, Math.max(0, (now - state.lastFrame) / 1000)); state.lastFrame = now;
update(dt); draw(now); requestAnimationFrame(frame);
}
document.addEventListener("keydown", event => {
if (!KEY_VECTOR[event.code]) return;
event.preventDefault();
if (!state.keys.has(event.code)) {
state.keys.add(event.code);
if (state.active) log("movement_key_down", { key: event.code, active_keys: [...state.keys] });
}
});
document.addEventListener("keyup", event => {
if (!KEY_VECTOR[event.code]) return;
event.preventDefault(); state.keys.delete(event.code);
if (state.active) log("movement_key_up", { key: event.code, active_keys: [...state.keys] });
});
canvas.addEventListener("pointerdown", event => {
event.preventDefault();
try { canvas.setPointerCapture?.(event.pointerId); } catch (_) {}
canvas.focus({ preventScroll: true });
if (event.button === 0) startField("pull", "canvas");
if (event.button === 2) startField("push", "canvas");
});
window.addEventListener("pointerup", event => {
if ((event.button === 0 && state.fieldMode === "pull") || (event.button === 2 && state.fieldMode === "push")) stopField("pointer_up");
});
canvas.addEventListener("contextmenu", event => event.preventDefault());
document.addEventListener("contextmenu", event => event.preventDefault());
document.addEventListener("selectstart", event => event.preventDefault());
for (const [selector, mode] of [["#pull-button", "pull"], ["#push-button", "push"]]) {
const button = $(selector);
button.addEventListener("pointerdown", event => { event.preventDefault(); startField(mode, "button"); });
button.addEventListener("pointerup", () => { if (state.fieldMode === mode) stopField("pointer_up"); });
button.addEventListener("pointercancel", () => { if (state.fieldMode === mode) stopField("pointer_cancel"); });
}
$("#start-overlay").addEventListener("pointerdown", begin);
$("#end-shift").addEventListener("click", () => endShift(true));
$("#save").addEventListener("click", saveLog); $("#save-summary").addEventListener("click", saveLog);
$("#overtime").addEventListener("click", startOvertime);
window.addEventListener("blur", () => { state.keys.clear(); stopField("window_blur"); });
document.addEventListener("visibilitychange", () => log("visibility_changed", { visibility: document.visibilityState, active: state.active, stats: { rescued: state.rescued, lost: state.lost, breaches: state.breaches } }));
window.addEventListener("beforeunload", () => log("session_unload", { active: state.active, stats: { rescued: state.rescued, lost: state.lost, breaches: state.breaches, broken: state.broken } }));
window.addEventListener("resize", resize);
resize(); updateUI(); requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,97 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sanctuary Wake — Experiment 005</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 005</span><h1>Sanctuary Wake</h1></div>
<div class="shift-stage"><span id="stage-kicker">SHIFT STAGE</span><b id="stage-name">Quiet Wake</b></div>
<div class="top-actions">
<button id="end-shift" type="button">End shift</button>
<button id="save" type="button">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="brief">
<span>THREE-MINUTE RECOVERY SHIFT</span>
<h2>Keep the sanctuary working.</h2>
<p>Bring escape pods into the sanctuary. Keep raiders and fast wreckage away. A breach is costly, but the shift continues.</p>
</section>
<section class="controls">
<h3>Keeper controls</h3>
<p><kbd>WASD</kbd><span>Move the keeper.</span></p>
<p><kbd>LEFT HOLD</kbd><span>Pull every loose body in range.</span></p>
<p><kbd>RIGHT HOLD</kbd><span>Push every loose body in range.</span></p>
<div class="field-buttons">
<button id="pull-button" type="button"><i></i><span><b>PULL</b><small>toward keeper</small></span></button>
<button id="push-button" type="button"><i></i><span><b>PUSH</b><small>away from keeper</small></span></button>
</div>
</section>
<section class="laws">
<h3>Field consequences</h3>
<div class="law pod"><i></i><p><b>ESCAPE POD</b><span>Sanctuary contact rescues it and repairs integrity.</span></p></div>
<div class="law raider"><i></i><p><b>RAIDER</b><span>Captures pods. Sanctuary impact damages integrity.</span></p></div>
<div class="law debris"><i></i><p><b>WRECKAGE</b><span>A fast hit destroys a raider. It can also damage the sanctuary.</span></p></div>
<div class="law neutral"><i></i><p><b>ONE FIELD</b><span>Pull and push affect all three kinds. Mass changes acceleration.</span></p></div>
</section>
<section class="metrics">
<div><span>SHIFT LEFT</span><b id="time-left">3:00</b></div>
<div><span>FIELD CHARGE</span><b id="charge">100%</b></div>
<div><span>RESCUED</span><b id="rescued">0</b></div>
<div><span>LOST</span><b id="lost">0</b></div>
<div><span>BREACHES</span><b id="breaches">0</b></div>
<div><span>RAIDERS BROKEN</span><b id="broken">0</b></div>
</section>
<section class="integrity">
<div><span>SANCTUARY INTEGRITY</span><b id="integrity-value">100%</b></div>
<div class="bar"><i id="integrity-bar"></i></div>
</section>
<section class="notes">
<h3>Field report</h3>
<div id="field-report">Awaiting shift start.</div>
</section>
</aside>
<section class="playfield">
<canvas id="field" tabindex="0" aria-label="Sanctuary recovery field"></canvas>
<div id="banner" class="banner" aria-live="polite"></div>
<div class="canvas-help">Move with WASD · hold left mouse to pull · hold right mouse to push</div>
</section>
</main>
<div id="start-overlay" class="overlay">
<div>
<span>THE FIELD DOES NOT CHOOSE FOR YOU</span>
<b>Pull and push affect pods, raiders, and wreckage together.</b>
<p>The shift lasts three minutes. Breaches vent the field instead of ending the run. Click to begin.</p>
</div>
</div>
<div id="summary-overlay" class="overlay hidden">
<div>
<span>SHIFT REPORT</span>
<b id="summary-title">Shift complete</b>
<div id="summary-stats" class="summary-stats"></div>
<p id="summary-copy">The measured shift is over.</p>
<div class="summary-actions">
<button id="save-summary" type="button">Save JSONL</button>
<button id="overtime" type="button">Continue without a timer</button>
</div>
</div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,116 @@
:root {
color-scheme: dark;
--bg: #05090d;
--panel: #0b141a;
--panel2: #111d24;
--line: #293b45;
--text: #e8f1ef;
--muted: #879b9f;
--mint: #6ee4bd;
--cyan: #7bdcf4;
--red: #f07370;
--amber: #efb45a;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; user-select: none; }
button { border: 1px solid var(--line); border-radius: 7px; background: #13212a; color: var(--text); font: inherit; cursor: pointer; touch-action: none; }
button:hover { border-color: #56717a; background: #1a2b35; }
h1, h2, h3, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 5px 0 0; font-size: 18px; }
h3 { margin: 0 0 9px; color: #a9bbb9; font-size: 9px; letter-spacing: .14em; text-transform: uppercase; }
header { height: 64px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 14px; padding: 8px 14px; border-bottom: 1px solid var(--line); background: #081015; }
.title > span, .brief > span, .overlay span { display: block; color: var(--mint); font-size: 8px; font-weight: 850; letter-spacing: .18em; }
.shift-stage { min-width: 170px; text-align: center; }
.shift-stage span, .shift-stage b { display: block; }
.shift-stage span { color: var(--muted); font-size: 7px; letter-spacing: .15em; }
.shift-stage b { margin-top: 3px; color: var(--amber); font-size: 12px; }
.top-actions { display: flex; justify-content: flex-end; gap: 7px; }
.top-actions button { padding: 8px 11px; }
main { height: calc(100vh - 64px); min-height: 0; display: grid; grid-template-columns: clamp(285px, 22vw, 345px) minmax(0, 1fr); overflow: hidden; }
aside { min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 12px 14px; border-right: 1px solid var(--line); background: #081015; }
aside section { margin-bottom: 10px; }
.brief { padding: 11px; border: 1px solid #355a4e; border-radius: 9px; background: linear-gradient(145deg, #10241e, #101920); }
.brief p { margin: 8px 0 0; color: #b8c7c4; font-size: 10px; line-height: 1.45; }
.controls { padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); }
.controls > p { display: grid; grid-template-columns: 76px 1fr; align-items: center; gap: 7px; margin: 6px 0; color: var(--muted); font-size: 8px; }
kbd { padding: 3px 5px; border: 1px solid #53666c; border-bottom-width: 2px; border-radius: 4px; background: #17252d; color: var(--text); font: 8px ui-monospace, monospace; text-align: center; }
.field-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 7px; }
.field-buttons button { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 6px; }
.field-buttons button.active { border-color: var(--cyan); background: #12313a; box-shadow: inset 0 0 18px #65d8f422; }
.field-buttons i { color: var(--cyan); font-size: 17px; font-style: normal; }
.field-buttons b, .field-buttons small { display: block; }
.field-buttons b { font-size: 9px; }
.field-buttons small { margin-top: 2px; color: var(--muted); font-size: 7px; }
.laws { display: grid; gap: 4px; }
.laws h3 { margin-bottom: 2px; }
.law { display: grid; grid-template-columns: 27px 1fr; align-items: center; gap: 7px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.law i { font-style: normal; font-size: 17px; text-align: center; }
.law p { margin: 0; }
.law b, .law span { display: block; }
.law b { font-size: 8px; letter-spacing: .08em; }
.law span { margin-top: 2px; color: var(--muted); font-size: 7px; line-height: 1.35; }
.law.pod i { color: var(--cyan); }
.law.raider i { color: var(--red); }
.law.debris i { color: var(--amber); }
.law.neutral i { color: var(--mint); }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.metrics div { min-width: 0; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.metrics span, .metrics b { display: block; }
.metrics span { overflow: hidden; color: var(--muted); font-size: 6px; letter-spacing: .08em; text-overflow: ellipsis; white-space: nowrap; }
.metrics b { margin-top: 4px; color: #d8e4e1; font: 10px ui-monospace, monospace; }
.integrity { padding: 8px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.integrity > div:first-child { display: flex; justify-content: space-between; align-items: center; }
.integrity span { color: var(--muted); font-size: 7px; letter-spacing: .1em; }
.integrity b { font: 10px ui-monospace, monospace; }
.bar { height: 7px; margin-top: 8px; overflow: hidden; border-radius: 7px; background: #1b272d; }
.bar i { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, var(--red), var(--amber), var(--mint)); transform-origin: left; }
.notes { min-height: 55px; padding: 7px 9px; border-left: 2px solid #415a61; color: #a6b8b6; font-size: 8px; line-height: 1.4; }
.notes h3 { margin-bottom: 6px; }
.playfield { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: radial-gradient(circle at 50% 48%, #11212a, #05090c 73%); }
#field { display: block; width: 100%; height: 100%; outline: none; touch-action: none; }
.canvas-help { position: absolute; z-index: 3; left: 50%; bottom: 13px; translate: -50%; padding: 7px 11px; border-radius: 6px; background: #071015dc; color: #809399; font-size: 9px; pointer-events: none; }
.banner { position: absolute; z-index: 5; left: 50%; top: 20px; translate: -50% -8px; min-width: 250px; padding: 10px 16px; border: 1px solid #4a7569; border-radius: 8px; background: #10231edc; color: var(--mint); opacity: 0; text-align: center; pointer-events: none; transition: .2s; font-size: 11px; font-weight: 750; }
.banner.show { opacity: 1; translate: -50% 0; }
.overlay { position: fixed; z-index: 20; inset: 64px 0 0 clamp(285px, 22vw, 345px); display: grid; place-items: center; background: #030709d9; }
.overlay.hidden { display: none; }
.overlay > div { width: min(590px, 87%); padding: 23px 27px; border: 1px solid #426e62; border-radius: 10px; background: #101b20f3; text-align: center; }
.overlay b, .overlay p { display: block; }
.overlay > div > b { margin-top: 7px; font-size: 15px; }
.overlay p { margin: 9px 0 0; color: var(--muted); font-size: 10px; line-height: 1.45; }
.summary-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 15px; }
.summary-stats div { padding: 9px; border: 1px solid var(--line); border-radius: 6px; background: #0a1318; }
.summary-stats span, .summary-stats b { display: block; }
.summary-stats span { color: var(--muted); font-size: 7px; }
.summary-stats b { margin-top: 4px; color: var(--mint); font: 13px ui-monospace, monospace; }
.summary-actions { display: flex; justify-content: center; gap: 8px; margin-top: 15px; }
.summary-actions button { padding: 9px 13px; }
#toast { position: fixed; z-index: 30; left: calc(50% + 150px); bottom: 20px; translate: -50% 12px; max-width: 500px; padding: 10px 14px; border: 1px solid #4c7368; border-radius: 8px; background: #10231e; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 10px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 900px) {
header { grid-template-columns: auto 1fr auto; padding-inline: 8px; }
.title h1 { font-size: 15px; }
.title > span { font-size: 6px; }
.top-actions button { padding: 7px; font-size: 9px; }
main { grid-template-columns: 270px minmax(0, 1fr); }
.overlay { left: 270px; }
}
@media (max-width: 640px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
.shift-stage { text-align: left; }
.top-actions { justify-content: flex-start; }
main { height: auto; grid-template-columns: 1fr; overflow: visible; }
.playfield { height: min(80vh, 650px); }
.overlay { display: none; }
#toast { left: 50%; }
}

View file

@ -0,0 +1,57 @@
# Experiment 005 Preliminary Analysis — Session 69cdaf46
Status: telemetry and player report analyzed.
Source: `JSONL/sanctuary-wake-69cdaf46-11e4-498f-abe0-ee791a81fad4.jsonl`
## Session Summary
- 1,019 saved events over 245 seconds of wall time.
- The measured shift ran to its automatic end at 180.0 simulation seconds.
- The player saved about three seconds after the summary appeared and did not enter overtime.
- Final state: 6 pods rescued, 18 lost, 17 raiders broken, no breaches, 41% sanctuary integrity.
- 289 movement key-down events and 289 matching releases indicate continuous active piloting rather than waiting out the timer.
- 89 field sessions: 60 pull and 29 push, totaling 59.4 active seconds.
- 53/89 field sessions ended through charge depletion rather than release.
- 43 keeper impacts occurred.
## Time Course
- First keeper/wreckage impact: 3.0 s.
- First rescue: 28.8 s.
- First pod loss: 32.8 s.
- First raider destruction: two simultaneous breaks at 47.8 s.
- Sanctuary remained at 100% until 150 s, then took six impacts during the final 30 seconds and ended at 41%.
- No emergency breach occurred, so the intended recoverable-failure event was never directly tested.
The player used pull and push in every 30-second interval, but pull increasingly dominated. Field time stayed near 911 seconds per interval even as session count increased; depletion endings rose from none in the first 30 seconds to 13/16 in the final interval. A plausible interpretation is that later use became repeated short attempts constrained by an often-empty charge meter rather than deliberation about when to stop. The player report must distinguish intentional rationing, confusing responsiveness, and simple input frustration.
Seventeen wreckage/raider kills prove the advertised collision interaction occurred. Five happened while a field was actively logged; the remainder happened under residual momentum or without immediate field input. Telemetry cannot determine which, if any, were deliberately set up by the player.
## Competing Explanations
1. The core activity—radially shepherding multiple autonomous bodies—was intrinsically unpleasant or illegible.
2. Pull/push created outcomes, but clutter and shared influence prevented intentional multi-object decisions.
3. The charge meter interrupted actions without creating a worthwhile resource decision.
4. Rescue stakes were too abstract to create care; losses became noise rather than emotionally meaningful failures.
5. The player was actively solving conventional pressure but continued only because a three-minute timer defined completion.
6. Interesting recoveries never emerged: pods disappeared on contact and sanctuary damage arrived late, leaving little reversible intermediate failure.
7. Some wreckage kills were intentional and locally satisfying, but the surrounding activity overwhelmed that positive moment.
## Player Report
The player wanted to stop almost immediately. They understood the instructions, but the activity did not seem interesting. Later wreckage accumulation made the situation feel impossible.
A few wreckage kills were deliberate. Once the field filled, however, nearly any intervention risked pushing pods into raiders or wreckage into the sanctuary. Attacking a raider felt inseparable from harming the player's own objective. Charge was an annoyance that made an already impossible situation harder, not a meaningful resource decision.
Controls were fine and the chaos was readable. The player did not care what happened. The decisive complaint was not perceptual overload but **unmanageable state**: consequences were visible, yet the available actions no longer offered credible improvement.
## Final Interpretation
Experiment 005 did not create recoverable chaos. It created accumulating contamination. Pods were removed instantly when caught, wreckage persisted, and the indiscriminate radial field increasingly coupled every local intervention to collateral damage. A recoverable failure should generate a new tractable problem; here failures reduced the future action space.
This distinction matters. Reducing spawn rates or increasing charge would make the activity easier, but would not address the player's immediate lack of interest or indifference to rescue outcomes. Controls, feedback, and collision readability are specifically exonerated as primary causes.
The experiment weakens the idea that pressure, systemic coupling, or a nominal protect/rescue objective can supply motivation. Coupling is useful only while the player can isolate leverage within it. “Everything affects everything” becomes paralysis rather than emergence when tools cannot select, sequence, or bound consequences.
The next probe should not be another custodial field-management game. A higher-information contrast is **assertive, selective agency**: direct movement and targeted verbs in a compact combat or traversal activity where the player creates outcomes rather than babysitting an accumulating state. Keep failure local and resettable. Do not add upgrades until the base actions demonstrate value.

View file

@ -0,0 +1,3 @@
# Experiment 005 Results
No playtest has been analyzed yet. JSONL telemetry saves to the repository-level `JSONL/` directory.

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" \
--directory "$EXPERIMENT_DIR/prototype" \
--log-directory "$REPO_ROOT/JSONL" \
--port 8000

View file

@ -0,0 +1,11 @@
# Experiment 006 — Breakline
Run:
```bash
./experiments/006_breakline/run.sh
```
Then open <http://127.0.0.1:8000>.
All four arrangements are available immediately. Play whichever ones you want, stop when you want, then press **Save JSONL**. Do not read `hypothesis.md` until after reporting your experience.

View file

@ -0,0 +1,58 @@
# Experiment 006 Hypothesis — Private Until After Play
## Question
Are a few targeted, assertive movement/combat verbs intrinsically more valuable to this player than indirect construction, abstract manipulation, or custodial field management?
## Why This Experiment
Experiment 005 was readable and controllable at the input level, but its indiscriminate field progressively erased good actions. The player also did not care about its rescue outcome from the beginning. Rebalancing debris or charge would not test that deeper failure.
Breakline supplies immediate self-relevant stakes and selective verbs:
- a short directional strike that damages, knocks, and reflects;
- a targeted tether that pulls light enemies but pulls the player toward anchors;
- an offensive dash that crosses danger and damages bodies in its path.
Enemy projectiles, charges, and body collisions use the same momentum space. Failure restores the current arrangement rather than accumulating damage. There is no timer, resource bar, upgrade, score requirement, or unlock. Four arrangements are simultaneously available, including an optional remix.
## Competing Interpretations
1. Expressive targeted action produces enjoyable execution even without progression.
2. Combat becomes interesting only when qualitative build choices alter it.
3. The player enjoys commercial action games for polish, audiovisual feel, multiplayer context, or content—not these mechanical verbs in isolation.
4. Target selection and aim feel fiddly, making the probe an interface test.
5. One verb dominates, reducing combat to repetition as in Experiment 002.
6. Clearing authored enemy sets creates compliance but no desire to replay or improve.
## Evidence Priorities
Strong evidence:
- using different verbs in response to enemy state without instruction;
- deliberately reflecting a projectile into another enemy;
- tethering or striking an enemy into a damaging collision;
- using an anchor tether as movement rather than only offense;
- voluntary replay/remix or an attempt to execute a cleaner sequence;
- a concrete moment the player wanted to reproduce.
Ambiguous evidence:
- clearing arrangements;
- many attacks or deaths;
- long play caused by difficulty;
- trying each button once.
Failure evidence:
- immediate desire to stop despite usable controls;
- attack or dash spam as a universal answer;
- interactions happen accidentally and do not become intentional;
- local failure feels like repetition rather than a fresh attempt;
- no desire to replay after learning the verbs.
## Feedback Questions
1. Which verb, if any, felt good enough to use for its own sake rather than merely because it dealt damage?
2. Did any collision, reflection, tether movement, or recovery feel intentional and worth repeating?
3. When did they want to stop, and was the reason execution feel, shallow enemies, lack of progression/context, or complete exhaustion of the action space?

View file

@ -0,0 +1,562 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const $$ = selector => [...document.querySelectorAll(selector)];
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const PLAYER_RADIUS = .021;
const KEY_VECTOR = {
KeyW: [0, -1], ArrowUp: [0, -1], KeyS: [0, 1], ArrowDown: [0, 1],
KeyA: [-1, 0], ArrowLeft: [-1, 0], KeyD: [1, 0], ArrowRight: [1, 0]
};
const ENEMY_DEF = {
wisp: { radius: .018, mass: .7, hp: 1, color: "#ef706e" },
gunner: { radius: .022, mass: 1, hp: 3, color: "#b58cff" },
anchor: { radius: .031, mass: 3.8, hp: 6, color: "#efb25a" }
};
const ARENAS = {
drift: {
kicker: "ARRANGEMENT 01", name: "Drift", copy: "A loose pack. Clear it however you want.",
enemies: [["wisp", .18, .2], ["wisp", .5, .14], ["wisp", .82, .21], ["wisp", .2, .72], ["wisp", .8, .76], ["wisp", .5, .84]]
},
crossfire: {
kicker: "ARRANGEMENT 02", name: "Crossfire", copy: "Slow bolts and closing bodies share the same space.",
enemies: [["gunner", .14, .15], ["gunner", .86, .15], ["gunner", .5, .86], ["wisp", .25, .46], ["wisp", .75, .46], ["wisp", .5, .25]]
},
weight: {
kicker: "ARRANGEMENT 03", name: "Weight", copy: "Heavy anchors telegraph their charges and reverse your tether.",
enemies: [["anchor", .18, .2], ["anchor", .82, .2], ["gunner", .18, .79], ["gunner", .82, .79], ["wisp", .34, .28], ["wisp", .66, .28], ["wisp", .32, .72], ["wisp", .68, .72]]
},
remix: { kicker: "UNBOUNDED ARRANGEMENT", name: "Remix", copy: "A fresh mixture each reset. No reward is attached." }
};
const state = {
session: crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`,
started: Date.now(), logs: [], startedGame: false, active: false, complete: false,
arenaId: "drift", arenaTime: 0, attempt: 0, deaths: 0, completed: new Set(),
keys: new Set(), enemies: [], projectiles: [], particles: [], effects: [], enemyId: 0, projectileId: 0,
pointer: { x: .5, y: .3, inside: false }, lastFrame: performance.now(), snapshotClock: 0,
player: { x: .5, y: .54, vx: 0, vy: 0, health: 5, invulnerable: 0, lastDamage: -99, dashTime: 0, dashHits: new Set() },
cooldown: { strike: 0, tether: 0, dash: 0 },
actions: { strikes: 0, strikeHits: 0, reflections: 0, tethers: 0, tetherHits: 0, dashes: 0, dashHits: 0, collisions: 0, kills: 0 },
chain: 0, maxChain: 0, lastKill: -99, remixCount: 0, random: null,
bannerTimer: null, toastTimer: null,
render: { size: 1, ox: 0, oy: 0, width: 1, height: 1 }
};
function hashSeed(text) {
let hash = 2166136261;
for (let i = 0; i < text.length; i++) { hash ^= text.charCodeAt(i); hash = Math.imul(hash, 16777619); }
return hash >>> 0;
}
function mulberry32(seed) {
return () => { seed |= 0; seed = seed + 0x6D2B79F5 | 0; let v = Math.imul(seed ^ seed >>> 15, 1 | seed); v = v + Math.imul(v ^ v >>> 7, 61 | v) ^ v; return ((v ^ v >>> 14) >>> 0) / 4294967296; };
}
state.random = mulberry32(hashSeed(state.session));
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
const round = value => Math.round(value * 1000) / 1000;
const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const normalize = (x, y) => { const length = Math.hypot(x, y) || 1; return [x / length, y / length]; };
function log(type, data = {}) {
const event = {
schema: 1, experiment: "006_breakline", prototype_revision: 1,
session_id: state.session, elapsed_ms: Date.now() - state.started,
arena: state.arenaId, attempt: state.attempt, arena_seconds: round(state.arenaTime), type, ...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("breakline-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function remixEnemies() {
const result = [];
const counts = { wisp: 4 + Math.floor(state.random() * 3), gunner: 2 + Math.floor(state.random() * 2), anchor: 1 + Math.floor(state.random() * 2) };
for (const [kind, count] of Object.entries(counts)) {
for (let i = 0; i < count; i++) {
let x, y;
do { x = .1 + state.random() * .8; y = .1 + state.random() * .8; } while (Math.hypot(x - .5, y - .54) < .22);
result.push([kind, x, y]);
}
}
return result;
}
function loadArena(reason, emit = true) {
state.attempt++;
state.arenaTime = 0; state.snapshotClock = 0; state.complete = false;
state.enemies = []; state.projectiles = []; state.particles = []; state.effects = [];
state.player = { x: .5, y: .54, vx: 0, vy: 0, health: 5, invulnerable: 0, lastDamage: -99, dashTime: 0, dashHits: new Set() };
state.cooldown = { strike: 0, tether: 0, dash: 0 };
state.actions = { strikes: 0, strikeHits: 0, reflections: 0, tethers: 0, tetherHits: 0, dashes: 0, dashHits: 0, collisions: 0, kills: 0 };
state.chain = 0; state.maxChain = 0; state.lastKill = -99;
const arena = ARENAS[state.arenaId];
const layout = state.arenaId === "remix" ? remixEnemies() : arena.enemies;
if (state.arenaId === "remix") state.remixCount++;
layout.forEach(([kind, x, y]) => spawnEnemy(kind, x, y));
state.active = state.startedGame;
updateArenaUI();
if (emit) log("arena_started", { reason, enemies: state.enemies.map(enemy => ({ id: enemy.id, kind: enemy.kind, position: [round(enemy.x), round(enemy.y)] })) });
}
function spawnEnemy(kind, x, y) {
const def = ENEMY_DEF[kind];
state.enemies.push({
id: ++state.enemyId, kind, x, y, vx: 0, vy: 0, radius: def.radius, mass: def.mass,
hp: def.hp, maxHp: def.hp, removed: false, hitFlash: 0, collisionCooldown: 0,
fireCooldown: .8 + state.random() * 1.2, chargeCooldown: 1.2 + state.random() * 1.5,
windup: 0, chargeVector: [0, 0]
});
}
function begin() {
if (state.startedGame) return;
state.startedGame = true; state.active = true; state.lastFrame = performance.now();
$("#start-overlay").classList.add("hidden"); canvas.focus({ preventScroll: true });
log("session_started", { viewport: [window.innerWidth, window.innerHeight] });
log("arena_started", { reason: "session_started", enemies: state.enemies.map(enemy => ({ id: enemy.id, kind: enemy.kind, position: [round(enemy.x), round(enemy.y)] })) });
}
function switchArena(id) {
if (!ARENAS[id] || id === state.arenaId && !state.complete) return;
if (state.startedGame && !state.complete) log("arena_abandoned", { reason: "switched", remaining: state.enemies.length, actions: state.actions });
state.arenaId = id; loadArena("selected", state.startedGame);
showBanner(ARENAS[id].name);
if (state.startedGame) canvas.focus({ preventScroll: true });
}
function resetArena(reason = "button") {
if (state.startedGame && !state.complete) log("arena_abandoned", { reason, remaining: state.enemies.length, actions: state.actions });
loadArena(reason, state.startedGame);
if (state.startedGame) { state.active = true; canvas.focus({ preventScroll: true }); }
}
function aimVector() {
return normalize(state.pointer.x - state.player.x, state.pointer.y - state.player.y);
}
function strike(source) {
if (!state.active || state.complete || state.cooldown.strike > 0) return;
const [ax, ay] = aimVector(); state.cooldown.strike = .31; state.actions.strikes++;
let hits = 0, reflections = 0;
for (const enemy of state.enemies) {
const dx = enemy.x - state.player.x, dy = enemy.y - state.player.y, range = Math.hypot(dx, dy);
if (range > .125 + enemy.radius || (dx * ax + dy * ay) / (range || 1) < .43) continue;
damageEnemy(enemy, 1, "strike");
enemy.vx += ax * .25 / enemy.mass; enemy.vy += ay * .25 / enemy.mass;
hits++; state.actions.strikeHits++;
}
for (const projectile of state.projectiles) {
if (projectile.owner !== "enemy") continue;
const dx = projectile.x - state.player.x, dy = projectile.y - state.player.y, range = Math.hypot(dx, dy);
if (range > .145 || (dx * ax + dy * ay) / (range || 1) < .3) continue;
projectile.owner = "player"; projectile.vx = ax * .43; projectile.vy = ay * .43; projectile.life = 2.2;
reflections++; state.actions.reflections++;
}
state.effects.push({ kind: "strike", x: state.player.x, y: state.player.y, ax, ay, life: .15, maxLife: .15 });
log("strike_used", { source, aim: [round(ax), round(ay)], hits, reflections, position: [round(state.player.x), round(state.player.y)] });
}
function tether(source) {
if (!state.active || state.complete || state.cooldown.tether > 0) return;
const [ax, ay] = aimVector(); state.cooldown.tether = .72; state.actions.tethers++;
let target = null, best = Infinity;
for (const enemy of state.enemies) {
const dx = enemy.x - state.player.x, dy = enemy.y - state.player.y, range = Math.hypot(dx, dy);
if (range > .43) continue;
const projection = dx * ax + dy * ay;
if (projection <= 0) continue;
const perpendicular = Math.abs(dx * ay - dy * ax);
const score = perpendicular * 4 + range * .08;
if (perpendicular < enemy.radius + .045 && score < best) { best = score; target = enemy; }
}
if (!target) {
state.effects.push({ kind: "tether_miss", x: state.player.x, y: state.player.y, ax, ay, life: .12, maxLife: .12 });
log("tether_used", { source, hit: false, aim: [round(ax), round(ay)], position: [round(state.player.x), round(state.player.y)] });
return;
}
state.actions.tetherHits++;
const [dx, dy] = normalize(target.x - state.player.x, target.y - state.player.y);
let effect;
if (target.kind === "anchor") {
state.player.vx += dx * .48; state.player.vy += dy * .48; state.player.invulnerable = Math.max(state.player.invulnerable, .12); effect = "player_pulled";
} else {
target.vx -= dx * .48 / target.mass; target.vy -= dy * .48 / target.mass; effect = "enemy_pulled";
}
state.effects.push({ kind: "tether", x: state.player.x, y: state.player.y, tx: target.x, ty: target.y, life: .18, maxLife: .18 });
log("tether_used", { source, hit: true, target_id: target.id, target_kind: target.kind, effect, distance: round(distance(state.player, target)) });
}
function dash(source) {
if (!state.active || state.complete || state.cooldown.dash > 0) return;
let dx = 0, dy = 0;
for (const code of state.keys) { const v = KEY_VECTOR[code]; if (v) { dx += v[0]; dy += v[1]; } }
if (!dx && !dy) [dx, dy] = aimVector(); else [dx, dy] = normalize(dx, dy);
state.player.vx = dx * .72; state.player.vy = dy * .72; state.player.dashTime = .17;
state.player.invulnerable = .22; state.player.dashHits = new Set(); state.cooldown.dash = 1.15; state.actions.dashes++;
state.effects.push({ kind: "dash", x: state.player.x, y: state.player.y, ax: dx, ay: dy, life: .25, maxLife: .25 });
log("dash_used", { source, direction: [round(dx), round(dy)], position: [round(state.player.x), round(state.player.y)] });
}
function damageEnemy(enemy, amount, cause) {
if (enemy.removed) return;
enemy.hp -= amount; enemy.hitFlash = .12;
log("enemy_damaged", { enemy_id: enemy.id, enemy_kind: enemy.kind, amount, cause, hp_after: Math.max(0, enemy.hp), position: [round(enemy.x), round(enemy.y)] });
burst(enemy.x, enemy.y, ENEMY_DEF[enemy.kind].color, 5);
if (enemy.hp > 0) return;
enemy.removed = true; state.actions.kills++;
state.chain = state.arenaTime - state.lastKill <= 2.5 ? state.chain + 1 : 1;
state.lastKill = state.arenaTime; state.maxChain = Math.max(state.maxChain, state.chain);
burst(enemy.x, enemy.y, ENEMY_DEF[enemy.kind].color, 14);
log("enemy_killed", { enemy_id: enemy.id, enemy_kind: enemy.kind, cause, chain: state.chain, remaining_after: state.enemies.filter(candidate => !candidate.removed).length });
}
function damagePlayer(amount, cause, source = null) {
if (state.player.invulnerable > 0 || !state.active) return;
state.player.health -= amount; state.player.invulnerable = .65; state.player.lastDamage = state.arenaTime;
burst(state.player.x, state.player.y, "#ef706e", 10);
log("player_damaged", { amount, cause, source_id: source?.id || null, health_after: Math.max(0, state.player.health), position: [round(state.player.x), round(state.player.y)] });
if (state.player.health <= 0) defeat();
}
function defeat() {
if (!state.active) return;
state.active = false; state.deaths++; state.keys.clear();
log("player_defeated", { duration_seconds: round(state.arenaTime), remaining: state.enemies.filter(enemy => !enemy.removed).length, actions: state.actions, deaths: state.deaths });
showBanner("Contact lost · restoring arrangement");
setTimeout(() => { if (!state.active && state.startedGame) loadArena("defeat_restart", true); }, 800);
}
function completeArena() {
if (state.complete || !state.active) return;
state.complete = true; state.completed.add(state.arenaId);
log("arena_completed", { duration_seconds: round(state.arenaTime), actions: state.actions, max_chain: state.maxChain, deaths_before_clear: state.deaths });
showBanner(`${ARENAS[state.arenaId].name} clear`);
updateArenaUI();
}
function updatePlayer(dt) {
state.player.invulnerable = Math.max(0, state.player.invulnerable - dt);
state.cooldown.strike = Math.max(0, state.cooldown.strike - dt);
state.cooldown.tether = Math.max(0, state.cooldown.tether - dt);
state.cooldown.dash = Math.max(0, state.cooldown.dash - dt);
if (state.player.health < 5 && state.arenaTime - state.player.lastDamage > 4) {
state.player.health = Math.min(5, state.player.health + dt * .45);
}
if (state.player.dashTime > 0) {
state.player.dashTime = Math.max(0, state.player.dashTime - dt);
for (const enemy of state.enemies) {
if (enemy.removed || state.player.dashHits.has(enemy.id) || distance(state.player, enemy) > PLAYER_RADIUS + enemy.radius + .016) continue;
state.player.dashHits.add(enemy.id); damageEnemy(enemy, 2, "dash"); state.actions.dashHits++;
const [dx, dy] = normalize(enemy.x - state.player.x, enemy.y - state.player.y);
enemy.vx += dx * .28 / enemy.mass; enemy.vy += dy * .28 / enemy.mass;
}
} else {
let dx = 0, dy = 0;
for (const code of state.keys) { const vector = KEY_VECTOR[code]; if (vector) { dx += vector[0]; dy += vector[1]; } }
if (dx || dy) { [dx, dy] = normalize(dx, dy); state.player.vx += dx * .95 * dt; state.player.vy += dy * .95 * dt; }
const drag = Math.pow(.035, dt); state.player.vx *= drag; state.player.vy *= drag;
const speed = Math.hypot(state.player.vx, state.player.vy), max = .31;
if (speed > max) { state.player.vx *= max / speed; state.player.vy *= max / speed; }
}
state.player.x += state.player.vx * dt; state.player.y += state.player.vy * dt;
bounce(state.player, PLAYER_RADIUS, .42);
}
function updateEnemies(dt) {
for (const enemy of state.enemies) {
if (enemy.removed) continue;
enemy.hitFlash = Math.max(0, enemy.hitFlash - dt); enemy.collisionCooldown = Math.max(0, enemy.collisionCooldown - dt);
const dx = state.player.x - enemy.x, dy = state.player.y - enemy.y, range = Math.hypot(dx, dy) || 1;
if (enemy.kind === "wisp") {
enemy.vx += dx / range * .085 * dt; enemy.vy += dy / range * .085 * dt;
} else if (enemy.kind === "gunner") {
const sign = range > .34 ? 1 : range < .22 ? -1 : 0;
enemy.vx += dx / range * .04 * sign * dt; enemy.vy += dy / range * .04 * sign * dt;
enemy.vx += -dy / range * .012 * dt; enemy.vy += dx / range * .012 * dt;
enemy.fireCooldown -= dt;
if (enemy.fireCooldown <= 0) { shoot(enemy, dx / range, dy / range); enemy.fireCooldown = 1.75 + state.random() * .8; }
} else {
if (enemy.windup > 0) {
const before = enemy.windup; enemy.windup -= dt; enemy.vx *= Math.pow(.03, dt); enemy.vy *= Math.pow(.03, dt);
if (before > 0 && enemy.windup <= 0) {
enemy.vx = enemy.chargeVector[0] * .43; enemy.vy = enemy.chargeVector[1] * .43;
log("anchor_charged", { enemy_id: enemy.id, direction: enemy.chargeVector, position: [round(enemy.x), round(enemy.y)] });
}
} else {
enemy.chargeCooldown -= dt;
if (enemy.chargeCooldown <= 0) {
enemy.windup = .72; enemy.chargeVector = [round(dx / range), round(dy / range)]; enemy.chargeCooldown = 3.1 + state.random() * 1.1;
log("anchor_windup", { enemy_id: enemy.id, direction: enemy.chargeVector, position: [round(enemy.x), round(enemy.y)] });
} else { enemy.vx += dx / range * .012 * dt; enemy.vy += dy / range * .012 * dt; }
}
}
const drag = Math.pow(enemy.kind === "anchor" ? .985 : .965, dt * 60); enemy.vx *= drag; enemy.vy *= drag;
const max = enemy.kind === "anchor" ? .44 : enemy.kind === "wisp" ? .15 : .13;
const speed = Math.hypot(enemy.vx, enemy.vy);
if (speed > max) { enemy.vx *= max / speed; enemy.vy *= max / speed; }
enemy.x += enemy.vx * dt; enemy.y += enemy.vy * dt; bounce(enemy, enemy.radius, .72);
}
resolveEnemyCollisions(); resolvePlayerEnemyContacts();
state.enemies = state.enemies.filter(enemy => !enemy.removed);
if (state.enemies.length === 0) completeArena();
}
function shoot(enemy, dx, dy) {
const projectile = { id: ++state.projectileId, owner: "enemy", x: enemy.x + dx * (enemy.radius + .012), y: enemy.y + dy * (enemy.radius + .012), vx: dx * .245, vy: dy * .245, radius: .008, life: 4, removed: false };
state.projectiles.push(projectile); log("projectile_fired", { projectile_id: projectile.id, enemy_id: enemy.id, position: [round(projectile.x), round(projectile.y)], direction: [round(dx), round(dy)] });
}
function updateProjectiles(dt) {
for (const projectile of state.projectiles) {
projectile.x += projectile.vx * dt; projectile.y += projectile.vy * dt; projectile.life -= dt;
if (projectile.life <= 0 || projectile.x < 0 || projectile.x > 1 || projectile.y < 0 || projectile.y > 1) { projectile.removed = true; continue; }
if (projectile.owner === "enemy" && distance(projectile, state.player) < projectile.radius + PLAYER_RADIUS) {
projectile.removed = true; damagePlayer(1, "projectile", projectile); continue;
}
if (projectile.owner === "player") {
for (const enemy of state.enemies) {
if (enemy.removed || distance(projectile, enemy) >= projectile.radius + enemy.radius) continue;
projectile.removed = true; damageEnemy(enemy, 2, "reflected_projectile");
enemy.vx += projectile.vx * .5 / enemy.mass; enemy.vy += projectile.vy * .5 / enemy.mass; break;
}
}
}
state.projectiles = state.projectiles.filter(projectile => !projectile.removed);
}
function resolveEnemyCollisions() {
for (let i = 0; i < state.enemies.length; i++) {
const a = state.enemies[i]; if (a.removed) continue;
for (let j = i + 1; j < state.enemies.length; j++) {
const b = state.enemies[j]; if (b.removed) continue;
const dx = b.x - a.x, dy = b.y - a.y, length = Math.hypot(dx, dy), overlap = a.radius + b.radius - length;
if (overlap <= 0) continue;
const nx = length > .0001 ? dx / length : 1, ny = length > .0001 ? dy / length : 0;
const relative = Math.abs((b.vx - a.vx) * nx + (b.vy - a.vy) * ny);
separateBounce(a, b, nx, ny, overlap);
if (relative >= .17 && a.collisionCooldown === 0 && b.collisionCooldown === 0) {
a.collisionCooldown = b.collisionCooldown = .35; state.actions.collisions++;
damageEnemy(a, 2, "body_collision"); damageEnemy(b, 2, "body_collision");
log("damaging_collision", { a_id: a.id, b_id: b.id, relative_speed: round(relative), position: [round((a.x + b.x) / 2), round((a.y + b.y) / 2)] });
}
}
}
}
function separateBounce(a, b, nx, ny, overlap) {
const total = a.mass + b.mass;
a.x -= nx * overlap * b.mass / total; a.y -= ny * overlap * b.mass / total;
b.x += nx * overlap * a.mass / total; b.y += ny * overlap * a.mass / total;
const relative = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;
if (relative >= 0) return;
const impulse = -1.55 * relative / (1 / a.mass + 1 / b.mass);
a.vx -= impulse * nx / a.mass; a.vy -= impulse * ny / a.mass;
b.vx += impulse * nx / b.mass; b.vy += impulse * ny / b.mass;
}
function resolvePlayerEnemyContacts() {
for (const enemy of state.enemies) {
if (enemy.removed) continue;
const dx = enemy.x - state.player.x, dy = enemy.y - state.player.y, length = Math.hypot(dx, dy), overlap = PLAYER_RADIUS + enemy.radius - length;
if (overlap <= 0) continue;
const nx = length > .0001 ? dx / length : 1, ny = length > .0001 ? dy / length : 0;
enemy.x += nx * overlap * .7; enemy.y += ny * overlap * .7;
state.player.x -= nx * overlap * .3; state.player.y -= ny * overlap * .3;
const impact = Math.hypot(enemy.vx - state.player.vx, enemy.vy - state.player.vy);
damagePlayer(enemy.kind === "anchor" && impact > .18 ? 2 : 1, enemy.kind === "anchor" ? "anchor_contact" : "enemy_contact", enemy);
enemy.vx += nx * .08 / enemy.mass; enemy.vy += ny * .08 / enemy.mass;
}
}
function bounce(body, radius, restitution) {
if (body.x < radius) { body.x = radius; body.vx = Math.abs(body.vx) * restitution; }
if (body.x > 1 - radius) { body.x = 1 - radius; body.vx = -Math.abs(body.vx) * restitution; }
if (body.y < radius) { body.y = radius; body.vy = Math.abs(body.vy) * restitution; }
if (body.y > 1 - radius) { body.y = 1 - radius; body.vy = -Math.abs(body.vy) * restitution; }
}
function burst(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = state.random() * Math.PI * 2, speed = .04 + state.random() * .14;
state.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: .25 + state.random() * .45, maxLife: .7, color });
}
}
function updateEffects(dt) {
for (const particle of state.particles) { particle.x += particle.vx * dt; particle.y += particle.vy * dt; particle.vx *= Math.pow(.12, dt); particle.vy *= Math.pow(.12, dt); particle.life -= dt; }
state.particles = state.particles.filter(particle => particle.life > 0);
for (const effect of state.effects) effect.life -= dt;
state.effects = state.effects.filter(effect => effect.life > 0);
}
function update(dt) {
if (!state.active || state.complete) { updateEffects(dt); return; }
state.arenaTime += dt; updatePlayer(dt); updateEnemies(dt); updateProjectiles(dt); updateEffects(dt);
state.snapshotClock += dt;
if (state.snapshotClock >= 1.5) {
state.snapshotClock -= 1.5;
log("arena_snapshot", {
player: [round(state.player.x), round(state.player.y)], health: round(state.player.health),
cooldowns: Object.fromEntries(Object.entries(state.cooldown).map(([key, value]) => [key, round(value)])),
remaining: state.enemies.length, enemy_counts: state.enemies.reduce((a, enemy) => (a[enemy.kind] = (a[enemy.kind] || 0) + 1, a), {}),
projectiles: state.projectiles.length, actions: { ...state.actions }
});
}
updateArenaUI();
}
function updateArenaUI() {
const arena = ARENAS[state.arenaId];
$("#arena-kicker").textContent = arena.kicker; $("#arena-name").textContent = arena.name; $("#arena-copy").textContent = arena.copy;
$("#health").textContent = Array.from({ length: 5 }, (_, index) => index < Math.ceil(state.player.health) ? "●" : "○").join(" ");
$("#remaining").textContent = state.enemies.length; $("#chain").textContent = state.chain;
$("#arena-time").textContent = `${Math.floor(state.arenaTime / 60)}:${String(Math.floor(state.arenaTime % 60)).padStart(2, "0")}`;
const max = { strike: .31, tether: .72, dash: 1.15 };
for (const key of Object.keys(max)) $(`#${key}-ready`).style.transform = `scaleX(${1 - clamp(state.cooldown[key] / max[key], 0, 1)})`;
$$("[data-arena]").forEach(button => {
button.classList.toggle("selected", button.dataset.arena === state.arenaId);
button.classList.toggle("complete", state.completed.has(button.dataset.arena));
});
}
async function saveLog() {
log("save_requested", { event_count_before_save: state.logs.length, completed: [...state.completed], deaths: state.deaths });
const filename = `breakline-${state.session}.jsonl`, payload = state.logs.join("\n") + "\n";
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body: payload });
if (!response.ok) throw new Error(`server returned ${response.status}`);
const result = await response.json(); log("log_saved", { path: result.path, saved_events: result.events }); showToast(`Saved ${result.events} events to ${result.path}`);
} catch (error) {
const blob = new Blob([payload], { type: "application/x-ndjson" }), link = document.createElement("a");
link.href = URL.createObjectURL(blob); link.download = filename; link.click(); setTimeout(() => URL.revokeObjectURL(link.href), 1000);
log("save_fallback_download", { message: String(error) }); showToast("Server save unavailable; downloaded the JSONL instead.");
}
}
function resize() {
const rect = canvas.getBoundingClientRect(), dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.max(1, Math.round(rect.width * dpr)); canvas.height = Math.max(1, Math.round(rect.height * dpr)); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const size = Math.min(rect.width, rect.height) * .91;
state.render = { size, ox: (rect.width - size) / 2, oy: (rect.height - size) / 2, width: rect.width, height: rect.height };
}
const screen = point => ({ x: state.render.ox + point.x * state.render.size, y: state.render.oy + point.y * state.render.size });
function pointerFromEvent(event) {
const rect = canvas.getBoundingClientRect();
state.pointer.x = clamp((event.clientX - rect.left - state.render.ox) / state.render.size, 0, 1);
state.pointer.y = clamp((event.clientY - rect.top - state.render.oy) / state.render.size, 0, 1);
state.pointer.inside = true;
}
function draw(now) {
const { width, height, size, ox, oy } = state.render;
ctx.clearRect(0, 0, width, height); ctx.save(); ctx.beginPath(); ctx.rect(ox, oy, size, size); ctx.clip();
ctx.fillStyle = "#071016"; ctx.fillRect(ox, oy, size, size); drawGrid();
for (const projectile of state.projectiles) drawProjectile(projectile);
for (const enemy of state.enemies) drawEnemy(enemy, now);
for (const particle of state.particles) drawParticle(particle);
for (const effect of state.effects) drawEffect(effect);
drawPlayer(); drawAim(); ctx.restore();
ctx.strokeStyle = "#38505a"; ctx.lineWidth = 1; ctx.strokeRect(ox + .5, oy + .5, size - 1, size - 1);
}
function drawGrid() {
const { size, ox, oy } = state.render; ctx.strokeStyle = "rgba(87,126,137,.1)"; ctx.lineWidth = 1;
for (let i = 1; i < 12; i++) {
ctx.beginPath(); ctx.moveTo(ox + i * size / 12, oy); ctx.lineTo(ox + i * size / 12, oy + size); ctx.stroke();
ctx.beginPath(); ctx.moveTo(ox, oy + i * size / 12); ctx.lineTo(ox + size, oy + i * size / 12); ctx.stroke();
}
ctx.strokeStyle = "rgba(110,228,189,.08)"; ctx.beginPath(); ctx.arc(ox + size / 2, oy + size / 2, size * .22, 0, Math.PI * 2); ctx.stroke();
}
function drawPlayer() {
const point = screen(state.player), r = PLAYER_RADIUS * state.render.size, [ax, ay] = aimVector();
ctx.save(); ctx.translate(point.x, point.y); ctx.rotate(Math.atan2(ay, ax) + Math.PI / 2);
ctx.shadowColor = state.player.invulnerable > 0 ? "#70d8f3" : "#6ee4bd"; ctx.shadowBlur = 14;
ctx.fillStyle = "#173b33"; ctx.strokeStyle = state.player.invulnerable > 0 ? "#70d8f3" : "#6ee4bd"; ctx.lineWidth = 2.5;
ctx.beginPath(); ctx.moveTo(0, -r); ctx.lineTo(r * .78, r * .8); ctx.lineTo(0, r * .5); ctx.lineTo(-r * .78, r * .8); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore();
}
function drawEnemy(enemy, now) {
const point = screen(enemy), r = enemy.radius * state.render.size, color = enemy.hitFlash > 0 ? "#ffffff" : ENEMY_DEF[enemy.kind].color;
ctx.save(); ctx.translate(point.x, point.y); ctx.shadowColor = color; ctx.shadowBlur = 9; ctx.fillStyle = `${enemy.kind === "wisp" ? "#421f25" : enemy.kind === "gunner" ? "#302344" : "#44331e"}`; ctx.strokeStyle = color; ctx.lineWidth = 2;
ctx.beginPath();
if (enemy.kind === "wisp") ctx.arc(0, 0, r, 0, Math.PI * 2);
else if (enemy.kind === "gunner") { ctx.moveTo(0, -r); ctx.lineTo(r, 0); ctx.lineTo(0, r); ctx.lineTo(-r, 0); ctx.closePath(); }
else { for (let i = 0; i < 6; i++) { const a = i / 6 * Math.PI * 2; i ? ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r) : ctx.moveTo(Math.cos(a) * r, Math.sin(a) * r); } ctx.closePath(); }
ctx.fill(); ctx.stroke();
if (enemy.kind === "anchor" && enemy.windup > 0) {
const alpha = .35 + Math.sin(now / 55) * .25; ctx.strokeStyle = `rgba(239,112,110,${alpha})`; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(enemy.chargeVector[0] * state.render.size * .32, enemy.chargeVector[1] * state.render.size * .32); ctx.stroke();
}
if (enemy.hp < enemy.maxHp) { ctx.shadowBlur = 0; ctx.fillStyle = "#172127"; ctx.fillRect(-r, r + 6, r * 2, 3); ctx.fillStyle = color; ctx.fillRect(-r, r + 6, r * 2 * enemy.hp / enemy.maxHp, 3); }
ctx.restore();
}
function drawProjectile(projectile) {
const point = screen(projectile); ctx.fillStyle = projectile.owner === "player" ? "#6ee4bd" : "#ef706e"; ctx.shadowColor = ctx.fillStyle; ctx.shadowBlur = 10;
ctx.beginPath(); ctx.arc(point.x, point.y, projectile.radius * state.render.size, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0;
}
function drawParticle(particle) {
const point = screen(particle); ctx.globalAlpha = clamp(particle.life / particle.maxLife, 0, 1); ctx.fillStyle = particle.color;
ctx.beginPath(); ctx.arc(point.x, point.y, 2.2, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1;
}
function drawEffect(effect) {
const point = screen(effect), alpha = clamp(effect.life / effect.maxLife, 0, 1); ctx.save(); ctx.globalAlpha = alpha;
if (effect.kind === "strike") {
ctx.strokeStyle = "#6ee4bd"; ctx.lineWidth = 5; const angle = Math.atan2(effect.ay, effect.ax);
ctx.beginPath(); ctx.arc(point.x, point.y, state.render.size * .12, angle - 1.05, angle + 1.05); ctx.stroke();
} else if (effect.kind === "dash") {
ctx.strokeStyle = "#70d8f3"; ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(point.x, point.y); ctx.lineTo(point.x - effect.ax * state.render.size * .1, point.y - effect.ay * state.render.size * .1); ctx.stroke();
} else {
const end = effect.kind === "tether" ? screen({ x: effect.tx, y: effect.ty }) : screen({ x: effect.x + effect.ax * .42, y: effect.y + effect.ay * .42 });
ctx.strokeStyle = effect.kind === "tether" ? "#b58cff" : "#5e526f"; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(point.x, point.y); ctx.lineTo(end.x, end.y); ctx.stroke();
}
ctx.restore();
}
function drawAim() {
if (!state.pointer.inside) return;
const player = screen(state.player), pointer = screen(state.pointer); ctx.strokeStyle = "rgba(210,235,230,.18)"; ctx.lineWidth = 1; ctx.setLineDash([3, 7]);
ctx.beginPath(); ctx.moveTo(player.x, player.y); ctx.lineTo(pointer.x, pointer.y); ctx.stroke(); ctx.setLineDash([]);
ctx.strokeStyle = "rgba(220,240,236,.55)"; ctx.beginPath(); ctx.arc(pointer.x, pointer.y, 7, 0, Math.PI * 2); ctx.stroke();
}
function showBanner(message) { const banner = $("#banner"); banner.textContent = message; banner.classList.add("show"); clearTimeout(state.bannerTimer); state.bannerTimer = setTimeout(() => banner.classList.remove("show"), 1400); }
function showToast(message) { const toast = $("#toast"); toast.textContent = message; toast.classList.add("show"); clearTimeout(state.toastTimer); state.toastTimer = setTimeout(() => toast.classList.remove("show"), 2800); }
function frame(now) {
const dt = Math.min(.035, Math.max(0, (now - state.lastFrame) / 1000)); state.lastFrame = now;
update(dt); draw(now); requestAnimationFrame(frame);
}
document.addEventListener("keydown", event => {
if (KEY_VECTOR[event.code]) {
event.preventDefault();
if (!state.keys.has(event.code)) { state.keys.add(event.code); if (state.active) log("movement_key_down", { key: event.code, active_keys: [...state.keys] }); }
} else if (event.code === "Space") { event.preventDefault(); if (!event.repeat) dash("keyboard"); }
});
document.addEventListener("keyup", event => {
if (!KEY_VECTOR[event.code]) return; event.preventDefault(); state.keys.delete(event.code);
if (state.active) log("movement_key_up", { key: event.code, active_keys: [...state.keys] });
});
canvas.addEventListener("pointermove", pointerFromEvent);
canvas.addEventListener("pointerenter", event => { pointerFromEvent(event); state.pointer.inside = true; });
canvas.addEventListener("pointerleave", () => { state.pointer.inside = false; });
canvas.addEventListener("pointerdown", event => { event.preventDefault(); pointerFromEvent(event); canvas.focus({ preventScroll: true }); if (event.button === 0) strike("pointer"); if (event.button === 2) tether("pointer"); });
canvas.addEventListener("contextmenu", event => event.preventDefault());
document.addEventListener("contextmenu", event => event.preventDefault()); document.addEventListener("selectstart", event => event.preventDefault());
$("#start-overlay").addEventListener("pointerdown", begin);
$$("[data-arena]").forEach(button => button.addEventListener("click", () => switchArena(button.dataset.arena)));
$("#reset").addEventListener("click", () => resetArena("button")); $("#save").addEventListener("click", saveLog);
window.addEventListener("blur", () => state.keys.clear());
document.addEventListener("visibilitychange", () => log("visibility_changed", { visibility: document.visibilityState, active: state.active, remaining: state.enemies.length }));
window.addEventListener("beforeunload", () => log("session_unload", { completed: [...state.completed], deaths: state.deaths, remaining: state.enemies.length }));
window.addEventListener("resize", resize);
loadArena("initial", false); resize(); updateArenaUI(); requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,88 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Breakline — Experiment 006</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 006</span><h1>Breakline</h1></div>
<nav id="arrangements" aria-label="Combat arrangements">
<button data-arena="drift" class="selected"><span>01</span> Drift</button>
<button data-arena="crossfire"><span>02</span> Crossfire</button>
<button data-arena="weight"><span>03</span> Weight</button>
<button data-arena="remix"><span></span> Remix</button>
</nav>
<div class="top-actions">
<button id="reset" type="button">Reset arrangement</button>
<button id="save" type="button">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="brief">
<span id="arena-kicker">ARRANGEMENT 01</span>
<h2 id="arena-name">Drift</h2>
<p id="arena-copy">A loose pack. Clear it however you want.</p>
</section>
<section class="controls">
<h3>Direct controls</h3>
<p><kbd>WASD</kbd><span>Move.</span></p>
<p><kbd>LEFT</kbd><span>Strike toward the cursor.</span></p>
<p><kbd>RIGHT</kbd><span>Tether the aimed body.</span></p>
<p><kbd>SPACE</kbd><span>Dash through danger.</span></p>
</section>
<section class="verbs">
<h3>Your verbs</h3>
<div><i class="strike"></i><p><b>STRIKE</b><span>Knocks bodies and reflects hostile bolts toward your aim.</span></p></div>
<div><i class="tether"></i><p><b>TETHER</b><span>Pulls wisps/gunners to you. Anchors pull you to them.</span></p></div>
<div><i class="dash">»</i><p><b>DASH</b><span>Invulnerable during the burst; damages bodies crossed.</span></p></div>
<div><i class="impact"></i><p><b>IMPACT</b><span>Fast enemy-on-enemy collisions hurt both bodies.</span></p></div>
</section>
<section class="enemies">
<h3>Arrangement bodies</h3>
<div><i class="wisp-icon"></i><span><b>WISP</b><small>Light · closes distance</small></span></div>
<div><i class="gunner-icon"></i><span><b>GUNNER</b><small>Light · fires slow bolts</small></span></div>
<div><i class="anchor-icon"></i><span><b>ANCHOR</b><small>Heavy · telegraphed charge</small></span></div>
</section>
<section class="readouts">
<div><span>HEALTH</span><b id="health">● ● ● ● ●</b></div>
<div><span>REMAINING</span><b id="remaining">6</b></div>
<div><span>ARRANGEMENT TIME</span><b id="arena-time">0:00</b></div>
<div><span>CLEAN CHAIN</span><b id="chain">0</b></div>
</section>
<section class="cooldowns">
<div><span>STRIKE</span><i><b id="strike-ready"></b></i></div>
<div><span>TETHER</span><i><b id="tether-ready"></b></i></div>
<div><span>DASH</span><i><b id="dash-ready"></b></i></div>
</section>
<section class="note">All arrangements are available now. Clearing one grants no stats or new verbs. A defeat simply restores the current arrangement.</section>
</aside>
<section class="playfield">
<canvas id="field" tabindex="0" aria-label="Breakline combat field"></canvas>
<div id="banner" class="banner" aria-live="polite"></div>
<div class="canvas-help">Aim with the mouse · left strike · right tether · Space dash</div>
</section>
</main>
<div id="start-overlay" class="overlay">
<div>
<span>NOTHING UNLOCKS LATER</span>
<b>Your entire moveset is available now.</b>
<p>Clear, replay, switch arrangements, or stop whenever you want. Click to begin.</p>
</div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,102 @@
:root {
color-scheme: dark;
--bg: #06090e;
--panel: #0c141b;
--line: #293b46;
--text: #e9f1ef;
--muted: #87999e;
--mint: #6ee4bd;
--cyan: #70d8f3;
--violet: #b58cff;
--amber: #efb25a;
--red: #ef706e;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; user-select: none; }
button { border: 1px solid var(--line); border-radius: 7px; background: #13212a; color: var(--text); font: inherit; cursor: pointer; }
button:hover { border-color: #56717a; background: #1a2b35; }
button.selected { border-color: var(--mint); background: #123028; color: var(--mint); }
h1, h2, h3, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 4px 0 0; font-size: 21px; }
h3 { margin: 0 0 7px; color: #a9bbb9; font-size: 8px; letter-spacing: .14em; text-transform: uppercase; }
header { height: 64px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; padding: 8px 14px; border-bottom: 1px solid var(--line); background: #091015; }
.title > span, .brief > span, .overlay span { display: block; color: var(--mint); font-size: 8px; font-weight: 850; letter-spacing: .18em; }
nav { display: flex; gap: 6px; }
nav button { min-width: 92px; padding: 8px 10px; }
nav button span { margin-right: 4px; color: #6e8080; font: 8px ui-monospace, monospace; }
nav button.complete::after { content: " ✓"; color: var(--mint); }
.top-actions { display: flex; justify-content: flex-end; gap: 7px; }
.top-actions button { padding: 8px 10px; }
main { height: calc(100vh - 64px); min-height: 0; display: grid; grid-template-columns: clamp(285px, 22vw, 345px) minmax(0, 1fr); overflow: hidden; }
aside { min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 12px 14px; border-right: 1px solid var(--line); background: #091015; }
aside section { margin-bottom: 10px; }
.brief { padding: 11px; border: 1px solid #35594e; border-radius: 9px; background: linear-gradient(145deg, #10231d, #101920); }
.brief p { margin: 7px 0 0; color: #b8c7c4; font-size: 9px; line-height: 1.4; }
.controls { padding: 9px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.controls p { display: grid; grid-template-columns: 66px 1fr; gap: 7px; align-items: center; margin: 5px 0; color: var(--muted); font-size: 8px; }
kbd { padding: 3px 5px; border: 1px solid #53666c; border-bottom-width: 2px; border-radius: 4px; background: #17252d; color: var(--text); font: 8px ui-monospace, monospace; text-align: center; }
.verbs { display: grid; gap: 4px; }
.verbs h3 { margin-bottom: 2px; }
.verbs > div { display: grid; grid-template-columns: 27px 1fr; gap: 7px; align-items: center; padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.verbs i { font-style: normal; font-size: 17px; text-align: center; }
.verbs p { margin: 0; }
.verbs b, .verbs span { display: block; }
.verbs b { font-size: 8px; letter-spacing: .08em; }
.verbs span { margin-top: 2px; color: var(--muted); font-size: 7px; line-height: 1.3; }
.verbs .strike { color: var(--mint); } .verbs .tether { color: var(--violet); } .verbs .dash { color: var(--cyan); } .verbs .impact { color: var(--amber); }
.enemies { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.enemies h3 { grid-column: 1 / -1; margin-bottom: 1px; }
.enemies > div { min-width: 0; display: flex; gap: 5px; align-items: center; padding: 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.enemies i { font-style: normal; font-size: 13px; }
.enemies b, .enemies small { display: block; }
.enemies b { font-size: 7px; }
.enemies small { margin-top: 2px; overflow: hidden; color: var(--muted); font-size: 6px; text-overflow: ellipsis; white-space: nowrap; }
.wisp-icon { color: var(--red); } .gunner-icon { color: var(--violet); } .anchor-icon { color: var(--amber); }
.readouts { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.readouts div { padding: 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.readouts span, .readouts b { display: block; }
.readouts span { color: var(--muted); font-size: 6px; letter-spacing: .09em; }
.readouts b { margin-top: 4px; color: #dce7e4; font: 9px ui-monospace, monospace; }
#health { color: var(--mint); letter-spacing: 2px; }
.cooldowns { display: grid; gap: 5px; }
.cooldowns > div { display: grid; grid-template-columns: 52px 1fr; gap: 7px; align-items: center; }
.cooldowns span { color: var(--muted); font-size: 7px; }
.cooldowns i { height: 5px; overflow: hidden; border-radius: 5px; background: #1b282e; }
.cooldowns b { display: block; height: 100%; width: 100%; transform-origin: left; background: var(--mint); }
.note { padding: 8px 9px; border-left: 2px solid #415a61; color: var(--muted); font-size: 8px; line-height: 1.4; }
.playfield { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: radial-gradient(circle at 50% 48%, #111f29, #05090c 74%); }
#field { display: block; width: 100%; height: 100%; outline: none; touch-action: none; cursor: crosshair; }
.canvas-help { position: absolute; z-index: 3; left: 50%; bottom: 13px; translate: -50%; padding: 7px 11px; border-radius: 6px; background: #071015dc; color: #809399; font-size: 9px; pointer-events: none; }
.banner { position: absolute; z-index: 5; left: 50%; top: 20px; translate: -50% -8px; min-width: 250px; padding: 10px 16px; border: 1px solid #4a7569; border-radius: 8px; background: #10231edc; color: var(--mint); opacity: 0; text-align: center; pointer-events: none; transition: .2s; font-size: 11px; font-weight: 750; }
.banner.show { opacity: 1; translate: -50% 0; }
.overlay { position: fixed; z-index: 20; inset: 64px 0 0 clamp(285px, 22vw, 345px); display: grid; place-items: center; background: #030709d9; }
.overlay.hidden { display: none; }
.overlay > div { width: min(570px, 87%); padding: 23px 27px; border: 1px solid #426e62; border-radius: 10px; background: #101b20f3; text-align: center; }
.overlay b, .overlay p { display: block; }
.overlay b { margin-top: 7px; font-size: 15px; }
.overlay p { margin: 9px 0 0; color: var(--muted); font-size: 10px; line-height: 1.45; }
#toast { position: fixed; z-index: 30; left: calc(50% + 150px); bottom: 20px; translate: -50% 12px; max-width: 500px; padding: 10px 14px; border: 1px solid #4c7368; border-radius: 8px; background: #10231e; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 10px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 1050px) {
nav button { min-width: auto; padding: 7px; font-size: 9px; }
nav button span { display: none; }
main { grid-template-columns: 270px minmax(0, 1fr); }
.overlay { left: 270px; }
}
@media (max-width: 700px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
nav { flex-wrap: wrap; }
.top-actions { justify-content: flex-start; }
main { height: auto; grid-template-columns: 1fr; overflow: visible; }
.playfield { height: min(80vh, 650px); }
.overlay { display: none; }
#toast { left: 50%; }
}

View file

@ -0,0 +1,77 @@
# Experiment 006 Preliminary Analysis — Session 360f3f3f
Status: complete.
Source: `JSONL/breakline-360f3f3f-1e5c-442d-8fdd-7f4976b17b29.jsonl`
## Session Summary
- 788 saved events over 231.8 seconds.
- All four arrangements were cleared without a defeat.
- The player saved after clearing Remix, then voluntarily selected and cleared Drift and Crossfire again before saving a second time.
- Six total clears: Drift twice, Crossfire twice, Weight once, Remix once.
- 42 total kills: 22 strike, 12 reflected projectile, and 8 damaging body collision.
- 56 strikes produced 29 direct hit events and 34 projectile reflections.
- Only five tethers were used; all five hit their intended target class (two wisps, one gunner, two anchors).
- Only one dash was used, and it hit no enemy.
- Three player-damage events and no defeat/restart.
## Clear Sequence
| Arrangement | Attempt | Time | Notable action profile |
|---|---:|---:|---|
| Drift | 1 | 10.9 s | 5 strikes, 1 tether, 6 strike kills |
| Crossfire | 2 | 21.9 s | 8 reflections, 5 direct strike hits, 1 non-damaging dash |
| Weight | 3 | 26.8 s | 7 reflections, 4 damaging collisions, anchor tether |
| Remix | 4 | 25.7 s | all three tether target classes, 5 reflections, 4 damaging collisions |
| Drift replay | 5 | 24.4 s | **one strike hit and killed all six wisps simultaneously**, six-kill chain |
| Crossfire replay | 6 | 34.4 s | 14 reflections, only 2 direct strike hits; reflection-heavy clear |
## Preliminary Interpretation
This is the strongest voluntary-replay evidence in the project. The replay happened after all authored arrangements were complete and after the first save, so it was not needed for coverage or telemetry submission.
The replays also do not look like ordinary repetition. Drift replay clustered all six wisps and killed them with one strike. Crossfire replay sharply emphasized reflection over direct hits. These patterns strongly suggest self-imposed execution experiments, but telemetry cannot establish the player's intent or enjoyment.
Strike/reflection dominated the whole session. This can support two opposite readings:
1. Reflecting and timing a broad strike was intrinsically satisfying enough to inspire challenge variants.
2. Strike was simply the safest universal verb, while tether and dash lacked useful leverage.
The contrast matters. If the player enjoyed deliberately grouping Drift and performing a reflection-heavy Crossfire clear, H20 receives the first meaningful positive evidence for an intrinsically valued base action. If those replays were diagnostic, accidental, or merely completion cleanup, the behavioral signal is weaker.
Eight body-collision kills occurred in Weight/Remix, but they may have resulted automatically from anchor charges. Five successful tethers prove the control worked; low reuse indicates either quick exhaustion, poor utility, or lack of appeal. The single dash use with no hit suggests dash was unnecessary, forgotten, or unsatisfying rather than merely difficult.
## Questions Needed
1. Was the second Drift run a deliberate attempt to group and kill all six wisps with one strike? What motivated it and how did success feel?
2. Was the second Crossfire run deliberately reflection-heavy, and were any body-collision kills intentionally engineered or worth repeating?
3. Why were tether and dash mostly abandoned, when did the player want to stop, and did strike/reflection feel good beyond being effective?
## Initial Player Report
The second Drift run was not a planned one-strike challenge. The player was testing whether all six wisps could occupy exactly one position, continuing the overlap/extreme-configuration interest seen in Experiment 004. They had missed that enemies collide with one another. When the cluster stopped compressing, they swung once and incidentally killed all six.
The second Crossfire run **was** a deliberate reflection experiment. The player had initially missed that reflection existed, then returned because trying it seemed interesting. They compared it to reflecting a Minecraft ghast fireball back at the ghast. This establishes intent and interest in the interaction, but not yet whether the execution was satisfying or whether they wanted more after answering the question.
The eight body-collision kills were not understood as such. They mean enemies striking one another at sufficient relative speed, not player ramming. The player asked whether they could run into enemies to kill them, showing the logged collisions were not deliberately engineered. Do not cite them as successful systemic play.
Tether was consciously abandoned after several attempts because pulling an enemy closer worsened position without a sufficient payoff. Its selectivity was not enough; the consequence still harmed the player and failed to create leverage.
Dash was not judged necessary. The player also reports that reactive dodge mechanics do not naturally enter their attention during action: they generally must plan to use one before entering a situation or they forget it. This is not a blanket rejection of dodging as unfun. It is evidence that a reactive defensive verb may fail to participate in play unless encounters create anticipatory use or the verb is also part of an already intended action.
## Revised Preliminary Interpretation
Experiment 006 did not broadly validate a three-verb expressive moveset. Strike was sufficient, tether was negatively valued, dash remained cognitively inactive, and collision causality was missed.
It did isolate the first interaction the player voluntarily replayed because the mechanic itself sounded interesting: reflecting enemy offense back at its source. The likely valuable property is not generic assertiveness. Reflection is a legible **reversal of initiative**: the enemy creates a timed opportunity, and one precise player action converts threat into offense. It has an immediate, concrete payoff and a familiar Minecraft analogy.
The final report rejects that narrower hypothesis as implemented. The player was ready to stop after the reflection replay. Reflection was interesting as an idea but not enjoyable in use. Aiming the mouse at a moving enemy while also timing the nearby projectile divided attention; watching that pair made other incoming projectiles easy to miss.
This distinguishes **mechanic recognition** from **interaction enjoyment**. The familiar “return the attack” concept motivated one diagnostic replay, but executing it did not create satisfaction or appetite for another challenge. The replay remains useful behavioral evidence of interest, not evidence of fun.
The report does not prove counters or reversals are categorically unsuitable. Breakline required simultaneous cursor tracking, projectile-range timing, target motion prediction, and peripheral threat monitoring, while direct striking remained easier. A counter with a fixed return direction, automatic targeting, clearer single-threat cadence, or a much larger payoff could behave differently. Those are untested alternatives, not recommended revisions: the higher-value next experiment should move above isolated action verbs and test whether qualitative chosen growth supplies the missing reason to care about otherwise serviceable action.
## Final Result
Experiment 006 failed to find an intrinsically enjoyable base verb. The player completed and voluntarily investigated two edge cases, but was ready to stop afterward. Strike was an adequate universal action; tether usually converted control into danger; dash was not recruited into attention; reflection was conceptually appealing but operationally overloaded attention. Assertive selective combat, stripped of progression and context, was insufficient.

View file

@ -0,0 +1,3 @@
# Experiment 006 Results
No playtest has been analyzed yet. JSONL telemetry saves to the repository-level `JSONL/` directory.

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" \
--directory "$EXPERIMENT_DIR/prototype" \
--log-directory "$REPO_ROOT/JSONL" \
--port 8000

View file

@ -0,0 +1,23 @@
# Experiment 007 — Catalyst Trials
Two short trials use the same movement, weapon, enemies, and encounters. Their capability choices differ.
Run from the repository root:
```bash
./experiments/007_catalyst_trials/run.sh
```
Then open <http://127.0.0.1:8000>.
Controls:
- Move with `WASD` or the arrow keys.
- Aim with the mouse.
- Hold the left mouse button to fire.
- Pick one catalyst after each field.
Both trials are available immediately. Play them in either displayed order, replay one if you want, or stop whenever you are ready. When finished, click **Save JSONL**; the server writes it directly into `JSONL/`.
Please play before reading [`hypothesis.md`](hypothesis.md).

View file

@ -0,0 +1,71 @@
# Experiment 007 Hypothesis — Private Until After Play
## Question
Does rapid, deliberately chosen **qualitative capability growth** create authorship, anticipation, and satisfying payoff where isolated action verbs and purely numerical growth do not?
## Why This Experiment
Experiment 006 found that a mechanic can be interesting enough to investigate without being enjoyable in execution. Bare targeted combat did not earn continued play. Across the project, Experiment 002 nevertheless produced the longest sessions while the player repeatedly chose upgrades and became dramatically stronger. That result was confounded: every dimension eventually amplified the same wall-blast loop.
The player's prior preferences—especially Risk of Rain 2 with Artifact of Command, Crab Champions, Warframe, and Minecraft modpacks—suggest a possible higher-level source of value: choosing a capability trajectory and then seeing combinations express themselves in play.
Catalyst Trials provides two short runs on the same field, with the same weapon, enemies, movement, wave layouts, pacing, health rules, and number of upgrade decisions. Session order is randomized behind neutral Trial I/II labels.
- **Amplification condition:** choices improve damage, firing cadence, or projectile width. They change efficiency but not the causal structure of an attack.
- **Mutation condition:** choices add forks, kill-blooms, or hit-triggered arcs. Every family can stack and their products can trigger the other selected effects, allowing visible cascades.
All three choices are offered at every intermission. There are no random drops and no unlock gates.
## Competing Interpretations
1. Qualitative composition creates anticipation and a desire to see the next consequence; numerical amplification does not.
2. Any rapid growth sustains play through escalating power, regardless of whether it changes interaction structure.
3. Upgrade selection creates short-term compliance/novelty but the underlying activity remains unwanted.
4. Mutation effects are simply stronger, more spectacular, or easier, confounding authorship with power and feedback.
5. The player values planning a known build, but four decisions are too few for meaningful authorship.
6. The player values discovery of interactions, but fully described choices remove that discovery.
## Evidence Priorities
Strong evidence:
- deliberation between qualitative options based on an intended combination;
- anticipation stated before a combination pays off;
- recognizing one selected effect as causing another;
- voluntarily replaying to assemble a different combination;
- wanting another upgrade or another field specifically to see what the build becomes;
- a clear contrast in stop desire between matched conditions.
Ambiguous evidence:
- completing both trials;
- more kills, shots, or survival time;
- selecting one family repeatedly;
- visually larger cascades;
- playing the second trial because the instructions imply comparison.
Failure evidence:
- choices are obvious or inconsequential;
- both conditions feel like waiting for the same shooting task to end;
- cascade readability collapses into noise;
- the player is ready to stop before a build identity forms;
- one mutation is a universal answer and eliminates consideration.
## Analysis Guardrails
- Randomized order must be reported because familiarity and fatigue remain large confounds in one session.
- Do not treat longer clear time as greater engagement; variant balance will not be exact.
- Separate reported interest in reading/selecting an upgrade from enjoyment of using its result.
- Do not infer “the player likes builds” from completion or selection alone.
- If mutation wins, ask whether the valued part was choosing, predicting interactions, audiovisual cascade, raw power, or seeing an authored plan materialize.
## Feedback Questions
Ask after inspecting telemetry:
1. At any choice, were they trying to make a particular interaction happen, or just taking what sounded strongest?
2. Was there a selected effect whose result they wanted to see again or build around?
3. In each trial, when did they first feel ready to stop, and did the next choice ever reverse that feeling?

View file

@ -0,0 +1,577 @@
(() => {
"use strict";
const $ = selector => document.querySelector(selector);
const $$ = selector => [...document.querySelectorAll(selector)];
const canvas = $("#field");
const ctx = canvas.getContext("2d");
const KEY_VECTOR = {
KeyW: [0, -1], ArrowUp: [0, -1], KeyS: [0, 1], ArrowDown: [0, 1],
KeyA: [-1, 0], ArrowLeft: [-1, 0], KeyD: [1, 0], ArrowRight: [1, 0]
};
const COLORS = { mint: "#68e0b5", cyan: "#66d8f2", violet: "#b28bf5", amber: "#efb55e", red: "#ee6f72" };
const ENEMY_DEF = {
mote: { radius: .018, hp: 2, speed: .066, color: COLORS.red },
husk: { radius: .028, hp: 5, speed: .038, color: COLORS.amber }
};
const WAVE_COUNTS = [7, 10, 13, 17, 22];
const UPGRADE_DEF = {
power: { icon: "◆", name: "Power Core", short: "Direct shot damage", description: "Shots deal 70% more damage.", color: COLORS.amber },
rate: { icon: "»", name: "Cadence Drive", short: "Firing cadence", description: "The emitter fires 35% faster.", color: COLORS.cyan },
width: { icon: "◉", name: "Wide Aperture", short: "Projectile width", description: "Shots become 55% wider.", color: COLORS.mint },
fork: { icon: "⋔", name: "Fork", short: "Fragments on impact", description: "Primary hits launch two sideways fragments.", color: COLORS.cyan },
bloom: { icon: "✦", name: "Bloom", short: "Seeking sparks on kill", description: "Destroyed bodies release three seeking sparks.", color: COLORS.mint },
arc: { icon: "ϟ", name: "Arc", short: "Periodic chained hit", description: "Every fifth projectile hit arcs into two nearby bodies.", color: COLORS.violet }
};
function hashSeed(text) {
let hash = 2166136261;
for (let i = 0; i < text.length; i++) { hash ^= text.charCodeAt(i); hash = Math.imul(hash, 16777619); }
return hash >>> 0;
}
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
const round = value => Math.round(value * 1000) / 1000;
const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const normalize = (x, y) => { const length = Math.hypot(x, y) || 1; return [x / length, y / length]; };
const session = crypto.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`;
const mutationFirst = (hashSeed(session) & 1) === 0;
const state = {
session, sessionStarted: Date.now(), logs: [], order: mutationFirst ? ["mutation", "amplification"] : ["amplification", "mutation"],
selectedSlot: 0, startedGame: false, active: false, choosing: false, complete: false,
trialAttempt: 0, wave: 0, waveAttempt: 0, trialTime: 0, waveTime: 0, completed: new Set(),
upgrades: { power: 0, rate: 0, width: 0, fork: 0, bloom: 0, arc: 0 }, choiceNumber: 0,
player: { x: .5, y: .5, vx: 0, vy: 0, health: 6, invulnerable: 0, lastDamage: -99 },
keys: new Set(), pointer: { x: .5, y: .25, inside: false, firing: false }, fireCooldown: 0,
enemies: [], bullets: [], particles: [], effects: [], enemyId: 0, bulletId: 0,
hitCounter: 0, actions: null, lastFrame: performance.now(), snapshotClock: 0, clearDelay: 0,
render: { size: 1, ox: 0, oy: 0 }, bannerTimer: null, toastTimer: null
};
function freshActions() {
return { shots: 0, primary_hits: 0, fragment_hits: 0, spark_hits: 0, arcs: 0, arc_targets: 0, kills: 0, damage_taken: 0 };
}
state.actions = freshActions();
function log(type, data = {}) {
const event = {
schema: 1, experiment: "007_catalyst_trials", prototype_revision: 1,
session_id: state.session, elapsed_ms: Date.now() - state.sessionStarted,
slot: state.selectedSlot + 1, condition: state.order[state.selectedSlot], trial_attempt: state.trialAttempt,
wave: state.wave + 1, wave_attempt: state.waveAttempt, trial_seconds: round(state.trialTime), type, ...data
};
state.logs.push(JSON.stringify(event));
try { localStorage.setItem("catalyst-trials-last-jsonl", state.logs.join("\n") + "\n"); } catch (_) {}
}
function buildSummary() {
return Object.fromEntries(Object.entries(state.upgrades).filter(([, level]) => level > 0));
}
function conditionKeys() {
return state.order[state.selectedSlot] === "mutation" ? ["fork", "bloom", "arc"] : ["power", "rate", "width"];
}
function weaponStats() {
return {
damage: Math.pow(1.7, state.upgrades.power),
interval: .22 * Math.pow(.65, state.upgrades.rate),
radius: .0085 * Math.pow(1.55, state.upgrades.width)
};
}
function waveLayout(index) {
const count = WAVE_COUNTS[index];
const result = [];
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2 + index * .47;
const ring = .35 + ((i * 7 + index * 3) % 4) * .035;
const kind = index >= 1 && (i + index) % (index >= 3 ? 4 : 5) === 0 ? "husk" : "mote";
result.push({ kind, x: .5 + Math.cos(angle) * ring, y: .5 + Math.sin(angle) * ring });
}
return result;
}
function spawnWave(reason) {
state.waveAttempt++;
state.waveTime = 0; state.snapshotClock = 0; state.clearDelay = 0;
state.enemies = []; state.bullets = []; state.particles = []; state.effects = [];
state.player = { x: .5, y: .5, vx: 0, vy: 0, health: 6, invulnerable: 1, lastDamage: -99 };
state.fireCooldown = 0; state.pointer.firing = false; state.hitCounter = 0;
for (const entry of waveLayout(state.wave)) {
const def = ENEMY_DEF[entry.kind];
state.enemies.push({ id: ++state.enemyId, kind: entry.kind, x: entry.x, y: entry.y, vx: 0, vy: 0, radius: def.radius, hp: def.hp, maxHp: def.hp, hitFlash: 0, contactCooldown: 0, removed: false });
}
state.active = state.startedGame; state.choosing = false; state.complete = false;
$("#choice-overlay").classList.add("hidden");
$("#complete-overlay").classList.add("hidden");
updateUI();
log("wave_started", { reason, enemy_count: state.enemies.length, layout: state.enemies.map(enemy => ({ id: enemy.id, kind: enemy.kind, position: [round(enemy.x), round(enemy.y)] })), build: buildSummary() });
showBanner(`Field ${state.wave + 1}`);
canvas.focus({ preventScroll: true });
}
function startTrial(slot, reason) {
if (state.startedGame && (state.active || state.choosing) && !state.complete) log("trial_abandoned", { reason: "switched_or_restarted", wave: state.wave + 1, remaining: state.enemies.filter(enemy => !enemy.removed).length, build: buildSummary(), actions: state.actions });
state.selectedSlot = slot; state.trialAttempt++; state.wave = 0; state.waveAttempt = 0; state.trialTime = 0;
state.choiceNumber = 0; state.upgrades = { power: 0, rate: 0, width: 0, fork: 0, bloom: 0, arc: 0 };
state.actions = freshActions(); state.complete = false; state.choosing = false;
$$(".trial").forEach((button, index) => button.classList.toggle("selected", index === slot));
log("trial_started", { reason, condition_order: state.order, condition: state.order[slot] });
spawnWave("trial_started");
}
function begin() {
if (state.startedGame) return;
state.startedGame = true;
$("#start-overlay").classList.add("hidden");
log("session_started", { viewport: [window.innerWidth, window.innerHeight], condition_order: state.order });
startTrial(state.selectedSlot, "session_started");
}
function selectSlot(slot) {
if (!state.startedGame) {
state.selectedSlot = slot;
$$(".trial").forEach((button, index) => button.classList.toggle("selected", index === slot));
$("#begin").textContent = `Begin Trial ${slot === 0 ? "I" : "II"}`;
updateUI();
return;
}
if (slot === state.selectedSlot && !state.complete) return;
startTrial(slot, "selected");
}
function fire() {
if (!state.active || state.fireCooldown > 0) return;
const stats = weaponStats();
const [dx, dy] = normalize(state.pointer.x - state.player.x, state.pointer.y - state.player.y);
state.fireCooldown = stats.interval; state.actions.shots++;
spawnBullet(state.player.x + dx * .025, state.player.y + dy * .025, dx * .64, dy * .64, stats.damage, stats.radius, "primary", 1.45, true);
state.effects.push({ kind: "muzzle", x: state.player.x, y: state.player.y, dx, dy, life: .08, maxLife: .08 });
log("shot_fired", { aim: [round(dx), round(dy)], position: [round(state.player.x), round(state.player.y)], stats: { damage: round(stats.damage), interval: round(stats.interval), radius: round(stats.radius) } });
}
function spawnBullet(x, y, vx, vy, damage, radius, kind, life, canFork = false, targetId = null) {
state.bullets.push({ id: ++state.bulletId, x, y, vx, vy, damage, radius, kind, life, canFork, targetId, removed: false, hitIds: new Set() });
}
function nearestEnemies(origin, count, range, excluded = new Set()) {
return state.enemies.filter(enemy => !enemy.removed && !excluded.has(enemy.id) && distance(origin, enemy) <= range)
.sort((a, b) => distance(origin, a) - distance(origin, b)).slice(0, count);
}
function projectileHit(enemy, bullet) {
if (enemy.removed || bullet.removed || bullet.hitIds.has(enemy.id)) return;
bullet.hitIds.add(enemy.id); bullet.removed = true;
if (bullet.kind === "primary") state.actions.primary_hits++;
if (bullet.kind === "fragment") state.actions.fragment_hits++;
if (bullet.kind === "spark") state.actions.spark_hits++;
damageEnemy(enemy, bullet.damage, bullet.kind, bullet);
state.hitCounter++;
if (state.upgrades.arc > 0) {
const threshold = Math.max(2, 6 - state.upgrades.arc);
if (state.hitCounter % threshold === 0) triggerArc(enemy, bullet.damage * .72);
}
if (bullet.kind === "primary" && bullet.canFork && state.upgrades.fork > 0) triggerFork(enemy, bullet);
}
function triggerFork(enemy, source) {
const count = state.upgrades.fork * 2;
const base = Math.atan2(source.vy, source.vx);
for (let i = 0; i < count; i++) {
const side = i % 2 === 0 ? -1 : 1;
const layer = Math.floor(i / 2);
const angle = base + side * (.72 + layer * .34);
spawnBullet(enemy.x, enemy.y, Math.cos(angle) * .48, Math.sin(angle) * .48, .72, .0065, "fragment", .75, false);
}
state.effects.push({ kind: "fork", x: enemy.x, y: enemy.y, life: .18, maxLife: .18 });
log("mutation_triggered", { mutation: "fork", source_enemy: enemy.id, products: count });
}
function triggerBloom(enemy) {
const count = 1 + state.upgrades.bloom * 2;
const targets = nearestEnemies(enemy, count, .65, new Set([enemy.id]));
for (let i = 0; i < count; i++) {
const target = targets[i % Math.max(1, targets.length)];
const angle = (i / count) * Math.PI * 2;
spawnBullet(enemy.x, enemy.y, Math.cos(angle) * .12, Math.sin(angle) * .12, .82, .007, "spark", 1.5, false, target?.id || null);
}
state.effects.push({ kind: "bloom", x: enemy.x, y: enemy.y, life: .28, maxLife: .28 });
log("mutation_triggered", { mutation: "bloom", source_enemy: enemy.id, products: count, target_ids: targets.map(target => target.id) });
}
function triggerArc(origin, damage) {
const count = 1 + state.upgrades.arc;
const targets = nearestEnemies(origin, count, .28, new Set([origin.id]));
state.actions.arcs++; state.actions.arc_targets += targets.length;
for (const target of targets) {
state.effects.push({ kind: "arc", x: origin.x, y: origin.y, tx: target.x, ty: target.y, life: .15, maxLife: .15 });
damageEnemy(target, damage, "arc", null);
}
log("mutation_triggered", { mutation: "arc", source_enemy: origin.id, target_ids: targets.map(target => target.id), damage: round(damage) });
}
function damageEnemy(enemy, amount, cause, bullet) {
if (enemy.removed) return;
enemy.hp -= amount; enemy.hitFlash = .1;
if (bullet) {
const [dx, dy] = normalize(bullet.vx, bullet.vy);
enemy.vx += dx * .025; enemy.vy += dy * .025;
}
burst(enemy.x, enemy.y, cause === "arc" ? COLORS.violet : COLORS.mint, 4);
log("enemy_damaged", { enemy_id: enemy.id, enemy_kind: enemy.kind, amount: round(amount), cause, hp_after: round(Math.max(0, enemy.hp)) });
if (enemy.hp > 0) return;
enemy.removed = true; state.actions.kills++;
burst(enemy.x, enemy.y, ENEMY_DEF[enemy.kind].color, 13);
log("enemy_killed", { enemy_id: enemy.id, enemy_kind: enemy.kind, cause, remaining_after: state.enemies.filter(candidate => !candidate.removed).length });
if (state.upgrades.bloom > 0) triggerBloom(enemy);
}
function damagePlayer(enemy) {
if (state.player.invulnerable > 0 || !state.active) return;
state.player.health--; state.player.invulnerable = .85; state.player.lastDamage = state.trialTime; state.actions.damage_taken++;
const [dx, dy] = normalize(state.player.x - enemy.x, state.player.y - enemy.y);
state.player.vx += dx * .22; state.player.vy += dy * .22;
burst(state.player.x, state.player.y, COLORS.red, 11);
log("player_damaged", { enemy_id: enemy.id, enemy_kind: enemy.kind, health_after: state.player.health, position: [round(state.player.x), round(state.player.y)] });
if (state.player.health <= 0) defeat();
}
function defeat() {
state.active = false; state.pointer.firing = false; state.keys.clear();
log("player_defeated", { wave_seconds: round(state.waveTime), remaining: state.enemies.filter(enemy => !enemy.removed).length, build: buildSummary(), actions: state.actions });
showBanner("Field destabilized · restoring");
setTimeout(() => { if (state.startedGame && !state.active && !state.choosing && !state.complete) spawnWave("defeat_restart"); }, 850);
}
function waveCleared() {
if (!state.active) return;
state.active = false; state.pointer.firing = false;
log("wave_completed", { duration_seconds: round(state.waveTime), build: buildSummary(), health: state.player.health, actions: state.actions });
if (state.wave >= WAVE_COUNTS.length - 1) {
completeTrial();
return;
}
showChoices();
}
function showChoices() {
state.choosing = true; state.choiceNumber++;
const keys = conditionKeys();
const container = $("#choices");
container.replaceChildren();
for (const key of keys) {
const def = UPGRADE_DEF[key];
const button = document.createElement("button");
button.className = "choice"; button.dataset.upgrade = key;
const next = state.upgrades[key] + 1;
const stackText = state.upgrades[key] ? `Current level ${state.upgrades[key]} · choose for level ${next}` : "Not selected yet";
const scaled = key === "fork" ? `Level ${next}: ${next * 2} fragments per primary hit.`
: key === "bloom" ? `Level ${next}: ${1 + next * 2} sparks per kill.`
: key === "arc" ? `Level ${next}: every ${Math.max(2, 6 - next)} hits, up to ${1 + next} targets.`
: `${def.description}`;
button.innerHTML = `<i style="color:${def.color}">${def.icon}</i><b>${def.name}</b><small>${scaled}</small><em>${stackText}</em>`;
button.addEventListener("click", () => chooseUpgrade(key));
container.append(button);
}
$("#choice-overlay").classList.remove("hidden");
log("choices_shown", { choice_number: state.choiceNumber, options: keys.map(key => ({ key, current_level: state.upgrades[key], next_level: state.upgrades[key] + 1 })) });
}
function chooseUpgrade(key) {
if (!state.choosing || !conditionKeys().includes(key)) return;
state.upgrades[key]++; state.choosing = false;
log("upgrade_chosen", { choice_number: state.choiceNumber, upgrade: key, new_level: state.upgrades[key], build: buildSummary() });
state.wave++; state.waveAttempt = 0;
updateUI(); spawnWave("upgrade_chosen");
}
function completeTrial() {
state.complete = true; state.active = false; state.choosing = false; state.completed.add(state.selectedSlot);
log("trial_completed", { duration_seconds: round(state.trialTime), condition: state.order[state.selectedSlot], build: buildSummary(), actions: state.actions });
$$(".trial")[state.selectedSlot].classList.add("complete");
const other = 1 - state.selectedSlot;
$("#complete-title").textContent = `Trial ${state.selectedSlot === 0 ? "I" : "II"} stabilized.`;
$("#complete-copy").textContent = state.completed.has(other) ? "Both trials are complete. Replay, save, or stop whenever you want." : "The other trial is available now. Start it, replay this one, save, or stop.";
$("#other-trial").textContent = `Start Trial ${other === 0 ? "I" : "II"}`;
$("#other-trial").dataset.slot = String(other);
$("#complete-overlay").classList.remove("hidden");
updateUI();
}
function updatePlayer(dt) {
state.player.invulnerable = Math.max(0, state.player.invulnerable - dt);
state.fireCooldown = Math.max(0, state.fireCooldown - dt);
if (state.player.health < 6 && state.trialTime - state.player.lastDamage > 3.5) {
state.player.health = Math.min(6, state.player.health + dt * .45);
}
let dx = 0, dy = 0;
for (const code of state.keys) { const vector = KEY_VECTOR[code]; if (vector) { dx += vector[0]; dy += vector[1]; } }
if (dx || dy) {
[dx, dy] = normalize(dx, dy);
state.player.vx += dx * .92 * dt; state.player.vy += dy * .92 * dt;
}
const drag = Math.pow(.025, dt); state.player.vx *= drag; state.player.vy *= drag;
const speed = Math.hypot(state.player.vx, state.player.vy);
if (speed > .29) { state.player.vx *= .29 / speed; state.player.vy *= .29 / speed; }
state.player.x = clamp(state.player.x + state.player.vx * dt, .035, .965);
state.player.y = clamp(state.player.y + state.player.vy * dt, .035, .965);
if (state.pointer.firing) fire();
}
function updateEnemies(dt) {
for (const enemy of state.enemies) {
if (enemy.removed) continue;
enemy.hitFlash = Math.max(0, enemy.hitFlash - dt); enemy.contactCooldown = Math.max(0, enemy.contactCooldown - dt);
const [dx, dy] = normalize(state.player.x - enemy.x, state.player.y - enemy.y);
const def = ENEMY_DEF[enemy.kind];
enemy.vx += dx * def.speed * 4.5 * dt; enemy.vy += dy * def.speed * 4.5 * dt;
const drag = Math.pow(.12, dt); enemy.vx *= drag; enemy.vy *= drag;
const speed = Math.hypot(enemy.vx, enemy.vy);
if (speed > def.speed) { enemy.vx *= def.speed / speed; enemy.vy *= def.speed / speed; }
enemy.x = clamp(enemy.x + enemy.vx * dt, enemy.radius, 1 - enemy.radius);
enemy.y = clamp(enemy.y + enemy.vy * dt, enemy.radius, 1 - enemy.radius);
if (distance(enemy, state.player) < enemy.radius + .022) damagePlayer(enemy);
}
for (let i = 0; i < state.enemies.length; i++) {
const a = state.enemies[i]; if (a.removed) continue;
for (let j = i + 1; j < state.enemies.length; j++) {
const b = state.enemies[j]; if (b.removed) continue;
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || .001, minimum = a.radius + b.radius;
if (d >= minimum) continue;
const overlap = (minimum - d) * .5, nx = dx / d, ny = dy / d;
a.x -= nx * overlap; a.y -= ny * overlap; b.x += nx * overlap; b.y += ny * overlap;
}
}
state.enemies = state.enemies.filter(enemy => !enemy.removed);
}
function updateBullets(dt) {
for (const bullet of state.bullets) {
if (bullet.removed) continue;
bullet.life -= dt;
if (bullet.kind === "spark" && bullet.targetId) {
let target = state.enemies.find(enemy => enemy.id === bullet.targetId && !enemy.removed);
if (!target) {
target = nearestEnemies(bullet, 1, .7)[0]; bullet.targetId = target?.id || null;
}
if (target) {
const [tx, ty] = normalize(target.x - bullet.x, target.y - bullet.y);
bullet.vx += tx * 1.25 * dt; bullet.vy += ty * 1.25 * dt;
const speed = Math.hypot(bullet.vx, bullet.vy) || 1;
bullet.vx = bullet.vx / speed * .47; bullet.vy = bullet.vy / speed * .47;
}
}
bullet.x += bullet.vx * dt; bullet.y += bullet.vy * dt;
if (bullet.life <= 0 || bullet.x < -.04 || bullet.x > 1.04 || bullet.y < -.04 || bullet.y > 1.04) { bullet.removed = true; continue; }
for (const enemy of state.enemies) {
if (!enemy.removed && distance(bullet, enemy) <= bullet.radius + enemy.radius) { projectileHit(enemy, bullet); break; }
}
}
state.bullets = state.bullets.filter(bullet => !bullet.removed);
}
function updateEffects(dt) {
for (const particle of state.particles) { particle.life -= dt; particle.x += particle.vx * dt; particle.y += particle.vy * dt; particle.vx *= Math.pow(.12, dt); particle.vy *= Math.pow(.12, dt); }
for (const effect of state.effects) effect.life -= dt;
state.particles = state.particles.filter(particle => particle.life > 0);
state.effects = state.effects.filter(effect => effect.life > 0);
}
function update(dt) {
if (!state.active) return;
state.trialTime += dt; state.waveTime += dt; state.snapshotClock += dt;
updatePlayer(dt); updateEnemies(dt); updateBullets(dt); updateEffects(dt);
if (state.enemies.length === 0) {
state.clearDelay += dt;
if (state.clearDelay >= .65) waveCleared();
}
if (state.snapshotClock >= 5) {
state.snapshotClock = 0;
log("state_snapshot", { player: [round(state.player.x), round(state.player.y)], health: round(state.player.health), remaining: state.enemies.length, projectiles: state.bullets.length, build: buildSummary(), actions: state.actions });
}
updateUI();
}
function burst(x, y, color, count) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2, speed = .035 + Math.random() * .11;
state.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, color, life: .18 + Math.random() * .32, maxLife: .5, radius: .002 + Math.random() * .003 });
}
}
function worldToScreen(x, y) {
return [state.render.ox + x * state.render.size, state.render.oy + y * state.render.size];
}
function draw() {
const dpr = window.devicePixelRatio || 1, width = canvas.clientWidth, height = canvas.clientHeight;
if (canvas.width !== Math.round(width * dpr) || canvas.height !== Math.round(height * dpr)) {
canvas.width = Math.round(width * dpr); canvas.height = Math.round(height * dpr);
}
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.clearRect(0, 0, width, height);
const size = Math.max(100, Math.min(width - 38, height - 38));
state.render = { size, ox: (width - size) / 2, oy: (height - size) / 2 };
ctx.fillStyle = "#081219"; ctx.fillRect(state.render.ox, state.render.oy, size, size);
ctx.strokeStyle = "#182c34"; ctx.lineWidth = 1;
for (let i = 1; i < 10; i++) {
const p = i / 10 * size;
ctx.beginPath(); ctx.moveTo(state.render.ox + p, state.render.oy); ctx.lineTo(state.render.ox + p, state.render.oy + size); ctx.stroke();
ctx.beginPath(); ctx.moveTo(state.render.ox, state.render.oy + p); ctx.lineTo(state.render.ox + size, state.render.oy + p); ctx.stroke();
}
ctx.strokeStyle = "#36515b"; ctx.lineWidth = 2; ctx.strokeRect(state.render.ox, state.render.oy, size, size);
for (const effect of state.effects) drawEffect(effect);
for (const bullet of state.bullets) {
const [x, y] = worldToScreen(bullet.x, bullet.y);
ctx.fillStyle = bullet.kind === "spark" ? COLORS.mint : bullet.kind === "fragment" ? COLORS.cyan : "#f1f6dc";
ctx.shadowColor = ctx.fillStyle; ctx.shadowBlur = bullet.kind === "primary" ? 7 : 11;
ctx.beginPath(); ctx.arc(x, y, Math.max(2.5, bullet.radius * size), 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0;
}
for (const enemy of state.enemies) drawEnemy(enemy);
for (const particle of state.particles) {
const [x, y] = worldToScreen(particle.x, particle.y);
ctx.globalAlpha = clamp(particle.life / particle.maxLife, 0, 1); ctx.fillStyle = particle.color;
ctx.beginPath(); ctx.arc(x, y, particle.radius * size, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1;
}
drawPlayer();
}
function drawEnemy(enemy) {
const [x, y] = worldToScreen(enemy.x, enemy.y), radius = enemy.radius * state.render.size;
ctx.save(); ctx.translate(x, y);
ctx.fillStyle = enemy.hitFlash > 0 ? "#ffffff" : ENEMY_DEF[enemy.kind].color;
ctx.shadowColor = ENEMY_DEF[enemy.kind].color; ctx.shadowBlur = 9;
ctx.beginPath();
if (enemy.kind === "husk") {
for (let i = 0; i < 6; i++) { const a = i / 6 * Math.PI * 2; const px = Math.cos(a) * radius, py = Math.sin(a) * radius; i ? ctx.lineTo(px, py) : ctx.moveTo(px, py); }
ctx.closePath();
} else ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill(); ctx.shadowBlur = 0;
ctx.strokeStyle = "#081015"; ctx.lineWidth = 2; ctx.stroke();
if (enemy.hp < enemy.maxHp) {
ctx.fillStyle = "#1b2b30"; ctx.fillRect(-radius, radius + 5, radius * 2, 3);
ctx.fillStyle = COLORS.mint; ctx.fillRect(-radius, radius + 5, radius * 2 * clamp(enemy.hp / enemy.maxHp, 0, 1), 3);
}
ctx.restore();
}
function drawPlayer() {
const [x, y] = worldToScreen(state.player.x, state.player.y), radius = .021 * state.render.size;
const [ax, ay] = normalize(state.pointer.x - state.player.x, state.pointer.y - state.player.y);
ctx.save(); ctx.translate(x, y); ctx.rotate(Math.atan2(ay, ax));
ctx.globalAlpha = state.player.invulnerable > 0 && Math.floor(state.player.invulnerable * 16) % 2 ? .35 : 1;
ctx.fillStyle = COLORS.mint; ctx.shadowColor = COLORS.mint; ctx.shadowBlur = 12;
ctx.beginPath(); ctx.moveTo(radius * 1.35, 0); ctx.lineTo(-radius * .8, radius * .8); ctx.lineTo(-radius * .52, 0); ctx.lineTo(-radius * .8, -radius * .8); ctx.closePath(); ctx.fill();
ctx.shadowBlur = 0; ctx.restore();
}
function drawEffect(effect) {
const alpha = clamp(effect.life / effect.maxLife, 0, 1); ctx.save(); ctx.globalAlpha = alpha;
if (effect.kind === "arc") {
const [x, y] = worldToScreen(effect.x, effect.y), [tx, ty] = worldToScreen(effect.tx, effect.ty);
ctx.strokeStyle = COLORS.violet; ctx.lineWidth = 3; ctx.shadowColor = COLORS.violet; ctx.shadowBlur = 10;
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo((x + tx) / 2 + (Math.random() - .5) * 12, (y + ty) / 2 + (Math.random() - .5) * 12); ctx.lineTo(tx, ty); ctx.stroke();
} else {
const [x, y] = worldToScreen(effect.x, effect.y);
ctx.strokeStyle = effect.kind === "bloom" ? COLORS.mint : effect.kind === "fork" ? COLORS.cyan : "#eef8de";
ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(x, y, (1 - alpha) * .05 * state.render.size + 4, 0, Math.PI * 2); ctx.stroke();
}
ctx.restore();
}
function updateUI() {
const roman = state.selectedSlot === 0 ? "I" : "II";
$("#trial-kicker").textContent = `TRIAL ${roman} · FIELD ${state.wave + 1} OF 5`;
$("#trial-name").textContent = state.upgrades && Object.values(state.upgrades).some(Boolean) ? "Catalyzed" : "Uncalibrated";
$("#health").textContent = Array.from({ length: 6 }, (_, i) => i < Math.ceil(state.player.health) ? "●" : "○").join(" ");
$("#wave").textContent = `${state.wave + 1} / 5`;
$("#remaining").textContent = String(state.enemies.length);
const seconds = Math.floor(state.trialTime); $("#trial-time").textContent = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
const stats = weaponStats();
const values = { power: stats.damage, rate: .22 / stats.interval, width: stats.radius / .0085 };
for (const key of ["power", "rate", "width"]) {
$(`#${key}-value`).textContent = `${values[key].toFixed(1)}×`;
$(`#${key}-bar`).style.width = `${clamp(20 + Math.log2(values[key]) * 22, 20, 100)}%`;
}
const list = $("#build-list"); list.replaceChildren();
const selected = Object.entries(state.upgrades).filter(([, level]) => level > 0);
if (!selected.length) {
const empty = document.createElement("p"); empty.className = "empty"; empty.textContent = "No catalysts selected yet."; list.append(empty);
} else {
for (const [key, level] of selected) {
const def = UPGRADE_DEF[key], item = document.createElement("div"); item.className = "build-item";
item.innerHTML = `<i style="color:${def.color}">${def.icon}</i><span><b>${def.name}</b><small>${def.short}</small></span><em>LV ${level}</em>`;
list.append(item);
}
}
}
function showBanner(message) {
const banner = $("#banner"); banner.textContent = message; banner.classList.add("show");
clearTimeout(state.bannerTimer); state.bannerTimer = setTimeout(() => banner.classList.remove("show"), 1000);
}
function toast(message) {
const element = $("#toast"); element.textContent = message; element.classList.add("show");
clearTimeout(state.toastTimer); state.toastTimer = setTimeout(() => element.classList.remove("show"), 3300);
}
async function saveLog() {
log("session_saved", { completed_slots: [...state.completed].map(slot => slot + 1), current_build: buildSummary(), actions: state.actions, event_count_before_save: state.logs.length });
const body = state.logs.join("\n") + "\n", filename = `catalyst-trials-${state.session}.jsonl`;
try {
const response = await fetch("/api/playtest-log", { method: "POST", headers: { "Content-Type": "application/x-ndjson", "X-Playtest-Filename": filename }, body });
if (!response.ok) throw new Error(`server returned ${response.status}`);
const result = await response.json(); toast(`Saved ${result.events} events to ${result.path}`); return;
} catch (error) {
const blob = new Blob([body], { type: "application/x-ndjson" }), link = document.createElement("a");
link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href);
toast(`Server save unavailable; downloaded ${filename}`);
}
}
function pointerPosition(event) {
const rect = canvas.getBoundingClientRect();
state.pointer.x = clamp((event.clientX - rect.left - state.render.ox) / state.render.size, 0, 1);
state.pointer.y = clamp((event.clientY - rect.top - state.render.oy) / state.render.size, 0, 1);
state.pointer.inside = true;
}
canvas.addEventListener("pointermove", pointerPosition);
canvas.addEventListener("pointerdown", event => {
pointerPosition(event); if (event.button === 0) { state.pointer.firing = true; fire(); }
event.preventDefault(); canvas.focus({ preventScroll: true });
});
window.addEventListener("pointerup", event => { if (event.button === 0) state.pointer.firing = false; });
canvas.addEventListener("pointerleave", () => { state.pointer.inside = false; });
canvas.addEventListener("contextmenu", event => event.preventDefault());
window.addEventListener("keydown", event => {
if (KEY_VECTOR[event.code]) { state.keys.add(event.code); event.preventDefault(); }
});
window.addEventListener("keyup", event => { state.keys.delete(event.code); });
window.addEventListener("blur", () => { state.keys.clear(); state.pointer.firing = false; });
document.addEventListener("visibilitychange", () => { if (state.startedGame) log("visibility_changed", { hidden: document.hidden }); });
$$(".trial").forEach((button, slot) => button.addEventListener("click", () => selectSlot(slot)));
$("#begin").addEventListener("click", begin);
$("#restart").addEventListener("click", () => state.startedGame ? startTrial(state.selectedSlot, "restart_button") : begin());
$("#save").addEventListener("click", saveLog);
$("#complete-save").addEventListener("click", saveLog);
$("#other-trial").addEventListener("click", event => startTrial(Number(event.currentTarget.dataset.slot), "complete_other"));
$("#replay").addEventListener("click", () => startTrial(state.selectedSlot, "complete_replay"));
if (new URLSearchParams(location.search).has("validation")) {
window.__catalystDebug = { getState: () => state, startTrial, spawnWave, chooseUpgrade, damageEnemy, spawnBullet };
}
function frame(now) {
const dt = Math.min(.04, (now - state.lastFrame) / 1000 || 0); state.lastFrame = now;
update(dt); draw(); requestAnimationFrame(frame);
}
updateUI();
log("session_initialized", { condition_order: state.order, viewport: [window.innerWidth, window.innerHeight] });
spawnWave("initial_preview"); state.active = false;
requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,100 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catalyst Trials — Experiment 007</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 007</span><h1>Catalyst Trials</h1></div>
<nav aria-label="Trial selection">
<button class="trial selected" data-slot="0"><span>I</span> Trial I</button>
<button class="trial" data-slot="1"><span>II</span> Trial II</button>
</nav>
<div class="top-actions">
<button id="restart" type="button">Restart trial</button>
<button id="save" type="button">Save JSONL</button>
</div>
</header>
<main>
<aside>
<section class="brief">
<span id="trial-kicker">TRIAL I · FIELD 1 OF 5</span>
<h2 id="trial-name">Uncalibrated</h2>
<p>Clear each field. Between fields, choose one catalyst. Nothing is locked behind the other trial.</p>
</section>
<section class="controls">
<h3>Direct controls</h3>
<p><kbd>WASD</kbd><span>Move.</span></p>
<p><kbd>HOLD LEFT</kbd><span>Fire toward the cursor.</span></p>
</section>
<section class="build">
<h3>Current catalysts</h3>
<div id="build-list" class="build-list"><p class="empty">No catalysts selected yet.</p></div>
</section>
<section class="readouts">
<div><span>INTEGRITY</span><b id="health">● ● ● ● ● ●</b></div>
<div><span>FIELD</span><b id="wave">1 / 5</b></div>
<div><span>REMAINING</span><b id="remaining">0</b></div>
<div><span>TRIAL TIME</span><b id="trial-time">0:00</b></div>
</section>
<section class="weapon-readout">
<h3>Emitter</h3>
<div><span>POWER</span><i><b id="power-bar"></b></i><em id="power-value">1.0</em></div>
<div><span>CADENCE</span><i><b id="rate-bar"></b></i><em id="rate-value">1.0</em></div>
<div><span>WIDTH</span><i><b id="width-bar"></b></i><em id="width-value">1.0</em></div>
</section>
<section class="note">The two trials share the same five fields. Completion, speed, and kill count are not scores. You can switch, replay, or stop.</section>
</aside>
<section class="playfield">
<canvas id="field" tabindex="0" aria-label="Catalyst combat field"></canvas>
<div id="banner" class="banner" aria-live="polite"></div>
<div class="canvas-help">WASD move · aim with mouse · hold left to fire</div>
</section>
</main>
<div id="start-overlay" class="overlay start-overlay">
<div>
<span>TWO SHORT TRIALS</span>
<b>Same fields. Different catalysts.</b>
<p>Clear five compact fields in each trial. Choose one catalyst after every field. Both trials are available now; stop whenever you want.</p>
<button id="begin" type="button">Begin Trial I</button>
</div>
</div>
<div id="choice-overlay" class="overlay choice-overlay hidden">
<div>
<span>FIELD STABLE</span>
<b>Choose one catalyst</b>
<p>Every option remains available at later choices.</p>
<div id="choices"></div>
</div>
</div>
<div id="complete-overlay" class="overlay complete-overlay hidden">
<div>
<span>TRIAL COMPLETE</span>
<b id="complete-title">Field sequence cleared.</b>
<p id="complete-copy">You can start the other trial, replay this one, save now, or stop.</p>
<div class="complete-actions">
<button id="other-trial" type="button">Start Trial II</button>
<button id="replay" type="button">Replay this trial</button>
<button id="complete-save" type="button">Save JSONL</button>
</div>
</div>
</div>
<div id="toast" role="status"></div>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,105 @@
:root {
color-scheme: dark;
--bg: #05090d;
--panel: #0b141a;
--panel2: #101e26;
--line: #2b414b;
--text: #edf5f3;
--muted: #8da1a4;
--mint: #68e0b5;
--cyan: #66d8f2;
--violet: #b28bf5;
--amber: #efb55e;
--red: #ee6f72;
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, sans-serif; user-select: none; }
button { border: 1px solid var(--line); border-radius: 7px; background: #13232c; color: var(--text); font: inherit; cursor: pointer; }
button:hover { border-color: #5b7d86; background: #1a303a; }
button.selected { border-color: var(--mint); background: #123028; color: var(--mint); }
h1, h2, h3, p { margin-top: 0; }
h1 { margin: 0; font-size: 20px; }
h2 { margin: 4px 0 0; font-size: 21px; }
h3 { margin: 0 0 7px; color: #a9bcba; font-size: 8px; letter-spacing: .14em; text-transform: uppercase; }
header { height: 64px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; padding: 8px 14px; border-bottom: 1px solid var(--line); background: #081015; }
.title > span, .brief > span, .overlay span { display: block; color: var(--mint); font-size: 8px; font-weight: 850; letter-spacing: .18em; }
nav { display: flex; gap: 7px; }
nav button { min-width: 112px; padding: 8px 12px; }
nav button span { margin-right: 5px; color: #718488; font: 8px ui-monospace, monospace; }
nav button.complete::after { content: " ✓"; color: var(--mint); }
.top-actions { display: flex; justify-content: flex-end; gap: 7px; }
.top-actions button { padding: 8px 10px; }
main { height: calc(100vh - 64px); min-height: 0; display: grid; grid-template-columns: clamp(285px, 22vw, 345px) minmax(0, 1fr); overflow: hidden; }
aside { min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 12px 14px; border-right: 1px solid var(--line); background: #081015; }
aside section { margin-bottom: 11px; }
.brief { padding: 11px; border: 1px solid #356054; border-radius: 9px; background: linear-gradient(145deg, #10261f, #101a20); }
.brief p { margin: 7px 0 0; color: #bac9c6; font-size: 9px; line-height: 1.45; }
.controls { padding: 9px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); }
.controls p { display: grid; grid-template-columns: 72px 1fr; gap: 7px; align-items: center; margin: 5px 0; color: var(--muted); font-size: 8px; }
kbd { padding: 3px 5px; border: 1px solid #53686d; border-bottom-width: 2px; border-radius: 4px; background: #17272e; color: var(--text); font: 8px ui-monospace, monospace; text-align: center; }
.build-list { display: grid; gap: 5px; }
.build-list .empty { margin: 0; padding: 9px; border: 1px dashed #344a52; border-radius: 6px; color: #718589; font-size: 8px; }
.build-item { display: grid; grid-template-columns: 25px 1fr auto; gap: 7px; align-items: center; padding: 7px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.build-item i { display: grid; width: 24px; height: 24px; place-items: center; border-radius: 50%; background: #172830; font-style: normal; font-size: 13px; }
.build-item b, .build-item small { display: block; }
.build-item b { font-size: 8px; letter-spacing: .06em; }
.build-item small { margin-top: 2px; color: var(--muted); font-size: 7px; }
.build-item em { color: var(--mint); font: 9px ui-monospace, monospace; font-style: normal; }
.readouts { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.readouts div { padding: 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); }
.readouts span, .readouts b { display: block; }
.readouts span { color: var(--muted); font-size: 6px; letter-spacing: .09em; }
.readouts b { margin-top: 4px; color: #dce8e5; font: 9px ui-monospace, monospace; }
#health { color: var(--mint); letter-spacing: 1px; }
.weapon-readout { display: grid; gap: 6px; }
.weapon-readout h3 { margin-bottom: 1px; }
.weapon-readout > div { display: grid; grid-template-columns: 55px 1fr 28px; gap: 7px; align-items: center; }
.weapon-readout span { color: var(--muted); font-size: 7px; }
.weapon-readout i { height: 5px; overflow: hidden; border-radius: 5px; background: #1b2a31; }
.weapon-readout i b { display: block; width: 25%; height: 100%; background: var(--mint); transition: width .2s; }
.weapon-readout em { color: #c7d8d5; font: 8px ui-monospace, monospace; font-style: normal; text-align: right; }
.note { padding: 8px 9px; border-left: 2px solid #415c64; color: var(--muted); font-size: 8px; line-height: 1.45; }
.playfield { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: radial-gradient(circle at 50% 48%, #11232c, #04080b 76%); }
#field { display: block; width: 100%; height: 100%; outline: none; touch-action: none; cursor: crosshair; }
.canvas-help { position: absolute; z-index: 3; left: 50%; bottom: 13px; translate: -50%; padding: 7px 11px; border-radius: 6px; background: #071015dc; color: #809499; font-size: 9px; pointer-events: none; }
.banner { position: absolute; z-index: 5; left: 50%; top: 20px; translate: -50% -8px; min-width: 250px; padding: 10px 16px; border: 1px solid #4a7569; border-radius: 8px; background: #10231edc; color: var(--mint); opacity: 0; text-align: center; pointer-events: none; transition: .2s; font-size: 11px; font-weight: 750; }
.banner.show { opacity: 1; translate: -50% 0; }
.overlay { position: fixed; z-index: 20; inset: 64px 0 0 clamp(285px, 22vw, 345px); display: grid; place-items: center; background: #030709dc; }
.overlay.hidden { display: none; }
.overlay > div { width: min(700px, 90%); padding: 24px 27px; border: 1px solid #426e62; border-radius: 10px; background: #101b20f5; text-align: center; }
.overlay b, .overlay p { display: block; }
.overlay > div > b { margin-top: 7px; font-size: 16px; }
.overlay > div > p { margin: 9px 0 14px; color: var(--muted); font-size: 10px; line-height: 1.45; }
.start-overlay button, .complete-actions button { padding: 10px 15px; }
#choices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; margin-top: 16px; text-align: left; }
.choice { min-height: 132px; padding: 13px; border-color: #3b535c; background: #0d1a20; }
.choice:hover { border-color: var(--mint); transform: translateY(-1px); }
.choice i { display: grid; width: 34px; height: 34px; place-items: center; margin-bottom: 10px; border-radius: 50%; background: #172b32; color: var(--mint); font-style: normal; font-size: 18px; }
.choice b { font-size: 11px; }
.choice small { display: block; margin-top: 7px; color: var(--muted); font-size: 8px; line-height: 1.45; }
.choice em { display: block; margin-top: 9px; color: var(--amber); font: 7px ui-monospace, monospace; font-style: normal; text-transform: uppercase; }
.complete-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
#toast { position: fixed; z-index: 30; left: calc(50% + 150px); bottom: 20px; translate: -50% 12px; max-width: 500px; padding: 10px 14px; border: 1px solid #4c7368; border-radius: 8px; background: #10231e; box-shadow: 0 10px 30px #000b; opacity: 0; pointer-events: none; transition: .2s; font-size: 10px; }
#toast.show { opacity: 1; translate: -50% 0; }
@media (max-width: 1050px) {
nav button { min-width: auto; padding: 7px; font-size: 9px; }
main { grid-template-columns: 270px minmax(0, 1fr); }
.overlay { left: 270px; }
}
@media (max-width: 760px) {
body { overflow: auto; }
header { height: auto; grid-template-columns: 1fr; }
.top-actions { justify-content: flex-start; }
main { height: auto; grid-template-columns: 1fr; overflow: visible; }
.playfield { height: min(80vh, 650px); }
.overlay { inset: 0; }
#choices { grid-template-columns: 1fr; max-height: 65vh; overflow-y: auto; }
#toast { left: 50%; }
}

View file

@ -0,0 +1,123 @@
# Experiment 007 Preliminary Analysis — Session 56ccf2a6
Status: complete from updated telemetry plus player report.
Source: `JSONL/catalyst-trials-56ccf2a6-9613-4da1-86aa-3c5d61797784 (1).jsonl` supersedes the earlier partial save with the same session ID.
## Session Structure
- 2,900 events over 1,053.8 elapsed seconds, including long out-of-game gaps between later runs.
- The displayed mapping was Trial I = `mutation`, Trial II = `amplification`.
- The player completed Trial I, saved, completed Trial II, saved, then voluntarily completed three more Trial I runs with different builds.
- All 25 fields were cleared on their first attempt. There were no defeats and only two total player-damage events.
- Each of the five runs defeated the same 69 enemies. This confirms matched coverage; it is not enjoyment evidence.
## Run Summary
| Run | Condition | Final build | Active trial time | Final action profile |
|---|---|---|---:|---|
| Trial I | Mutation | Fork 1, Arc 3 | 47.7 s | 153 primary shots; 96 fragment hits; 32 Arc triggers |
| Trial II | Amplification | Rate 2, Width 1, Power 1 | 52.2 s | 288 primary shots; no secondary effects |
| Trial I replay | Mutation | Arc 4 | 60.0 s | 187 primary shots; 36 Arc triggers; no Fork/Bloom |
| Trial I combined replay | Mutation | Bloom 2, Fork 1, Arc 1 | 48.7 s | 58 fragment hits; 66 spark hits; 23 Arc triggers |
| Trial I Bloom replay | Mutation | Bloom 4 | 54.0 s | 140 primary shots; 136 spark hits |
Kill attribution:
- First mutation run: 26 primary, 32 fragment, 11 Arc.
- Amplification run: 69 primary.
- Arc-only replay: 47 primary, 22 Arc.
- Combined replay: 30 primary, 22 fragment, 11 spark, 6 Arc.
- Bloom-only replay: 42 primary, 27 spark.
The first mutation build produced a real causal combination: Fork fragments counted toward the hit cadence that triggered Arc. It also cleared later fields quickly, but speed and effect count cannot establish whether the combination felt satisfying, strategically authored, or merely powerful.
## Choice Sequence and Deliberation
| Run | Choices | Approximate deliberation per choice |
|---|---|---|
| First mutation | Fork → Arc → Arc → Arc | 4.0 s, 8.8 s, 6.0 s, 1.0 s |
| Amplification | Rate → Width → Power → Rate | 10.0 s, 3.8 s, 2.9 s, 2.5 s |
| Mutation replay | Arc → Arc → Arc → Arc | 1.0 s, 0.9 s, 0.6 s, 0.6 s |
The replay pattern is unusually specific. It began about five seconds after both displayed trials had been completed and the second save had succeeded. The near-immediate repeated Arc choices suggest a preformed test of pure stacking rather than ordinary indecision or accidental continuation. This is an inference; the player's motive is not logged.
Bloom was never selected. Telemetry cannot distinguish an unattractive description, an apparently weak effect, deliberate focus on the other interaction, or simple exhaustion of available decisions.
## Preliminary Interpretation
Experiment 007 produced the strongest behavioral evidence so far for testing a **capability trajectory** rather than only an isolated mechanic. Unlike the optional replays in 004 and 006, the third run changed a four-decision build to isolate one upgrade family's scaling after the player had already seen a mixed interaction build and the numerical condition.
That distinction is promising but still insufficient. Three alternative explanations remain live:
1. Arc stacking created genuine anticipation or payoff and motivated another run.
2. The player was analytically checking what maximum Arc did, without enjoying the shooting or result.
3. The completion screen's “other trial” action or the short run length made another diagnostic pass feel cheap enough to perform despite boredom.
The first mutation run also cannot yet be labeled authored synergy. Fork was selected before Arc, and its fragments did feed Arc, but the player may not have predicted or noticed that relationship. Repeated Arc could mean they valued the interaction, believed Arc was simply strongest, or wanted to remove Fork as a confound.
The amplification sequence sampled all three dimensions before returning to Rate. Its first choice took the longest deliberation of the session, but later choices accelerated. That could reflect learning the menu, an actual tradeoff, or declining care.
No telemetry pattern establishes that either condition reversed stop desire. The player saved after every complete run, which is helpful data hygiene but may also indicate they viewed each run as a required experiment.
## Follow-up Needed
1. Why did the player voluntarily replay Trial I with Arc selected four times, and did the result feel rewarding or merely answer a test question?
2. In the first Trial I run, did they intend Fork fragments to feed Arc, and why was Bloom never selected? In Trial II, were choices part of a plan or just apparent strength/coverage?
3. When did they first want to stop in each displayed trial, and did any upgrade choice or realized effect make them want to see the next field?
## Initial Player Report
The Arc-only replay was a deliberate test of how strong Arc could become without support. It felt weak alone. The player also performed a combined run with Bloom → Fork → Arc → Bloom and observed how well all three mutation families played off one another. The combined build felt “a lot better than expected.”
The first mutation sequence was not a predicted Fork→Arc plan. Fork was chosen because its description sounded interesting, then seemed weak. Arc was chosen next because it was the next interesting description. Bloom was initially skipped because the player incorrectly predicted its value; after trying it, they considered it the best of the three and regretted skipping it.
This wrong prediction is important. The positive result did not come from merely executing a plan described by the menu. The player sampled effects from an inaccurate prior, observed cross-effect behavior that exceeded expectation, revised the ranking of the options, isolated Arc in a replay, and separately tested Bloom/the combined system. That is the first clear multi-step loop in the project of:
> expectation → chosen test → surprising interaction → revised model → another build test
Trial II felt more boring as soon as the player finished reading its choices. They described its upgrades as things that would make Trial I's effects more fun, but not as effects that played off one another to create interesting differences. Trial I was interesting enough to replay.
## Revised Interpretation
Experiment 007 gives strong evidence for H21's qualitative-composition component and against the broader idea that any chosen power growth is equivalent. The matched numerical upgrades were recognized as useful but causally independent; their descriptions were enough to predict boredom before use. The mutation upgrades initially appeared individually weak, yet their interactions created an unexpectedly better result and motivated multiple unscripted builds.
The likely valuable property is not simply “qualitative upgrades” or spectacle. Each mutation changes the opportunity surface of the others:
- Fork creates extra hits, increasing Arc frequency.
- Fork and Arc can create kills, increasing Bloom emissions.
- Bloom sparks add hits, feeding Arc again.
- Arc kills can produce more Bloom sparks.
This is complexity through narrow causal interfaces, closely matching the original composition hypothesis. The components remain understandable alone, but their products cross the same hit/kill boundaries. A selection therefore changes both immediate output and the future value of other selections. Trial II's damage/rate/width choices changed throughput without creating new relationships.
The surprising reversal around Bloom is stronger evidence than merely choosing a preferred build. The menu did not make the best-feeling combination obvious, and the player learned by using it. This may be the first prototype where knowledge transferred into a new self-selected test with an answer that affected valued capability rather than an abstract marker.
Important limits remain:
- “Interesting” and “better than expected” do not yet establish that firing and movement became enjoyable in themselves.
- The combined build may have won through spectacle or raw crowd-clearing power rather than prediction/composition.
- The run was short and offered only three families, so strategy half-life is unknown.
- Bloom's homing reduces aim burden and may simply have made combat easier; this is bundled with its interaction role.
- Bloom scaling and field-clearing spectacle remain bundled; telemetry can describe the cascade but cannot say which property caused enjoyment.
## Continuation Report and Final Result
Across the continuation, the player performed combined and all-Bloom comparisons after the Arc-only run. This resolves the endpoint ambiguity: the first surprising combination generated further specific questions rather than merely concluding the session.
All-Bloom appeared to scale too aggressively, but the player described watching the field disappear after a few shots as “kind of fun to watch.” This is the first explicit positive enjoyment report tied to a prototype consequence, although it remains qualified and partly bundled with overtuned spectacle.
The player also began classifying the mutation families by enemy ecology: Bloom seemed strong against groups of small enemies, while Arc seemed strong against large enemies. For a short prototype, moving between skills and observing those different target profiles was interesting. This indicates that the value was not only a generic cascade. The player was learning a conditional capability map and using repeated builds to compare it.
Experiment 007 is the first successful probe in the program. It supports the following mechanism:
> A deliberately chosen component becomes interesting when it participates in legible causal chains, changes the value of other components, and produces a materially different capability against a recognizable problem class.
The positive loop contained all of the desired stages: inaccurate expectation, chosen test, surprising outcome, model revision, a new build question, repeated test, conditional knowledge about target types, and a visible power payoff.
The next experiment should preserve the hit/kill interface network and test its **strategy half-life**. It should introduce changing enemy ecologies and a limited composition budget so the player cannot simply take the full Fork+Arc+Bloom package every time. The critical comparison is between:
- adaptive composition using knowledge that transfers across fields; and
- obvious “swarm means Bloom, brute means Arc” counter-loadout work, repeating Experiment 003.
Do not merely nerf Bloom. Its aggressive scaling may be part of the observed payoff. Instead, cap runaway recursion enough to keep other builds observable, retain satisfying collapse, and vary mixtures/behaviors so several causal routes remain plausible.

View file

@ -0,0 +1,4 @@
# Experiment 007 Results
Playtest interpretations and telemetry summaries belong here.

View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" \
--directory "$EXPERIMENT_DIR/prototype" \
--log-directory "$REPO_ROOT/JSONL" \
--port 8000

View file

@ -0,0 +1,22 @@
# Experiment 008 — Catalyst Ecology
Build four-catalyst emitters for three different populations. All catalysts and expeditions are available immediately. After the four choices, each complete build continues through four additional fields.
Run from the repository root:
```bash
./experiments/008_catalyst_ecology/run.sh
```
Then open <http://127.0.0.1:8000>.
Controls:
- Move with `WASD` or the arrow keys.
- Aim with the mouse.
- Hold the left mouse button to fire.
- Choose one catalyst after each of the first four fields.
Play expeditions in any order, replay anything you want, and stop whenever you feel ready. Click **Save JSONL** at the end.
Please play before reading [`hypothesis.md`](hypothesis.md).

View file

@ -0,0 +1,73 @@
# Experiment 008 Hypothesis — Private Until After Play
## Question
Can the compositional interest found in Experiment 007 survive a strict four-choice budget and changing enemy ecologies, or does it collapse into a universal package or obvious counter-loadouts?
## Why This Experiment
Experiment 007 produced the first complete curiosity chain in the project. Fork, Arc, and Bloom interacted through shared hit/kill interfaces; an inaccurate prediction led to surprising behavior, revised knowledge, and several self-directed build runs. The player also spontaneously distinguished Bloom as useful against small enemies and Arc against large enemies.
That success exposes the original long-term risk: strategy crystallization. The full three-family package may be universally best, all-Bloom may erase every population through runaway scaling, or visible enemy types may turn adaptation into the prescribed part swapping that made Experiment 003 boring.
Catalyst Ecology retains the same movement/shooting substrate and the known Fork/Bloom/Arc vocabulary. It adds three components at narrow existing interfaces:
- **Focus:** repeated primary hits on one body cause a rupture;
- **Conduit:** secondary-effect hits charge a lance toward the healthiest body;
- **Resonance:** amplifies secondary-effect damage, but has no output alone.
Three expeditions—Shoal, Bastion, and Brood—use small bodies, large durable bodies, and spawning mixtures respectively. Each run permits only four choices, with duplicates allowed and all six catalysts offered every time. Revision 2 retains the complete build for fields five through eight after the first run showed that a single mature field was insufficient for the system to become observable.
## Competing Interpretations
1. Conditional enemy ecology sustains build questions and makes knowledge transfer without transferring one whole answer.
2. Ecology produces obvious “Bloom for small, Focus/Arc for large” counter-loadouts with no discovery.
3. The original three-effect package remains dominant, so the larger option space adds false choices.
4. All-Bloom still wins through raw exponential scaling and spectacle.
5. New cross-interface components create multiple viable causal routes: Fork/Bloom feeds Arc/Conduit, Focus starts kills that feed Bloom, and Resonance trades a mechanism slot for stronger products.
6. Four slots are too restrictive and feel like arbitrary prevention rather than productive composition.
7. The successful part of 007 was rapid escalation in one repeated field, not adapting between ecologies.
## Evidence Priorities
Strong evidence:
- a build is chosen based on a prediction about an ecology and then revised from its observed result;
- the same component is valued differently across expeditions for causal reasons;
- a new interaction creates another self-selected build question;
- replaying an expedition with a meaningfully different composition;
- more than one plausible solution is articulated for Brood/mixed populations;
- a powerful cascade remains legible enough that the player attributes it to selected interfaces.
Ambiguous evidence:
- clearing all expeditions;
- fastest clear time or largest cascade;
- taking the visually labeled small/large counter;
- using all six components across required runs;
- longer play caused by extra content.
Failure evidence:
- immediate obvious loadouts from the descriptions;
- one four-choice sequence used everywhere;
- repeated all-Bloom erasure without further questions;
- the slot limit makes choices feel like withholding obvious synergy;
- Brood spawning becomes clutter/attrition rather than an exploitable ecology;
- new trigger chains are unreadable noise.
## Analysis Guardrails
- Do not nerf a spectacular build merely because it is strong; determine whether strength ends or creates reasoning.
- Do not infer adaptation from different builds alone; ask whether the difference was predicted, sampled, or prescribed.
- Compare stop desire and new questions, not expedition duration.
- Focus and Conduit add some automatic targeting, so reduced aim burden remains a confound.
- Expeditions are deliberately not numerically matched. They test qualitative population response, not performance balance.
## Feedback Questions
After telemetry inspection:
1. What question or expectation, if any, drove each build and replay?
2. Did any expedition admit multiple interesting approaches, or did the populations prescribe their counters?
3. Did a new interaction create another thing they wanted to test, and when were they ready to stop?

View file

@ -0,0 +1,83 @@
(() => {
"use strict";
const $ = s => document.querySelector(s), $$ = s => [...document.querySelectorAll(s)];
const canvas = $("#field"), ctx = canvas.getContext("2d");
const COLORS = { mint: "#68e0b5", cyan: "#66d8f2", violet: "#b28bf5", amber: "#efb55e", red: "#ee6f72", rose: "#f18cba" };
const KEYS = { KeyW:[0,-1],ArrowUp:[0,-1],KeyS:[0,1],ArrowDown:[0,1],KeyA:[-1,0],ArrowLeft:[-1,0],KeyD:[1,0],ArrowRight:[1,0] };
const ENEMIES = {
mote:{name:"Motes",icon:"●",radius:.017,hp:2,speed:.068,color:COLORS.red},
husk:{name:"Husks",icon:"⬢",radius:.026,hp:7,speed:.044,color:COLORS.amber},
titan:{name:"Titans",icon:"◆",radius:.036,hp:18,speed:.029,color:COLORS.violet},
brood:{name:"Broods",icon:"✹",radius:.032,hp:12,speed:.024,color:COLORS.rose}
};
const EXPEDITIONS = {
shoal:{name:"Shoal",copy:"Small bodies arrive in increasingly dense currents.",waves:[{mote:7},{mote:12},{mote:18,husk:1},{mote:24,husk:2},{mote:32,husk:3},{mote:36,husk:4},{mote:40,husk:5,titan:1},{mote:44,husk:6,titan:2}]},
bastion:{name:"Bastion",copy:"Durable bodies concentrate health into fewer targets.",waves:[{husk:3},{husk:4},{titan:2,husk:2,mote:4},{titan:3,husk:3,mote:5},{titan:4,husk:4,mote:6},{titan:5,husk:5,mote:8},{titan:5,husk:7,mote:12},{titan:6,husk:8,mote:16}]},
brood:{name:"Brood",copy:"Brood bodies release fresh motes while mixed bodies close in.",waves:[{mote:7},{brood:1,mote:6},{brood:2,mote:8},{brood:3,husk:2,mote:8},{brood:3,titan:2,mote:10},{brood:4,titan:2,husk:3,mote:10},{brood:4,titan:3,husk:4,mote:12},{brood:5,titan:3,husk:5,mote:14}]}
};
const MODULES = {
fork:{icon:"⋔",name:"Fork",short:"Fragments on primary hit",color:COLORS.cyan},
bloom:{icon:"✦",name:"Bloom",short:"Seeking sparks on any kill",color:COLORS.mint},
arc:{icon:"ϟ",name:"Arc",short:"Chain damage after projectile hits",color:COLORS.violet},
focus:{icon:"◎",name:"Focus",short:"Rupture after repeated primary hits",color:COLORS.amber},
conduit:{icon:"↯",name:"Conduit",short:"Secondary hits charge a heavy lance",color:COLORS.rose},
resonance:{icon:"◈",name:"Resonance",short:"Amplifies every secondary effect",color:"#f2d47b"}
};
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)), round=v=>Math.round(v*1000)/1000;
const norm=(x,y)=>{const d=Math.hypot(x,y)||1;return[x/d,y/d]}, dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);
const session=crypto.randomUUID?crypto.randomUUID():`session-${Date.now()}`;
const state={session,startedAt:Date.now(),logs:[],started:false,active:false,choosing:false,complete:false,expedition:"shoal",attempt:0,wave:0,waveAttempt:0,runTime:0,waveTime:0,choice:0,completed:new Set(),
upgrades:{fork:0,bloom:0,arc:0,focus:0,conduit:0,resonance:0},player:{x:.5,y:.5,vx:0,vy:0,health:6,inv:0,lastDamage:-99},keys:new Set(),pointer:{x:.75,y:.5,firing:false},fireCd:0,
enemies:[],bullets:[],particles:[],effects:[],enemyId:0,bulletId:0,totalHitCounter:0,secondaryCounter:0,secondaryBudget:300,actions:null,lastFrame:performance.now(),snapshot:0,clearDelay:0,render:{size:1,ox:0,oy:0},bannerTimer:null,toastTimer:null};
const freshActions=()=>({shots:0,primary_hits:0,fragment_hits:0,spark_hits:0,lance_hits:0,arc_triggers:0,focus_ruptures:0,conduit_lances:0,kills:0,spawned_motes:0,damage_taken:0}); state.actions=freshActions();
function log(type,data={}){const e={schema:1,experiment:"008_catalyst_ecology",prototype_revision:2,session_id:state.session,elapsed_ms:Date.now()-state.startedAt,expedition:state.expedition,run_attempt:state.attempt,wave:state.wave+1,wave_attempt:state.waveAttempt,run_seconds:round(state.runTime),type,...data};state.logs.push(JSON.stringify(e));try{localStorage.setItem("catalyst-ecology-last-jsonl",state.logs.join("\n")+"\n")}catch(_){}}
const build=()=>Object.fromEntries(Object.entries(state.upgrades).filter(([,v])=>v));
const secondaryScale=()=>1+state.upgrades.resonance*.55;
function populationText(spec){return Object.entries(spec).map(([k,v])=>`${v} ${ENEMIES[k].name}`).join(" · ")}
function layout(spec){const entries=[];for(const[k,count]of Object.entries(spec)){for(let i=0;i<count;i++)entries.push(k)}return entries.map((kind,i)=>{const n=entries.length,a=i/n*Math.PI*2+state.wave*.39,ring=.34+((i*5+state.wave)%4)*.035;return{kind,x:.5+Math.cos(a)*ring,y:.5+Math.sin(a)*ring}})}
function spawnEnemy(kind,x,y,spawned=false){const d=ENEMIES[kind];state.enemies.push({id:++state.enemyId,kind,x,y,vx:0,vy:0,radius:d.radius,hp:d.hp,maxHp:d.hp,removed:false,flash:0,contact:0,focusHits:0,spawnCd:2+Math.random()*.7,spawnsLeft:kind==="brood"?2+Math.floor(state.wave/2):0});if(spawned)state.actions.spawned_motes++}
function spawnWave(reason){state.waveAttempt++;state.waveTime=0;state.snapshot=0;state.clearDelay=0;state.totalHitCounter=0;state.secondaryCounter=0;state.secondaryBudget=300;state.enemies=[];state.bullets=[];state.particles=[];state.effects=[];state.player={x:.5,y:.5,vx:0,vy:0,health:6,inv:1,lastDamage:-99};state.fireCd=0;state.pointer.firing=false;layout(EXPEDITIONS[state.expedition].waves[state.wave]).forEach(e=>spawnEnemy(e.kind,e.x,e.y));state.active=state.started;state.choosing=false;state.complete=false;$("#choice-overlay").classList.add("hidden");$("#complete-overlay").classList.add("hidden");updateUI();log("wave_started",{reason,population:EXPEDITIONS[state.expedition].waves[state.wave],enemy_count:state.enemies.length,build:build()});showBanner(`Field ${state.wave+1}`);canvas.focus({preventScroll:true})}
function startRun(id,reason){if(state.started&&(state.active||state.choosing)&&!state.complete)log("run_abandoned",{reason,remaining:state.enemies.length,build:build(),actions:state.actions});state.expedition=id;state.attempt++;state.wave=0;state.waveAttempt=0;state.runTime=0;state.choice=0;state.upgrades={fork:0,bloom:0,arc:0,focus:0,conduit:0,resonance:0};state.actions=freshActions();state.complete=false;state.choosing=false;$$('.expedition').forEach(b=>b.classList.toggle("selected",b.dataset.expedition===id));log("run_started",{reason,expedition:id});spawnWave("run_started")}
function begin(){if(state.started)return;state.started=true;$("#start-overlay").classList.add("hidden");log("session_started",{viewport:[innerWidth,innerHeight]});startRun(state.expedition,"session_started")}
function selectExpedition(id){if(!state.started){state.expedition=id;$$('.expedition').forEach(b=>b.classList.toggle("selected",b.dataset.expedition===id));$("#begin").textContent=`Begin ${EXPEDITIONS[id].name}`;updateUI();return}if(id===state.expedition&&!state.complete)return;startRun(id,"selected")}
function spawnBullet(x,y,vx,vy,damage,radius,kind,life,targetId=null){state.bullets.push({id:++state.bulletId,x,y,vx,vy,damage,radius,kind,life,targetId,removed:false})}
function fire(){if(!state.active||state.fireCd>0)return;const[dx,dy]=norm(state.pointer.x-state.player.x,state.pointer.y-state.player.y);state.fireCd=.21;state.actions.shots++;spawnBullet(state.player.x+dx*.025,state.player.y+dy*.025,dx*.66,dy*.66,1,.0085,"primary",1.5);log("shot_fired",{aim:[round(dx),round(dy)],position:[round(state.player.x),round(state.player.y)]})}
function nearest(origin,count,range,exclude=new Set()){return state.enemies.filter(e=>!e.removed&&!exclude.has(e.id)&&dist(origin,e)<=range).sort((a,b)=>dist(origin,a)-dist(origin,b)).slice(0,count)}
function healthiest(){return state.enemies.filter(e=>!e.removed).sort((a,b)=>b.hp-a.hp)[0]}
function spendBudget(n=1){if(state.secondaryBudget<n)return false;state.secondaryBudget-=n;return true}
function secondaryHit(enemy,kind){if(kind==="lance")return;state.secondaryCounter++;const l=state.upgrades.conduit;if(!l)return;const threshold=Math.max(3,9-l*2);if(state.secondaryCounter%threshold!==0||!spendBudget())return;const target=healthiest();if(!target)return;state.actions.conduit_lances++;spawnBullet(state.player.x,state.player.y,0,0,(3+l*1.8)*secondaryScale(),.012,"lance",1.7,target.id);effect("conduit",state.player.x,state.player.y);log("module_triggered",{module:"conduit",target_id:target.id,threshold})}
function triggerFork(enemy,bullet){const l=state.upgrades.fork;if(!l)return;const count=l*2,base=Math.atan2(bullet.vy,bullet.vx);let made=0;for(let i=0;i<count&&spendBudget();i++){const side=i%2?1:-1,layer=Math.floor(i/2),a=base+side*(.62+layer*.28),target=nearest(enemy,1,.55,new Set([enemy.id]))[0];spawnBullet(enemy.x,enemy.y,Math.cos(a)*.46,Math.sin(a)*.46,.65*secondaryScale(),.0065,"fragment",.9,target?.id||null);made++}effect("fork",enemy.x,enemy.y);log("module_triggered",{module:"fork",source_id:enemy.id,products:made})}
function triggerBloom(enemy){const l=state.upgrades.bloom;if(!l)return;const count=2+l,targets=nearest(enemy,count,.8,new Set([enemy.id]));let made=0;for(let i=0;i<count&&spendBudget();i++){const a=i/count*Math.PI*2,target=targets[i%Math.max(1,targets.length)];spawnBullet(enemy.x,enemy.y,Math.cos(a)*.1,Math.sin(a)*.1,.7*secondaryScale(),.007,"spark",1.6,target?.id||null);made++}effect("bloom",enemy.x,enemy.y);log("module_triggered",{module:"bloom",source_id:enemy.id,products:made,target_ids:targets.map(t=>t.id)})}
function triggerArc(origin){const l=state.upgrades.arc;if(!l)return;const threshold=Math.max(3,7-l);if(state.totalHitCounter%threshold!==0)return;const targets=nearest(origin,1+l,.32,new Set([origin.id]));state.actions.arc_triggers++;for(const t of targets){if(!spendBudget())break;lineEffect("arc",origin,t);damageEnemy(t,.78*secondaryScale(),"arc");secondaryHit(t,"arc")}log("module_triggered",{module:"arc",source_id:origin.id,target_ids:targets.map(t=>t.id),threshold})}
function triggerFocus(enemy){const l=state.upgrades.focus;if(!l)return;enemy.focusHits++;const threshold=Math.max(2,6-l);if(enemy.focusHits<threshold)return;enemy.focusHits=0;if(!spendBudget())return;state.actions.focus_ruptures++;effect("focus",enemy.x,enemy.y);damageEnemy(enemy,(3+l*2)*secondaryScale(),"focus");secondaryHit(enemy,"focus");log("module_triggered",{module:"focus",source_id:enemy.id,threshold})}
function hit(enemy,bullet){if(enemy.removed||bullet.removed)return;bullet.removed=true;if(bullet.kind==="primary")state.actions.primary_hits++;else if(bullet.kind==="fragment")state.actions.fragment_hits++;else if(bullet.kind==="spark")state.actions.spark_hits++;else if(bullet.kind==="lance")state.actions.lance_hits++;damageEnemy(enemy,bullet.damage,bullet.kind);if(bullet.kind==="primary"){triggerFocus(enemy);triggerFork(enemy,bullet)}else secondaryHit(enemy,bullet.kind);state.totalHitCounter++;triggerArc(enemy)}
function damageEnemy(enemy,amount,cause){if(enemy.removed)return;enemy.hp-=amount;enemy.flash=.1;burst(enemy.x,enemy.y,cause==="arc"?COLORS.violet:COLORS.mint,4);log("enemy_damaged",{enemy_id:enemy.id,enemy_kind:enemy.kind,amount:round(amount),cause,hp_after:round(Math.max(0,enemy.hp))});if(enemy.hp>0)return;enemy.removed=true;state.actions.kills++;burst(enemy.x,enemy.y,ENEMIES[enemy.kind].color,12);log("enemy_killed",{enemy_id:enemy.id,enemy_kind:enemy.kind,cause,remaining_after:state.enemies.filter(e=>!e.removed).length});triggerBloom(enemy)}
function damagePlayer(enemy){if(state.player.inv>0||!state.active)return;state.player.health--;state.player.inv=.85;state.player.lastDamage=state.runTime;state.actions.damage_taken++;const[dx,dy]=norm(state.player.x-enemy.x,state.player.y-enemy.y);state.player.vx+=dx*.22;state.player.vy+=dy*.22;burst(state.player.x,state.player.y,COLORS.red,10);log("player_damaged",{enemy_id:enemy.id,enemy_kind:enemy.kind,health_after:state.player.health});if(state.player.health<=0)defeat()}
function defeat(){state.active=false;state.pointer.firing=false;state.keys.clear();log("player_defeated",{remaining:state.enemies.length,build:build(),actions:state.actions});showBanner("Field restoring");setTimeout(()=>{if(state.started&&!state.active&&!state.choosing&&!state.complete)spawnWave("defeat_restart")},800)}
function clearWave(){if(!state.active)return;state.active=false;state.pointer.firing=false;log("wave_completed",{duration_seconds:round(state.waveTime),health:state.player.health,build:build(),actions:state.actions});const waves=EXPEDITIONS[state.expedition].waves;if(state.wave===waves.length-1){completeRun();return}if(state.choice<4){showChoices();return}state.wave++;state.waveAttempt=0;const expectedAttempt=state.attempt,expectedExpedition=state.expedition;log("post_build_field_advanced",{next_population:waves[state.wave],build:build()});updateUI();showBanner(`Build retained · Field ${state.wave+1}`);setTimeout(()=>{if(state.attempt===expectedAttempt&&state.expedition===expectedExpedition&&!state.active&&!state.choosing&&!state.complete)spawnWave("post_build_advance")},650)}
function moduleDescription(key,next){if(key==="fork")return `${next*2} fragments leave every primary impact.`;if(key==="bloom")return `${2+next} seeking sparks leave every destroyed body.`;if(key==="arc")return `Every ${Math.max(3,7-next)} projectile hits chain into up to ${1+next} nearby bodies.`;if(key==="focus")return `${Math.max(2,6-next)} repeated primary hits rupture one body for ${3+next*2} damage.`;if(key==="conduit")return `Every ${Math.max(3,9-next*2)} secondary hits launch a ${Math.round((3+next*1.8)*10)/10}-damage lance at the healthiest body.`;return `All fragment, spark, Arc, Focus, and Conduit damage becomes ${(1+next*.55).toFixed(2)}×.`}
function showChoices(){state.choosing=true;state.choice++;const nextSpec=EXPEDITIONS[state.expedition].waves[state.wave+1];$("#next-forecast").textContent=`Next field: ${populationText(nextSpec)}.`;const box=$("#choices");box.replaceChildren();for(const[key,d]of Object.entries(MODULES)){const next=state.upgrades[key]+1,b=document.createElement("button");b.className="choice";b.dataset.module=key;b.innerHTML=`<i style="color:${d.color}">${d.icon}</i><b>${d.name}</b><small>${moduleDescription(key,next)}</small><em>${state.upgrades[key]?`Level ${state.upgrades[key]}${next}`:"Not selected"}</em>`;b.onclick=()=>choose(key);box.append(b)}$("#choice-overlay").classList.remove("hidden");log("choices_shown",{choice_number:state.choice,next_population:nextSpec,options:Object.keys(MODULES).map(k=>({key:k,current_level:state.upgrades[k]}))})}
function choose(key){if(!state.choosing||!MODULES[key])return;state.upgrades[key]++;state.choosing=false;log("module_chosen",{choice_number:state.choice,module:key,new_level:state.upgrades[key],build:build()});state.wave++;state.waveAttempt=0;spawnWave("module_chosen")}
function completeRun(){state.complete=true;state.active=false;state.completed.add(state.expedition);log("run_completed",{duration_seconds:round(state.runTime),build:build(),actions:state.actions});document.querySelector(`[data-expedition="${state.expedition}"]`).classList.add("complete");const ids=Object.keys(EXPEDITIONS),next=ids[(ids.indexOf(state.expedition)+1)%ids.length];$("#complete-title").textContent=`${EXPEDITIONS[state.expedition].name} stabilized.`;$("#next-expedition").textContent=`Start ${EXPEDITIONS[next].name}`;$("#next-expedition").dataset.expedition=next;$("#complete-overlay").classList.remove("hidden");updateUI()}
function updatePlayer(dt){state.player.inv=Math.max(0,state.player.inv-dt);state.fireCd=Math.max(0,state.fireCd-dt);if(state.player.health<6&&state.runTime-state.player.lastDamage>3.5)state.player.health=Math.min(6,state.player.health+dt*.45);let x=0,y=0;for(const k of state.keys){const v=KEYS[k];if(v){x+=v[0];y+=v[1]}}if(x||y){[x,y]=norm(x,y);state.player.vx+=x*.92*dt;state.player.vy+=y*.92*dt}const drag=Math.pow(.025,dt);state.player.vx*=drag;state.player.vy*=drag;const s=Math.hypot(state.player.vx,state.player.vy);if(s>.29){state.player.vx*=.29/s;state.player.vy*=.29/s}state.player.x=clamp(state.player.x+state.player.vx*dt,.04,.96);state.player.y=clamp(state.player.y+state.player.vy*dt,.04,.96);if(state.pointer.firing)fire()}
function updateEnemies(dt){for(const e of state.enemies){if(e.removed)continue;e.flash=Math.max(0,e.flash-dt);e.contact=Math.max(0,e.contact-dt);if(e.kind==="brood"&&e.spawnsLeft>0){e.spawnCd-=dt;if(e.spawnCd<=0){e.spawnCd=2.35;e.spawnsLeft--;const a=Math.random()*Math.PI*2;spawnEnemy("mote",clamp(e.x+Math.cos(a)*.055,.04,.96),clamp(e.y+Math.sin(a)*.055,.04,.96),true);log("brood_spawned",{brood_id:e.id,remaining_spawns:e.spawnsLeft})}}const[dx,dy]=norm(state.player.x-e.x,state.player.y-e.y),d=ENEMIES[e.kind];e.vx+=dx*d.speed*4.5*dt;e.vy+=dy*d.speed*4.5*dt;const drag=Math.pow(.12,dt);e.vx*=drag;e.vy*=drag;const s=Math.hypot(e.vx,e.vy);if(s>d.speed){e.vx*=d.speed/s;e.vy*=d.speed/s}e.x=clamp(e.x+e.vx*dt,e.radius,1-e.radius);e.y=clamp(e.y+e.vy*dt,e.radius,1-e.radius);if(dist(e,state.player)<e.radius+.022)damagePlayer(e)}for(let i=0;i<state.enemies.length;i++){const a=state.enemies[i];if(a.removed)continue;for(let j=i+1;j<state.enemies.length;j++){const b=state.enemies[j];if(b.removed)continue;const dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||.001,min=a.radius+b.radius;if(d>=min)continue;const o=(min-d)/2,nx=dx/d,ny=dy/d;a.x-=nx*o;a.y-=ny*o;b.x+=nx*o;b.y+=ny*o}}state.enemies=state.enemies.filter(e=>!e.removed)}
function updateBullets(dt){for(const b of state.bullets){if(b.removed)continue;b.life-=dt;if(b.targetId&&b.kind!=="primary"){let t=state.enemies.find(e=>e.id===b.targetId&&!e.removed);if(!t){t=b.kind==="lance"?healthiest():nearest(b,1,.8)[0];b.targetId=t?.id||null}if(t){const[dx,dy]=norm(t.x-b.x,t.y-b.y);b.vx+=dx*1.3*dt;b.vy+=dy*1.3*dt;const targetSpeed=b.kind==="lance"?.65:.48,s=Math.hypot(b.vx,b.vy)||1;b.vx=b.vx/s*targetSpeed;b.vy=b.vy/s*targetSpeed}}b.x+=b.vx*dt;b.y+=b.vy*dt;if(b.life<=0||b.x<-.05||b.x>1.05||b.y<-.05||b.y>1.05){b.removed=true;continue}for(const e of state.enemies)if(!e.removed&&dist(b,e)<=b.radius+e.radius){hit(e,b);break}}state.bullets=state.bullets.filter(b=>!b.removed)}
function updateEffects(dt){for(const p of state.particles){p.life-=dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.vx*=Math.pow(.12,dt);p.vy*=Math.pow(.12,dt)}for(const e of state.effects)e.life-=dt;state.particles=state.particles.filter(p=>p.life>0);state.effects=state.effects.filter(e=>e.life>0)}
function update(dt){if(!state.active)return;state.runTime+=dt;state.waveTime+=dt;state.snapshot+=dt;updatePlayer(dt);updateEnemies(dt);updateBullets(dt);updateEffects(dt);if(!state.enemies.length){state.clearDelay+=dt;if(state.clearDelay>.6)clearWave()}if(state.snapshot>=5){state.snapshot=0;log("state_snapshot",{player:[round(state.player.x),round(state.player.y)],health:round(state.player.health),remaining:state.enemies.length,projectiles:state.bullets.length,secondary_budget:state.secondaryBudget,build:build(),actions:state.actions})}updateUI()}
function burst(x,y,color,count){for(let i=0;i<count;i++){const a=Math.random()*Math.PI*2,s=.03+Math.random()*.1;state.particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,color,life:.18+Math.random()*.3,maxLife:.48,radius:.002+Math.random()*.003})}}
function effect(kind,x,y){state.effects.push({kind,x,y,life:.22,maxLife:.22})}function lineEffect(kind,a,b){state.effects.push({kind,x:a.x,y:a.y,tx:b.x,ty:b.y,life:.16,maxLife:.16})}
const screen=(x,y)=>[state.render.ox+x*state.render.size,state.render.oy+y*state.render.size];
function draw(){const dpr=devicePixelRatio||1,w=canvas.clientWidth,h=canvas.clientHeight;if(canvas.width!==Math.round(w*dpr)||canvas.height!==Math.round(h*dpr)){canvas.width=Math.round(w*dpr);canvas.height=Math.round(h*dpr)}ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,w,h);const size=Math.max(100,Math.min(w-38,h-38));state.render={size,ox:(w-size)/2,oy:(h-size)/2};ctx.fillStyle="#081219";ctx.fillRect(state.render.ox,state.render.oy,size,size);ctx.strokeStyle="#182c34";ctx.lineWidth=1;for(let i=1;i<10;i++){const p=i/10*size;ctx.beginPath();ctx.moveTo(state.render.ox+p,state.render.oy);ctx.lineTo(state.render.ox+p,state.render.oy+size);ctx.stroke();ctx.beginPath();ctx.moveTo(state.render.ox,state.render.oy+p);ctx.lineTo(state.render.ox+size,state.render.oy+p);ctx.stroke()}ctx.strokeStyle="#36515b";ctx.lineWidth=2;ctx.strokeRect(state.render.ox,state.render.oy,size,size);for(const e of state.effects)drawEffect(e);for(const b of state.bullets){const[x,y]=screen(b.x,b.y);ctx.fillStyle=b.kind==="primary"?"#f5f8df":b.kind==="fragment"?COLORS.cyan:b.kind==="spark"?COLORS.mint:COLORS.rose;ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=9;ctx.beginPath();ctx.arc(x,y,Math.max(2.5,b.radius*size),0,Math.PI*2);ctx.fill();ctx.shadowBlur=0}for(const e of state.enemies)drawEnemy(e);for(const p of state.particles){const[x,y]=screen(p.x,p.y);ctx.globalAlpha=clamp(p.life/p.maxLife,0,1);ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(x,y,p.radius*size,0,Math.PI*2);ctx.fill();ctx.globalAlpha=1}drawPlayer()}
function drawEnemy(e){const[x,y]=screen(e.x,e.y),r=e.radius*state.render.size;ctx.save();ctx.translate(x,y);ctx.fillStyle=e.flash?"#fff":ENEMIES[e.kind].color;ctx.shadowColor=ENEMIES[e.kind].color;ctx.shadowBlur=8;ctx.beginPath();const sides=e.kind==="mote"?0:e.kind==="husk"?6:e.kind==="titan"?4:8;if(!sides)ctx.arc(0,0,r,0,Math.PI*2);else for(let i=0;i<sides;i++){const a=i/sides*Math.PI*2,px=Math.cos(a)*r,py=Math.sin(a)*r;i?ctx.lineTo(px,py):ctx.moveTo(px,py)}ctx.closePath();ctx.fill();ctx.shadowBlur=0;ctx.strokeStyle="#081015";ctx.lineWidth=2;ctx.stroke();if(e.hp<e.maxHp){ctx.fillStyle="#1b2b30";ctx.fillRect(-r,r+5,r*2,3);ctx.fillStyle=COLORS.mint;ctx.fillRect(-r,r+5,r*2*clamp(e.hp/e.maxHp,0,1),3)}if(e.focusHits){ctx.strokeStyle=COLORS.amber;ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,0,r+4,-Math.PI/2,-Math.PI/2+Math.PI*2*e.focusHits/Math.max(2,6-state.upgrades.focus));ctx.stroke()}ctx.restore()}
function drawPlayer(){const[x,y]=screen(state.player.x,state.player.y),r=.021*state.render.size,[ax,ay]=norm(state.pointer.x-state.player.x,state.pointer.y-state.player.y);ctx.save();ctx.translate(x,y);ctx.rotate(Math.atan2(ay,ax));ctx.globalAlpha=state.player.inv&&Math.floor(state.player.inv*16)%2?.35:1;ctx.fillStyle=COLORS.mint;ctx.shadowColor=COLORS.mint;ctx.shadowBlur=12;ctx.beginPath();ctx.moveTo(r*1.35,0);ctx.lineTo(-r*.8,r*.8);ctx.lineTo(-r*.52,0);ctx.lineTo(-r*.8,-r*.8);ctx.closePath();ctx.fill();ctx.restore()}
function drawEffect(e){const a=clamp(e.life/e.maxLife,0,1);ctx.save();ctx.globalAlpha=a;if(e.tx!==undefined){const[x,y]=screen(e.x,e.y),[tx,ty]=screen(e.tx,e.ty);ctx.strokeStyle=e.kind==="arc"?COLORS.violet:COLORS.rose;ctx.lineWidth=3;ctx.shadowColor=ctx.strokeStyle;ctx.shadowBlur=9;ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo((x+tx)/2+(Math.random()-.5)*10,(y+ty)/2+(Math.random()-.5)*10);ctx.lineTo(tx,ty);ctx.stroke()}else{const[x,y]=screen(e.x,e.y);ctx.strokeStyle=e.kind==="bloom"?COLORS.mint:e.kind==="fork"?COLORS.cyan:e.kind==="focus"?COLORS.amber:COLORS.rose;ctx.lineWidth=2;ctx.beginPath();ctx.arc(x,y,(1-a)*.05*state.render.size+4,0,Math.PI*2);ctx.stroke()}ctx.restore()}
function updateUI(){const ids=Object.keys(EXPEDITIONS),idx=ids.indexOf(state.expedition),ex=EXPEDITIONS[state.expedition],total=ex.waves.length;$("#expedition-kicker").textContent=`EXPEDITION ${String(idx+1).padStart(2,"0")} · FIELD ${state.wave+1} OF ${total}`;$("#expedition-name").textContent=ex.name;$("#expedition-copy").textContent=ex.copy;$("#wave").textContent=`${state.wave+1} / ${total}`;$("#remaining").textContent=String(state.enemies.length);$("#health").textContent=Array.from({length:6},(_,i)=>i<Math.ceil(state.player.health)?"●":"○").join(" ");const s=Math.floor(state.runTime);$("#run-time").textContent=`${Math.floor(s/60)}:${String(s%60).padStart(2,"0")}`;const forecast=$("#forecast-list");forecast.replaceChildren();for(const[k,v]of Object.entries(ex.waves[state.wave])){const d=ENEMIES[k],el=document.createElement("div");el.className="population";el.innerHTML=`<i style="color:${d.color}">${d.icon}</i><b>${v}</b> ${d.name}`;forecast.append(el)}const box=$("#build-list");box.replaceChildren();const selected=Object.entries(state.upgrades).filter(([,v])=>v);if(!selected.length){const p=document.createElement("p");p.className="empty";p.textContent="No catalysts selected.";box.append(p)}else for(const[k,v]of selected){const d=MODULES[k],el=document.createElement("div");el.className="build-item";el.innerHTML=`<i style="color:${d.color}">${d.icon}</i><span><b>${d.name}</b><small>${d.short}</small></span><em>LV ${v}</em>`;box.append(el)}}
function showBanner(text){const b=$("#banner");b.textContent=text;b.classList.add("show");clearTimeout(state.bannerTimer);state.bannerTimer=setTimeout(()=>b.classList.remove("show"),1000)}function toast(text){const t=$("#toast");t.textContent=text;t.classList.add("show");clearTimeout(state.toastTimer);state.toastTimer=setTimeout(()=>t.classList.remove("show"),3300)}
async function save(){log("session_saved",{completed:[...state.completed],current_build:build(),actions:state.actions,event_count_before_save:state.logs.length});const body=state.logs.join("\n")+"\n",filename=`catalyst-ecology-${state.session}.jsonl`;try{const r=await fetch("/api/playtest-log",{method:"POST",headers:{"Content-Type":"application/x-ndjson","X-Playtest-Filename":filename},body});if(!r.ok)throw Error(r.status);const j=await r.json();toast(`Saved ${j.events} events to ${j.path}`)}catch(_){const blob=new Blob([body],{type:"application/x-ndjson"}),a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=filename;a.click();URL.revokeObjectURL(a.href);toast(`Server unavailable; downloaded ${filename}`)}}
function pointer(e){const r=canvas.getBoundingClientRect();state.pointer.x=clamp((e.clientX-r.left-state.render.ox)/state.render.size,0,1);state.pointer.y=clamp((e.clientY-r.top-state.render.oy)/state.render.size,0,1)}
canvas.onpointermove=pointer;canvas.onpointerdown=e=>{pointer(e);if(e.button===0){state.pointer.firing=true;fire()}e.preventDefault();canvas.focus({preventScroll:true})};addEventListener("pointerup",e=>{if(e.button===0)state.pointer.firing=false});canvas.oncontextmenu=e=>e.preventDefault();addEventListener("keydown",e=>{if(KEYS[e.code]){state.keys.add(e.code);e.preventDefault()}});addEventListener("keyup",e=>state.keys.delete(e.code));addEventListener("blur",()=>{state.keys.clear();state.pointer.firing=false});document.addEventListener("visibilitychange",()=>{if(state.started)log("visibility_changed",{hidden:document.hidden})});
$$('.expedition').forEach(b=>b.onclick=()=>selectExpedition(b.dataset.expedition));$("#begin").onclick=begin;$("#restart").onclick=()=>state.started?startRun(state.expedition,"restart_button"):begin();$("#save").onclick=save;$("#complete-save").onclick=save;$("#next-expedition").onclick=e=>startRun(e.currentTarget.dataset.expedition,"complete_next");$("#replay").onclick=()=>startRun(state.expedition,"complete_replay");
if(new URLSearchParams(location.search).has("validation"))window.__ecologyDebug={getState:()=>state,startRun,spawnWave,choose,damageEnemy,spawnBullet};
function frame(now){const dt=Math.min(.04,(now-state.lastFrame)/1000||0);state.lastFrame=now;update(dt);draw();requestAnimationFrame(frame)}
updateUI();log("session_initialized",{viewport:[innerWidth,innerHeight]});spawnWave("initial_preview");state.active=false;requestAnimationFrame(frame);
})();

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catalyst Ecology — Experiment 008</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="title"><span>EXPERIMENT 008</span><h1>Catalyst Ecology</h1></div>
<nav aria-label="Expedition selection">
<button class="expedition selected" data-expedition="shoal"><span>01</span> Shoal</button>
<button class="expedition" data-expedition="bastion"><span>02</span> Bastion</button>
<button class="expedition" data-expedition="brood"><span>03</span> Brood</button>
</nav>
<div class="top-actions"><button id="restart">Restart</button><button id="save">Save JSONL</button></div>
</header>
<main>
<aside>
<section class="brief"><span id="expedition-kicker">EXPEDITION 01 · FIELD 1 OF 8</span><h2 id="expedition-name">Shoal</h2><p id="expedition-copy"></p></section>
<section class="forecast"><h3>Current population</h3><div id="forecast-list"></div></section>
<section class="controls"><h3>Controls</h3><p><kbd>WASD</kbd><span>Move.</span></p><p><kbd>HOLD LEFT</kbd><span>Fire toward cursor.</span></p></section>
<section class="build"><h3>Four-choice build</h3><div id="build-list"><p class="empty">No catalysts selected.</p></div></section>
<section class="readouts">
<div><span>INTEGRITY</span><b id="health">● ● ● ● ● ●</b></div><div><span>FIELD</span><b id="wave">1 / 8</b></div>
<div><span>REMAINING</span><b id="remaining">0</b></div><div><span>RUN TIME</span><b id="run-time">0:00</b></div>
</section>
<section class="note">All six catalysts remain available at every choice. Repeats stack. Four selections fit in one emitter.</section>
</aside>
<section class="playfield"><canvas id="field" tabindex="0"></canvas><div id="banner" class="banner"></div><div class="canvas-help">WASD move · aim with mouse · hold left to fire</div></section>
</main>
<div id="start-overlay" class="overlay"><div><span>THREE POPULATIONS</span><b>Build within four choices.</b><p>Choose during the first four fields, then keep the complete build through four more. Everything is available now. Stop whenever you want.</p><button id="begin">Begin Shoal</button></div></div>
<div id="choice-overlay" class="overlay hidden"><div class="wide"><span>FIELD STABLE</span><b>Choose one catalyst</b><p id="next-forecast"></p><div id="choices"></div></div></div>
<div id="complete-overlay" class="overlay hidden"><div><span>EXPEDITION COMPLETE</span><b id="complete-title"></b><p>Choose another expedition, replay this one, save, or stop.</p><div class="complete-actions"><button id="next-expedition"></button><button id="replay">Replay this expedition</button><button id="complete-save">Save JSONL</button></div></div></div>
<div id="toast"></div>
<script src="app.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,113 @@
# Experiment 008 Preliminary Analysis — Session 0ac8e60d
Status: first-run telemetry and report complete; corrective revision required before interpreting H23.
Source: `JSONL/catalyst-ecology-0ac8e60d-a488-4d4d-a4d4-3d220981ef67.jsonl`
## Session Structure
- 2,042 events over 374.0 seconds from initialization to save.
- Shoal, Bastion, and Brood were played in displayed order and each completed once.
- All 15 fields cleared on their first attempt. There were no defeats and six total damage events.
- No expedition was replayed, restarted, switched away from, or abandoned.
- The player saved once after all three expeditions.
Completion is likely compliance with the requested comparison. The lack of replay is meaningful only after asking whether the option space felt exhausted, the player had already formed conclusions, or no build question justified another run.
## Build Sequences
| Expedition | Choice sequence | Final build | Active run time |
|---|---|---|---:|
| Shoal | Bloom → Resonance → Arc → Focus | Bloom, Resonance, Arc, Focus | 48.9 s |
| Bastion | Conduit → Bloom → Arc → Fork | Conduit, Bloom, Arc, Fork | 78.6 s |
| Brood | Bloom → Focus → Arc → Fork | Bloom, Focus, Arc, Fork | 87.5 s |
Bloom and Arc formed a common core across all three populations. The two remaining slots varied:
- Shoal uniquely used Resonance and omitted Fork/Conduit.
- Bastion uniquely used Conduit and omitted Focus/Resonance.
- Brood used Focus and Fork, omitting Conduit/Resonance.
Every one of the six available modules was selected somewhere, but no module was stacked. This may reflect ecology-based choice, coverage/sampling of new options, or a belief that one level of several interacting mechanisms is better than specialization.
## Deliberation Pattern
The first choice in each expedition took much longer than later choices:
- Shoal first choice: about 38.9 seconds.
- Bastion first choice: about 33.5 seconds.
- Brood first choice: about 25.5 seconds.
- Later choices generally took 16 seconds.
The player therefore spent substantial time reading or planning when the build was empty, then selected quickly as the run developed. The decreasing first-choice times could indicate growing understanding of the six-module vocabulary, fatigue, or a progressively clearer plan.
Conduit was selected first in Bastion despite having no output without secondary hits. It remained inactive during field two, then began firing after Bloom was added. Sixteen Conduit lances ultimately hit and six killed targets. This is potentially strong evidence of planning around a future interface, but it could also be experimentation with an attractive description or a misunderstanding corrected by the next choice.
## Population and Effect Results
### Shoal
- 99 kills: 93 Motes and 6 Husks.
- Bloom sparks caused 52 kills; Arc caused 12; Focus caused one.
- Field time fell from 13.9 seconds at baseline to 7.1 seconds in the final field despite a much larger population.
- Focus arrived last and ruptured only once, consistent with most targets dying too quickly to accumulate repeated hits.
- The secondary budget never approached exhaustion (observed minimum 214/300).
### Bastion
- 40 kills: 9 Titans, 16 Husks, and 15 Motes.
- Primary fire still caused 27 kills; Conduit lances caused six.
- Conduit fired/hit 16 times after secondary generators became available.
- Final fields remained around 1820 seconds, so the build did not erase durable populations.
- The secondary budget remained ample (observed minimum 201/300).
### Brood
- 79 kills, including 27 Motes spawned during combat.
- Bloom produced 103 spark hits; Fork produced 46 fragment hits; Arc triggered 32 times; Focus ruptured eight times.
- Final build used all four direct mechanisms and produced all four corresponding kill causes.
- Field time increased with the population from 8.5 seconds to 22.1 seconds.
- The lowest observed secondary budget was 134/300; the recursion cap did not determine the outcome.
## Preliminary Interpretation
Experiment 008 did not immediately collapse into one identical four-slot answer. The player used all six modules across three distinct builds, and the modules materially interacted. It also did not provoke the repeated build tests seen in 007.
The common Bloom+Arc core is the central ambiguity. It may mean:
1. knowledge from 007 transferred successfully as a reusable component pair while the remaining slots adapted;
2. Bloom+Arc is already a partially crystallized universal answer, leaving only two low-stakes flex slots;
3. the player was deliberately holding a familiar baseline while sampling the three new modules;
4. the choices were made for novelty/coverage rather than population response.
The three expedition builds look plausibly ecology-aware, but telemetry cannot establish why. Shoal's Bloom+Resonance generated strong small-target cascades. Bastion's Conduit converted secondary activity into attacks on the healthiest target. Brood combined Focus for durable spawners with Fork/Bloom/Arc for their Motes. Those relationships may have been planned, noticed afterward, or entirely incidental.
Unlike Experiment 003's obvious counter-parts, no module names a specific enemy class. Components still operate through hit/kill interfaces. However, the population descriptions themselves may make the build split obvious enough that it becomes counter-loadout work. The player's subjective decision process is necessary to discriminate H23.
The four-choice budget did preserve tradeoffs: no run could include all six modules. But it may also have suppressed the power-escalation payoff that motivated 007's specialization replays. No repeats were selected, including no all-Bloom attempt, and no run created a logged follow-on test.
## Follow-up Needed
1. What expectation drove each distinctive first choice—especially Bloom for Shoal/Brood and inactive-until-supported Conduit for Bastion—and were later choices completing a plan or sampling?
2. Did the populations create several plausible builds, obvious counters, or a universal Bloom+Arc core with two unimportant flex slots? How did the four-choice limit feel?
3. Did any result surprise or satisfy enough to suggest another build, and why did the player stop after one run per expedition rather than replaying as in 007?
## Player Report
The distinct builds were not substantially planned. Conduit was chosen first because its dependency was misunderstood, not because the player was deliberately investing in a future secondary-hit engine. Do not cite that sequence as forward planning.
The player believes the populations may permit multiple approaches, but could not formulate which effects played off one another well because each expedition contained too few rounds. They described the repeated experience as beginning to see something cool emerge and then having the expedition end before anything really cool happened.
## Final First-Run Interpretation
Experiment 008 failed to measure strategy half-life because it ended at build maturity. Four selections occurred after the first four fields, leaving only field five for the complete build:
- Shoal's complete build existed for 7.1 seconds.
- Bastion's complete build existed for 19.8 seconds.
- Brood's complete build existed for 22.1 seconds.
The larger six-module vocabulary and changing populations required more observation than Experiment 007, but the run structure supplied no additional mature-build time. Lack of replay is therefore not good evidence that the system exhausted its questions. The player's report and telemetry agree that the prototype terminated during emergence rather than after it.
The proper correction is structural, not a new mechanic or balance pass: preserve the exact four choices and current populations, then add several post-build fields so the player can observe the same completed composition against increasing and mixed populations. This keeps the slot-budget test intact while allowing H23 to become measurable.
Do not add replacement choices yet. Swapping would bundle ecology adaptation with interface/optimization work before establishing whether a mature four-module build is interesting to watch and understand. Do not increase the choice budget; the current question specifically concerns composition under exclusion.

View file

@ -0,0 +1,79 @@
# Experiment 008 Revision 2 Preliminary Analysis — Session 7b7c7a92
Status: telemetry and brief initial report inspected; targeted follow-up pending.
Source: `JSONL/catalyst-ecology-7b7c7a92-42cb-4ab2-8a81-d1316ea972c5.jsonl`
## Session Structure
- 5,338 events over 609.4 elapsed seconds, including a roughly 196-second break between Shoal and Bastion.
- All three eight-field expeditions completed once in displayed order.
- All 24 fields cleared first attempt; no deaths, restarts, abandoned runs, or replays.
- Twelve choices total and exactly three post-build transitions per expedition, confirming the corrective structure worked.
- Seven damage events across the session. Combat difficulty was not a meaningful constraint.
- One save occurred after all three expeditions.
The player reports that revision 2 made it “a bit easier to see how my choices impacted my play.” This validates the correction at the legibility/exposure level, not yet at the enjoyment or strategy-half-life level.
## Builds
| Expedition | Choice sequence | Complete build |
|---|---|---|
| Shoal | Bloom → Fork → Conduit → Fork | Bloom 1, Fork 2, Conduit 1 |
| Bastion | Focus → Fork → Bloom → Fork | Focus 1, Fork 2, Bloom 1 |
| Brood | Arc → Fork → Arc → Resonance | Arc 2, Fork 1, Resonance 1 |
Unlike revision 1, Bloom+Arc was not a universal core. Fork appeared everywhere, but at different levels and with different consumers. Each expedition specialized by repeating a module or using Resonance rather than sampling four distinct mechanisms.
The builds are causally distinct:
- Shoal used Fork and Bloom to create secondary hits, then Conduit converted them into 69 heavy lances. Its 237 kills were distributed across primary (46), fragments (73), sparks (70), and lances (48).
- Bastion used repeated primaries for 22 Focus ruptures while Fork produced 658 fragment hits. Bloom supplied 151 spark hits after kills began. Focus caused 13 kills and fragments 60.
- Brood omitted Bloom entirely. Fork generated hits for Arc; Arc was stacked, then Resonance amplified both. Arc triggered 114 times and caused 54 kills; fragments caused 83.
This is much stronger behavioral evidence of conditional composition than revision 1. It is still not proof of deliberate ecology reasoning because the player may have been comparing builds experimentally or avoiding repetition from the previous session.
## Mature-Build Exposure
Each complete build persisted through fields five through eight:
| Expedition | Mature field times | Main growth during mature fields |
|---|---|---|
| Shoal | 9.8, 10.1, 9.9, 11.2 s | Conduit lances rose from 18 cumulative to 69; spark hits from 111 to 349 |
| Bastion | 16.8, 18.2, 19.5, 21.7 s | Fork hits rose from 198 to 658; Focus ruptures from 12 to 22 |
| Brood | 10.6, 12.9, 17.1, 15.1 s | Arc triggers rose from 44 to 114; spawned Motes from 24 to 69 |
The extra fields supplied approximately 41 seconds of mature Shoal, 76 seconds of mature Bastion, and 56 seconds of mature Brood, versus 722 seconds in revision 1. The player now reports clearer choice impact, so the first-run measurement defect was successfully repaired.
No field exhausted the secondary budget, though Bastion approached it most closely (observed minimum 18/300). Large effect chains were therefore bounded but not usually clipped.
## Choice Deliberation
Deliberation did not simply decline with familiarity:
- Shoal Conduit took about 15.0 seconds.
- Bastion's first Focus took about 11.5 seconds and final Fork stack about 14.0 seconds.
- Brood's final Resonance took about 9.8 seconds.
Those pauses are consistent with real build consideration, but reading, distraction, and uncertainty remain alternate explanations. The long Conduit choice is particularly notable because the first session's Conduit selection was a misunderstanding; revision 2 placed it into a functioning secondary-hit engine.
## Preliminary Interpretation
Revision 2 establishes that the system can visibly express multiple four-choice builds across different ecologies. It weakens the immediate crystallization interpretation from revision 1: Bloom+Arc was not universal, stacking occurred, and one expedition deliberately or experimentally omitted Bloom despite its known strength.
It does not yet establish why the builds differed or whether the extra exposure was enjoyable. Three readings remain:
1. The player transferred module knowledge and selected conditional causal engines for each population.
2. The player intentionally sampled three distinct builds to make the experiment informative, regardless of preference.
3. More mature fields made effects readable, but the run still lacked enough decision points or surprise to motivate replay.
Fork may be emerging as a reusable abstraction rather than a complete answer. It appeared in all builds because extra hits feed Bloom, Arc, and Conduit, while its partner determined the resulting capability. That would match “reuse components, not answers.” Alternatively, Fork×1/2 may simply be the strongest generic throughput option and thus an early universal core.
No replay followed the clearer mature exposure. As always, this is ambiguous: three eight-field runs may have provided enough evidence, may have become repetitive, or may not have produced a new specific question. The corrective test now permits that question to be asked meaningfully.
## Follow-up Needed
1. Were the three specialized builds chosen in response to each population, to test different combinations, or for another reason? What was the intended role of Fork in all three?
2. During fields five through eight, did any interaction become surprising, satisfying, or fun rather than merely easier to read? When, if at all, did the extra fields become repetition?
3. Did the results suggest a specific alternative build worth replaying, and did the four-slot limit now feel productive or still premature/restrictive?

View file

@ -0,0 +1,4 @@
# Experiment 008 Results
Playtest interpretations and telemetry summaries belong here.

View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
EXPERIMENT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$EXPERIMENT_DIR/../.." && pwd)"
exec python3 "$REPO_ROOT/tools/playtest_server.py" \
--directory "$EXPERIMENT_DIR/prototype" \
--log-directory "$REPO_ROOT/JSONL" \
--port 8000

794
research/agent_handoff.md Normal file
View file

@ -0,0 +1,794 @@
# Agent Handoff — Private Research Notes
Last updated: 2026-08-17, after the successful Experiment 007 result and validated Experiment 008 implementation.
This file is written so another agent can continue the research program without reconstructing the reasoning. The player intends not to read it before playtesting, to avoid expectation effects. It contains design hypotheses, likely failure interpretations, and things to watch for.
## Current Handoff Snapshot — Read This First
Date: 2026-08-17.
The current playable is **Experiment 008 — Catalyst Ecology revision 2** in `experiments/008_catalyst_ecology/`. Corrective telemetry is inspected and the detailed player report is pending. Experiment 007 is complete and is the first successful probe.
Run it with:
```bash
./experiments/008_catalyst_ecology/run.sh
```
Then open `http://127.0.0.1:8000`. The custom local server writes validated logs directly to repository `JSONL/` when the player presses **Save JSONL**. Port 8000 was free and all Codex validation processes were stopped at handoff.
### The actual research objective
Do not optimize a known game concept or assume the original “magic engineering” aspiration is already correct. The task is to discover what this particular player finds fun through small playable experiments. Behavioral persistence, objective completion, experimentation, difficulty, and high input counts are not fun scores.
The player's explicit epistemic warning is central: they are an imperfect playtester. Preserve four separate layers:
1. observed behavior;
2. reported felt experience;
3. the player's causal explanation;
4. the designer's competing interpretations.
Their reports of boredom, frustration, interest, and desire to stop are high-value evidence. Their explanations and predictions about unbuilt variants are hypotheses, not ground truth. Likewise, do not override their experience merely because telemetry shows active or sophisticated play.
### Current working model
The strongest current inference is not “the player dislikes systems,” automation, puzzles, pressure, construction, or chaos in general. Every tested prototype has failed in a more specific way:
- system distinctions did not affect an adequate answer;
- technical manipulation had no valued downstream consequence;
- one tactic became universal;
- physical layout was cosmetic and parts were counters;
- a visible model was not strategically legible;
- a genuine self-directed question produced no satisfying consequence;
- readable chaos accumulated until interventions became unavoidable self-sabotage.
The current compact theory is:
> A systemic question is more likely to matter when its answer increases agency inside an activity the player already values. Coupling is useful only while it creates selective leverage; indiscriminate consequences can erase good actions rather than create emergence.
This remains a hypothesis. Experiment 006 deliberately tests the lower layer first: whether immediate, assertive, targeted action has any intrinsic value before adding engineering, progression, world attachment, or multiplayer context.
### Experiment history in one page
#### 000 — Resonance Bench
Abstract node-and-signal construction. The player solved all observations in roughly three minutes, did not notice cycles or defined vector direction, and had no desire to continue. Permissive predicates meant the intended laws never entered the strategy. This is not clean evidence against technical systems generally.
#### 001 — Flux Familiar
The signal network controlled a body in a spatial arena. The player wanted to stop almost immediately. It felt like brute-force steering through an indirect interface; imagining sensors/AI sounded tedious. Treat that imagined variant as a prediction, not a tested result.
#### 002 — Conversion Breach
Direct heat extraction/injection and heat-to-momentum combat. This produced the longest play and the first contextual tactical revision: extracting heat simultaneously created ammunition, slowed enemies, prepared brittle collision damage, and depleted future fuel. The player learned not to maximize extraction blindly.
However, wall impacts became the universal answer. Revision 2 matched Calm and Breach, fixed input/layout/wall issues, and reached Breach wave 29, but extreme multiplicative conversion made starting heat erase later waves. Pressure and rapid power sustained activity; they did not sustain new reasoning or reported interest. Do not use run length as proof of fun.
#### 003 — Rig Trials
A directly piloted modular rig faced Haul, Furnace, and Gale. The player called it boring despite completing everything. Controls were fine. Furnace meant “replace almost everything with Sinks,” Gale meant “restore default,” and lattice placement barely mattered. Requirements caused obvious counter-loadouts, not meaningful recomposition.
The player independently invented a two-tractor cargo-juggling workaround after overlooking Sinks. It was still boring. This corrected an important methodological mistake: voluntary experimentation/problem solving is not sufficient evidence of fun.
#### 004 — Invariant Rooms / player title Paired Rooms
Two bodies moved oppositely; walls blocked them independently, allowing midpoint ratcheting. Rooms 1 and 2 were obvious without requiring a model. Room 3 jumped abruptly to latent-state planning and was brute-forced in 94 commands. The visible yellow midpoint cross was not noticed as a planning tool during the required sequence.
In the open chamber, the player performed a genuine unrequired experiment: they tried to move the yellow cross against a wall. That question was not satisfying to answer. This is the cleanest evidence so far that self-generated curiosity-like behavior alone is not enough. Better scaffolding could repair the transfer test, but would not give the abstract answer value.
#### 005 — Sanctuary Wake
A direct keeper used indiscriminate pull/push on pods, raiders, and wreckage during a three-minute rescue shift. The player wanted to stop almost immediately but actively completed the exact timer: 6 rescued, 18 lost, 17 raiders broken, 89 field sessions, and no overtime. Controls were fine and the chaos was readable.
The player did not care about the outcome. As wreckage accumulated, nearly every intervention risked pushing pods into raiders or wreckage into the sanctuary. The state was unmanageable, not incomprehensible. Charge depletion was merely annoying. This was accumulating contamination, not recoverable chaos: mistakes shrank the future action space.
### Why Experiment 006 exists
Breakline is a controlled contrast to 005 and tests H20: **assertive, selective agency may be more intrinsically valuable than custodial control**.
It has:
- direct WASD movement;
- an aimed short strike that damages, knocks, and reflects hostile projectiles;
- a targeted tether that pulls wisps/gunners toward the player but pulls the player toward heavy anchors;
- an invulnerable offensive dash that damages enemies crossed;
- damaging fast enemy-on-enemy collisions;
- health regeneration after a safe interval;
- automatic local restoration after defeat;
- Drift, Crossfire, Weight, and seeded Remix available immediately.
It intentionally has no timer, field resource meter, upgrades, unlocks, persistent damage, construction, rescue objective, or hidden law. Clearing grants no power. The player can switch, replay, remix, or stop at any time.
The key question is not whether they clear all arrangements. It is whether any verb or interaction is enjoyable enough to repeat, refine, or combine voluntarily.
### Experiment 006 validation state
Files:
- `experiments/006_breakline/prototype/index.html`
- `experiments/006_breakline/prototype/style.css`
- `experiments/006_breakline/prototype/app.js`
- `experiments/006_breakline/hypothesis.md`
- `experiments/006_breakline/README.md`
- `experiments/006_breakline/run.sh`
Validation completed:
- JavaScript and shell syntax pass.
- The complete layout was visually inspected at the player's 1672×976 viewport.
- Browser input produced light-enemy tethering and heavy-anchor reverse tethering.
- Strike reflection produced reflected-projectile damage.
- Offensive dash and strike produced damage/kills.
- Player damage, enemy fire/charge telegraphs, arrangement switching, Remix, and direct JSONL saving were exercised.
- A final browser route cleared Drift in 9.97 seconds with six kills, a five-kill chain, four strike hits, two dash hits, and no defeat.
- The validation JSONL was deleted; only player logs remain under `JSONL/`.
- The validation server and Chromium were stopped. Port 8000 was free.
Known validation limitation: the damaging enemy-on-enemy collision branch was code-reviewed but not deliberately triggered during the final clear route. Do not claim otherwise. The mechanism may still prove too difficult to use intentionally.
### Experiment 006 telemetry summary
The player saved `JSONL/breakline-360f3f3f-1e5c-442d-8fdd-7f4976b17b29.jsonl`. Preliminary analysis is in `experiments/006_breakline/results/360f3f3f-preliminary-analysis.md`.
They cleared all four arrangements without defeat, saved, then voluntarily replayed and cleared Drift and Crossfire before saving again. The replay patterns are unusually specific: Drift replay used one strike to hit and kill all six wisps simultaneously, while Crossfire replay used 14 reflections and only two direct strike hits. Across the session there were 42 kills: 22 strike, 12 reflected projectile, and 8 body collision.
This initially looked like the strongest voluntary replay signal yet, but the completed report below establishes why replay must not be labeled fun from behavior alone. Strike/reflection dominated (56 strikes, 34 reflections); tether was used only five times and dash once with no hit.
### Experiment 006 complete player report
The Drift replay was another overlap experiment, not an intentional one-strike clear. The player had missed enemy-enemy collision, saw the cluster would not compress further, and happened to kill all six with the next swing.
The Crossfire replay was intentional: the player noticed reflection late and returned because it seemed interesting, comparing it to reflecting a Minecraft ghast fireball back at itself. The final follow-up established that this was mechanic recognition, not enjoyment. The player was ready to stop after the replay. In use, reflection required aiming at a moving enemy while timing a nearby projectile, which pulled attention away from other incoming projectiles.
Body-collision kills were incidental and not understood. Tether was abandoned because pulling enemies closer worsened position with little payoff. Dash was unnecessary and cognitively easy to forget; the player says reactive dodge mechanics generally require preplanning for them to remember, though they do not claim dodging cannot be fun.
The final result is that 006 found no intrinsically enjoyable base verb. Strike was adequate, tether was actively disadvantageous, dash stayed outside attention, and reflection was conceptually appealing but operationally attention-splitting. Do not interpret optional replay as fun. Also do not generalize this into “all counters are bad”; target aiming, projectile timing, and peripheral defense were bundled together.
The next high-information hypothesis is **authored capability trajectory**. Experiment 002 suggests rapid chosen growth can sustain play, but its numerical upgrades converged on one universal answer. A controlled 007 should put two short runs on the same low-demand combat substrate: one with straightforward numerical amplification and one with exact, qualitative effects that visibly combine. Measure anticipation, choice deliberation, voluntary continuation/replay, and whether the player wants to see another interaction. Do not use kill counts or run completion as a fun score.
### Experiment 007 implementation and validation
Experiment 007 is implemented as **Catalyst Trials**. It uses a square top-down field with only WASD/arrow movement, cursor aim, and hold-left firing. There are no secondary verbs, enemy projectiles, dodge buttons, random drops, unlocks, or upgrade scarcity. Contact threats are intentionally simple so 006's multi-target reflection burden is not reproduced.
Each session randomly maps `mutation` and `amplification` to neutral **Trial I** and **Trial II** slots. Both trials are immediately selectable and share the exact same five deterministic enemy layouts, baseline weapon, enemy health/speed, player health rules, and four inter-field choices. The order is stored in every JSONL event as `condition` and initially as `condition_order`.
The amplification condition always offers:
- Power Core: ×1.7 direct damage per level;
- Cadence Drive: firing interval ×0.65 per level;
- Wide Aperture: projectile radius ×1.55 per level.
The mutation condition always offers:
- Fork: a primary hit emits two additional fragments per level;
- Bloom: a kill emits `1 + 2 × level` seeking sparks;
- Arc: every `max(2, 6 - level)` projectile hits damages up to `1 + level` nearby enemies.
Fork fragments can kill and trigger Bloom. Bloom sparks count as projectile hits and can trigger Arc. Arc kills can trigger Bloom. Recursion is bounded: fragments cannot fork again and arc damage does not increment the arc counter.
The full-screen layout was visually inspected at 1672×976. JavaScript and shell syntax pass. A headless play pass cleared all five amplification fields with the planned build `power 2 / rate 1 / width 1`; this was reachability validation, not balance or enjoyment evidence. A deterministic browser pass then completed all five mutation fields with `fork 1 / bloom 2 / arc 1`, recorded Fork, Bloom, and Arc triggers, and successfully uploaded 282 valid events through the server. The validation JSONL was moved out of the repository afterward. The mutation debug interface exists only when the page is loaded with `?validation=1`.
Expected first-run logs are named `JSONL/catalyst-trials-<session>.jsonl`.
After the user plays:
1. Find the newest Catalyst Trials JSONL and read `condition_order` before comparing Trial I/II.
2. Reconstruct trial order, starts/restarts, wave clear times, deaths, choice order, deliberation time from `choices_shown` to `upgrade_chosen`, mutation trigger chains, saves, and any replay/switch.
3. Write a preliminary result under `experiments/007_catalyst_trials/results/` before asking follow-ups.
4. Ask about intention and experience, not whether qualitative upgrades were “better.” Useful neutral distinctions: whether any choice pursued a planned interaction or just apparent strength; whether its realized result was satisfying versus merely legible/interesting; when stop desire began in each trial; whether a later choice reversed it; whether they wanted a different build afterward.
5. Treat completing both trials as likely compliance with the comparison. Do not interpret more triggers, faster clears, deaths, longer duration, or a repeated choice as fun without report/behavioral support.
Important confounds:
- Trial order is randomized but this is still one player/session; familiarity and fatigue remain.
- Mutation is more audiovisual and may be stronger despite approximate pacing control.
- Upgrade descriptions expose their behavior, so this tests composition/authorship more than hidden discovery.
- Four decisions may be too few for a build identity, but adding a long run before the core earns it would repeat prior mistakes.
- The action substrate is intentionally merely serviceable. A null result could mean progression cannot rescue an unwanted activity, not that all buildcraft is unfun.
### Experiment 007 complete report
The player saved `JSONL/catalyst-trials-56ccf2a6-9613-4da1-86aa-3c5d61797784.jsonl`. Preliminary analysis is in `experiments/007_catalyst_trials/results/56ccf2a6-preliminary-analysis.md`.
The randomized mapping was Trial I = mutation and Trial II = amplification. The player cleared Trial I with Fork 1 / Arc 3, saved, cleared Trial II with Rate 2 / Width 1 / Power 1, saved, then voluntarily replayed Trial I with Arc 4 and saved again. All 15 fields cleared first attempt; there were only two damage events and no defeats.
The Arc-only replay began after both trials were already complete and saved. Its four choices took approximately 1.0, 0.9, 0.6, and 0.6 seconds. The player confirms it was a deliberate test of Arc's isolated strength; Arc alone felt weak.
The player also performed a combined run, tried Bloom, and saw that all three effects played off one another much better than expected. They initially picked Fork and then Arc only because each description sounded interesting, not because they predicted the hit-count interaction. Bloom was skipped because they guessed its value incorrectly; after use, they considered it the best effect and regretted skipping it.
Trial I was interesting enough to replay. Trial II appeared boring as soon as its menu was read. The player saw its numerical upgrades as things that could enhance Trial I's effects, but not as effects that created interesting differences with each other. This is the project's first clear chain of wrong expectation → test → surprising cross-effect result → revised model → additional build test.
The updated save `JSONL/catalyst-trials-56ccf2a6-9613-4da1-86aa-3c5d61797784 (1).jsonl` supersedes the partial file and contains all five runs: 2,900 events, 25 first-attempt field clears, and no defeats. The later builds were Bloom 2 / Fork 1 / Arc 1 and Bloom 4. Bloom appeared to scale too aggressively, but watching everything disappear after only a few shots was “kind of fun to watch.” The player also spontaneously mapped Bloom to small enemies and Arc to large enemies, identifying conditional target profiles within the very short demo.
This makes 007 the project's first successful probe: inaccurate prediction → chosen test → surprising causal interaction → revised model → several further build questions → conditional knowledge → visible power payoff. Independent numerical upgrades were useful but boring because they did not play off one another.
Do not collapse this into “qualitative upgrades are fun.” Fork/Arc/Bloom share narrow hit/kill interfaces, whereas the amplification options are independent. Bloom also adds homing, and mutation adds spectacle/raw effective power. The next experiment should test whether this causal composition survives a limited module budget and changing enemy ecologies without becoming either a universal full-synergy build or the obvious counter-loadout work that failed in 003.
### Experiment 008 implementation and validation
Experiment 008 is implemented as **Catalyst Ecology**. It preserves 007's WASD/arrow movement, cursor aim, hold-left firing, local wave reset, and exact unrestricted choices. There are no enemy projectiles or secondary combat buttons.
Three expeditions are available immediately:
- **Shoal:** increasingly dense Mote populations with a few Husks late;
- **Bastion:** health concentrated in Husks and Titans, with a few Motes late;
- **Brood:** Brood bodies release a bounded number of Motes into mixed populations.
Corrective revision 2 gives each run eight fields and four inter-field selections after fields one through four. The completed build then persists unchanged through fields five through eight. All six catalysts are offered at each selection, duplicates stack, and there is no unlock order:
- Fork: primary hits emit `2 × level` fragments;
- Bloom: kills emit `2 + level` seeking sparks;
- Arc: every `max(3, 7 - level)` projectile hits chain into up to `1 + level` nearby bodies;
- Focus: `max(2, 6 - level)` repeated primary hits on one body produce a rupture for `3 + 2 × level` base damage;
- Conduit: every `max(3, 9 - 2 × level)` secondary hits launches a heavy homing lance at the healthiest body;
- Resonance: multiplies all non-primary damage by `1 + 0.55 × level`, but creates no output alone.
Fork/Bloom projectiles feed Arc and Conduit. Arc and Focus impacts feed Conduit. Kills from any source feed Bloom. Conduit lances do not recursively charge Conduit. A per-field budget caps secondary projectile/impact production at 300, preserving large collapses while preventing unbounded Bloom loops.
Validation completed:
- JavaScript, shell, and Python server syntax pass.
- The 1672×976 layout was visually inspected; instructions, population forecast, build, field, and overlays fit without page scrolling.
- Revision 1 deterministic browser flow completed Shoal, Bastion, and Brood with all four choices and all five original fields.
- Brood spawning occurred and remained bounded.
- Focused high-health-target validation triggered Fork 8 times, Focus once, Arc 6 times, and Conduit 3 times; Bloom triggered in the expedition pass. Resonance scaling was active in the focused build.
- Direct upload saved 686 valid JSONL events. The validation file was moved out of `JSONL/` afterward.
Expected player logs are `JSONL/catalyst-ecology-<session>.jsonl`.
After the user plays:
1. Inspect expedition order, completed/abandoned/replayed runs, choice sequences and timing, field population/build combinations, module triggers, kill causes by enemy type, Brood spawns, secondary-budget exhaustion, damage/defeats, and saves.
2. Write a preliminary result in `experiments/008_catalyst_ecology/results/` before asking questions.
3. Ask what prediction drove each distinctive build, whether population differences admitted multiple approaches or prescribed counters, which cascades remained understandable, and when stop desire began.
4. Do not infer adaptation merely from different builds. A Shoal/Bloom and Bastion/Focus split may be obvious compliance unless it creates revisions or further questions.
5. Do not infer that a universal build is bad solely because it is powerful. Determine whether it ends reasoning or becomes an expressive baseline with situational variations.
### Experiment 008 first-run telemetry and report; corrective revision required
The player saved `JSONL/catalyst-ecology-0ac8e60d-a488-4d4d-a4d4-3d220981ef67.jsonl`. Preliminary analysis is in `experiments/008_catalyst_ecology/results/0ac8e60d-preliminary-analysis.md`.
They completed Shoal, Bastion, and Brood once each, in order, with no defeats or restarts and no replay after all three. Builds were:
- Shoal: Bloom → Resonance → Arc → Focus;
- Bastion: Conduit → Bloom → Arc → Fork;
- Brood: Bloom → Focus → Arc → Fork.
Bloom+Arc was universal, while every one of the six modules was sampled somewhere and none was stacked. First-choice deliberation was long (approximately 39, 34, and 26 seconds), while later choices were mostly rapid. Conduit was picked first in Bastion, did nothing alone, then activated after Bloom supplied secondary hits. This could be planned interface composition, experimentation, or misunderstanding.
The player reports that the builds were not meaningfully planned and Conduit-first was a misunderstanding. They believe multiple approaches may exist, but each expedition was too short to formulate which effects played together well. Their repeated experience was starting to see something cool emerge and then having the expedition end before anything really cool happened.
Telemetry confirms the structural cause: the fourth selection was active for only field five—7.1 seconds in Shoal, 19.8 in Bastion, and 22.1 in Brood. Therefore no replay and different build choices cannot be used to infer crystallization, adaptation, or exhaustion.
Correct 008 by retaining the exact four-choice budget and module mechanics while adding multiple post-build fields. Do not add swapping or more slots yet; those would introduce new confounds before mature-build interest is measured.
Corrective revision 2 is implemented. Every expedition now contains eight fields. Choices remain after fields one through four; fields five through eight automatically retain the complete build. Added fields extend each existing ecology with increasing density and mixed durable bodies. JSONL events now use `prototype_revision: 2` and log each automatic transition as `post_build_field_advanced`.
Revision 2 validation completed three full synthetic runs using the first-session builds. Each run recorded exactly eight `wave_completed` events, four `module_chosen` events, three `post_build_field_advanced` events, and one `run_completed` event. The validation JSONL was moved out of the repository. JavaScript syntax still passes. No module mechanics, choice descriptions, damage values, slot counts, or control rules changed.
After the corrective playtest, compare mature fields five through eight within each expedition. Ask whether interactions became understandable, whether any full build developed or flattened over those fields, whether a specific alternative build arose, and when the extra exposure shifted from useful observation to repetition. Do not compare total duration directly to revision 1 as enjoyment evidence; revision 2 deliberately contains more fields.
### Experiment 008 revision 2 telemetry; follow-up pending
The player saved `JSONL/catalyst-ecology-7b7c7a92-42cb-4ab2-8a81-d1316ea972c5.jsonl`; analysis is in `experiments/008_catalyst_ecology/results/7b7c7a92-revision-2-preliminary-analysis.md`.
All three eight-field expeditions completed first attempt without replay. Builds were strongly differentiated:
- Shoal: Bloom → Fork → Conduit → Fork;
- Bastion: Focus → Fork → Bloom → Fork;
- Brood: Arc → Fork → Arc → Resonance.
Bloom+Arc was no longer universal. Fork appeared in every build but fed different consumers. Mature-build exposure increased to approximately 41 seconds in Shoal, 76 in Bastion, and 56 in Brood. The player says it was “a bit easier to see how my choices impacted my play,” confirming the length correction improved visibility.
Telemetry alone cannot distinguish population-aware composition from deliberate experiment coverage. Ask why each build differed, what role Fork played, whether any mature interaction was satisfying/surprising rather than only readable, when extra fields became repetitive, whether a specific alternative build arose, and how the four-slot limit felt after sustained exposure.
### Historical Experiment 006 interpretation branches
- **Immediate boredom with controls judged fine:** assertive agency alone is insufficient. Do not add upgrades automatically. Ask what known action games provide at the moment-to-moment level that this lacks: expressive mastery, audiovisual impact, enemy responsiveness, social context, build payoff, or authored spectacle.
- **One verb feels good but encounters are shallow:** preserve that verb and make the smallest comparison that changes contextual use; do not inflate the whole moveset.
- **Combat is enjoyable but no replay:** distinguish satisfying one-pass execution from a system that generates continued questions. A later qualitative-build experiment may then be warranted.
- **Remix/replay or cleaner-execution attempts:** inspect whether they are driven by mastery, collision experiments, or completion cleanup before treating them as fun.
- **One universal spam strategy:** this repeats strategy crystallization at the action layer. Do not patch only with cooldown increases; determine why other verbs lack leverage.
- **Aim/targeting frustration:** repair 006 as a corrective revision before inferring anything about H20.
- **Commercial-action polish complaint:** treat that seriously. A browser prototype may be below the minimum feel threshold for testing combat, which would limit conclusions rather than disprove the hypothesis.
### Global methodological traps
- Do not build a general experiment harness yet.
- Do not infer fun from duration, completion, input count, upgrades chosen, or voluntary deviation.
- Do not respond to every complaint with balance tuning; identify whether it predates the balance failure.
- Do not turn visible state into “legibility” by definition. A display matters only when the player notices and uses it to predict consequences.
- Do not call chaos recoverable merely because the run continues. Recovery must restore or transform agency.
- Do not add pressure to make an unvalued activity compulsory.
- Do not add progression before a base action earns continued use; Experiment 002 showed that power can prolong a shallow loop.
- Do not interpret “I don't care” as a request for more fiction by default. Prototype labels cannot establish attachment, but stronger narrative also cannot repair weak agency automatically.
- Preserve exact player language and maintain alternate explanations.
### Logging/server implementation
`tools/playtest_server.py` is a local-only static server plus `POST /api/playtest-log`. It validates safe filenames, UTF-8 JSONL, a single experiment/session, and a 10 MB limit, then atomically writes into repository `JSONL/`. Every experiment `run.sh` uses it. Save buttons fall back to browser download if the endpoint is unavailable.
If port 8000 is occupied after agent validation, inspect with `ss -ltnp 'sport = :8000'` and stop only the exact process the agent started. A prior validation server was accidentally left running once, so cleanup must be explicit.
### Canonical current files
- `codex_game_design_research_plan.md` — original research mandate and preference priors.
- `research/current_model.md` — synthesized current preference model.
- `research/hypotheses.md` — H01H23 with evidence and confidence.
- `research/experiment_index.md` — compact experiment history.
- `research/agent_handoff.md` — this private operational record.
- `experiments/*/hypothesis.md` — per-experiment private intent.
- `experiments/*/results/` — qualitative and telemetry analyses.
- `JSONL/` — player session logs only; validation logs should not remain.
## Post-Playtest Update
Experiment 000 has now been played. The player solved all three observations in about three minutes or less, performed no voluntary experimentation, did not perceive cyclic timing or defined vector direction, and observed that arbitrary timing/direction would have solved the predicates. After completion there was no reason to improve because the solution was already adequate.
This means 000 failed to test its intended mechanics cleanly: its acceptance predicates were too tolerant for timing/phase to matter. It also supplied no consequential behavior space after binary completion. Do not interpret completion as understanding, and do not interpret the lack of curiosity as definitive rejection of technical magic.
The selected next experiment is **001 — Flux Familiar**. Preserve deterministic cyclic sources and signal transformations, but make flux drive visible motion in a bounded physical arena. Direction should literally change travel direction; timing should visibly change trajectory; repeated signals should create repeated motion. The critical comparison is whether embodiment and continuously varying consequences rescue interest, versus the substrate remaining limited even when its laws matter.
### Experiment 001 implementation and validation
Experiment 001 is now implemented in `experiments/001_flux_familiar/` as another dependency-free browser prototype. It retains the six operations and vector/link laws from 000, with three deterministic sources at periods 3, 5, and 7. One bond node maps its combined input to force on a persistent-momentum familiar in a canvas arena.
The arena provides two spatial stars, a low-speed cradle, collision geometry, boundary rebounds, a persistent colored trail, a velocity arrow, coordinate/speed/impact readouts, and three distinct constellations. Changing constellation or resetting the familiar retains the network. All constellations are available immediately; an initially implemented completion gate was deliberately removed because it would manufacture continued play through progression and violate the project's epistemic-progression principle.
The cradle requires both stars to have been touched and 14 consecutive ticks inside its radius below a small speed threshold. This is intended to make arbitrary wandering much less competent than in 000 while leaving many possible trajectories.
Browser validation used real palette, port, configuration, link deletion, and Step handlers. A deliberately crude feedback-driving script added a 180-degree Turner, used east/west and north/south impulses, woke both stars, settled at approximately `(0.23, 0.20)`, completed the constellation, and generated expected telemetry including node configuration, links, steps, star wakes, trajectory snapshots, impacts, and completion. It took hundreds of manually accelerated simulation steps because the validation controller repeatedly rewired sparse periodic sources; this proves reachability only and is not evidence about intended difficulty or enjoyment.
Current interpretive risks for 001:
- The player may solve it by manually rewiring directions, treating the network as awkward arrow keys rather than a program.
- Motion may improve legibility but still expose a small, fully exhausted signal vocabulary.
- The familiar may be only a more animated oscilloscope rather than a world consequence the player cares about.
- A route could become a finite authored puzzle with no post-solution question.
- Rebounds or long random wandering might eventually touch stars, although stable docking should still require control.
- If the player wants direct manipulation rather than autonomous construction, distinguish interface indirection from lack of system depth.
## Experiment 001 Result and Second Pivot
The player wanted to stop almost immediately. It felt like awkward steering: because sources and operations lacked field information, every connection was brute-force movement in a needed direction. There was no route worth refining and no voluntary continuation. Crucially, the player preemptively rejected the apparent fix: field-aware signals and nodes would amount to manually programming an AI, which they expect to be tedious. They again cited no incentive to improve an adequate solution.
Do not immediately build a third graph/controller prototype merely by adding sensors, conditional nodes, path planning, longer routes, or scores. That would not isolate why the first two lacked value. This is a prioritization decision, not a permanent rejection of automation.
The new high-value hypotheses are direct agency and instrumental stakes. Experiment 002 should be a substantial exploration pivot: directly control a character in a tiny action sandbox and personally manipulate stable physical/magical properties. Candidate laws are thermal transfer, energy storage/conversion, momentum, mass, brittle freezing, and collision. Understanding should create tactical power rather than a prettier solution.
Use a closely related pair inside 002:
- calm field with inert or nonthreatening constructs;
- breach field with the same entities and laws applying pressure.
This distinguishes whether direct manipulation itself generates questions and whether pressure creates meaningful incentive or merely masks shallow mechanics. It tests one alternate explanation; it must not be treated as the new assumed answer. Do not turn the prototype into a content-heavy shooter. Keep all operations available from the start. Different enemy/property configurations may define problems; do not randomly withhold the player's verbs.
### Experiment 002 implementation and validation
Experiment 002 is implemented in `experiments/002_conversion_breach/`. It is a dependency-free top-down canvas prototype with two freely selectable variants. Calm constructs obey all property and collision laws but do not pursue or injure the player. Breach constructs use the same laws while pursuing and causing contact damage. Switching variants resets kills and calibration to the same baseline; resetting within a variant preserves calibration.
Direct verbs are movement, heat extraction into a reservoir, reverse injection from the reservoir, and conversion of stored heat into an aimed kinetic impulse. Temperature changes mobility and creates brittle below 15 or unstable above 105. Momentum and mass determine collision damage. Brittle collision damage is strongly amplified. An unstable collision releases heat and radial momentum to nearby constructs. Three defeats create a fixed opportunity class with a player choice of reservoir capacity, transfer rate, or conversion efficiency; these modify degrees of existing verbs rather than unlock operations.
Validated through real browser input events:
- Extracting from the nearby Rime construct reached 12 degrees/brittle and raised reservoir energy; one aimed discharge into geometry produced a 62-damage brittle impact and destroyed it.
- Injecting a nearby Ember construct reached 112 degrees/unstable; a positioned discharge into geometry produced an unstable release.
- Repeating successful brittle conversions three times opened all three calibration choices; selecting transfer rate closed the modal and changed rate from 27 to 35.1.
- Breach mode pursuit caused logged player damage and eventual collapse when the stationary test player did nothing.
- JSONL begins with session/reset events and covers tether boundaries, thermal state changes, discharges, impacts, unstable releases, defeats, field snapshots, variant changes, damage, collapse, calibration offers, and calibration choices.
Key interpretive risks:
- “Freeze then hit” and “overheat then hit” may be prescribed status-effect combos, not an engineering discovery space.
- Action motion/feedback may provide surface stimulation while the property system remains shallow.
- The calm field may lack purpose; the breach field may create obligation without curiosity.
- A single brittle-wall tactic may dominate every construct despite mass/property variation.
- Explicitly listing laws improves readability but reduces discovery; distinguish understanding from curiosity.
- Generic numeric calibration may motivate briefly without changing qualitative decisions.
- Failure of 002 would not prove direct agency is wrong; action feel, tuning, targeting, and insufficient domain depth are alternate causes.
## Experiment 002 First Playtest
The player exported `JSONL/conversion-breach-0732452b-3ce4-4329-aa69-b121180c61a8.jsonl`. Detailed evidence and interpretation are in `experiments/002_conversion_breach/results/0732452b-analysis.md`.
This is the first promising result. Total logged duration was about 16:10, with roughly 14:23 represented by Breach events. The player reached wave 11, cleared ten waves, destroyed 74 Breach constructs, used 67 discharges with no zero-target shots, and made 24 calibration choices. Choices were conversion 19, capacity four, transfer one. Conversion reached an extreme multiplier of 85.071. There were 139 extraction and 45 injection tether sessions. Reservoir was below five in 28% of Breach snapshots. Only one unstable release occurred; 69/74 defeats came from walls.
The player reported changing from maximal extraction to conditional extraction: avoid creating a final depleted brittle enemy unless it was already positioned for a wall blast. That is real tactical adaptation around a coupled state/resource tradeoff. They also voluntarily tested personal ramming, which was not implemented, after inferring it from visible collision damage. Preserve this as positive curiosity evidence and a feedback failure.
Do not adopt the statement that brittle targets take less damage as mechanical fact. Code multiplies brittle collision damage by 13. The likely actual problem was a resource deadlock: the final enemy had been drained, the reservoir was empty, and no other heat source remained. Cooling also slowed the target. The player's mistaken causal model means feedback did not explain why the situation was difficult.
Pressure remains unresolved. Calm lacked continuing waves and looked like a sandbox/tutorial. It received no comparable goal framing. The next build should be a corrective revision of 002, not yet a new conceptual experiment.
Required corrections:
- Calm must have the same continuing waves and calibration cadence, differing only in pursuit/contact damage.
- Explicitly tell the player both modes are complete variants.
- Add pointer-only click-to-move, thermal-mode buttons, and Space discharge. The player now has a mouse, so retain these as general accessibility improvements but do not treat trackpad simultaneity as a central next-test variable.
- Fix full-screen fitting, obvious sidebar scrolling, and log viewport resizes.
- Block thermal tether through obstacles.
- Use robust obstacle penetration resolution to stop clustered phasing.
- Improve state/resource/impact feedback where practical.
Do not erase the original result when behavior changes. Mark subsequent logs as a corrective revision.
Corrective revision 2 is now implemented. Calm and Breach share wave progression, calibration cadence, population, layout changes, and physical laws; only pursuit and contact damage differ. It also fits the game inside the viewport with an independently scrolling sidebar, blocks thermal transfer and kinetic discharge through obstacles, strengthens obstacle penetration resolution, clarifies brittle/unstable impact consequences, and provides click-to-move plus pointer-selectable transfer modes. Revision 2 logs include `prototype_revision: 2`.
Real-browser validation passed at 1920×1080 and 1365×768. In both sizes the arena remained wholly visible and the sidebar remained independently scrollable. Browser input tests confirmed that click-to-move changed the player's later logged position, Extract and Inject buttons emitted the expected mode changes, Breach switching worked, a reachable target produced inspection feedback, a wall-occluded target did not, and all new telemetry carried revision 2. JavaScript syntax validation also passed.
### Current test intent for corrective revision 2
This is not primarily a test of whether bug fixes make the game more pleasant. It is a matched comparison intended to reduce uncertainty around H08 (pressure), H13 (instrumental stakes), and H15 (coupled state tradeoffs).
Highest-value observations:
- Whether Calm receives meaningful play now that it looks and behaves like a complete run rather than a sandbox.
- Whether the player still conditions heat extraction on position, remaining enemies, and available fuel when the through-wall tactic is removed.
- Whether Breach is preferred because pursuit creates interesting context, because it adds action intensity, or merely because Calm lacks urgency.
- Whether the same wall-blast/conversion strategy immediately dominates both variants despite the corrections.
- Whether the player voluntarily tries injection, unstable propagation, collision arrangements, ramming, or another unrequired tactic.
- Whether chosen conversion growth is itself the continuation motive; revision 2 deliberately preserves the old upgrade scaling so the corrected comparison changes fewer variables.
Do not treat a longer Breach session by itself as proof that pressure is fun. Compare tactical variety, voluntary experiments, adaptation, and stated desire to continue. Conversely, a short Calm session is not automatically rejection of low-pressure play: it could still lack a valued activity despite equivalent progression.
The player's new mouse means input-device friction should no longer be used as the default explanation for later behavior. The pointer-only additions remain available but are not the central independent variable.
## Experiment 002 Corrective Playtest
Revision-2 log: `JSONL/conversion-breach-bb30ddfc-8e4f-4e18-bb7e-22235266d9ed.jsonl`. Detailed analysis: `experiments/002_conversion_breach/results/bb30ddfc-analysis.md`.
The session lasted about 19:39: 2:55 in Calm and 16:44 in Breach. Calm defeated 12 constructs and reached wave 2. Breach defeated 195 and reached wave 29. The matched comparison weakly supports pressure as a continuation driver, but not as the cause of deeper fun.
Mouse input measurably changed behavior. Breach calibration choices were capacity 33, conversion 24, transfer 8, versus the first run's conversion-heavy pattern. There were 85 Breach injection tethers and 16 unstable releases. Do not claim the input complaint was merely superficial.
Nevertheless, the decision structure converged. Wall impacts produced 176/207 total defeats. Final Breach statistics were capacity 1,255, transfer 220.247, conversion 374.144. A small/default heat amount became enough to erase the field. The player said they could not add much beyond the prior report and wanted the next experiment.
Interpret this as progression-driven continuation plus short-run strategy crystallization, not as either clear enjoyment or a failed playtest. The player continued for a long time, but their strategy did not need to change qualitatively and they expressed no further curiosity. Duration and action count are behavioral evidence, not a fun score.
Minor remaining medium issues: Shift-right-click could invoke the browser context menu and clicking UI text could select it. The player explicitly regarded these as medium artifacts. Do not spend another turn polishing 002 unless later comparison requires it.
### Selected Experiment 003 direction
Build **Rig Trials**, an exploratory pivot that tests H06, H12, and H17. The player modifies a compact physical rig and directly pilots it through several qualitatively different requirements. There is no numerical power progression. All parts are available from the start. Requirements should make physical layout, exposure, adjacency, mass, heat, or utility placement matter, while knowledge about each part transfers.
The crucial distinction from 001 is that construction changes the capabilities of the body the player directly controls; it does not program an autonomous agent. The distinction from 002 is that successive problems should demand recomposition instead of amplifying one extermination loop.
Risks to watch:
- a symmetric universal rig solves every trial;
- construction becomes a conventional loadout menu with cosmetic placement;
- course execution dominates build reasoning;
- each trial prescribes one obvious part, turning adaptation into checklist work;
- rebuilding from scratch is tedious; preserve the existing rig between trials and make edits cheap;
- crude vehicle controls can invalidate the test.
Strong evidence would be voluntary rebuilds based on observed physical behavior, carrying a useful subassembly across trials while changing the overall layout, or testing a construction idea not demanded by the displayed pass condition. Completion alone is weak evidence.
### Experiment 003 implementation and validation
Experiment 003 is implemented in `experiments/003_rig_trials/`. It is dependency-free HTML/CSS/canvas JavaScript. Run `./run.sh` from that directory.
The player receives a working ten-of-twelve-budget starter rig: four cardinal drives and one upward-facing tractor around a fixed core. A five-by-five lattice supports Drive, Tractor, Sink, Ward, Ballast, and Frame modules. Orthogonal connectivity is required. Drive exhaust can be blocked by a module behind its force arrow; tractor faces require open space; sink output depends on exposed sides and core adjacency; upward leading wards reduce furnace input; ballast disproportionately reduces wind acceleration. Edits preserve the conceptual rig but restart only the current field attempt. Trials wait for an arena click after edits/switches so the player is not blown around while working.
Trials:
- Haul: attach and return two fragments; attached cargo adds mass.
- Furnace: cross a heat curtain before 100°; drive use adds heat, and speed, cooling, or leading wards can address the requirement.
- Gale: stabilize three spatial beacons as the wind direction changes; outer-boundary contact restarts the attempt.
All trials and parts are available from the beginning. Completion marks a trial but grants no power. The build persists across trial switches.
Validation evidence:
- `node --check` passes.
- The full interface was visually inspected in Chromium at 1672×976, matching the latest player's exported viewport.
- Browser input tests produced module placement/removal, rotation, trial switching, drive input boundaries, attempt resets, failures, completion, snapshots, and revision-1 telemetry with no runtime exceptions.
- A drive placed immediately behind the starter's right-facing drive blocked that original exhaust. This was correctly shown as one invalid module and produced no extra right thrust.
- The untouched starter thermally collapsed in Furnace at x≈0.688 and 100.3°. A parallel, unblocked second right drive completed Furnace after curtain tuning. This establishes that the starter is not universal and that a speed-focused revision works.
- Haul rejected a misaligned tractor activation and accepted a corrected, close approach from the exposed face. Attachment applied the extra cargo mass. An open-loop headless return route did not complete because momentum carried the rig into course geometry; do not claim that automated validation completed Haul.
- Before the attempt-engagement gate was added, an unattended starter rig was repeatedly blown out in Gale. After the gate, trial switching/editing remains stationary until the canvas is clicked. Full Gale completion was not browser-automated; its forces and path were reviewed for controllability, with ballast intentionally improving the drive-to-wind ratio.
Interpretive caution: the builder explicitly lists module laws to prevent interface confusion. This tests application and adaptation more than blind discovery. If it feels like prescribed part-swapping, do not fix it merely by hiding descriptions; determine whether placement had enough combinatorial consequence.
## Experiment 003 First Playtest
Log: `JSONL/rig-trials-e1fa0f4d-9828-431e-8158-ddf826a41770.jsonl`. Detailed preliminary analysis: `experiments/003_rig_trials/results/e1fa0f4d-analysis.md`.
The player completed all three trials in about 6:47 and called the experience “pretty boring.” They invited focused questions. Do not mistake complete coverage for positive engagement.
Haul occupied about 4:29 and showed 16 attachments, 11 releases, 12 failed tractor activations, three deliveries, five collisions, and two thermal collapses. The latter is a design contamination: global drive heat caused failure in a trial whose briefing did not foreground heat. The successful Haul build simply added a downward tractor to the starter.
Furnace occupied about 34 seconds. After one thermal failure, the player rapidly replaced most modules with sinks, leaving one right drive and five sinks. It passed in 7.9 seconds. Gale occupied about 85 seconds; after reconstructing cardinal drives it passed in 32.9 seconds with no ballast. These may be prescribed counters rather than meaningful recomposition.
Ask three questions before choosing 004:
1. Was Haul boring mainly because driving/tractor alignment was awkward, or was the task uninteresting even when it worked?
2. Did Furnace/Gale rebuilding contain a decision they cared about, or merely announce which parts to stack?
3. Did lattice placement ever seem consequential, or did it feel like a slower equipment menu because module counts dominated geometry?
### Experiment 003 player answers and final interpretation
The player said controls and tractor alignment were not awkward. They initially overlooked Sinks and interpreted heat as a limited movement bar, so they added a second tractor and juggled both fragments home. This unintended workaround was still boring. Knowing about Sinks would only make Haul easier.
Furnace was solved exactly by reducing the rig to the core, forward Drive, and Sinks. Gale was solved by restoring the default rig. Placement did not feel important; re-adding/orienting Drives was only mildly annoying.
Therefore the primary failure was not input friction. Parts were scalar counters/required verbs, geometry was mostly cosmetic, and requirements announced obvious full-loadout substitutions. Requirement change without compositional depth is not useful adaptation.
The two-tractor workaround revises an earlier inference: voluntary experimentation is not sufficient evidence of fun. It can be instrumental compliance under confusion. Look for valued questions, understandable surprise, desire to see consequences, and chained experiments—not deviation alone.
Selected Experiment 004 direction: **Invariant Rooms**, a clean exploration of H03/H18. Avoid construction, upgrades, enemies, and disclosed solution rules. Give the player a small directly controlled spatial environment with an unfamiliar but deterministic law. The first room should make the anomaly observable, a later room should require transfer of the learned model, and an optional situation should permit a self-directed prediction. The law must be explainable from feedback rather than arbitrary one-off room tricks.
### Experiment 004 implementation and validation
Experiment 004 is implemented in `experiments/004_invariant_rooms/`. The player-facing title is **Paired Rooms** so the word “invariant” does not prime a particular analysis. It is dependency-free HTML/CSS/JavaScript and uses the shared playtest server.
The hidden stable law is opposed movement with independent wall blocking: Self moves with the command, Echo moves against it, and each stops separately at walls. Ordinary movement preserves the pair midpoint; asymmetric blocking moves it. Simultaneous animation, colored trails, collision flashes, a pair line, midpoint marker, and displacement readouts make consequences inspectable without stating the law.
The measured sequence has three rooms:
- an open two-command introduction to opposed movement;
- a three-command wall-pinning case that isolates independent blocking;
- an asymmetric transfer room whose shortest unordered solution is `UUUUURRDR`.
Breadth-first search verified the final room and its nine-command shortest route. A fourth open chamber has no sockets or completion state and records optional post-sequence movement. Undo and room reset are immediate. Every command logs before/after state, displacements, blocking, midpoint change, history depth, and whether it occurred in the open chamber.
Validation passed for JavaScript, shell, and Python syntax. Chromium was inspected at the player's 1672×976 viewport. Browser-driven input completed all required rooms via their intended solutions, reached the open chamber, produced 36 pre-save events, and a real Save button click wrote a valid 37-line JSONL file through the server. The generated validation log was then removed so `JSONL/` contains only player data.
### Direct JSONL saving
The player explicitly requested that logs stop requiring manual file moves. `tools/playtest_server.py` now serves prototypes and accepts validated, single-session JSONL at `POST /api/playtest-log`, writing atomically to repository `JSONL/`. All experiment `run.sh` scripts use it. All six prototype save buttons POST directly and fall back to browser download if the endpoint is unavailable. The endpoint was tested with the 653-event Experiment 003 log against a temporary directory; the saved file matched byte-for-byte. Real Chromium Save button clicks were also verified in Experiments 003, 004, and 005.
### Experiment 004 first-run telemetry, report pending
The player completed the run and saved `JSONL/invariant-rooms-a7ad2d34-cba4-43a5-942e-1f7dc1ad0295.jsonl`. Preliminary analysis is in `experiments/004_invariant_rooms/results/a7ad2d34-preliminary-analysis.md`.
The run lasted 161.2 seconds. First Pair was solved optimally in two moves. One Holds took five moves (`LRRRR`). Transfer took 94 attempted commands / 80.9 seconds rather than the nine-move optimum, with broad state exploration and no undo/reset. Most importantly, the player paused 14.1 seconds on entering the explicitly non-required open chamber, then made 88 attempts over about 46 seconds and ended with both occupants exactly overlapped at `(6,7)`.
This is the project's strongest behavioral evidence of a possible self-generated goal, but do not label it fun or curiosity yet. Ask whether overlap was intentional, whether Transfer involved a predictive model or input search, and whether achieving overlap generated satisfaction or a next question.
### Experiment 004 player report and interpretation
The player never felt able to predict the system. Rooms 1 and 2 were obvious without thought; Transfer was too large a complexity jump and was completed mainly by guessing around locally promising states. Only after replay/inspection did the player realize the yellow midpoint cross could have been used as a planning reference.
The open-chamber activity was intentional experimentation, but not the inferred overlap goal. The player wanted to see whether the yellow cross could be moved against a wall. The open chamber is what made them notice the cross. Reaching the result was not satisfying.
Therefore 004 both found and rejected a simplistic inference: a self-directed question occurred, but it did not generate fun. The scaffold also failed because trivial early solutions bypassed the model and the final room suddenly demanded it. More intermediate rooms could repair transfer measurement, but should not be the immediate next build; the self-chosen abstract marker experiment already suggests that predictive mastery without valued consequence is insufficient.
Selected next direction: test H19 with a direct, compact, recoverable-chaos activity. Use a coupled physical verb that affects salvage, threats, and the player's safety together. Avoid construction, hidden puzzle laws, unlocks, and multiplicative stat growth. Changing field states should create opportunities for improvised saves and weaponization, while failures should degrade the situation rather than immediately end the run. The question is whether systemic knowledge becomes rewarding when it changes agency in motion.
### Experiment 005 implementation and validation
Experiment 005 is implemented in `experiments/005_sanctuary_wake/` as **Sanctuary Wake**. It is a three-minute direct-action rescue shift. The keeper moves with WASD and projects a radial pull or push field. That one field indiscriminately affects escape pods, raiders, and wreckage. Pods repair the sanctuary when rescued, raiders capture pods and damage the sanctuary, and fast wreckage can break raiders or damage the sanctuary. Field charge limits continuous use. Sanctuary failure triggers a repulsive emergency vent and restores partial integrity rather than ending play.
All laws are player-facing; this is not another hidden-rule test. Spawns change from Quiet Wake through Scavengers, Debris Front, and Convergence using a session-seeded problem sequence. There are no upgrades. The player can end early. At the end, optional overtime explicitly grants no new tool or power.
Syntax checks pass. The 1672×976 layout was visually inspected before and after the start overlay; the full instruction/metric sidebar now fits without requiring scroll at that viewport. Browser-driven validation covered movement, pull/push field sessions and depletion, stage changes, object spawning, keeper impacts, pod capture, sanctuary impacts, early ending, overtime, and direct JSONL saving. A focused real-physics route moved to an initial pod, pulled it while returning to the sanctuary, and generated `pod_rescued`. The saved validation log was removed afterward. The fast-wreckage/raider collision branch was reviewed and is mechanically reachable but was not produced by the short browser route; do not claim a live validation of that particular tactic.
### Experiment 005 first-run telemetry, report pending
The player saved `JSONL/sanctuary-wake-69cdaf46-11e4-498f-abe0-ee791a81fad4.jsonl` and described the game as “kind of sucked.” Preliminary analysis is in `experiments/005_sanctuary_wake/results/69cdaf46-preliminary-analysis.md`.
They actively completed the exact 180-second measured shift, then saved without overtime. The final result was 6 rescued, 18 lost, 17 raiders broken, no breaches, and 41% sanctuary integrity. There were 89 field sessions (60 pull, 29 push); 53 ended by charge depletion. Continuous movement and field inputs rule out passive waiting, but do not imply enjoyment. Sanctuary damage only began at 150 seconds, and the intended recoverable breach never happened.
Ask whether the timer prolonged play after the desire to stop, whether any of the 17 wreckage kills were intentional, whether shared influence ever created a valued decision rather than clutter, and whether charge depletion felt like a resource choice or an interruption.
### Experiment 005 player report and final interpretation
The player wanted to stop almost immediately. Instructions were understood, but the activity did not seem interesting. Controls were fine and the chaos remained readable. They did not care about the rescue outcome.
A few wreckage kills were deliberate. As wreckage accumulated, almost any field use risked pushing pods into raiders or wreckage into the sanctuary. The state felt impossible to manage, not difficult to perceive. Charge depletion was annoying and made an already impossible situation harder rather than creating resource strategy.
Do not respond by merely reducing rocks, increasing charge, or adding a stronger rescue narrative. Immediate disinterest predates overload, and nominal rescue stakes produced no care. The later structural failure was that indiscriminate coupling destroyed selective leverage. Experiment 005 also mislabeled accumulating irreversible loss as recoverable chaos; mistakes shrank the action space rather than becoming tractable new situations.
Selected next direction: test H20 with assertive, targeted action. Use a compact combat/movement arena with a few expressive verbs, local resettable failure, and no upgrades. Avoid timers as the reason to continue. The player should create and exploit specific interactions rather than maintain a global state. Only add progression or engineering if the base action earns it.
### Experiment 006 implementation and validation
Experiment 006 is implemented in `experiments/006_breakline/` as **Breakline**. It provides three targeted verbs: a short aimed strike that knocks and reflects hostile bolts, a tether that pulls light enemies but pulls the player toward heavy anchors, and an invulnerable offensive dash. Fast enemy-on-enemy collisions damage both bodies. Health regenerates after a damage-free delay; defeat restores the current arrangement after 0.8 seconds.
Drift, Crossfire, Weight, and seeded Remix are all available from the start. Nothing unlocks, there is no timer or resource meter, and clearing an arrangement grants no stats. Telemetry records every verb and result, reflections, collision damage, kills and cause, enemy attacks, player damage/defeat, switching/restarts, periodic state, completions, and saves.
JavaScript and shell syntax checks pass. The complete sidebar and arena were inspected at 1672×976. Browser-driven validation produced light-enemy tethering, heavy reverse-tether movement, strike damage, offensive dash damage, projectile reflection and reflected-projectile damage, enemy kills, player damage, arrangement switching, and direct JSONL saving. After a final introductory potency adjustment, a real input route cleared Drift in 9.97 seconds with six kills, a five-kill clean chain, four strike hits, two dash hits, and no defeat. The generated validation JSONL was removed. Collision damage is covered by the shared physics branch but was not deliberately produced in the final clear route.
## Playtester Epistemology Guardrail
The player explicitly reminded the project that they are an imperfect playtester. Preserve this distinction in every interpretation:
- behavior is observed evidence;
- felt experience is a high-value report;
- the player's explanation is one causal hypothesis;
- predictions about unbuilt variants are lower-confidence evidence;
- the designer must maintain alternate root-cause explanations.
For Experiment 001, “I stopped almost immediately” and “I did not refine anything” are strong observations. “A field-aware AI version would still be tedious” is a useful prediction, not a result. Plausible roots include arbitrary goals, low expressive depth, low knowledge leverage, lack of tradeoffs, no downstream consequence, construction friction, static completion, or indirect control. Enjoyment of Factorio, Minecraft, and Robocraft is counterevidence against a blanket claim that automation is unfun.
## Project State
The only original repository file was `codex_game_design_research_plan.md`. It defines a research program, not a request for a final game. The immediate instruction was to create lightweight research records and build only Experiment 0, then stop for a player playtest.
Created:
- root `README.md`;
- `research/current_model.md`;
- `research/hypotheses.md`;
- `research/experiment_index.md`;
- this handoff;
- `experiments/000_magic_language/` with experiment notes, runnable prototype, and results directory.
The prototype is pure HTML/CSS/JavaScript with no dependencies. Run `experiments/000_magic_language/run.sh`, then open `http://localhost:8000`.
## Question Actually Being Tested
The first-order question is:
> Does manipulating a consistent, programmable-feeling magical system generate a curiosity chain strong enough that the player voluntarily forms and tests a question not required by the objectives?
This is intentionally narrower than “is this a good game?” and precedes tests of pressure, world consequences, run structure, co-op, randomness, progression, or module libraries.
The strongest positive evidence is not objective completion. It is voluntary modification of a working network, especially if an initially surprising result becomes understandable and suggests a second experiment.
The important competing interpretations are:
1. The abstract engineering interaction is intrinsically interesting.
2. The laws have potential, but abstract instruments provide insufficient purpose; output must affect a world or entity the player cares about.
3. The interaction is understandable but feels like familiar circuit assembly rather than discovery.
4. The core could be interesting, but construction or visualization friction prevents a fair test.
5. The system is below the minimum expressive complexity needed for emergence; adding one carefully chosen domain or operation might be warranted, but adding content indiscriminately would not be.
Do not classify a negative reaction until distinguishing these explanations.
## Why This Prototype Shape Was Chosen
I chose a visual node-and-wire “flux bench,” rather than a terminal language or character-controlled game, because Experiment 0 specifically needs readable feedback, timing, phase, spatial causality, and debugging. It avoids confounding the test with movement, combat, authored levels, procedural content, or world fiction.
The prototype attempts to embody “magic as executable natural law” while keeping the first implementation small. It is not intended as the final magic system or final interface.
The central potentially generative interaction is feedback. A closed path returns attenuated old flux. Because flux has vector phase and every link costs a tick, loop length and phase rotation determine whether returning flux reinforces, cancels, filters, or oscillates against new flux. This is intended to yield a legible “what if?” interaction rather than a recipe.
## Implemented Laws
1. Flux is a two-dimensional vector. Vector direction is called phase and vector length is strength.
2. Multiple arrivals at the same node on a tick combine by vector addition. Aligned flux reinforces; opposed flux cancels; angled flux produces an intermediate phase.
3. Every directed link delays propagation by one simulation tick, retains 92% of strength, and splits a node's output evenly across all outgoing links. This preserves a meaningful conservation constraint and makes fan-out a cost rather than free duplication.
4. Closed paths recirculate old flux. Loss keeps ordinary loops bounded, while periodic injection can sustain or build a pattern.
5. Operations are available from the beginning and transform flux consistently.
Implemented operations:
- **Confluence:** passes the vector sum unchanged.
- **Turner:** rotates by -90, +90, or 180 degrees.
- **Echo:** delays by 16 additional ticks.
- **Vessel:** adds arrivals into a 1%-per-tick leaky vector store, releases the whole store when its configurable threshold is crossed, then empties.
- **Threshold:** passes only arrivals at or above a configurable strength.
- **Polarizer:** projects flux onto a configurable signed axis and dissipates the perpendicular component.
Fixed phenomena:
- Dawn well: strength 1 east every 2 ticks.
- Tide well: strength 1.15 north every 3 ticks.
- Dusk well: strength 0.9 west every 5 ticks.
These sources are deterministic to keep the baseline controlled. Later experiments may randomize constraints, but random part availability is deliberately excluded.
## Observations and Their Purpose
The UI calls these “observations,” not quests, because they do not grant rewards or unlock parts.
- **Continuity:** deliver strength greater than 0.18 on at least 10 of the latest 12 ticks. This asks whether intermittent input can be made persistent and makes feedback/timing relevant.
- **Impulse:** produce one arrival of strength at least 2.40. This makes accumulation and constructive reinforcement relevant.
- **Reversal:** send two nontrivial, nearly opposite phases to one lens within 10 ticks. This makes phase transformation or coordinating sources relevant.
There are multiple valid approaches. The intent is to give enough direction to learn the interface without prescribing a single tutorial sequence. Completing all three produces only a message asking the player to alter a working network to answer their own question.
Potential weakness: the observations may still behave like conventional puzzle objectives and cause compliance rather than curiosity. If the player stops immediately after them, ask whether they were satisfied/done, bored because they had mentally solved the system, or never developed an unrequired question.
## UI and Instrumentation
The bench provides draggable nodes, directed curved links, animated link color/width based on current phase/strength, per-node vector readouts and arrows, pause/run/step/tempo controls, three two-component oscilloscope histories, delete/reset controls, and short visible law descriptions.
The scopes show x as a solid trace and y as a dotted trace. A current change hides inactive component traces so a quiet scope is not mistaken for an active colored signal.
The telemetry stays intentionally local and lightweight. Events are appended as JSON Lines in memory and mirrored to local storage under `resonance-bench-last-jsonl`. The **Export JSONL** button downloads them. Events currently cover:
- session start/unload and visibility;
- nodes added, moved, configured, and removed;
- links added/removed, including whether a new link closes a cycle;
- simulation pause/resume/manual step and tempo changes;
- vessel releases;
- individual and total observation completion;
- periodic 20-tick network snapshots;
- bench resets and exports.
Telemetry is evidence about behavior, not a fun score. Particularly useful later: construction after all observations, cycle creation, rewiring/removal, parameter changes, time between changes, and continued activity after a working state.
## Validation Already Performed
- `node --check experiments/000_magic_language/prototype/app.js` passes after correcting one escaped label string.
- A real headless Chromium load successfully initialized all palette entries, fixed nodes, scope cards, simulation ticks, and live node visuals.
- A 1440×1000 screenshot was visually inspected. The desktop layout is clear, with sources left, lenses right, central build space, scrollable research sidebar, and scopes below.
- A 1024×768 screenshot was visually inspected. The right-side lens x-position was corrected to keep lenses within the work area at medium widths. There may still be slight vertical crowding at this viewport; this was the exact check being continued when the user accidentally interrupted the turn.
- Browser-driven construction tests used the actual click handlers and manual Step button, not direct simulation calls.
- Focused Continuity test: Dawn → Confluence, with Confluence → itself and Confluence → Continuity lens. Continuity completed by tick 35; latest displayed signal was about 0.25 east.
- Focused Impulse test: Dusk → Vessel → Impulse lens. Impulse completed within 80 ticks.
- Focused Reversal test: Dawn and Dusk directly connected to Polarity lens. Reversal completed within 30 ticks.
- Telemetry in browser local storage contained session, node, link, manual step, periodic snapshot, vessel release, and observation completion events.
One combined test only completed Reversal because the same Dawn source was split across several outgoing links. This is expected under the conservation law and is useful evidence that fan-out has a real cost; it was followed by isolated reachability tests rather than weakening the law.
## Historical Checklist Before Experiment 000 Handoff
This checklist was completed before the first playtest and is retained only as implementation history.
1. Finish medium-height node-overlap inspection and adjust vertical placement if necessary.
2. Re-run JavaScript syntax check.
3. Confirm `run.sh` is executable and serves the prototype.
4. Stop temporary validation server/browser processes.
5. Mark the working plan complete.
6. Tell the player how to run it and ask only the few feedback questions that distinguish hypotheses.
Do not implement Experiment 1 before the player's playtest.
## Current Concerns and Interpretive Risks
### It may be too recognizably “circuits with fantasy names”
Phase vectors, links, gates, delay, and scopes map closely to signal processing. If the player says it feels like ordinary electronics, do not solve that by renaming parts or adding particles. Determine whether they dislike the abstraction, already know its consequence space too well, or want physical domains (temperature, momentum, matter, fields) with richer cross-domain effects.
### The law sheet may reveal too much
The UI openly states that loop timing and rotation control reinforcement. This trades mystery for experimental legibility. The test is not whether the player can discover the existence of vector addition; it is whether known stable laws create their own questions. Still, if exploration feels over-explained, a controlled variant could move some consequences from instruction to observation without permission-gating operations.
### Abstract instruments may be emotionally sterile
The player may reason competently yet not care about satisfying a scope. That supports testing the same underlying system with observable world consequences, not immediately discarding the laws.
### Objectives might over-direct behavior
The three observations mention behavior but not exact constructions. Still, they cue continuity, accumulation, and polarity. The primary evidence must remain what happens after or outside completion. Avoid treating completion speed as enjoyment.
### Feedback may become the universal answer
The prototype intentionally makes loops powerful enough to notice. If every observation collapses into “add the correct loop,” that is early strategy crystallization. Do not reflexively nerf loops; first determine whether varied timing/phase creates multiple architectures or whether one self-loop template dominates.
### Unlimited component placement removes resource tradeoffs
This is deliberate for Experiment 0. It isolates intrinsic system manipulation. Resource or interface budgets belong in later controlled variants only if the base interaction earns them.
### No persistence for the constructed bench
Only telemetry persists locally; the actual graph resets on reload. This is acceptable for a 530 minute disposable prototype. Do not build a save system unless repeated testing demonstrates the need.
### Browser download logs require the player to export
The local-storage mirror provides limited recovery, but Codex cannot automatically receive the player's log. Ask the player to share the exported JSONL if practical, but do not make feedback contingent on it.
## Feedback to Request After Play
Ask only these initially:
1. When did you first want to stop, and was it because you had solved the system, wiring became work, or you simply felt done?
2. What, if anything, did you try that no observation required?
3. What did you want to try next, if anything?
Also request the exported JSONL, but free-form comments are higher-value than forcing a format. Do not ask for a numeric rating.
## How to Interpret Likely Responses
- **Voluntary experiments plus a new systems question:** support H01/H02/H03. Identify the exact interaction that caused the chain before choosing a variant.
- **Objectives completed, immediate stop, no new question:** possible intrinsic weakness or rapid crystallization. Ask what complete mental model/answer they formed.
- **Interesting reasoning, but no reason to care about output:** prioritize a tightly controlled world-consequence variant.
- **Wanted to experiment but wiring/debugging was irritating:** repair interface/legibility before changing the system; otherwise the comparison is contaminated.
- **Wanted another physical quantity/domain:** determine the missing interaction the player imagined. Add the smallest domain interface that tests that need; do not inflate the component list.
- **Pressure/combat/co-op requested:** record it, but avoid adding it unless the player says the calm engineering itself was already promising or a comparison specifically needs pressure.
- **A single dominant loop solved everything:** design an adversarial requirement against whole-solution reuse while preserving the learned loop knowledge.
## Candidate Next Experiment Selection
Do not precommit. Choose based on the highest-information uncertainty after feedback:
- If the core is promising and adaptation is the uncertainty: static requirement versus one changed requirement, same components and source conditions.
- If purpose is the uncertainty: same system and objective values, but the lens visibly powers/protects/changes a small world entity.
- If the interface masks the core: make a corrective revision to 000, not a nominal new experiment.
- If the core yields no curiosity even when understood and easy to use: explore a substantially different substrate rather than adding content.
The repository plan suggests dynamic constraints next, but explicitly says experiment order must follow information gain, not a fixed roadmap.

187
research/current_model.md Normal file
View file

@ -0,0 +1,187 @@
# Current Preference Model
Status: **updated after Experiment 008's under-length first run; corrective revision 2 is validated and ready**
The leading long-term theory remains that fun may come from learning a compact set of consistent laws, constructing a system from them, and discovering consequences that create further self-directed questions. Experiment 000 did not provide positive evidence: its permissive abstract observations were solved in about three minutes, produced no voluntary experimentation, and did not make phase, timing, or cyclic behavior perceptible or necessary.
After Experiment 000, the working theory was:
> A law is not meaningfully legible because it is documented or rendered. It becomes legible when predicting it is necessary to explain and control an outcome the player can perceive and care about.
Binary completion without a consequential quality gradient gives little reason to improve a merely adequate system. A technical system also needs an open consequence space: after solving the stated problem, changing the machine must plausibly create another behavior worth seeing.
Experiment 001 made output spatial and continuous, but the player wanted to stop almost immediately. Moving a familiar through the field felt like brute-force steering through an indirect interface. The player predicts that field sensors or a more capable controller would still feel like tedious manual AI programming. That prediction is evidence about their current expectation, not a tested result; it should lower the priority of that extension without being treated as proof against automation generally.
The newest model is:
> In both tested prototypes, engineering had little value beyond reaching an arbitrary terminal condition, so construction and refinement felt like work without leverage.
The player may need to participate directly in an intrinsically engaging ongoing activity—combat, movement, cooperative crisis, or recoverable chaos—where systems knowledge is a means to greater agency, power, and improvisation. In that framing, optimization has an external purpose rather than asking the player to care about elegance for its own sake.
This is not permission to hide a weak system beneath action spectacle. A controlled calm/pressure comparison can test whether direct manipulation has its own interesting interactions and whether pressure helps or harms them. It is an exploration probe, not the newly assumed final direction.
Experiment 002 produced the first sustained engagement and voluntary tactical revision. The player stayed for about 16 logged minutes, cleared ten Breach waves, adapted extraction based on position and remaining fuel, concentrated upgrades into conversion efficiency, and tested an unimplemented ramming hypothesis. This supports a more specific candidate:
> Interest may arise when one operation changes several strategically relevant domains at once, creating a stateful tradeoff whose value depends on context.
Extracting heat was simultaneously resource acquisition, enemy slowing, brittle preparation, and depletion of future available energy. The player learned not to maximize it blindly. This is more informative than the action theme alone.
The first result was heavily confounded. Corrective revision 2 made Calm and Breach matched complete runs and fixed the major interface/physics defects. The second session lasted about 19:39, including 2:55 in Calm and 16:44 in Breach, but did not produce a new reported layer of interest. The player is ready to move on.
Revision 2 clarifies the failure shape. Easier mouse input broadened upgrade choices and injection use, but it did not broaden the strategy. Wall-directed conversion caused 176 of 207 defeats. All three upgrade dimensions eventually amplified the same throughput loop, conversion reached 374×, and the starting scale of heat became sufficient to erase later waves.
The updated model is:
> Instrumental stakes and rapid power can sustain activity, but continued input is not the same as continued reasoning. A system loses value when every improvement amplifies one complete answer rather than changing which answer fits the current problem.
Pressure probably increases persistence, but it has not yet been shown to create curiosity. The next experiment should test changing qualitative requirements and physical construction without autonomous programming or escalating numerical power.
Experiment 003 tested that proposal and was boring despite completion of all three trials in about 6:47. The player explicitly ruled out awkward controls as the main cause. Haul, Furnace, and Gale each collapsed into an obvious part response, and physical placement barely mattered. Requirement changes produced loadout replacement, not adaptation of a meaningful architecture.
The player nevertheless invented a two-tractor cargo-juggling workaround after overlooking Sinks and misunderstanding the heat bar. This yields an important methodological correction:
> Voluntary experimentation shows agency and problem solving, but it is not sufficient evidence of fun. The experiment itself must generate a question or consequence the player values.
The next high-value uncertainty is legible surprise. No experiment so far has cleanly required the player to infer an unfamiliar stable law and transfer that model. All have either disclosed the relevant laws, admitted obvious brute force, or offered direct counter-parts.
Experiment 004 tested that uncertainty and split it into two results. Rooms 1 and 2 were obvious without requiring a predictive model. Transfer jumped directly to a state-planning problem, so the player guessed through 94 commands and never felt able to predict the system. The yellow midpoint cross was visible but did not become meaningful until the open chamber. Rendering state is not the same as making it strategically legible.
The player then formed a genuine unrequired question: could the yellow cross be moved against a wall? They spent about a minute testing it. This is the cleanest self-directed experiment yet, but answering it was explicitly not satisfying. Therefore curiosity-like behavior is not sufficient, even outside an authored objective.
The updated leading model is:
> A systemic question is more likely to matter when its answer changes the player's agency inside an already valued, ongoing activity. An abstract answer, optimized marker, or completed predicate is not enough by itself.
This does not prove that pure discovery or spatial puzzles cannot be fun for the player; Experiment 004's transfer scaffold was flawed. It does lower the priority of polishing isolated rule-discovery puzzles. The higher-information next probe is direct improvisation under recoverable, changing circumstances, with systemic verbs that affect useful and dangerous objects together and no runaway stat progression.
Experiment 005 tested that probe and failed almost immediately at the motivational level. The player understood the instructions but did not find the rescue activity interesting and did not care what happened. They nevertheless actively completed the three-minute shift, reinforcing that duration and input volume cannot stand in for enjoyment.
Later failure had a distinct cause: the chaos was readable but unmanageable. Accumulating wreckage and indiscriminate radial force meant that attacking raiders also threatened pods and the sanctuary. The field's shared consequences did not create fertile tradeoffs; they erased credible good actions. Charge depletion further reduced agency without adding a valued decision.
The updated model is:
> Coupling is valuable only when it creates leverage the player can selectively exploit. If every intervention propagates unavoidable collateral damage, readable emergence becomes paralysis.
Experiment 005 also did not implement “recoverable chaos” successfully. Captured pods disappeared, debris accumulated, and failures shrank the future action space. A truly recoverable mistake should transform into a different manageable problem, not permanent clutter or irreversible loss.
The next high-information contrast is assertive rather than custodial agency: directly create outcomes with targeted movement/combat verbs in a compact field, with local resettable failures and no upgrades. This tests whether the player values expressive action itself before construction, progression, or world-care layers are added.
Experiment 006 tested that contrast and did not find an intrinsically enjoyable base verb. The player cleared all four arrangements and voluntarily replayed two, but the replays were investigations rather than a desire for mastery. Grouping enemies continued the prior “can these overlap?” edge-case test and ended in an accidental six-kill strike. Reflection was noticed late and deliberately revisited because the concept resembled returning a Minecraft ghast fireball, but the player was ready to stop afterward.
Reflection is an especially useful correction to the method. Its concept attracted attention, while its execution did not: aiming at a moving enemy, judging projectile distance, and monitoring other threats competed for focus. Thus discovery, voluntary replay, a familiar analogy, and successful use can all occur without the interaction being fun.
Strike was merely sufficient, tether usually worsened position by pulling danger closer, and dash never became necessary or cognitively available. This lowers confidence that stripped-down assertive action is an intrinsic foundation. It does not establish that action, counters, or dodging are globally unsuitable; it establishes that isolated verbs without a valued trajectory or context have now failed alongside isolated construction, discovery, and rescue.
The strongest remaining cross-experiment candidate is not a single mechanic but a **trajectory of authored capability**. Experiment 002's rapid upgrades sustained by far the most play, but numerical multiplication collapsed into one answer. The player's prior preferences also repeatedly combine direct participation with deliberately chosen builds and dramatic power expression. The next experiment should therefore contrast numerical improvement with qualitative, player-chosen transformations over the same short action substrate. Its purpose is to test whether composing a build and witnessing its consequences supplies value that the bare action lacks—not to assume progression will rescue it.
Experiment 007 implements that comparison as two randomized-order trials. Both use the same low-demand hold-to-fire combat, five fixed fields, health rules, and four exact choices. One trial offers only damage, cadence, and projectile-width amplification. The other offers stacking Fork, Bloom, and Arc effects whose products can interact. The key outcome is whether a choice creates anticipation, causal understanding, or desire for another consequence—not trial completion, choice count, clear speed, or effect volume.
Experiment 007 produced the first clear positive curiosity chain. The player first chose Fork because it sounded interesting, then Arc for the same reason, without predicting their interaction. They initially underestimated and skipped Bloom. After completing both conditions, they replayed mutation with Arc stacked four times to test its isolated strength, found it weak alone, and performed another unsaved run involving Bloom/all three effects. The combined behavior was much better than expected, caused them to revise Bloom from skipped to the strongest option, and made the mutation trial interesting enough to replay.
The numerical condition was predicted to be boring as soon as its menu was read. Its choices were recognized as useful additions to the mutation system, but not as relationships that created interesting differences by themselves. This makes the leading model more specific:
> Chosen growth becomes interesting when components alter the future value and behavior of other components through legible interfaces. Independent improvements increase output; compositional effects create new things to predict, observe, and retest.
Fork, Arc, and Bloom formed a bounded causal network through hit and kill events. The player could understand each component, guess incorrectly about their combined value, and revise their model through play. This resembles the original “deep systems, narrow interfaces” aspiration more closely than the globally entangled chaos of 005 or the obvious counter-parts of 003.
The combined result was not an endpoint. The player performed two further runs to test more builds, including all-Bloom. They reported that watching an aggressively scaling Bloom build erase the field after a few shots was “kind of fun,” and began mapping Bloom to small enemies and Arc to large enemies. This is the first prototype to produce both an explicit positive enjoyment statement and a sustained chain of self-selected comparative experiments.
The updated leading model is:
> Fun is emerging from authored causal composition: exact component choice, surprising but legible cross-effect behavior, conditional strengths against different problem classes, and a visible power payoff that makes revised understanding worth expressing.
This does not establish that the shooter substrate is independently fun. It may be functioning primarily as a fast, legible oscilloscope for a build. That is acceptable at this research stage: unlike the arbitrary scopes in 000, the output creates differentiated agency and a spectacle the player sometimes enjoys.
The next risk is strategy crystallization. Fork+Arc+Bloom may become a universal package, all-Bloom may simply overpower every ecology, or enemy distinctions may prescribe obvious counters as in 003. The next experiment should retain the same causal vocabulary while limiting simultaneous components and presenting changing mixtures/behaviors. It should test whether the player can transfer knowledge and still face genuine composition choices, not whether more upgrade content is automatically better.
Experiment 008 implements that extension. It preserves Fork, Bloom, and Arc, then adds Focus at the repeated-primary-hit interface, Conduit at the secondary-hit interface, and Resonance as a modifier of all secondary effects. Three immediately available expeditions emphasize small bodies, concentrated durable bodies, and spawning mixtures. Corrective revision 2 uses eight fields: four exact choices occur after fields one through four, then the complete four-choice build persists through fields five through eight.
The intended test is not whether more modules or enemy types are more entertaining. It is whether the player carries causal knowledge into a new population, forms a build prediction, encounters a result that revises that prediction, and generates another composition question. Different builds without that reasoning could be obvious counter-loadout work rather than the desired mechanism.
The first 008 run did not provide enough mature-build exposure to answer that question. The player selected three different builds but reports little planning; Conduit-first was a misunderstanding. More importantly, each fourth selection was followed by only one field. Complete builds existed for about 7 seconds in Shoal, 20 seconds in Bastion, and 22 seconds in Brood. The player repeatedly began to see something cool emerge just as the expedition ended.
This is a structural measurement failure, not evidence against the compositional model. Compared with 007, the module vocabulary and populations grew more complex while mature-build observation time did not. Corrective revision 2 adds three fields to every expedition without changing the four-choice budget or module mechanics. The four selections still occur after fields one through four, and the completed build now persists through fields five through eight. Only after this sustained observation can replay, adaptation, universal cores, or obvious counter-loadouts be interpreted.
## Methodological Guardrail: Reports Are Evidence, Not Ground Truth
The player is an imperfect observer of their own enjoyment, as every playtester is. Treat reports of boredom, friction, desire, and behavior as high-value evidence. Treat causal explanations and predictions about hypothetical versions as hypotheses.
Maintain distinctions between:
- **observed behavior:** when they stopped, what they did, what they voluntarily tried;
- **reported experience:** what felt tedious, obvious, confusing, or pointless;
- **player causal theory:** why they believe it felt that way;
- **designer interpretation:** alternate causes consistent with the same evidence.
Do not reflexively implement the player's proposed fix, and do not reflexively eliminate a whole design family because the player predicts a variant would fail. Seek prototypes that discriminate root causes.
Current alternate explanations for the two failures remain:
- The goals were arbitrary and had no downstream value.
- The systems had too little expressive depth or knowledge leverage.
- Solutions contained no meaningful tradeoffs and converged immediately.
- Feedback exposed state but not interesting consequence.
- Construction friction exceeded the value of its output.
- Static completion removed all incentive for refinement.
- Indirect control itself may be a poor fit, but evidence from enjoyed building games argues this depends on context.
The most promising problem shape currently has:
- stable laws and changing circumstances;
- randomness in the problem or opportunity, not in access to exact solutions;
- reusable components and mental models, without reusable whole answers;
- enough external consequence to make engineering choices matter, but not necessarily time pressure;
- legible feedback that makes surprising behavior understandable after inspection.
The leading threat remains **strategy crystallization**, but Experiment 000 failed earlier than that: arbitrary signal handling was competent enough that its central distinctions never entered the strategy. Before testing long-term crystallization, a prototype must make its laws causally discriminating without collapsing into one authored waveform puzzle.
## Evidence From Experiment 000
- All observations completed in approximately three minutes or less.
- No desire to improve or continue after completion.
- No unrequired experiment.
- Source cycles and vector directions were not perceived.
- Random timing and direction would have been sufficient for the observation predicates.
- The player described the available possibility space as limited.
## Evidence From Experiment 001
- Desire to stop was almost immediate.
- Signal construction felt like awkward steering.
- Route completion was brute-force directional movement, not meaningful system reasoning.
- No desire to refine a completed route or try another physical problem.
- The player predicts that adding world-aware inputs would create tedious manual AI programming; this extension was not actually tested.
- Making consequences visible was insufficient; the consequence itself was not desirable enough to optimize.
## Evidence From Experiment 002
- About 16 logged minutes, roughly 14 in Breach; ten complete waves cleared.
- Tactical revision based on temperature, position, remaining enemies, and reservoir state.
- 74 Breach defeats, 67 non-wasted discharges, 184 tether sessions.
- Upgrade choices were strongly instrumental: 19 conversion, four capacity, one transfer.
- Reservoir was nearly empty in 28% of Breach snapshots, yet capacity was not considered the main solution; output per unit was.
- Voluntary ramming experiment, despite player collision damage not existing.
- Later damage avoidance improved dramatically, but learning, runaway power, and wall exploitation are confounded.
- In the first session, Calm versus pressure was untested because Calm lacked equivalent waves and was perceived as a sandbox.
- The trackpad conflict is historical evidence from the first 002 run. The player now has a mouse, so it should not anchor interpretation of the next run.
- Revision 2 gave Calm a matched structure. Calm received about 2:55 versus about 16:44 in Breach, which weakly supports pressure as a continuation driver but not as a source of deeper reasoning or enjoyment.
- Revision 2 produced 207 defeats and 69 upgrade choices without a qualitative strategy change. Duration, waves, and input count must not be used as stand-alone fun measures.
## Unknowns With High Information Value
- Does the coupled heat/resource tradeoff remain interesting after controls and wall exploits are corrected?
- Does an equivalent goal without pursuit/player damage sustain tactical reasoning?
- Was continued play driven by property reasoning, wave progression, runaway power, or conventional action feedback?
- Does bodily participation in collision create useful agency, or only risk and confusion?
- Are transfer/conversion laws interesting when exploited tactically against varied physical properties?
- Does unrestricted access create agency, or remove a useful sense of progression?
- Does modifying a directly piloted physical construction feel different from programming an autonomous controller?
- Do qualitatively changing requirements cause useful recomposition, or merely repeated loadout work?
- Does discovering an initially unexplained but consistent environmental rule create interest when application, not construction, is the interface?
- Does a learned rule transfer to a new situation in a way that feels empowering rather than like solving a short authored riddle?
- Can a simple coupled physical verb support enjoyable improvisation when the field continually creates recoverable problems rather than terminal puzzles?
- Does a self-generated tactic feel rewarding when it protects, rescues, or weaponizes something in motion rather than only changing an abstract measurement?
- Are targeted, assertive verbs intrinsically more engaging than globally coupled rescue/maintenance verbs?
- Does local execution mastery create a reason to replay when there is no build progression or accumulating world state?
- Does deliberately choosing qualitative capability transformations create authorship and anticipation that numerical growth does not?
- Does a build interaction produce a desire to see the next consequence even when the underlying combat is only serviceable?

View file

@ -0,0 +1,13 @@
# Experiment Index
| Experiment | Question | Result | Confidence change | Next question |
|---|---|---|---|---|
| [000 — Resonance Bench](../experiments/000_magic_language/README.md) | Does a consistent programmable-feeling magical system provoke self-directed experimentation? | Failed to provoke curiosity; solved in ≤3 minutes, tolerant predicates made timing/direction irrelevant | H02 down; H03/H07/H10/H11 more salient | Does embodied output make the same laws perceptible and worth manipulating? |
| [001 — Flux Familiar](../experiments/001_flux_familiar/README.md) | Does mapping the same signals to visible motion create a consequential, open behavior space? | Failed almost immediately; felt like brute-force steering, and smarter automation was explicitly predicted to be tedious | H02/H11 down; H12/H13 added | Does direct systemic action under meaningful stakes create curiosity and incentive? |
| [002 — Conversion Breach](../experiments/002_conversion_breach/README.md) | Is directly exploiting transfer, storage, temperature, and momentum interesting, and does pressure help? | Two long Breach runs but little renewed discovery; revision 2 reached wave 29 and numerically collapsed into the same wall-blast answer | H16 supported as persistence, not durable fun; H08 weakly supported for continuation; H15 reduced | Do changing qualitative requirements make direct physical construction worth revising? |
| [003 — Rig Trials](../experiments/003_rig_trials/README.md) | Is modifying a directly piloted construction across different physical requirements enjoyable? | Boring despite all trials completed in ~6:47; obvious counter-loadouts and inconsequential placement, not awkward controls | H06/H17 down as implemented; voluntary experimentation shown insufficient | Does inferring and transferring a surprising stable law create real interest? |
| [004 — Invariant Rooms](../experiments/004_invariant_rooms/README.md) | Does learning an unfamiliar environmental law and transferring it across rooms generate curiosity? | A self-directed marker experiment occurred, but was unsatisfying; transfer was brute-forced because the predictive model never formed | H03 reinforced as a requirement; H18 down as a sufficient source of fun | Can systemic knowledge create valued agency during recoverable chaos rather than an abstract terminal answer? |
| [005 — Sanctuary Wake](../experiments/005_sanctuary_wake/README.md) | Does a coupled physical tool become enjoyable in a changing, recoverable rescue crisis without stat growth? | Wanted to stop immediately; readable chaos became unmanageable, charge reduced agency, and rescue outcomes inspired no care | H08/H19 down as sufficient; indiscriminate coupling identified as harmful | Is assertive, selective action intrinsically more valuable than custodial management? |
| [006 — Breakline](../experiments/006_breakline/README.md) | Are targeted combat/movement verbs enjoyable without progression, timers, or accumulating failure? | No; all sets were cleared, but strike dominated, tether hurt positioning, dash was forgotten, and reflection was interesting to inspect but attention-splitting rather than fun | H20 down as a sufficient cause; voluntary replay again separated from enjoyment | Does rapid, deliberately chosen qualitative transformation make otherwise serviceable action worth continuing? |
| [007 — Catalyst Trials](../experiments/007_catalyst_trials/README.md) | Does rapid qualitative chosen growth create value beyond matched numerical amplification? | Successful: mutation prompted at least four repeat/comparison runs, wrong predictions, revised build knowledge, conditional small/big-enemy mapping, and a qualified fun report; numerical choices were boring | H21/H22 strongly up; H23 added; first clear curiosity chain | Can changing enemy ecologies and limited composition preserve reasoning without prescribing counter-loadouts? |
| [008 — Catalyst Ecology](../experiments/008_catalyst_ecology/README.md) | Can a four-choice compositional system remain interesting across small, durable, and spawning populations? | Revision 2 completed; three distinct mature builds were clearer in use, Bloom+Arc ceased being universal, but no replay occurred; report pending | H23 receives behavioral support but intention/enjoyment remain unresolved | Were builds ecological reasoning or experiment coverage, and did mature exposure create fun or only legibility? |

188
research/hypotheses.md Normal file
View file

@ -0,0 +1,188 @@
# Hypotheses
Confidence is deliberately qualitative until there is playtest evidence.
## H01 — Curiosity chains are a primary source of fun
- **Confidence:** low as a sufficient cause; remains plausible when answers increase valued agency
- **Evidence for:** stated interest in Antichamber, Portal, Minecraft modpacks, engineering, systemic interactions, and becoming powerful through understanding. In 002 the player tested ramming and revised heat-extraction tactics. In 004 they voluntarily tested whether the midpoint marker could be moved against a wall after all required rooms.
- **Evidence against:** Experiments 000 and 001 produced no voluntary experimentation. The unintended workaround in 003 was boring. The clean self-chosen marker experiment in 004 was explicitly not satisfying.
- **Experiments:** 000, 001, 002, 003, 004
- **Unresolved:** Does a question become enjoyable when its answer creates useful power, rescue, or improvisational options in an ongoing activity?
## H02 — Consistent laws plus feedback are intrinsically interesting
- **Confidence:** low for the tested signal language; unresolved for richer physical systems
- **Evidence for:** preference for programming/electronics-like magic and systematic exploitation.
- **Evidence against:** Experiment 000 felt limited; Experiment 001 made consequences visible but still felt like brute-force work. Consistency alone has shown no intrinsic pull.
- **Experiments:** 000, 001
- **Unresolved:** Would the same laws become interesting when their effects are embodied, or is the substrate itself too familiar/limited?
## H03 — Legible surprise is more valuable than raw complexity
- **Confidence:** high as a design requirement; not sufficient as a source of fun
- **Evidence for:** the desired curiosity loop requires an unexpected result that makes sense in hindsight.
- **Evidence against:** no positive surprise occurred in Experiment 000. Experiment 002 created a ramming hypothesis but failed to make its nonexistent result readable. In 004, the visible midpoint marker was not noticed as a planning reference until after the difficult room; early rooms did not require the model and the transfer jump became brute force.
- **Experiments:** 000, 002, 003, 004
- **Unresolved:** Can consequences teach a model at the moment it becomes useful without stopping an ongoing activity for tutorial puzzles?
## H04 — A general-purpose solution becomes boring when it covers too much
- **Confidence:** high prior, untested here
- **Evidence for:** Factorio main-bus example and explicit strategy-crystallization concern.
- **Evidence against:** universal solutions can remain satisfying when embodiment, execution, or social context varies.
- **Experiments:** not yet tested; later scenario variants should attack dominant networks.
- **Unresolved:** What is the strategy half-life of a flux network?
## H05 — Reusable abstractions should remove repetition without removing reasoning
- **Confidence:** medium-high prior
- **Evidence for:** interest in programming, modules, and deep systems with narrow interfaces.
- **Evidence against:** saved modules may encourage whole-answer reuse or make recomposition feel like integration chores.
- **Experiments:** planned only after the base interaction earns another test.
- **Unresolved:** What is the useful granularity of a reusable magical module?
## H06 — Changing requirements may preserve reasoning
- **Confidence:** low when requirements map directly to counter-parts
- **Evidence for:** adaptation can prevent complete solutions from crystallizing.
- **Evidence against:** changes may invalidate work and feel arbitrary or annoying.
- **Experiments:** 003 changed requirements, but Furnace prescribed Sinks and Gale prescribed restoration of cardinal Drives. The player largely replaced the loadout rather than adapting an architecture.
- **Unresolved:** Can requirement changes preserve reasoning when the shared system has real compositional structure rather than one-dimensional counters?
## H07 — Engineering may need consequences in a cared-about world
- **Confidence:** medium; a visible consequence alone was insufficient
- **Evidence for:** many preferred games give construction combat, survival, traversal, or world consequences. Experiment 000's abstract instruments produced no reason to improve once their binary predicates passed.
- **Evidence against:** Experiment 001's physical familiar was only an output to steer and did not create a reason to care about refinement. In 005, explicit pod rescue and sanctuary integrity also produced no care. A genuinely developed world or relationship may differ from prototype labels and counters.
- **Experiments:** 001, 005
- **Unresolved:** Does an observable settlement/ecosystem add purpose or distract from the lab?
## H08 — Pressure may make decisions emotionally meaningful
- **Confidence:** low as a sufficient cause; may still support an already valued activity
- **Evidence for:** enjoyment of recoverable chaos, co-op PvE, action games, and improvisation.
- **Evidence against:** pressure can interrupt careful engineering and obscure whether the base system works.
- **Experiments:** In revision 2, matched Calm received about 2:55 and Breach about 16:44. In 005, the player actively completed a pressured three-minute shift despite wanting to stop almost immediately.
- **Unresolved:** Can pressure heighten an independently enjoyable action system without being mistaken for the source of enjoyment?
## H09 — Problem randomness with solution choice is preferable to part randomness
- **Confidence:** high prior
- **Evidence for:** stated preference for Risk of Rain 2 with Artifact of Command.
- **Evidence against:** unavailable parts can sometimes provoke creative substitutions.
- **Experiments:** 002 offered the same calibration class with exact choice. Revision 1 heavily favored conversion. With easier input in revision 2, Breach choices broadened to capacity 33, conversion 24, and transfer 8, but all choices amplified the same dominant loop.
- **Unresolved:** Which opportunity classes create meaningful choices without prescribing builds?
## H10 — Some systems require a minimum expressive complexity before they are fun
- **Confidence:** medium; adding complexity to the rejected automation activity is now specifically disfavored
- **Evidence for:** feedback, timing, storage, and conversion need interactions before emergence appears.
- **Evidence against:** adding primitives can disguise a weak core and increase learning cost. The effective limitation may have been the observation space rather than the operation set.
- **Experiments:** 000 starts with six transformations and should be judged for missing expressiveness separately from mere lack of content.
- **Unresolved:** Is a richer directly manipulated physical system interesting, or does complexity still become work?
## H11 — A quality gradient is needed to motivate refinement
- **Confidence:** low as a sufficient explanation; quality gradients may be irrelevant without a valued activity
- **Evidence for:** in both prototypes, a merely adequate solution gave no reason to improve; the player explicitly cited absent incentive.
- **Evidence against:** some systems provoke play without scores or optimization gradients; curiosity can motivate variation on its own.
- **Experiments:** 001 provided continuously visible routes and still did not prompt refinement. 002's waves and power growth did prompt repeated refinement, but their causal contribution is confounded.
- **Unresolved:** Does improvement become desirable only when it increases power/survival/options inside an engaging activity?
## H12 — Direct agency is preferable to programming an autonomous solution
- **Confidence:** medium
- **Evidence for:** indirect control was described as awkward steering; the player expects adding AI inputs to make the task more tedious, not more fun. Many preferred games emphasize immediate character control.
- **Evidence against:** Factorio, Minecraft building, Robocraft, and engineering preferences show that indirect construction can be enjoyed in other motivational contexts. The sensor/AI variant was predicted, not played.
- **Experiments:** 002 produced sustained engagement and some tactical adaptation, but its direct control was bundled with pressure, progression, and a richer tradeoff. Revision 2 showed that direct control alone did not prevent strategic repetition.
- **Unresolved:** Is building enjoyable when it changes the capabilities of an artifact the player directly pilots, rather than specifying an autonomous controller?
## H13 — Optimization needs instrumental stakes
- **Confidence:** high after 002, though the kind of stake remains unresolved
- **Evidence for:** the player twice cited no incentive to make an adequate solution better. In 002, survival, wave clearing, and escalating power coincided with ten waves of continued tactical adjustment and strongly instrumental upgrade choices.
- **Evidence against:** voluntary sandbox creativity can occur without external rewards when the expressive medium itself is compelling.
- **Experiments:** 002 calm versus pressure
- **Unresolved:** Which stakes create desire rather than obligation: survival, power, protecting others, scarcity, or social consequence?
## H14 — The failures came from low knowledge leverage, not automation itself
- **Confidence:** medium
- **Evidence for:** both prototypes admitted obvious solutions whose results were largely insensitive to deeper laws. The player enjoys other games where constructed systems amplify capability at scale.
- **Evidence against:** even imagining richer field-aware control sounded tedious to the player, although that prediction is untested.
- **Experiments:** not directly tested; preserve as a competing explanation during and after 002.
- **Unresolved:** What minimal system would let one discovery qualitatively expand capability rather than merely improve a score or satisfy a predicate?
## H15 — Coupled state changes create meaningful tradeoffs
- **Confidence:** medium; useful early, vulnerable to power collapse
- **Evidence for:** extracting heat simultaneously created ammunition, slowed a threat, prepared brittleness, and depleted future environmental energy. The player stopped maximizing extraction blindly and made it conditional on enemy count and wall position.
- **Evidence against:** wall impact remained dominant in revision 2, producing 176/207 defeats. Extreme conversion made a small heat supply sufficient and erased the scarcity/state coupling later in the run.
- **Experiments:** 002; corrective 002 revision should preserve this coupling.
- **Unresolved:** How many domain consequences are needed before a choice becomes fertile rather than opaque? Does the tradeoff remain interesting without player-damage pressure?
## H16 — Rapid, chosen power growth can sustain engagement
- **Confidence:** high as a continuation mechanism, low as evidence of durable fun
- **Evidence for:** the player continued through wave 29 in revision 2 while capacity reached 1,255, transfer 220, and conversion 374. Later waves became very fast.
- **Evidence against:** the player reported little more to say and wanted to move on. Multiplicative power sustained activity while reducing decisions to one dominant answer.
- **Experiments:** 002
- **Unresolved:** Can rapid power be a satisfying payoff after qualitative reasoning without replacing that reasoning?
## H17 — Physical construction may be enjoyable when it changes a directly piloted capability
- **Confidence:** low for weakly spatial modular loadouts; broader hypothesis unresolved
- **Evidence for:** the player enjoys construction games where the built object becomes their practical capability. Experiment 001's construction instead specified autonomous movement and felt like manual AI programming.
- **Evidence against:** 003 placement barely mattered, trials reduced to obvious compositions, and reorienting Drives added mild friction. Direct piloting did not rescue shallow construction.
- **Experiments:** 003
- **Unresolved:** Would actual load paths, torque, damage topology, or geometry make construction meaningful, or would it remain implementation work?
## H18 — Inferring and transferring an unfamiliar stable law can be intrinsically rewarding
- **Confidence:** low for an isolated abstract law; unresolved when knowledge grants consequential agency
- **Evidence for:** several enjoyed games turn a newly understood rule into immediate capability. Experiment 004 did provoke one self-chosen question about moving the midpoint marker.
- **Evidence against:** the predictive model did not form during 004's required rooms, Transfer became guessing, and answering the self-chosen marker question was not satisfying. An unintended workaround in 003 was also not fun.
- **Experiments:** 004
- **Unresolved:** Is the missing ingredient better scaffolding, or more importantly a consequence that makes acquired knowledge useful inside a valued activity?
## H19 — Recoverable chaos can make systemic agency enjoyable
- **Confidence:** low for indiscriminate accumulating chaos; refined version remains untested
- **Evidence for:** preference history includes Helldivers 2, Lethal Company, Risk of Rain 2, Crab Champions, and co-op PvE; Experiment 002's pressure variant sustained far more activity than matched Calm and produced early tactical adaptation.
- **Evidence against:** pressure in 002 sustained a shallow universal wall-blast loop. In 005 the chaos was readable but became unmanageable: accumulated wreckage made nearly every intervention cause collateral damage, charge reduced options further, and nominal rescue stakes created no care.
- **Experiments:** 005
- **Unresolved:** Can local mistakes remain recoverable when failure changes the immediate problem without permanently shrinking the action space?
## H20 — Assertive, selective agency is more intrinsically valuable than custodial control
- **Confidence:** low as a sufficient cause
- **Evidence for:** the player's preference history includes combat and movement games with targeted, immediate verbs. Experiment 002's direct attacks sustained more interest than controller construction, abstract puzzles, rig management, or rescue shepherding.
- **Evidence against:** In 006 the player was ready to stop after one optional replay. Strike was sufficient rather than intrinsically satisfying, tether worsened position, dash stayed outside attention, and reflection was interesting conceptually but required unpleasantly divided aim/timing/threat attention.
- **Experiments:** 006
- **Unresolved:** Does assertive action become valuable as the expression and payoff of chosen growth, team coordination, or broader consequence rather than as an isolated activity?
## H21 — Qualitative chosen growth creates value through authorship and payoff
- **Confidence:** high for short-run interest and continued build testing; medium for sustained fun
- **Evidence for:** the player prefers Risk of Rain 2 with Artifact of Command and enjoys Warframe, Crab Champions, Minecraft modpacks, and becoming extremely powerful through understanding. In 002, rapid chosen power growth sustained the longest sessions. In 007, the mutation condition prompted a logged Arc-only replay plus at least three additional reported runs involving Bloom, the combined system, and all-Bloom. The player formed an inaccurate ranking from descriptions, observed unexpectedly strong cross-effect behavior, revised Bloom upward, explicitly contrasted this with boring independent numerical upgrades, and kept generating specific comparative builds. Watching an all-Bloom cascade erase the field was reported as “kind of fun.”
- **Evidence against:** 002's upgrades all amplified one wall-blast answer and the player reported little continuing interest. In 003, changing parts was obvious loadout work rather than expressive authorship. Bloom in 007 appeared overtuned, so raw exponential power and spectacle remain confounded with composition. Long-term strategy half-life is untested.
- **Experiments:** 002, 003, 007
- **Unresolved:** Does a larger but still legible network of hit/kill/status interfaces sustain chained build questions under changing enemy ecologies, or crystallize into a full-synergy package or obvious counters?
## H22 — Cross-effect interfaces matter more than independently useful upgrades
- **Confidence:** high for the tested short comparison
- **Evidence for:** In 007, both conditions offered exact chosen growth on the same fields. The player predicted boredom from Trial II's independently useful damage/rate/width options, but found Trial I interesting enough for multiple replays after Fork, Arc, and Bloom interacted through shared hit/kill events. They specifically said Trial II's upgrades would have made Trial I more fun but did not create interesting differences among themselves. Later runs mapped Bloom to small enemies and Arc to large enemies, showing conditional knowledge rather than only higher throughput.
- **Evidence against:** Mutation also had more spectacle, automatic targeting through Bloom, and possibly greater effective power. Those factors were not independently controlled. Only three interacting families were tested.
- **Experiments:** 007
- **Unresolved:** Is the payoff driven by planning causal composition, by surprising emergent cascades, by reduced execution burden, or by audiovisual/raw-power expression?
## H23 — Changing enemy ecology can preserve compositional reasoning
- **Confidence:** unresolved; first 008 run was too short at build maturity
- **Evidence for:** Without prompting, the player concluded that Bloom excelled against small enemies while Arc performed better against large enemies. Conditional target profiles can make component knowledge transfer while preventing one exact build from answering every field.
- **Evidence against:** Experiment 003's changing requirements collapsed into obvious prescribed counter-parts. In 007, taking all mutation families together may dominate, and all-Bloom's aggressive scaling may erase ecology distinctions through raw power.
- **Experiments:** 003 is negative adjacent evidence; 007 generated the hypothesis; 008 first run supplied only one short field after the fourth choice and could not isolate it.
- **Evidence update:** In 008 the player used three different builds and believed multiple approaches may exist, but reported that each expedition ended just as something cool began emerging. The fourth module was active for only 722 seconds. Conduit-first was a misunderstanding, not planned interface composition.
- **Unresolved:** Can mixed/behavioral ecologies support several viable causal compositions, or does adaptation become “equip the labeled counter”?

126
tools/playtest_server.py Normal file
View file

@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""Serve one prototype and save validated playtest JSONL into the repository."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import re
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
import tempfile
from urllib.parse import urlparse
MAX_LOG_BYTES = 10 * 1024 * 1024
SAFE_FILENAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,159}\.jsonl$")
class PlaytestHandler(SimpleHTTPRequestHandler):
server_version = "FindMyFunPlaytestServer/1"
def do_GET(self) -> None:
if urlparse(self.path).path == "/api/playtest-log":
self._json_response(200, {"ready": True, "destination": "JSONL/"})
return
super().do_GET()
def do_POST(self) -> None:
if urlparse(self.path).path != "/api/playtest-log":
self._json_response(404, {"error": "unknown endpoint"})
return
raw_length = self.headers.get("Content-Length")
try:
length = int(raw_length or "")
except ValueError:
self._json_response(411, {"error": "valid Content-Length required"})
return
if length <= 0 or length > MAX_LOG_BYTES:
self._json_response(413, {"error": f"log must be between 1 and {MAX_LOG_BYTES} bytes"})
return
filename = self.headers.get("X-Playtest-Filename", "")
if not SAFE_FILENAME.fullmatch(filename) or ".." in filename:
self._json_response(400, {"error": "unsafe or invalid JSONL filename"})
return
body = self.rfile.read(length)
try:
text = body.decode("utf-8")
events = [json.loads(line) for line in text.splitlines() if line.strip()]
except (UnicodeDecodeError, json.JSONDecodeError) as error:
self._json_response(400, {"error": f"invalid JSONL: {error}"})
return
if not events or not all(isinstance(event, dict) for event in events):
self._json_response(400, {"error": "JSONL must contain event objects"})
return
experiment = events[0].get("experiment")
session_id = events[0].get("session_id")
if not experiment or not session_id:
self._json_response(400, {"error": "first event must contain experiment and session_id"})
return
if any(event.get("experiment") != experiment or event.get("session_id") != session_id for event in events):
self._json_response(400, {"error": "all events must belong to one experiment and session"})
return
destination = self.server.log_directory / filename
normalized = text if text.endswith("\n") else text + "\n"
temporary_path: Path | None = None
try:
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=self.server.log_directory, prefix=".playtest-", suffix=".tmp", delete=False) as temporary:
temporary.write(normalized)
temporary.flush()
os.fsync(temporary.fileno())
temporary_path = Path(temporary.name)
os.replace(temporary_path, destination)
except OSError as error:
if temporary_path is not None:
temporary_path.unlink(missing_ok=True)
self._json_response(500, {"error": f"could not save log: {error}"})
return
self._json_response(201, {"saved": True, "path": f"JSONL/{filename}", "events": len(events), "bytes": len(normalized.encode("utf-8"))})
def _json_response(self, status: int, payload: dict[str, object]) -> None:
body = (json.dumps(payload) + "\n").encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
class PlaytestServer(ThreadingHTTPServer):
def __init__(self, address: tuple[str, int], handler: type[PlaytestHandler], directory: Path, log_directory: Path):
self.log_directory = log_directory
self.log_directory.mkdir(parents=True, exist_ok=True)
super().__init__(address, lambda *args, **kwargs: handler(*args, directory=str(directory), **kwargs))
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--directory", type=Path, required=True)
parser.add_argument("--log-directory", type=Path, required=True)
parser.add_argument("--port", type=int, default=8000)
args = parser.parse_args()
directory = args.directory.resolve(strict=True)
log_directory = args.log_directory.resolve()
server = PlaytestServer(("127.0.0.1", args.port), PlaytestHandler, directory, log_directory)
print(f"Serving {directory} at http://127.0.0.1:{args.port}")
print(f"Playtest logs save to {log_directory}")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
if __name__ == "__main__":
main()