@lampdotdev/lamp 0.0.0-stage → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +357 -2
  3. package/Resources/Devices/roku.json +410 -0
  4. package/Resources/Devices/xbox-one.json +367 -0
  5. package/Resources/GameKit/NOTICE.md +63 -0
  6. package/Resources/GameKit/apple/LampPage.swift +1646 -0
  7. package/Resources/GameKit/apple/LampPlayer.swift +46 -0
  8. package/Resources/GameKit/apple/LampWebApp.swift +63 -0
  9. package/Resources/GameKit/apple/LampWebGame.swift +87 -0
  10. package/Resources/GameKit/apple/Native.swift +12 -0
  11. package/Resources/GameKit/build.sh +55 -0
  12. package/Resources/GameKit/lamp_game.h +116 -0
  13. package/Resources/GameKit/lamp_host.h +19 -0
  14. package/Resources/GameKit/lamp_kit.cpp +61 -0
  15. package/Resources/GameKit/lamp_sound.cpp +455 -0
  16. package/Resources/GameKit/lamp_xbox.cpp +374 -0
  17. package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
  18. package/Resources/GameKit/libraries/cgltf.h +7240 -0
  19. package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
  20. package/Resources/GameKit/libraries/dr_wav.h +9209 -0
  21. package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
  22. package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
  23. package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
  24. package/Resources/GameKit/libraries/sokol_log.h +335 -0
  25. package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
  26. package/Resources/GameKit/libraries/stb_image.h +7988 -0
  27. package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
  28. package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
  29. package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
  30. package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
  31. package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
  32. package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
  33. package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
  34. package/Resources/GameKit/web/lamp-game.js +3036 -0
  35. package/Resources/GameKit/web/lamp-phaser.js +128 -0
  36. package/Resources/WebHost/App.exe +0 -0
  37. package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
  38. package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
  39. package/Resources/WebHost/lamp_controllers.h +1506 -0
  40. package/Resources/WebHost/lamp_web_host.cpp +1173 -0
  41. package/Resources/WebLibraries/earlier.txt +318 -0
  42. package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
  43. package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
  44. package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
  45. package/Resources/WebLibraries/qrcode/LICENSE +21 -0
  46. package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
  47. package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
  48. package/Resources/WebLibraries/rapier/LICENSE +201 -0
  49. package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
  50. package/Resources/WebLibraries/rapier/rapier.js +2 -0
  51. package/Resources/WebLibraries/three/LICENSE +21 -0
  52. package/Resources/WebLibraries/three/NOTICE.md +4 -0
  53. package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
  54. package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
  55. package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
  56. package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
  57. package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
  58. package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
  59. package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
  60. package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
  61. package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
  62. package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
  63. package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
  64. package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
  65. package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
  66. package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
  67. package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
  68. package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
  69. package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
  70. package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
  71. package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
  72. package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
  73. package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
  74. package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
  75. package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
  76. package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
  77. package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
  78. package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
  79. package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
  80. package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
  81. package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
  82. package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
  83. package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
  84. package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
  85. package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
  86. package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
  87. package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
  88. package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
  89. package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
  90. package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
  91. package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
  92. package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
  93. package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
  94. package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
  95. package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
  96. package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
  97. package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
  98. package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
  99. package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
  100. package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
  101. package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
  102. package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
  103. package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
  104. package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
  105. package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
  106. package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
  107. package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
  108. package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
  109. package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
  110. package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
  111. package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
  112. package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
  113. package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
  114. package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
  115. package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
  116. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
  117. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
  118. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
  119. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
  120. package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
  121. package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
  122. package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
  123. package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
  124. package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
  125. package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
  126. package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
  127. package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
  128. package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
  129. package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
  130. package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
  131. package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
  132. package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
  133. package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
  134. package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
  135. package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
  136. package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
  137. package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
  138. package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
  139. package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
  140. package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
  141. package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
  142. package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
  143. package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
  144. package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
  145. package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
  146. package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
  147. package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
  148. package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
  149. package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
  150. package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
  151. package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
  152. package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
  153. package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
  154. package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
  155. package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
  156. package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
  157. package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
  158. package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
  159. package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
  160. package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
  161. package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
  162. package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
  163. package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
  164. package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
  165. package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
  166. package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
  167. package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
  168. package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
  169. package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
  170. package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
  171. package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
  172. package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
  173. package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
  174. package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
  175. package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
  176. package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
  177. package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
  178. package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
  179. package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
  180. package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
  181. package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
  182. package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
  183. package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
  184. package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
  185. package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
  186. package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
  187. package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
  188. package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
  189. package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
  190. package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
  191. package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
  192. package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
  193. package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
  194. package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
  195. package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
  196. package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
  197. package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
  198. package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
  199. package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
  200. package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
  201. package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
  202. package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
  203. package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
  204. package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
  205. package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
  206. package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
  207. package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
  208. package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
  209. package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
  210. package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
  211. package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
  212. package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
  213. package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
  214. package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
  215. package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
  216. package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
  217. package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
  218. package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
  219. package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
  220. package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
  221. package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
  222. package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
  223. package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
  224. package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
  225. package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
  226. package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
  227. package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
  228. package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
  229. package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
  230. package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
  231. package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
  232. package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
  233. package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
  234. package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
  235. package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
  236. package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
  237. package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
  238. package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
  239. package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
  240. package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
  241. package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
  242. package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
  243. package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
  244. package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
  245. package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
  246. package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
  247. package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
  248. package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
  249. package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
  250. package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
  251. package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
  252. package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
  253. package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
  254. package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
  255. package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
  256. package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
  257. package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
  258. package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
  259. package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
  260. package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
  261. package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
  262. package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
  263. package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
  264. package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
  265. package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
  266. package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
  267. package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
  268. package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
  269. package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
  270. package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
  271. package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
  272. package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
  273. package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
  274. package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
  275. package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
  276. package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
  277. package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
  278. package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
  279. package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
  280. package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
  281. package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
  282. package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
  283. package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
  284. package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
  285. package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
  286. package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
  287. package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
  288. package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
  289. package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
  290. package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
  291. package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
  292. package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
  293. package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
  294. package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
  295. package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
  296. package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
  297. package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
  298. package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
  299. package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
  300. package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
  301. package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
  302. package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
  303. package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
  304. package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
  305. package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
  306. package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
  307. package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
  308. package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
  309. package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
  310. package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
  311. package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
  312. package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
  313. package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
  314. package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
  315. package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
  316. package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
  317. package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
  318. package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
  319. package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
  320. package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
  321. package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
  322. package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
  323. package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
  324. package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
  325. package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
  326. package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
  327. package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
  328. package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
  329. package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
  330. package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
  331. package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
  332. package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
  333. package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
  334. package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
  335. package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
  336. package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
  337. package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
  338. package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
  339. package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
  340. package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
  341. package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
  342. package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
  343. package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
  344. package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
  345. package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
  346. package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
  347. package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
  348. package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
  349. package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
  350. package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
  351. package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
  352. package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
  353. package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
  354. package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
  355. package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
  356. package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
  357. package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
  358. package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
  359. package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
  360. package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
  361. package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
  362. package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
  363. package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
  364. package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
  365. package/Resources/WebLibraries/three/three.core.js +61162 -0
  366. package/Resources/WebLibraries/three/three.module.js +19537 -0
  367. package/Resources/WebLibraries/three/three.tsl.js +41903 -0
  368. package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
  369. package/Resources/skills/2d-game/SKILL.md +305 -0
  370. package/Resources/skills/3d-game/SKILL.md +274 -0
  371. package/Resources/skills/accounts/SKILL.md +41 -0
  372. package/Resources/skills/android-design/SKILL.md +159 -0
  373. package/Resources/skills/app-ai/SKILL.md +154 -0
  374. package/Resources/skills/app-intents/SKILL.md +31 -0
  375. package/Resources/skills/app-kinds/SKILL.md +122 -0
  376. package/Resources/skills/apple-watch/SKILL.md +43 -0
  377. package/Resources/skills/assets/SKILL.md +183 -0
  378. package/Resources/skills/building-blocks/SKILL.md +97 -0
  379. package/Resources/skills/device-control/SKILL.md +56 -0
  380. package/Resources/skills/device-integrations/SKILL.md +278 -0
  381. package/Resources/skills/finder-files/SKILL.md +19 -0
  382. package/Resources/skills/game/SKILL.md +414 -0
  383. package/Resources/skills/game-3d/SKILL.md +12 -0
  384. package/Resources/skills/game-art/SKILL.md +11 -0
  385. package/Resources/skills/game-feel/SKILL.md +13 -0
  386. package/Resources/skills/game-levels/SKILL.md +12 -0
  387. package/Resources/skills/game-live/SKILL.md +12 -0
  388. package/Resources/skills/game-physics/SKILL.md +13 -0
  389. package/Resources/skills/game-playtest/SKILL.md +26 -0
  390. package/Resources/skills/game-ui/SKILL.md +278 -0
  391. package/Resources/skills/github-projects/SKILL.md +59 -0
  392. package/Resources/skills/icloud-sync/SKILL.md +30 -0
  393. package/Resources/skills/iphone-hardware/SKILL.md +57 -0
  394. package/Resources/skills/iphone-widgets/SKILL.md +53 -0
  395. package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
  396. package/Resources/skills/mac-widgets/SKILL.md +60 -0
  397. package/Resources/skills/maps/SKILL.md +8 -0
  398. package/Resources/skills/menu-bar/SKILL.md +36 -0
  399. package/Resources/skills/more-apps/SKILL.md +53 -0
  400. package/Resources/skills/phone-controllers/SKILL.md +79 -0
  401. package/Resources/skills/phone-pages/SKILL.md +173 -0
  402. package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
  403. package/Resources/skills/scene-3d/SKILL.md +27 -0
  404. package/Resources/skills/scripting/SKILL.md +17 -0
  405. package/Resources/skills/share-extension/SKILL.md +22 -0
  406. package/Resources/skills/tv-design/SKILL.md +72 -0
  407. package/Resources/skills/web-app/SKILL.md +136 -0
  408. package/Resources/skills/web-game/SKILL.md +95 -0
  409. package/Resources/skills/web-or-native/SKILL.md +103 -0
  410. package/Resources/skills/wish-links/SKILL.md +156 -0
  411. package/THIRD_PARTY_NOTICES.md +170 -0
  412. package/cli/android.mjs +941 -0
  413. package/cli/browser.mjs +268 -0
  414. package/cli/calls.mjs +390 -0
  415. package/cli/dev.mjs +773 -0
  416. package/cli/devices.mjs +984 -0
  417. package/cli/lamp.mjs +1016 -0
  418. package/cli/lampapp.mjs +332 -0
  419. package/cli/lamps/android-host/LampPageGateway.kt +345 -0
  420. package/cli/lamps/android-host/app/build.gradle.kts +40 -0
  421. package/cli/lamps/android-host/build.gradle.kts +4 -0
  422. package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
  423. package/cli/lamps/android-host/game/MainActivity.kt +173 -0
  424. package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
  425. package/cli/lamps/android-host/gradle.properties +8 -0
  426. package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
  427. package/cli/lamps/android-host/page/MainActivity.kt +193 -0
  428. package/cli/lamps/android-host/settings.gradle.kts +30 -0
  429. package/cli/lamps/android-host/tools.json +43 -0
  430. package/cli/lamps/lamp-controllers.js +2 -0
  431. package/cli/lamps/lamp-for-games.js +6863 -0
  432. package/cli/lamps/lamp.js +7023 -0
  433. package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
  434. package/cli/lamps/mac-starter/APP.md +12 -0
  435. package/cli/lamps/mac-starter/Package.swift +8 -0
  436. package/cli/lamps/mac-starter/Sources/App.swift +11 -0
  437. package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
  438. package/cli/lamps/mac-starter/Sources/LampSupport.swift +18097 -0
  439. package/cli/lamps/mac-starter/build.sh +336 -0
  440. package/cli/lamps/mac-starter/gitignore +3 -0
  441. package/cli/lamps/mac-starter/lamp-starter.json +6 -0
  442. package/cli/lamps/web-starter/APP.md +8 -0
  443. package/cli/lamps/web-starter/app.js +1 -0
  444. package/cli/lamps/web-starter/index.html +17 -0
  445. package/cli/lamps/web-starter/lamp-starter.json +5 -0
  446. package/cli/lamps/web-starter/lamp.js +7023 -0
  447. package/cli/lamps/web-starter/style.css +25 -0
  448. package/cli/link.mjs +138 -0
  449. package/cli/mac.mjs +621 -0
  450. package/cli/mdns.mjs +205 -0
  451. package/cli/msix.mjs +413 -0
  452. package/cli/network.mjs +209 -0
  453. package/cli/outside.mjs +140 -0
  454. package/cli/page.js +165 -0
  455. package/cli/place.mjs +298 -0
  456. package/cli/png.mjs +184 -0
  457. package/cli/share.mjs +202 -0
  458. package/cli/skill/SKILL.md +255 -0
  459. package/cli/skills.mjs +71 -0
  460. package/cli/state.mjs +173 -0
  461. package/cli/update.mjs +57 -0
  462. package/package.json +20 -4
  463. package/site/lib/quota.js +59 -0
  464. package/site/lib/shared-wishes.js +996 -0
@@ -0,0 +1,209 @@
1
+ // Everything on the person's home network, one record per address, for when they're looking for devices: SSDP and the
2
+ // integrations find what Lamp can work with (devices.mjs); this finds the rest, so a list can say what else is there.
3
+ // It only reads: one small datagram to each address of this computer's own subnet, so its neighbor table fills (a
4
+ // phone that ignores everything else still answers ARP), that table, and every Bonjour service on the network
5
+ // (mdns.mjs). And the TVs: what a Samsung or an LG TV says of itself. Nothing is kept on disk: the names a network
6
+ // holds are often people's ("…'s iPhone").
7
+
8
+ import { execFile } from "node:child_process";
9
+ import dgram from "node:dgram";
10
+ import { readFileSync } from "node:fs";
11
+ import { networkInterfaces } from "node:os";
12
+ import { isPrivate, onNetwork, ownAddresses, send, xmlAt } from "./devices.mjs";
13
+ import { everything } from "./mdns.mjs";
14
+
15
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
16
+
17
+ /** What a system program says, or "" when it says nothing (it isn't there, or it failed). Never through a shell. */
18
+ const said = (file, args) => new Promise((done) => execFile(file, args, { timeout: 5000 }, (error, out) => done(error ? "" : String(out))));
19
+
20
+ // MARK: The subnet
21
+
22
+ /** An IPv4 address as a number, or null. */
23
+ export function valueOf(address) {
24
+ const parts = String(address).split(".");
25
+ if (parts.length !== 4 || !parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) < 256)) return null;
26
+ return parts.reduce((value, part) => (value * 256) + Number(part), 0);
27
+ }
28
+ const textOf = (value) => [24, 16, 8, 0].map((shift) => Math.floor(value / 2 ** shift) % 256).join(".");
29
+
30
+ /**
31
+ * The subnet to sweep for an address and its mask: no more than the 256 addresses around this computer's on a bigger
32
+ * network, and none for an address that isn't on a home network.
33
+ */
34
+ export function subnetOf(address, netmask) {
35
+ const mine = valueOf(address), mask = valueOf(netmask);
36
+ if (mine === null || mask === null) return null;
37
+ const actual = [...mask.toString(2)].filter((bit) => bit === "1").length;
38
+ if (actual < 8 || actual > 30 || !onNetwork(address)) return null;
39
+ const prefix = Math.max(actual, 24), size = 2 ** (32 - prefix);
40
+ return { network: textOf(mine - (mine % size)), prefix, actualPrefix: actual, mine: address };
41
+ }
42
+
43
+ const inSubnet = (subnet, address) => {
44
+ const value = valueOf(address), size = 2 ** (32 - subnet.prefix);
45
+ return value !== null && value - (value % size) === valueOf(subnet.network);
46
+ };
47
+
48
+ /** The default route: the interface it uses and the gateway it goes through. Null when there's none. */
49
+ export async function defaultRoute() {
50
+ if (process.platform === "darwin") {
51
+ const text = await said("/sbin/route", ["-n", "get", "default"]);
52
+ const gateway = /^\s*gateway:\s*(\S+)/m.exec(text)?.[1], name = /^\s*interface:\s*(\S+)/m.exec(text)?.[1];
53
+ return name ? { interface: name, gateway: valueOf(gateway) === null ? null : gateway } : null;
54
+ }
55
+ if (process.platform === "linux") {
56
+ let table = "";
57
+ try { table = readFileSync("/proc/net/route", "utf8"); } catch { return null; }
58
+ for (const line of table.split("\n").slice(1)) {
59
+ const [name, destination, gateway, flags] = line.trim().split(/\s+/);
60
+ if (destination !== "00000000" || !(parseInt(flags, 16) & 2)) continue;
61
+ const bytes = gateway.match(/../g)?.reverse().map((byte) => parseInt(byte, 16));
62
+ return { interface: name, gateway: bytes?.length === 4 ? bytes.join(".") : null };
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /** The subnet of the interface the default route uses, when it's a home network's. */
69
+ export function subnetOn(name) {
70
+ const entry = (networkInterfaces()[name] ?? []).find((address) => address.family === "IPv4" && !address.internal);
71
+ return entry ? subnetOf(entry.address, entry.netmask) : null;
72
+ }
73
+
74
+ /** One datagram to each address of the subnet (one byte to the discard port, which nothing answers), a few at a time, paced, then a moment for the answers to ARP. */
75
+ export async function sweep(subnet, { batch = 16, pause = 25, settle = 1500 } = {}) {
76
+ const socket = dgram.createSocket("udp4"), size = 2 ** (32 - subnet.prefix), network = valueOf(subnet.network);
77
+ socket.on("error", () => {});
78
+ await new Promise((done) => socket.bind(0, done));
79
+ const addresses = [];
80
+ for (let offset = 1; offset < size - 1; offset++) if (textOf(network + offset) !== subnet.mine) addresses.push(textOf(network + offset));
81
+ try {
82
+ for (let start = 0; start < addresses.length; start += batch) {
83
+ for (const address of addresses.slice(start, start + batch)) socket.send(Buffer.from([0]), 9, address, () => {});
84
+ await sleep(pause);
85
+ }
86
+ await sleep(settle);
87
+ } finally { try { socket.close(); } catch {} }
88
+ }
89
+
90
+ /** A hardware address with two digits to each part ("ae:b0:a9:01:eb:44"); null for a multicast or broadcast one, or none. */
91
+ function hardware(text) {
92
+ const parts = String(text).toLowerCase().split(":");
93
+ if (parts.length !== 6 || !parts.every((part) => /^[0-9a-f]{1,2}$/.test(part))) return null;
94
+ const mac = parts.map((part) => part.padStart(2, "0"));
95
+ if (parseInt(mac[0], 16) & 1 || mac.every((part) => part === "ff")) return null;
96
+ return mac.join(":");
97
+ }
98
+
99
+ /**
100
+ * This computer's neighbor table: each neighbor's IPv4 address and hardware address, for those that answered. macOS
101
+ * shows it only to a process it lets on the local network: run on the runtime Lamp's app carries, it's empty, so
102
+ * Lamp's app reads its own (`Neighbours`).
103
+ */
104
+ export async function neighbours() {
105
+ const table = {};
106
+ if (process.platform === "darwin") {
107
+ for (const [, address, mac] of (await said("/usr/sbin/arp", ["-an"])).matchAll(/\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-fA-F:]+) on /g)) {
108
+ const known = hardware(mac);
109
+ if (known) table[address] = known;
110
+ }
111
+ } else if (process.platform === "linux") {
112
+ let text = "";
113
+ try { text = readFileSync("/proc/net/arp", "utf8"); } catch {}
114
+ for (const line of text.split("\n").slice(1)) {
115
+ const [address, , flags, mac] = line.trim().split(/\s+/);
116
+ const known = (parseInt(flags, 16) & 2) && hardware(mac);
117
+ if (known && valueOf(address) !== null) table[address] = known;
118
+ }
119
+ }
120
+ return table;
121
+ }
122
+
123
+ /**
124
+ * The hosts of this computer's subnet, each with its hardware address and its Bonjour services, from a sweep, the
125
+ * neighbor table and every Bonjour service, browsed while the sweep runs. `swept` says which subnet was swept: on a
126
+ * network bigger than a /24 only the 256 addresses around this computer's are.
127
+ */
128
+ export async function homeNetwork({ seconds = 3 } = {}) {
129
+ const services = everything({ seconds });
130
+ const route = await defaultRoute(), subnet = route ? subnetOn(route.interface) : null;
131
+ if (subnet) await sweep(subnet);
132
+ const table = await neighbours(), mine = ownAddresses();
133
+ const inside = (address) => (subnet ? inSubnet(subnet, address) : isPrivate(address)) || mine.has(address);
134
+ const hosts = new Map(), hostAt = (address) => hosts.get(address) ?? hosts.set(address, { address, mac: null, services: [] }).get(address);
135
+ for (const [address, mac] of Object.entries(table)) if (inside(address)) hostAt(address).mac = mac;
136
+ for (const { service, addresses } of await services) for (const address of addresses) if (inside(address)) hostAt(address).services.push(service);
137
+ for (const host of hosts.values()) {
138
+ host.isThisMac = mine.has(host.address);
139
+ host.isGateway = host.address === route?.gateway;
140
+ }
141
+ return { hosts: [...hosts.values()].sort((a, b) => (valueOf(a.address) ?? Infinity) - (valueOf(b.address) ?? Infinity)), swept: subnet, gateway: route?.gateway ?? null };
142
+ }
143
+
144
+ // MARK: TVs
145
+
146
+ /** The service an LG TV announces for itself; the description it points at is the TV's own. */
147
+ export const LG_TV = "urn:lge:device:tv:1";
148
+
149
+ /** A page on the TV, read with every guard a request to a device has: its body, or null. */
150
+ const fetched = (address) => (request) => send(request, address, {}, { timeout: 3000 }).then((answer) => (answer.status === 200 ? answer.body : null), () => null);
151
+
152
+ /** A Samsung TV's own description of itself, from its local API; null when it isn't one. */
153
+ export async function samsungAt(address, { get = fetched(address) } = {}) {
154
+ let device;
155
+ try { device = JSON.parse(String(await get({ port: 8001, path: "/api/v2/" }))).device; } catch { return null; }
156
+ if (device?.OS !== "Tizen") return null;
157
+ const modelID = typeof device.modelName === "string" ? device.modelName : "", frame = device.FrameTVSupport === "true";
158
+ // The size is in the model code (QN50…, UN65…).
159
+ const size = /^\d{2}$/.test(modelID.slice(2, 4)) ? modelID.slice(2, 4) : "";
160
+ const model = frame ? `The Frame${size ? ` ${size}` : ""}` : size ? `Smart TV ${size}` : modelID;
161
+ // The year is the first part of its internal model ("22_PONTUSM_FTV"), and each year has one Tizen.
162
+ const year = Number(String(device.model ?? "").slice(0, 2)) || 0;
163
+ const tizen = { 17: "3.0", 18: "4.0", 19: "5.0", 20: "5.5", 21: "6.0", 22: "6.5", 23: "7.0", 24: "8.0", 25: "9.0", 26: "10.0" }[year] ?? "";
164
+ const id = String(device.id ?? device.duid ?? address).replace("uuid:", "");
165
+ return { id: `samsung:${id}`, maker: "Samsung", name: typeof device.name === "string" ? device.name : "Samsung TV", model, modelID, address, os: "Tizen",
166
+ osVersion: tizen, resolution: typeof device.resolution === "string" ? device.resolution : "", frame,
167
+ developerIP: typeof device.developerIP === "string" ? device.developerIP : null, developerMode: device.developerMode === "1" };
168
+ }
169
+
170
+ /**
171
+ * An LG TV, from the UPnP descriptions its answers point at. It answers for several services, each with a
172
+ * description and an id of its own, and the first to answer is often one that says nothing of the TV (a streaming
173
+ * service's, a few bytes of "status=ok"), so each is tried until one is an LG TV's: the TV's own service first, which
174
+ * gives it the same id at every search. `udns` is every id it answered for, so a TV kept under another one is known.
175
+ */
176
+ export async function lgAt(address, answers, { get = fetched(address) } = {}) {
177
+ const locations = [];
178
+ const own = answers.filter((answer) => String(answer.target).toLowerCase() === LG_TV).map((answer) => answer.location);
179
+ for (const location of [...own, ...answers.map((answer) => answer.location)]) {
180
+ let url;
181
+ try { url = new URL(location); } catch { continue; }
182
+ if (url.hostname === address && url.protocol === "http:" && !locations.includes(location)) locations.push(location);
183
+ }
184
+ // A USN is "uuid:<udn>" or "uuid:<udn>::<type>".
185
+ const udns = new Set(answers.map((answer) => String(answer.usn)).filter((usn) => usn.toLowerCase().startsWith("uuid:")).map((usn) => usn.slice(5).split("::")[0]).filter(Boolean));
186
+ for (const location of locations) {
187
+ const url = new URL(location), body = await get({ port: Number(url.port) || 80, path: (url.pathname || "/") + url.search });
188
+ if (!body) continue;
189
+ const tag = (name) => xmlAt(body, name) ?? null;
190
+ if (!(tag("manufacturer") ?? "").toUpperCase().startsWith("LG")) continue;
191
+ const udn = (tag("UDN") ?? address).replace("uuid:", "");
192
+ return { id: `lg:${udn}`, maker: "LG", name: tag("friendlyName") ?? "LG TV", model: tag("modelName") ?? "webOS TV", modelID: tag("modelNumber") ?? "",
193
+ address, os: "webOS", osVersion: "", resolution: "", udns: [...new Set([...udns, udn])].sort() };
194
+ }
195
+ return null;
196
+ }
197
+
198
+ /** The TVs among a search's answers, each asked who it is: one whose answers say Samsung, or webOS or LG. */
199
+ export async function tvsIn(answers, { get } = {}) {
200
+ const byAddress = new Map();
201
+ for (const answer of answers) byAddress.set(answer.address, [...(byAddress.get(answer.address) ?? []), answer]);
202
+ const read = await Promise.all([...byAddress].map(([address, said]) => {
203
+ const words = said.map((answer) => `${answer.server}\n${answer.location ?? ""}\n${answer.target}`).join("\n").toLowerCase();
204
+ if (words.includes("samsung")) return samsungAt(address, get ? { get } : {});
205
+ if (words.includes("webos") || words.includes("lge")) return lgAt(address, said, get ? { get } : {});
206
+ return null;
207
+ }));
208
+ return read.filter(Boolean).filter((tv, index, all) => all.findIndex((other) => other.id === tv.id) === index);
209
+ }
@@ -0,0 +1,140 @@
1
+ // Lamp's app, reached the way the person's own coding agent reaches it (`OutsideServer`, Lamp's MCP endpoint): a Unix
2
+ // socket in that Lamp's own folder, private to the person, a hello line each way, then JSON-RPC a line at a time. lamp
3
+ // asks it to take a Mac app's folder on (`open_project`, which Lamp reviews with the person first), to put each change
4
+ // into the app it has open (`apply_change`, which says how it went in: live, held for a Reload, reopened…), and to
5
+ // look at it (`look_at_app`). What a change does to an open app is Lamp's app's to work out (`LiveReload`); this only
6
+ // carries the folder's path there and the answer back.
7
+
8
+ import { createConnection } from "node:net";
9
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { Problem, stateOf } from "./state.mjs";
13
+
14
+ // The handshake's version, as Lamp's helper sends it (`OutsideKit.protocolVersion`).
15
+ const PROTOCOL = 1;
16
+
17
+ /** What a Lamp is called where its folders are, by its bundle id (`OutsideKit.editionName`). */
18
+ export function editionName(bundle) {
19
+ if (bundle === "dev.lamp.host.dev" || bundle.startsWith("dev.lamp.host.dev.")) {
20
+ const instance = bundle.startsWith("dev.lamp.host.dev.") ? bundle.slice("dev.lamp.host.dev.".length) : "";
21
+ return "Lamp Dev" + (instance ? ` ${instance}` : "");
22
+ }
23
+ return bundle === "dev.lamp.host.nightly" ? "Lamp Nightly" : "Lamp";
24
+ }
25
+
26
+ const socketOf = (bundle) => join(homedir(), "Library", "Application Support", editionName(bundle), "MCP", "lamp.sock");
27
+
28
+ /** The Lamps lamp looks for, in order: the one LAMP_APP names (a bundle id), else the release and then Lamp Nightly. */
29
+ const candidates = () => process.env.LAMP_APP ? [process.env.LAMP_APP] : ["dev.lamp.host", "dev.lamp.host.nightly"];
30
+
31
+ /**
32
+ * A connection to a running Lamp, greeted: `call(tool, args)` answers what the tool said (its outcome, its words and
33
+ * its pictures). Null when no Lamp lamp knows of is answering.
34
+ */
35
+ export async function reachLamp(bundles = candidates()) {
36
+ for (const bundle of bundles) {
37
+ if (!/^dev\.lamp\.host(\.[A-Za-z0-9-]+)*$/.test(bundle) || !existsSync(socketOf(bundle))) continue;
38
+ const lamp = await connected(bundle).catch(() => null);
39
+ if (lamp) return lamp;
40
+ }
41
+ return null;
42
+ }
43
+
44
+ function connected(bundle) {
45
+ return new Promise((done, failed) => {
46
+ const socket = createConnection(socketOf(bundle));
47
+ let buffer = "", greeted = false, next = 1, ended = null;
48
+ const waiting = new Map();
49
+ // Once the connection is gone, every call is refused with why, those under way and those asked later alike.
50
+ const fail = (problem) => {
51
+ ended ??= problem;
52
+ for (const { failed: no } of waiting.values()) no(problem);
53
+ waiting.clear();
54
+ failed(problem);
55
+ };
56
+ socket.setEncoding("utf8");
57
+ socket.on("error", (error) => fail(new Problem(`couldn't reach ${editionName(bundle)}: ${error.message}`)));
58
+ socket.on("close", () => fail(new Problem(`${editionName(bundle)} closed the connection`)));
59
+ socket.on("connect", () => socket.write(JSON.stringify({ lamp_hello: { protocol: PROTOCOL, client: "lamp" } }) + "\n"));
60
+ const send = (method, params) => new Promise((answered, no) => {
61
+ if (ended) return no(ended);
62
+ const id = next++;
63
+ waiting.set(id, { answered, failed: no });
64
+ socket.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
65
+ });
66
+ socket.on("data", async (chunk) => {
67
+ buffer += chunk;
68
+ for (let at; (at = buffer.indexOf("\n")) >= 0; ) {
69
+ const line = buffer.slice(0, at);
70
+ buffer = buffer.slice(at + 1);
71
+ if (!line.trim()) continue;
72
+ let said;
73
+ try { said = JSON.parse(line); } catch { continue; }
74
+ if (!greeted) {
75
+ if (said.lamp_hello?.error) { socket.destroy(); return failed(new Problem(said.lamp_hello.error)); }
76
+ if (!said.lamp_hello) { socket.destroy(); return failed(new Problem(`${editionName(bundle)} didn't answer as Lamp does`)); }
77
+ greeted = true;
78
+ try {
79
+ await send("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "lamp", version: "1" } });
80
+ socket.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
81
+ } catch (error) { return failed(error); }
82
+ done({
83
+ name: editionName(bundle), bundle,
84
+ async call(tool, args) {
85
+ const result = await send("tools/call", { name: tool, arguments: args });
86
+ return answerOf(result);
87
+ },
88
+ close() { ended ??= new Problem("the connection to Lamp was closed"); socket.removeAllListeners("close"); socket.end(); },
89
+ });
90
+ continue;
91
+ }
92
+ const one = waiting.get(said.id);
93
+ if (!one) continue;
94
+ waiting.delete(said.id);
95
+ if (said.error) one.failed(new Problem(said.error.message ?? "Lamp refused it"));
96
+ else one.answered(said.result ?? {});
97
+ }
98
+ });
99
+ });
100
+ }
101
+
102
+ /** A tool's answer as lamp reads it: the outcome its words lead with, the words after it, and its pictures. */
103
+ export function answerOf(result) {
104
+ const content = Array.isArray(result?.content) ? result.content : [];
105
+ const text = content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
106
+ const outcome = /^Outcome: ([a-z_]+)\./.exec(text)?.[1] ?? null;
107
+ // The facts are one line of JSON under the outcome: kept for whoever reads them, out of the words.
108
+ const lines = text.split("\n");
109
+ let facts = {};
110
+ if (outcome && lines[1]?.startsWith("{")) { try { facts = JSON.parse(lines[1]); } catch {} }
111
+ const words = (outcome ? lines.slice(lines[1]?.startsWith("{") ? 2 : 1) : lines).join("\n").trim();
112
+ const pictures = content.filter((part) => part.type === "image" && part.data).map((part) => Buffer.from(part.data, "base64"));
113
+ return { outcome, facts, words, pictures, failed: result?.isError === true };
114
+ }
115
+
116
+ // MARK: Which folders are Lamp's app's
117
+
118
+ const markOf = (folder) => join(stateOf(folder), "in-lamp.json");
119
+
120
+ /** The Lamp a folder was given to (`lamp open --in-lamp`), by its bundle id, or null for one lamp runs itself. */
121
+ export function givenTo(folder) {
122
+ try { return JSON.parse(readFileSync(markOf(folder), "utf8")).bundle ?? null; } catch { return null; }
123
+ }
124
+
125
+ export function giveTo(folder, bundle) {
126
+ mkdirSync(stateOf(folder), { recursive: true });
127
+ writeFileSync(markOf(folder), JSON.stringify({ bundle }));
128
+ }
129
+
130
+ export function takeBack(folder) { rmSync(markOf(folder), { force: true }); }
131
+
132
+ /** The pictures an answer brought, kept where lamp keeps a look's, by their paths. */
133
+ export function keepPictures(folder, pictures) {
134
+ mkdirSync(stateOf(folder), { recursive: true });
135
+ return pictures.map((bytes, n) => {
136
+ const path = join(stateOf(folder), `lamp-look${n ? `-${n + 1}` : ""}.png`);
137
+ writeFileSync(path, bytes);
138
+ return path;
139
+ });
140
+ }
package/cli/page.js ADDED
@@ -0,0 +1,165 @@
1
+ // Given to every page lamp dev serves, before any script of its own. The server puts `LAMP` in front of it: `gen` the
2
+ // last change the page's files had when they were served, `run` which run of the server served them.
3
+ (() => {
4
+ // The browser's own functions, kept before the page changes them. The game runtime takes away the timers the
5
+ // game's code left running each time that code runs again, and a timer of lamp's set through the page's
6
+ // `setTimeout` would go with them, and the log with it.
7
+ const later = window.setTimeout.bind(window), ask = window.fetch.bind(window);
8
+ // A page opened again to show its beginning (`lamp look --reopen`) says so to the game runtime, which then starts
9
+ // the game from its start, its saves kept (`window.lampFreshStart` in lamp-game.js).
10
+ const FRESH = 'lamp.dev.fresh';
11
+ try { if (sessionStorage.getItem(FRESH)) { sessionStorage.removeItem(FRESH); window.lampFreshStart = true; } } catch (e) {}
12
+ const id = Math.random().toString(36).slice(2, 10);
13
+ // The tab, which stays the same as its page opens again: how a change went is told of the tab.
14
+ let tab = id;
15
+ try { tab = sessionStorage.getItem('lamp.dev.tab') || id; sessionStorage.setItem('lamp.dev.tab', tab); } catch (e) {}
16
+ window.__lampTab = tab;
17
+ const post = (path, body, lasting) => {
18
+ try { ask('/__lamp/' + path, { method: 'POST', body: JSON.stringify({ page: id, ...body }), keepalive: !!lasting }).catch(() => {}); } catch (e) {}
19
+ };
20
+ const said = message => post('said', message, true);
21
+
22
+ // What Lamp's app around a page gives it: with these the game runtime knows changes come while it's played, and says
23
+ // how each one went and when the game is past its loading screen.
24
+ const handlers = {
25
+ lampLive: { postMessage: said }, lampPlaying: { postMessage: state => said({ state: 'page-' + state }) },
26
+ // The person played while lamp held the game's controller (`lamp use stick`), and has it back.
27
+ lampDriveTakenOver: { postMessage: why => said({ state: 'taken-over', why: String(why || '') }) },
28
+ };
29
+ try { window.webkit = window.webkit || {}; window.webkit.messageHandlers = Object.assign(window.webkit.messageHandlers || {}, handlers); }
30
+ catch (e) { try { Object.defineProperty(window, 'webkit', { value: { messageHandlers: handlers }, configurable: true }); } catch (e) {} }
31
+
32
+ // The page's log, sent on a few lines at a time.
33
+ let lines = [], timer = null;
34
+ const say = (level, parts) => {
35
+ let text;
36
+ try { text = parts.map(p => typeof p === 'string' ? p : p instanceof Error ? (p.stack || p.message) : JSON.stringify(p)).join(' '); } catch (e) { text = String(parts[0]); }
37
+ lines.push({ level, text: String(text).slice(0, 4000) });
38
+ if (!timer) timer = later(() => { timer = null; const batch = lines; lines = []; post('log', { lines: batch }); }, 250);
39
+ };
40
+ for (const level of ['log', 'info', 'warn', 'error']) {
41
+ const real = console[level];
42
+ console[level] = (...parts) => { real.apply(console, parts); say(level === 'info' ? 'log' : level, parts); };
43
+ }
44
+ // A page with the game runtime logs its own errors; any other page's are logged here.
45
+ addEventListener('error', e => { if (!window.__lampLive) say('error', [e.message + ' (' + (String(e.filename || '').split('/').pop() || 'the page') + ':' + e.lineno + ')']); });
46
+ addEventListener('unhandledrejection', e => { if (!window.__lampLive) say('error', [String(e.reason && e.reason.message || e.reason)]); });
47
+ addEventListener('load', () => said({ state: 'page-loaded' }));
48
+
49
+ // Changes. The line says which change this copy's files had, so one saved while the page was opening comes as soon
50
+ // as the line is open; the browser says the last it heard when the line is opened again after a break.
51
+ // Opened again from where the player is, or from the game's beginning when that's asked: the page that opens next
52
+ // is told so before the runtime loads (see `FRESH` above). Any other page always starts over.
53
+ const reopen = e => {
54
+ if (e && e.data === 'fresh') {
55
+ try { sessionStorage.setItem(FRESH, '1'); } catch (e2) {}
56
+ return location.reload();
57
+ }
58
+ (window.__lampReloadNow || (() => location.reload()))();
59
+ };
60
+ const events = new EventSource('/__lamp/events?page=' + id + '&tab=' + tab + '&since=' + LAMP.gen);
61
+ // A server started again doesn't know what changed while it was away: the page opens again from the files as they are.
62
+ events.addEventListener('hello', e => { if (e.data !== LAMP.run) location.reload(); });
63
+ events.addEventListener('change', e => {
64
+ const change = JSON.parse(e.data);
65
+ // A game takes the change and keeps the player's place; any other page opens again.
66
+ let taken = false;
67
+ try { taken = !!(window.__lampLive && window.__lampLive(change)); } catch (e) {}
68
+ if (!taken) { said({ gen: change.gen, state: 'reloading' }); location.reload(); }
69
+ });
70
+ events.addEventListener('reload', reopen);
71
+ // With no browser lamp can look into, the page says what's on it itself.
72
+ events.addEventListener('look', e => post('looked', { ask: e.data, seen: window.__lampLook() }));
73
+
74
+ // What's on the page, as text: each control and canvas with the point at its middle, and the words shown.
75
+ const CONTROLS = 'button, a[href], input, select, textarea, summary, [role=button], [role=link], [role=tab], [role=checkbox], [role=switch], [role=slider], [role=menuitem], [contenteditable=""], [contenteditable=true]';
76
+ const words = text => String(text || '').replace(/\s+/g, ' ').trim().slice(0, 90);
77
+ const kind = el => {
78
+ const tag = el.tagName.toLowerCase(), role = el.getAttribute('role');
79
+ if (role) return role;
80
+ if (tag === 'a') return 'link';
81
+ if (tag === 'select') return 'choice';
82
+ if (tag === 'textarea' || el.isContentEditable) return 'field';
83
+ if (tag === 'input') return ({ checkbox: 'checkbox', radio: 'radio', range: 'slider', button: 'button', submit: 'button' })[el.type] || 'field';
84
+ return 'button';
85
+ };
86
+ const named = el => `${kind(el)} "${words(el.getAttribute('aria-label') || el.innerText || el.getAttribute('title') || el.getAttribute('placeholder') || el.getAttribute('alt') || el.name)}"`;
87
+
88
+ window.__lampLook = () => {
89
+ const lines = [], width = innerWidth, height = innerHeight;
90
+ const box = el => {
91
+ const r = el.getBoundingClientRect();
92
+ return r.width < 2 || r.height < 2 || r.bottom <= 0 || r.right <= 0 || r.top >= height || r.left >= width ? null : r;
93
+ };
94
+ const middle = r => Math.round(Math.min(Math.max(r.left + r.width / 2, 0), width - 1)) + ',' + Math.round(Math.min(Math.max(r.top + r.height / 2, 0), height - 1));
95
+ const walk = parent => {
96
+ for (const el of parent.children) {
97
+ if (lines.length > 160) return;
98
+ const tag = el.tagName.toLowerCase();
99
+ if (tag === 'script' || tag === 'style' || tag === 'template' || tag === 'noscript') continue;
100
+ const look = getComputedStyle(el);
101
+ // What's faded out, or nearly (a loading screen on its way out), isn't on the screen.
102
+ if (look.display === 'none' || Number(look.opacity) < 0.1) continue;
103
+ const hidden = look.visibility === 'hidden', r = hidden ? null : box(el);
104
+ if (r && tag === 'canvas') {
105
+ // A canvas drawn at another size than it's shown (a game's stage) says how a point of what's drawn is found
106
+ // in the picture.
107
+ const scale = el.width ? r.width / el.width : 1, round = n => Math.round(n * 100) / 100;
108
+ const drawn = Math.abs(scale - 1) > 0.02 && el.width > 1 ? ` (drawn at ${el.width}×${el.height}: its point x,y is at ${Math.round(r.left)}+x×${round(scale)}, ${Math.round(r.top)}+y×${round(r.height / el.height)} in the picture)` : '';
109
+ lines.push(`canvas ${Math.round(r.width)}×${Math.round(r.height)} at ${middle(r)}${drawn}`);
110
+ continue;
111
+ }
112
+ if (r && el.matches(CONTROLS)) {
113
+ const holds = el.type === 'checkbox' || el.type === 'radio' ? (el.checked ? ' (on)' : ' (off)')
114
+ : 'value' in el && typeof el.value === 'string' && el.value && tag !== 'button' ? ` = "${words(el.value)}"` : '';
115
+ lines.push(`${named(el)}${holds}${el.disabled ? ' (off for now)' : ''} at ${middle(r)}`);
116
+ continue;
117
+ }
118
+ if (r && tag === 'img') { const alt = words(el.alt); if (alt) lines.push(`picture "${alt}"`); continue; }
119
+ if (r) {
120
+ // Words set in pieces (a number in a span of its own, a heading in two colors) are one line, as they read.
121
+ const pieces = [...el.children];
122
+ const whole = pieces.length && (/^h[1-6]$/.test(tag) ? !el.querySelector(CONTROLS + ', canvas')
123
+ : pieces.every(c => getComputedStyle(c).display.startsWith('inline') && !c.children.length && !c.matches(CONTROLS) && c.tagName.toLowerCase() !== 'canvas'));
124
+ const own = words(whole ? el.innerText : [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join(' '));
125
+ if (own) lines.push(`${/^h[1-6]$/.test(tag) ? 'heading' : 'text'} "${own}"`);
126
+ if (whole) continue;
127
+ }
128
+ walk(el);
129
+ }
130
+ };
131
+ if (document.body) walk(document.body);
132
+ if (lines.length > 160) lines.push('… and more');
133
+ // A game drawn on a stage of its own shape, fitted in the window with bars beside or above it: the stage's size.
134
+ // One that fills the window, whatever its shape, has none.
135
+ let stage = null;
136
+ if (window.__lampLive) {
137
+ const canvas = [...document.querySelectorAll('canvas')].map(c => [c, c.getBoundingClientRect()]).sort((a, b) => b[1].width * b[1].height - a[1].width * a[1].height)[0];
138
+ if (canvas && canvas[0].width > 1 && canvas[0].height > 1 && (width - canvas[1].width >= 2 || height - canvas[1].height >= 2)) stage = [canvas[0].width, canvas[0].height];
139
+ }
140
+ return { tab, stage, title: document.title, path: location.pathname + location.search, width, height, dpr: devicePixelRatio || 1, game: !!window.__lampLive, screen: lines.join('\n') };
141
+ };
142
+
143
+ // The point of the control called this in the screen as text (`button "Add"`, or just `Add`), for a click by name.
144
+ window.__lampPoint = called => {
145
+ const wanted = String(called).trim();
146
+ for (const el of document.querySelectorAll(CONTROLS)) {
147
+ const r = el.getBoundingClientRect(), look = getComputedStyle(el);
148
+ if (r.width < 2 || r.height < 2 || look.display === 'none' || look.visibility === 'hidden') continue;
149
+ const name = named(el), label = name.slice(name.indexOf('"') + 1, -1);
150
+ if (name === wanted || label === wanted) return [Math.round(r.left + r.width / 2), Math.round(r.top + r.height / 2)];
151
+ }
152
+ return null;
153
+ };
154
+
155
+ // What's at a point of the page, in the screen's own words, so a click says what it landed on.
156
+ window.__lampAt = (x, y) => {
157
+ const el = document.elementFromPoint(x, y);
158
+ if (!el || el === document.documentElement || el === document.body) return 'nothing: the page\'s background';
159
+ const control = el.closest(CONTROLS);
160
+ if (control) return named(control);
161
+ if (el.tagName.toLowerCase() === 'canvas') return 'the canvas';
162
+ const text = words(el.innerText).slice(0, 50);
163
+ return text ? `the text "${text}", which isn't a control` : 'nothing that\'s a control';
164
+ };
165
+ })();