@lampdotdev/lamp 0.0.0-stage → 0.36.2-nightly.16
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.
- package/LICENSE +21 -0
- package/README.md +397 -2
- package/Resources/Devices/roku.json +410 -0
- package/Resources/Devices/xbox-one.json +367 -0
- package/Resources/GameKit/NOTICE.md +63 -0
- package/Resources/GameKit/apple/LampPage.swift +1813 -0
- package/Resources/GameKit/apple/LampPlayer.swift +46 -0
- package/Resources/GameKit/apple/LampWebApp.swift +63 -0
- package/Resources/GameKit/apple/LampWebGame.swift +87 -0
- package/Resources/GameKit/apple/Native.swift +12 -0
- package/Resources/GameKit/build.sh +55 -0
- package/Resources/GameKit/lamp_game.h +116 -0
- package/Resources/GameKit/lamp_host.h +19 -0
- package/Resources/GameKit/lamp_kit.cpp +61 -0
- package/Resources/GameKit/lamp_sound.cpp +455 -0
- package/Resources/GameKit/lamp_xbox.cpp +374 -0
- package/Resources/GameKit/libraries/HandmadeMath.h +3940 -0
- package/Resources/GameKit/libraries/cgltf.h +7240 -0
- package/Resources/GameKit/libraries/dr_mp3.h +5430 -0
- package/Resources/GameKit/libraries/dr_wav.h +9209 -0
- package/Resources/GameKit/libraries/sokol_debugtext.h +4754 -0
- package/Resources/GameKit/libraries/sokol_gfx.h +29558 -0
- package/Resources/GameKit/libraries/sokol_gl.h +4538 -0
- package/Resources/GameKit/libraries/sokol_log.h +335 -0
- package/Resources/GameKit/libraries/sokol_shape.h +1531 -0
- package/Resources/GameKit/libraries/stb_image.h +7988 -0
- package/Resources/GameKit/libraries/stb_vorbis.c +5584 -0
- package/Resources/GameKit/page/lamp-live.js +672 -0
- package/Resources/GameKit/scaffold/Game/game.cpp +142 -0
- package/Resources/GameKit/scaffold/Game/scene.glsl +49 -0
- package/Resources/GameKit/scaffold-web/Web/game.js +31 -0
- package/Resources/GameKit/scaffold-web/Web/index.html +13 -0
- package/Resources/GameKit/scaffold-web/Web/making.js +33 -0
- package/Resources/GameKit/scaffold-web/Web/world.js +33 -0
- package/Resources/GameKit/web/lamp-game.js +3042 -0
- package/Resources/GameKit/web/lamp-phaser.js +128 -0
- package/Resources/WebHost/App.exe +0 -0
- package/Resources/WebHost/Microsoft.Web.WebView2.Core.dll +0 -0
- package/Resources/WebHost/WebView2-LICENSE.txt +27 -0
- package/Resources/WebHost/lamp_controllers.h +1506 -0
- package/Resources/WebHost/lamp_web_host.cpp +1173 -0
- package/Resources/WebLibraries/earlier.txt +318 -0
- package/Resources/WebLibraries/phaser/LICENSE.md +20 -0
- package/Resources/WebLibraries/phaser/NOTICE.md +2 -0
- package/Resources/WebLibraries/phaser/phaser.esm.min.js +1 -0
- package/Resources/WebLibraries/qrcode/LICENSE +21 -0
- package/Resources/WebLibraries/qrcode/NOTICE.md +4 -0
- package/Resources/WebLibraries/qrcode/qrcode.js +2297 -0
- package/Resources/WebLibraries/rapier/LICENSE +201 -0
- package/Resources/WebLibraries/rapier/NOTICE.md +3 -0
- package/Resources/WebLibraries/rapier/rapier.js +2 -0
- package/Resources/WebLibraries/three/LICENSE +21 -0
- package/Resources/WebLibraries/three/NOTICE.md +4 -0
- package/Resources/WebLibraries/three/addons/animation/AnimationClipCreator.js +168 -0
- package/Resources/WebLibraries/three/addons/animation/CCDIKSolver.js +607 -0
- package/Resources/WebLibraries/three/addons/controls/ArcballControls.js +3543 -0
- package/Resources/WebLibraries/three/addons/controls/DragControls.js +452 -0
- package/Resources/WebLibraries/three/addons/controls/FirstPersonControls.js +512 -0
- package/Resources/WebLibraries/three/addons/controls/FlyControls.js +384 -0
- package/Resources/WebLibraries/three/addons/controls/MapControls.js +116 -0
- package/Resources/WebLibraries/three/addons/controls/OrbitControls.js +1985 -0
- package/Resources/WebLibraries/three/addons/controls/PointerLockControls.js +264 -0
- package/Resources/WebLibraries/three/addons/controls/TrackballControls.js +1106 -0
- package/Resources/WebLibraries/three/addons/controls/TransformControls.js +2003 -0
- package/Resources/WebLibraries/three/addons/curves/CurveExtras.js +694 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSCurve.js +169 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSSurface.js +98 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSUtils.js +532 -0
- package/Resources/WebLibraries/three/addons/curves/NURBSVolume.js +82 -0
- package/Resources/WebLibraries/three/addons/effects/AnaglyphEffect.js +274 -0
- package/Resources/WebLibraries/three/addons/effects/AsciiEffect.js +323 -0
- package/Resources/WebLibraries/three/addons/effects/OutlineEffect.js +489 -0
- package/Resources/WebLibraries/three/addons/effects/ParallaxBarrierEffect.js +155 -0
- package/Resources/WebLibraries/three/addons/effects/StereoEffect.js +91 -0
- package/Resources/WebLibraries/three/addons/environments/ColorEnvironment.js +59 -0
- package/Resources/WebLibraries/three/addons/environments/DebugEnvironment.js +102 -0
- package/Resources/WebLibraries/three/addons/environments/RoomEnvironment.js +185 -0
- package/Resources/WebLibraries/three/addons/geometries/BoxLineGeometry.js +92 -0
- package/Resources/WebLibraries/three/addons/geometries/ConvexGeometry.js +72 -0
- package/Resources/WebLibraries/three/addons/geometries/DecalGeometry.js +420 -0
- package/Resources/WebLibraries/three/addons/geometries/LoftGeometry.js +355 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricFunctions.js +100 -0
- package/Resources/WebLibraries/three/addons/geometries/ParametricGeometry.js +172 -0
- package/Resources/WebLibraries/three/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/Resources/WebLibraries/three/addons/geometries/TeapotGeometry.js +689 -0
- package/Resources/WebLibraries/three/addons/geometries/TextGeometry.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/AnimationPathHelper.js +302 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelper.js +166 -0
- package/Resources/WebLibraries/three/addons/helpers/LightProbeHelperGPU.js +102 -0
- package/Resources/WebLibraries/three/addons/helpers/OctreeHelper.js +109 -0
- package/Resources/WebLibraries/three/addons/helpers/PositionalAudioHelper.js +169 -0
- package/Resources/WebLibraries/three/addons/helpers/RapierHelper.js +59 -0
- package/Resources/WebLibraries/three/addons/helpers/RectAreaLightHelper.js +118 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelper.js +265 -0
- package/Resources/WebLibraries/three/addons/helpers/TextureHelperGPU.js +214 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexNormalsHelper.js +155 -0
- package/Resources/WebLibraries/three/addons/helpers/VertexTangentsHelper.js +133 -0
- package/Resources/WebLibraries/three/addons/helpers/ViewHelper.js +529 -0
- package/Resources/WebLibraries/three/addons/interactive/HTMLMesh.js +617 -0
- package/Resources/WebLibraries/three/addons/interactive/InteractiveGroup.js +224 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionBox.js +324 -0
- package/Resources/WebLibraries/three/addons/interactive/SelectionHelper.js +150 -0
- package/Resources/WebLibraries/three/addons/libs/fflate.module.js +2699 -0
- package/Resources/WebLibraries/three/addons/libs/lil-gui.module.min.js +8 -0
- package/Resources/WebLibraries/three/addons/libs/meshopt_decoder.module.js +196 -0
- package/Resources/WebLibraries/three/addons/libs/potpack.module.js +125 -0
- package/Resources/WebLibraries/three/addons/libs/stats.module.js +167 -0
- package/Resources/WebLibraries/three/addons/libs/tween.module.js +876 -0
- package/Resources/WebLibraries/three/addons/lighting/ClusteredLighting.js +153 -0
- package/Resources/WebLibraries/three/addons/lighting/DynamicLighting.js +82 -0
- package/Resources/WebLibraries/three/addons/lighting/LightMapper.js +295 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGrid.js +923 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridUtils.js +107 -0
- package/Resources/WebLibraries/three/addons/lighting/LightProbeGridWebGL.js +846 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIConeTracer.js +243 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIIrradiance.js +43 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGINode.js +617 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGISceneCollector.js +793 -0
- package/Resources/WebLibraries/three/addons/lighting/vxgi/VXGIVolume.js +1440 -0
- package/Resources/WebLibraries/three/addons/lights/LightProbeGenerator.js +337 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightTexturesLib.js +127 -0
- package/Resources/WebLibraries/three/addons/lights/RectAreaLightUniformsLib.js +40 -0
- package/Resources/WebLibraries/three/addons/lights/SunLight.js +100 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightNode.js +56 -0
- package/Resources/WebLibraries/three/addons/lights/SunLightShadow.js +319 -0
- package/Resources/WebLibraries/three/addons/lights/SunShadowNode.js +312 -0
- package/Resources/WebLibraries/three/addons/lines/Line2.js +56 -0
- package/Resources/WebLibraries/three/addons/lines/LineGeometry.js +157 -0
- package/Resources/WebLibraries/three/addons/lines/LineMaterial.js +742 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegments2.js +434 -0
- package/Resources/WebLibraries/three/addons/lines/LineSegmentsGeometry.js +298 -0
- package/Resources/WebLibraries/three/addons/lines/Wireframe.js +108 -0
- package/Resources/WebLibraries/three/addons/lines/WireframeGeometry2.js +49 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Line2.js +46 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/LineSegments2.js +419 -0
- package/Resources/WebLibraries/three/addons/lines/webgpu/Wireframe.js +86 -0
- package/Resources/WebLibraries/three/addons/loaders/EXRLoader.js +3411 -0
- package/Resources/WebLibraries/three/addons/loaders/FBXLoader.js +4660 -0
- package/Resources/WebLibraries/three/addons/loaders/FontLoader.js +262 -0
- package/Resources/WebLibraries/three/addons/loaders/GLTFLoader.js +5006 -0
- package/Resources/WebLibraries/three/addons/loaders/HDRLoader.js +463 -0
- package/Resources/WebLibraries/three/addons/loaders/MTLLoader.js +601 -0
- package/Resources/WebLibraries/three/addons/loaders/OBJLoader.js +961 -0
- package/Resources/WebLibraries/three/addons/loaders/PLYLoader.js +992 -0
- package/Resources/WebLibraries/three/addons/loaders/RGBELoader.js +18 -0
- package/Resources/WebLibraries/three/addons/loaders/STLLoader.js +423 -0
- package/Resources/WebLibraries/three/addons/loaders/SVGLoader.js +3297 -0
- package/Resources/WebLibraries/three/addons/loaders/TGALoader.js +539 -0
- package/Resources/WebLibraries/three/addons/loaders/UltraHDRLoader.js +757 -0
- package/Resources/WebLibraries/three/addons/math/Capsule.js +159 -0
- package/Resources/WebLibraries/three/addons/math/ColorConverter.js +62 -0
- package/Resources/WebLibraries/three/addons/math/ColorSpaces.js +147 -0
- package/Resources/WebLibraries/three/addons/math/ConvexHull.js +1695 -0
- package/Resources/WebLibraries/three/addons/math/ImprovedNoise.js +88 -0
- package/Resources/WebLibraries/three/addons/math/Lut.js +317 -0
- package/Resources/WebLibraries/three/addons/math/MeshSurfaceSampler.js +315 -0
- package/Resources/WebLibraries/three/addons/math/OBB.js +535 -0
- package/Resources/WebLibraries/three/addons/math/Octree.js +825 -0
- package/Resources/WebLibraries/three/addons/math/SimplexNoise.js +470 -0
- package/Resources/WebLibraries/three/addons/misc/ConvexObjectBreaker.js +539 -0
- package/Resources/WebLibraries/three/addons/misc/GPUComputationRenderer.js +508 -0
- package/Resources/WebLibraries/three/addons/misc/Gyroscope.js +78 -0
- package/Resources/WebLibraries/three/addons/misc/MorphAnimMesh.js +119 -0
- package/Resources/WebLibraries/three/addons/misc/MorphBlendMesh.js +425 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMap.js +370 -0
- package/Resources/WebLibraries/three/addons/misc/ProgressiveLightMapGPU.js +322 -0
- package/Resources/WebLibraries/three/addons/misc/RollerCoaster.js +661 -0
- package/Resources/WebLibraries/three/addons/misc/Sculptor.js +1679 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorMesh.js +1504 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorTools.js +1180 -0
- package/Resources/WebLibraries/three/addons/misc/SculptorUtils.js +345 -0
- package/Resources/WebLibraries/three/addons/misc/TileCreasedNormalsPlugin.js +270 -0
- package/Resources/WebLibraries/three/addons/misc/TubePainter.js +611 -0
- package/Resources/WebLibraries/three/addons/misc/Volume.js +520 -0
- package/Resources/WebLibraries/three/addons/misc/VolumeSlice.js +275 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifier.js +374 -0
- package/Resources/WebLibraries/three/addons/modifiers/CurveModifierGPU.js +256 -0
- package/Resources/WebLibraries/three/addons/modifiers/EdgeSplitModifier.js +299 -0
- package/Resources/WebLibraries/three/addons/modifiers/TessellateModifier.js +340 -0
- package/Resources/WebLibraries/three/addons/objects/GroundedSkybox.js +69 -0
- package/Resources/WebLibraries/three/addons/objects/Lensflare.js +489 -0
- package/Resources/WebLibraries/three/addons/objects/LensflareMesh.js +376 -0
- package/Resources/WebLibraries/three/addons/objects/MarchingCubes.js +1261 -0
- package/Resources/WebLibraries/three/addons/objects/Reflector.js +390 -0
- package/Resources/WebLibraries/three/addons/objects/ReflectorForSSRPass.js +393 -0
- package/Resources/WebLibraries/three/addons/objects/Refractor.js +389 -0
- package/Resources/WebLibraries/three/addons/objects/ShadowMesh.js +130 -0
- package/Resources/WebLibraries/three/addons/objects/Sky.js +342 -0
- package/Resources/WebLibraries/three/addons/objects/SkyMesh.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water.js +374 -0
- package/Resources/WebLibraries/three/addons/objects/Water2.js +403 -0
- package/Resources/WebLibraries/three/addons/objects/Water2Mesh.js +199 -0
- package/Resources/WebLibraries/three/addons/objects/WaterMesh.js +194 -0
- package/Resources/WebLibraries/three/addons/postprocessing/AfterimagePass.js +187 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BloomPass.js +274 -0
- package/Resources/WebLibraries/three/addons/postprocessing/BokehPass.js +218 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ClearPass.js +97 -0
- package/Resources/WebLibraries/three/addons/postprocessing/CubeTexturePass.js +146 -0
- package/Resources/WebLibraries/three/addons/postprocessing/DotScreenPass.js +114 -0
- package/Resources/WebLibraries/three/addons/postprocessing/EffectComposer.js +365 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FXAAPass.js +40 -0
- package/Resources/WebLibraries/three/addons/postprocessing/FilmPass.js +113 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GTAOPass.js +735 -0
- package/Resources/WebLibraries/three/addons/postprocessing/GlitchPass.js +177 -0
- package/Resources/WebLibraries/three/addons/postprocessing/HalftonePass.js +134 -0
- package/Resources/WebLibraries/three/addons/postprocessing/LUTPass.js +138 -0
- package/Resources/WebLibraries/three/addons/postprocessing/MaskPass.js +195 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutlinePass.js +776 -0
- package/Resources/WebLibraries/three/addons/postprocessing/OutputPass.js +148 -0
- package/Resources/WebLibraries/three/addons/postprocessing/Pass.js +191 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPass.js +193 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderPixelatedPass.js +314 -0
- package/Resources/WebLibraries/three/addons/postprocessing/RenderTransitionPass.js +267 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SAOPass.js +407 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SMAAPass.js +230 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAARenderPass.js +313 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSAOPass.js +527 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SSRPass.js +858 -0
- package/Resources/WebLibraries/three/addons/postprocessing/SavePass.js +132 -0
- package/Resources/WebLibraries/three/addons/postprocessing/ShaderPass.js +135 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TAARenderPass.js +243 -0
- package/Resources/WebLibraries/three/addons/postprocessing/TexturePass.js +131 -0
- package/Resources/WebLibraries/three/addons/postprocessing/UnrealBloomPass.js +541 -0
- package/Resources/WebLibraries/three/addons/shaders/ACESFilmicToneMappingShader.js +96 -0
- package/Resources/WebLibraries/three/addons/shaders/AfterimageShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/BasicShader.js +36 -0
- package/Resources/WebLibraries/three/addons/shaders/BleachBypassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/BlendShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader.js +151 -0
- package/Resources/WebLibraries/three/addons/shaders/BokehShader2.js +404 -0
- package/Resources/WebLibraries/three/addons/shaders/BrightnessContrastShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorCorrectionShader.js +59 -0
- package/Resources/WebLibraries/three/addons/shaders/ColorifyShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/ConvolutionShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/CopyShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/DOFMipMapShader.js +63 -0
- package/Resources/WebLibraries/three/addons/shaders/DepthLimitedBlurShader.js +183 -0
- package/Resources/WebLibraries/three/addons/shaders/DigitalGlitch.js +104 -0
- package/Resources/WebLibraries/three/addons/shaders/DotScreenShader.js +75 -0
- package/Resources/WebLibraries/three/addons/shaders/ExposureShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/FXAAShader.js +298 -0
- package/Resources/WebLibraries/three/addons/shaders/FilmShader.js +73 -0
- package/Resources/WebLibraries/three/addons/shaders/FocusShader.js +94 -0
- package/Resources/WebLibraries/three/addons/shaders/FreiChenShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/GTAOShader.js +437 -0
- package/Resources/WebLibraries/three/addons/shaders/GammaCorrectionShader.js +52 -0
- package/Resources/WebLibraries/three/addons/shaders/HalftoneShader.js +332 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalBlurShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/HorizontalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/HueSaturationShader.js +74 -0
- package/Resources/WebLibraries/three/addons/shaders/KaleidoShader.js +65 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityHighPassShader.js +68 -0
- package/Resources/WebLibraries/three/addons/shaders/LuminosityShader.js +54 -0
- package/Resources/WebLibraries/three/addons/shaders/MirrorShader.js +62 -0
- package/Resources/WebLibraries/three/addons/shaders/NormalMapShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/OutputShader.js +103 -0
- package/Resources/WebLibraries/three/addons/shaders/PoissonDenoiseShader.js +230 -0
- package/Resources/WebLibraries/three/addons/shaders/RGBShiftShader.js +61 -0
- package/Resources/WebLibraries/three/addons/shaders/SAOShader.js +201 -0
- package/Resources/WebLibraries/three/addons/shaders/SMAAShader.js +489 -0
- package/Resources/WebLibraries/three/addons/shaders/SSAOShader.js +331 -0
- package/Resources/WebLibraries/three/addons/shaders/SSRShader.js +396 -0
- package/Resources/WebLibraries/three/addons/shaders/SepiaShader.js +57 -0
- package/Resources/WebLibraries/three/addons/shaders/SobelOperatorShader.js +98 -0
- package/Resources/WebLibraries/three/addons/shaders/SubsurfaceScatteringShader.js +95 -0
- package/Resources/WebLibraries/three/addons/shaders/TechnicolorShader.js +51 -0
- package/Resources/WebLibraries/three/addons/shaders/ToonShader.js +349 -0
- package/Resources/WebLibraries/three/addons/shaders/TriangleBlurShader.js +79 -0
- package/Resources/WebLibraries/three/addons/shaders/UnpackDepthRGBAShader.js +60 -0
- package/Resources/WebLibraries/three/addons/shaders/VelocityShader.js +137 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalBlurShader.js +66 -0
- package/Resources/WebLibraries/three/addons/shaders/VerticalTiltShiftShader.js +70 -0
- package/Resources/WebLibraries/three/addons/shaders/VignetteShader.js +56 -0
- package/Resources/WebLibraries/three/addons/shaders/VolumeShader.js +285 -0
- package/Resources/WebLibraries/three/addons/shaders/WaterRefractionShader.js +106 -0
- package/Resources/WebLibraries/three/addons/textures/FlakesTexture.js +54 -0
- package/Resources/WebLibraries/three/addons/tsl/WebGLNodesHandler.js +607 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AfterImageNode.js +256 -0
- package/Resources/WebLibraries/three/addons/tsl/display/AnaglyphPassNode.js +550 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BilateralBlurNode.js +377 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BleachBypass.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/BloomNode.js +599 -0
- package/Resources/WebLibraries/three/addons/tsl/display/CRT.js +150 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ChromaticAberrationNode.js +163 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DenoiseNode.js +354 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DepthOfFieldNode.js +572 -0
- package/Resources/WebLibraries/three/addons/tsl/display/DotScreenNode.js +104 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FSR1Node.js +481 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FXAANode.js +357 -0
- package/Resources/WebLibraries/three/addons/tsl/display/FilmNode.js +101 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GTAONode.js +727 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GaussianBlurNode.js +412 -0
- package/Resources/WebLibraries/three/addons/tsl/display/GodraysNode.js +604 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ImportanceSampledEnvironment.js +564 -0
- package/Resources/WebLibraries/three/addons/tsl/display/LensflareNode.js +282 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Lut3DNode.js +109 -0
- package/Resources/WebLibraries/three/addons/tsl/display/MotionBlur.js +33 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OITPassNode.js +341 -0
- package/Resources/WebLibraries/three/addons/tsl/display/OutlineNode.js +916 -0
- package/Resources/WebLibraries/three/addons/tsl/display/ParallaxBarrierPassNode.js +90 -0
- package/Resources/WebLibraries/three/addons/tsl/display/PixelationPassNode.js +227 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RGBShiftNode.js +96 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RecurrentDenoiseNode.js +812 -0
- package/Resources/WebLibraries/three/addons/tsl/display/RetroPassNode.js +263 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SMAANode.js +729 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAAPassNode.js +344 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSAONode.js +422 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSGINode.js +698 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSRNode.js +1430 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SSSNode.js +479 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Sepia.js +24 -0
- package/Resources/WebLibraries/three/addons/tsl/display/Shape.js +29 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SharpenNode.js +284 -0
- package/Resources/WebLibraries/three/addons/tsl/display/SobelOperatorNode.js +168 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoCompositePassNode.js +190 -0
- package/Resources/WebLibraries/three/addons/tsl/display/StereoPassNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TAAUNode.js +700 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TRAANode.js +609 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TemporalReprojectNode.js +1024 -0
- package/Resources/WebLibraries/three/addons/tsl/display/TransitionNode.js +141 -0
- package/Resources/WebLibraries/three/addons/tsl/display/boxBlur.js +65 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlend.js +80 -0
- package/Resources/WebLibraries/three/addons/tsl/display/depthAwareBlur.js +63 -0
- package/Resources/WebLibraries/three/addons/tsl/display/hashBlur.js +55 -0
- package/Resources/WebLibraries/three/addons/tsl/display/lensDirt.js +30 -0
- package/Resources/WebLibraries/three/addons/tsl/display/radialBlur.js +68 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/ClusteredLightsNode.js +994 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/DynamicLightsNode.js +201 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/LightProbeGridNode.js +183 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/AmbientLightDataNode.js +67 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/DirectionalLightDataNode.js +117 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/HemisphereLightDataNode.js +105 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/PointLightDataNode.js +140 -0
- package/Resources/WebLibraries/three/addons/tsl/lighting/data/SpotLightDataNode.js +167 -0
- package/Resources/WebLibraries/three/addons/tsl/math/Bayer.js +84 -0
- package/Resources/WebLibraries/three/addons/tsl/math/curlNoise.js +107 -0
- package/Resources/WebLibraries/three/addons/tsl/math/voronoiNoise.js +112 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNode.js +542 -0
- package/Resources/WebLibraries/three/addons/tsl/shadows/TileShadowNodeHelper.js +212 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/GroundedSkybox.js +62 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/RNoise.js +51 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/Raymarching.js +70 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SoftParticles.js +53 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/SpecularHelpers.js +302 -0
- package/Resources/WebLibraries/three/addons/tsl/utils/TAAUtils.js +184 -0
- package/Resources/WebLibraries/three/addons/utils/BufferGeometryUtils.js +1523 -0
- package/Resources/WebLibraries/three/addons/utils/CameraUtils.js +82 -0
- package/Resources/WebLibraries/three/addons/utils/ColorUtils.js +76 -0
- package/Resources/WebLibraries/three/addons/utils/GaussianSplatUtils.js +229 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryCompressionUtils.js +547 -0
- package/Resources/WebLibraries/three/addons/utils/GeometryUtils.js +226 -0
- package/Resources/WebLibraries/three/addons/utils/LDrawUtils.js +211 -0
- package/Resources/WebLibraries/three/addons/utils/SceneOptimizer.js +459 -0
- package/Resources/WebLibraries/three/addons/utils/SceneUtils.js +363 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewer.js +244 -0
- package/Resources/WebLibraries/three/addons/utils/ShadowMapViewerGPU.js +233 -0
- package/Resources/WebLibraries/three/addons/utils/SkeletonUtils.js +496 -0
- package/Resources/WebLibraries/three/addons/utils/SortUtils.js +175 -0
- package/Resources/WebLibraries/three/addons/utils/UVsDebug.js +173 -0
- package/Resources/WebLibraries/three/addons/utils/WebGLTextureUtils.js +115 -0
- package/Resources/WebLibraries/three/addons/utils/WebGPUTextureUtils.js +81 -0
- package/Resources/WebLibraries/three/addons/utils/WorkerPool.js +167 -0
- package/Resources/WebLibraries/three/addons/utils/unwrapUVs.js +865 -0
- package/Resources/WebLibraries/three/three.core.js +61162 -0
- package/Resources/WebLibraries/three/three.module.js +19537 -0
- package/Resources/WebLibraries/three/three.tsl.js +41903 -0
- package/Resources/WebLibraries/three/three.webgpu.js +53714 -0
- package/Resources/share-rules.json +64 -0
- package/Resources/skills/2d-game/SKILL.md +305 -0
- package/Resources/skills/3d-game/SKILL.md +274 -0
- package/Resources/skills/accounts/SKILL.md +41 -0
- package/Resources/skills/android-design/SKILL.md +159 -0
- package/Resources/skills/app-ai/SKILL.md +154 -0
- package/Resources/skills/app-intents/SKILL.md +31 -0
- package/Resources/skills/app-kinds/SKILL.md +122 -0
- package/Resources/skills/apple-watch/SKILL.md +43 -0
- package/Resources/skills/assets/SKILL.md +183 -0
- package/Resources/skills/building-blocks/SKILL.md +97 -0
- package/Resources/skills/device-control/SKILL.md +56 -0
- package/Resources/skills/device-integrations/SKILL.md +278 -0
- package/Resources/skills/finder-files/SKILL.md +19 -0
- package/Resources/skills/game/SKILL.md +416 -0
- package/Resources/skills/game-3d/SKILL.md +12 -0
- package/Resources/skills/game-art/SKILL.md +11 -0
- package/Resources/skills/game-feel/SKILL.md +13 -0
- package/Resources/skills/game-levels/SKILL.md +12 -0
- package/Resources/skills/game-live/SKILL.md +12 -0
- package/Resources/skills/game-physics/SKILL.md +13 -0
- package/Resources/skills/game-playtest/SKILL.md +26 -0
- package/Resources/skills/game-ui/SKILL.md +278 -0
- package/Resources/skills/github-projects/SKILL.md +62 -0
- package/Resources/skills/icloud-sync/SKILL.md +30 -0
- package/Resources/skills/iphone-hardware/SKILL.md +57 -0
- package/Resources/skills/iphone-widgets/SKILL.md +53 -0
- package/Resources/skills/lg-developer-mode/SKILL.md +152 -0
- package/Resources/skills/mac-widgets/SKILL.md +60 -0
- package/Resources/skills/maps/SKILL.md +8 -0
- package/Resources/skills/menu-bar/SKILL.md +36 -0
- package/Resources/skills/more-apps/SKILL.md +53 -0
- package/Resources/skills/phone-controllers/SKILL.md +79 -0
- package/Resources/skills/phone-pages/SKILL.md +173 -0
- package/Resources/skills/roku-developer-mode/SKILL.md +106 -0
- package/Resources/skills/scene-3d/SKILL.md +27 -0
- package/Resources/skills/scripting/SKILL.md +17 -0
- package/Resources/skills/share-extension/SKILL.md +22 -0
- package/Resources/skills/tv-design/SKILL.md +72 -0
- package/Resources/skills/web-app/SKILL.md +170 -0
- package/Resources/skills/web-game/SKILL.md +95 -0
- package/Resources/skills/web-or-native/SKILL.md +108 -0
- package/Resources/skills/wish-links/SKILL.md +156 -0
- package/THIRD_PARTY_NOTICES.md +170 -0
- package/cli/ai.mjs +237 -0
- package/cli/android.mjs +941 -0
- package/cli/app-ai.mjs +473 -0
- package/cli/browser.mjs +284 -0
- package/cli/calls.mjs +594 -0
- package/cli/dev.mjs +820 -0
- package/cli/devices.mjs +984 -0
- package/cli/lamp.mjs +1326 -0
- package/cli/lampapp.mjs +332 -0
- package/cli/lamps/android-host/LampPageGateway.kt +345 -0
- package/cli/lamps/android-host/app/build.gradle.kts +40 -0
- package/cli/lamps/android-host/build.gradle.kts +4 -0
- package/cli/lamps/android-host/game/AndroidManifest.xml +33 -0
- package/cli/lamps/android-host/game/MainActivity.kt +173 -0
- package/cli/lamps/android-host/gradle/libs.versions.toml +39 -0
- package/cli/lamps/android-host/gradle.properties +8 -0
- package/cli/lamps/android-host/page/AndroidManifest.xml +35 -0
- package/cli/lamps/android-host/page/MainActivity.kt +193 -0
- package/cli/lamps/android-host/settings.gradle.kts +30 -0
- package/cli/lamps/android-host/tools.json +43 -0
- package/cli/lamps/lamp-controllers.js +2 -0
- package/cli/lamps/lamp-for-games.js +6922 -0
- package/cli/lamps/lamp.js +7082 -0
- package/cli/lamps/mac-starter/.lamp/meta.json +1 -0
- package/cli/lamps/mac-starter/APP.md +12 -0
- package/cli/lamps/mac-starter/Package.swift +8 -0
- package/cli/lamps/mac-starter/Sources/App.swift +11 -0
- package/cli/lamps/mac-starter/Sources/ContentView.swift +17 -0
- package/cli/lamps/mac-starter/Sources/LampSupport.swift +18215 -0
- package/cli/lamps/mac-starter/build.sh +336 -0
- package/cli/lamps/mac-starter/gitignore +3 -0
- package/cli/lamps/mac-starter/lamp-starter.json +6 -0
- package/cli/lamps/web-starter/APP.md +8 -0
- package/cli/lamps/web-starter/app.js +1 -0
- package/cli/lamps/web-starter/index.html +17 -0
- package/cli/lamps/web-starter/lamp-starter.json +5 -0
- package/cli/lamps/web-starter/lamp.js +7082 -0
- package/cli/lamps/web-starter/style.css +25 -0
- package/cli/link.mjs +202 -0
- package/cli/local-ai.mjs +193 -0
- package/cli/mac.mjs +621 -0
- package/cli/mdns.mjs +205 -0
- package/cli/msix.mjs +413 -0
- package/cli/network.mjs +209 -0
- package/cli/openrouter.mjs +133 -0
- package/cli/outside.mjs +203 -0
- package/cli/page.js +317 -0
- package/cli/place.mjs +298 -0
- package/cli/png.mjs +184 -0
- package/cli/share.mjs +270 -0
- package/cli/skill/SKILL.md +280 -0
- package/cli/skills.mjs +71 -0
- package/cli/state.mjs +176 -0
- package/cli/update.mjs +162 -0
- package/package.json +20 -4
- package/site/lib/quota.js +59 -0
- package/site/lib/shared-wishes.js +1727 -0
package/cli/page.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
const unlater = window.clearTimeout.bind(window), every = window.setInterval.bind(window), unevery = window.clearInterval.bind(window);
|
|
9
|
+
// A page opened again to show its beginning (`lamp look --reopen`) says so to the game runtime, which then starts
|
|
10
|
+
// the game from its start, its saves kept (`window.lampFreshStart` in lamp-game.js).
|
|
11
|
+
const FRESH = 'lamp.dev.fresh';
|
|
12
|
+
try { if (sessionStorage.getItem(FRESH)) { sessionStorage.removeItem(FRESH); window.lampFreshStart = true; } } catch (e) {}
|
|
13
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
14
|
+
// The tab, which stays the same as its page opens again: how a change went is told of the tab.
|
|
15
|
+
let tab = id;
|
|
16
|
+
try { tab = sessionStorage.getItem('lamp.dev.tab') || id; sessionStorage.setItem('lamp.dev.tab', tab); } catch (e) {}
|
|
17
|
+
window.__lampTab = tab;
|
|
18
|
+
const post = (path, body, lasting) => {
|
|
19
|
+
try { ask('/__lamp/' + path, { method: 'POST', body: JSON.stringify({ page: id, ...body }), keepalive: !!lasting }).catch(() => {}); } catch (e) {}
|
|
20
|
+
};
|
|
21
|
+
const said = message => post('said', message, true);
|
|
22
|
+
|
|
23
|
+
// MARK: The page's AI
|
|
24
|
+
// LampAI in lamp.js reaches Lamp's app through a host bridge when the app shows the page; here lamp itself answers
|
|
25
|
+
// (`/__lamp/ai`, with the person's own Claude Code or Codex), through the same shape of bridge lamp.js takes from
|
|
26
|
+
// Lamp's Android app: postMessage of a request, an onmessage with its answer.
|
|
27
|
+
if (!window.lampGatewayHost && !(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.lampGateway)) {
|
|
28
|
+
const gateway = { onmessage: null };
|
|
29
|
+
gateway.postMessage = (text) => {
|
|
30
|
+
let asked;
|
|
31
|
+
try { asked = JSON.parse(text); } catch (e) { return; }
|
|
32
|
+
const answer = (status, body) => { if (typeof gateway.onmessage === 'function') gateway.onmessage({ data: JSON.stringify({ id: asked.id, status, body }) }); };
|
|
33
|
+
const stop = new AbortController();
|
|
34
|
+
const timer = later(() => stop.abort(), Math.max(1000, Number(asked.timeout) || 240000));
|
|
35
|
+
ask('/__lamp/ai', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Lamp-Page': '1' }, body: JSON.stringify({ path: asked.path, body: asked.body }), signal: stop.signal })
|
|
36
|
+
.then((r) => r.json()).then((r) => answer(r.status, r.body), () => answer(502, JSON.stringify({ error: 'lamp dev took too long to answer.' })))
|
|
37
|
+
.finally(() => unlater(timer));
|
|
38
|
+
};
|
|
39
|
+
try { Object.defineProperty(window, 'lampGatewayHost', { value: gateway, configurable: true }); } catch (e) {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// MARK: The card on a change
|
|
43
|
+
// The same card Lamp's app puts on an open app as a change goes in (`LampTransition.Look`, TransitionLook.swift, which
|
|
44
|
+
// says how it looks: keep the two in step), at the foot of the page: Lamp's mark turning, "Updating" and what's
|
|
45
|
+
// changing (the words the coding agent gave with `lamp changing`, if it did), then "Updated" for a moment once the
|
|
46
|
+
// change is on screen, so every change is seen to land. It covers nothing but itself and lets every click through.
|
|
47
|
+
// The mark turns by the browser's compositor, so it keeps turning while the page is busy with the new code. Never in
|
|
48
|
+
// a picture lamp takes (`__lampLightHidden`), and still where less motion is asked for.
|
|
49
|
+
const light = (() => {
|
|
50
|
+
const HUES = ['#1f8a4a', '#7cbf3a', '#f6b81c', '#f08a1c', '#e2402a', '#d23778'];
|
|
51
|
+
// As in the Look: the card stays at least this long, "Updated" this long, and the mark turns once in this long.
|
|
52
|
+
const SHOWN_AT_LEAST = 1.2, HELD_AFTER = 1.4, SPIN_EVERY = 1.4;
|
|
53
|
+
const still = () => { try { return matchMedia('(prefers-reduced-motion: reduce)').matches; } catch (e) { return false; } };
|
|
54
|
+
let root = null, card = null, bands = null, title = null, detail = null, spinning = null, shownAt = 0, gone = null, hidden = false, safety = null;
|
|
55
|
+
// The card's way out ("Updated" fading, or a fade), which a change coming meanwhile stops.
|
|
56
|
+
let outgoing = null;
|
|
57
|
+
const make = () => {
|
|
58
|
+
if (root && root.isConnected) return;
|
|
59
|
+
root = document.createElement('div');
|
|
60
|
+
root.setAttribute('aria-hidden', 'true');
|
|
61
|
+
root.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483647;overflow:hidden;contain:strict';
|
|
62
|
+
card = document.createElement('div');
|
|
63
|
+
card.style.cssText = 'position:absolute;left:50%;bottom:min(20px,6vh);transform:translateX(-50%);display:flex;align-items:center;gap:8px;' +
|
|
64
|
+
'height:36px;box-sizing:border-box;padding:0 16px 0 12px;max-width:min(460px,calc(100vw - 32px));border-radius:18px;' +
|
|
65
|
+
'background:rgba(20,20,20,.86);border:1px solid rgba(255,255,255,.12);box-shadow:0 3px 12px rgba(0,0,0,.3);' +
|
|
66
|
+
'font:13px/17px -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;color:#fff;white-space:nowrap;opacity:0';
|
|
67
|
+
// Lamp's mark: its stripes on their slant, in a disc, which turns while the change is under way.
|
|
68
|
+
const mark = document.createElement('div');
|
|
69
|
+
mark.style.cssText = 'flex:none;width:18px;height:18px;border-radius:50%;overflow:hidden;position:relative';
|
|
70
|
+
bands = document.createElement('div');
|
|
71
|
+
bands.style.cssText = `position:absolute;left:-5.4px;top:-5.4px;width:28.8px;height:28.8px;transform:rotate(${-30}deg);` +
|
|
72
|
+
`background:linear-gradient(to bottom, ${HUES.map((hue, i) => `${hue} ${i * 100 / 6}% ${(i + 1) * 100 / 6}%`).join(', ')})`;
|
|
73
|
+
mark.appendChild(bands);
|
|
74
|
+
title = document.createElement('span');
|
|
75
|
+
title.style.cssText = 'flex:none;font-weight:600';
|
|
76
|
+
detail = document.createElement('span');
|
|
77
|
+
detail.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;opacity:.72';
|
|
78
|
+
card.append(mark, title, detail);
|
|
79
|
+
root.appendChild(card);
|
|
80
|
+
// Beside the body rather than in it: a game running its code again clears what its body held.
|
|
81
|
+
document.documentElement.appendChild(root);
|
|
82
|
+
if (hidden) root.style.visibility = 'hidden';
|
|
83
|
+
};
|
|
84
|
+
const goAfter = seconds => {
|
|
85
|
+
unlater(gone);
|
|
86
|
+
gone = later(() => { spinning = null; root && root.remove(); root = null; shownAt = 0; }, seconds * 1000);
|
|
87
|
+
};
|
|
88
|
+
const show = () => {
|
|
89
|
+
make();
|
|
90
|
+
unlater(gone);
|
|
91
|
+
if (outgoing) { outgoing.cancel(); outgoing = null; }
|
|
92
|
+
if (shownAt) return;
|
|
93
|
+
shownAt = performance.now();
|
|
94
|
+
card.animate([{ opacity: 0, transform: still() ? 'translateX(-50%)' : 'translate(-50%, 10px)' }, { opacity: 1, transform: 'translateX(-50%)' }],
|
|
95
|
+
{ duration: 250, easing: 'ease-out', fill: 'forwards' });
|
|
96
|
+
};
|
|
97
|
+
const spin = () => {
|
|
98
|
+
if (still() || spinning) return;
|
|
99
|
+
spinning = bands.animate([{ transform: 'rotate(-30deg)' }, { transform: 'rotate(330deg)' }], { duration: SPIN_EVERY * 1000, iterations: Infinity });
|
|
100
|
+
};
|
|
101
|
+
// Never left over a page that doesn't say its change is on screen (a game stopped by an error): it goes after a
|
|
102
|
+
// minute, as the card in Lamp's app goes when Lamp stops renewing it.
|
|
103
|
+
const watch = () => { unlater(safety); safety = later(() => api.fade(), 60000); };
|
|
104
|
+
let words = '';
|
|
105
|
+
const api = {
|
|
106
|
+
// A change came: the card comes up with "Updating", what's changing, and the mark turning.
|
|
107
|
+
glow(given) {
|
|
108
|
+
if (typeof given === 'string') words = given;
|
|
109
|
+
show();
|
|
110
|
+
watch();
|
|
111
|
+
title.textContent = 'Updating';
|
|
112
|
+
detail.textContent = words;
|
|
113
|
+
spin();
|
|
114
|
+
},
|
|
115
|
+
// The game stands still taking it in: the card stays as it is until `reveal`.
|
|
116
|
+
turn() { api.glow(); },
|
|
117
|
+
// The change is on screen: "Updated" for a moment, then the card goes.
|
|
118
|
+
reveal() {
|
|
119
|
+
if (!root) return;
|
|
120
|
+
unlater(safety);
|
|
121
|
+
title.textContent = 'Updated';
|
|
122
|
+
if (spinning) { spinning.cancel(); spinning = null; }
|
|
123
|
+
if (!still()) bands.parentNode.animate([{ transform: 'scale(1.25)' }, { transform: 'scale(1)' }], { duration: 300, easing: 'ease-out' });
|
|
124
|
+
const wait = Math.max(HELD_AFTER, SHOWN_AT_LEAST - (performance.now() - shownAt) / 1000);
|
|
125
|
+
if (outgoing) outgoing.cancel();
|
|
126
|
+
outgoing = card.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 300, delay: wait * 1000, fill: 'forwards', easing: 'ease-in-out' });
|
|
127
|
+
words = '';
|
|
128
|
+
goAfter(wait + 0.35);
|
|
129
|
+
},
|
|
130
|
+
// The change didn't go in now (held for a pause or a Reload): the card goes.
|
|
131
|
+
fade() {
|
|
132
|
+
unlater(safety);
|
|
133
|
+
if (!root) return;
|
|
134
|
+
if (outgoing) outgoing.cancel();
|
|
135
|
+
outgoing = card.animate([{ opacity: getComputedStyle(card).opacity }, { opacity: 0 }], { duration: 250, fill: 'forwards' });
|
|
136
|
+
words = '';
|
|
137
|
+
goAfter(0.3);
|
|
138
|
+
},
|
|
139
|
+
hide(on) { hidden = !!on; if (root) root.style.visibility = on ? 'hidden' : ''; },
|
|
140
|
+
get words() { return words; },
|
|
141
|
+
};
|
|
142
|
+
return api;
|
|
143
|
+
})();
|
|
144
|
+
// Left out of the pictures lamp takes of the page.
|
|
145
|
+
window.__lampLightHidden = on => light.hide(on);
|
|
146
|
+
// A page opening again for a change goes on saying so until its new code draws, with the same words.
|
|
147
|
+
const TURNING = 'lamp.dev.turning', WORDS = 'lamp.dev.words';
|
|
148
|
+
const turnAcross = () => { try { sessionStorage.setItem(TURNING, String(Date.now())); sessionStorage.setItem(WORDS, light.words); } catch (e) {} };
|
|
149
|
+
try {
|
|
150
|
+
const since = Number(sessionStorage.getItem(TURNING)), carried = sessionStorage.getItem(WORDS) || '';
|
|
151
|
+
sessionStorage.removeItem(TURNING);
|
|
152
|
+
sessionStorage.removeItem(WORDS);
|
|
153
|
+
if (since && Date.now() - since < 30000) light.glow(carried);
|
|
154
|
+
} catch (e) {}
|
|
155
|
+
// How the game said a change went, read for the card: it goes in now, stands still taking it in, waits, or is on screen.
|
|
156
|
+
const lit = message => {
|
|
157
|
+
const state = message && message.state;
|
|
158
|
+
if (state === 'swapped') light.reveal();
|
|
159
|
+
else if (state === 'rerun') light.turn();
|
|
160
|
+
else if (state === 'reloading') { light.turn(); turnAcross(); }
|
|
161
|
+
else if (state === 'held') light.fade();
|
|
162
|
+
// Once its new frames can show, as Lamp's app reads it (`PageLive`): a game put back where it was says so before its
|
|
163
|
+
// first frame and its shaders.
|
|
164
|
+
else if (state === 'shown') light.reveal();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// What Lamp's app around a page gives it: with these the game runtime knows changes come while it's played, and says
|
|
168
|
+
// how each one went and when the game is past its loading screen.
|
|
169
|
+
const handlers = {
|
|
170
|
+
lampLive: { postMessage: message => { lit(message); said(message); } }, lampPlaying: { postMessage: state => said({ state: 'page-' + state }) },
|
|
171
|
+
// The person played while lamp held the game's controller (`lamp use stick`), and has it back.
|
|
172
|
+
lampDriveTakenOver: { postMessage: why => said({ state: 'taken-over', why: String(why || '') }) },
|
|
173
|
+
};
|
|
174
|
+
try { window.webkit = window.webkit || {}; window.webkit.messageHandlers = Object.assign(window.webkit.messageHandlers || {}, handlers); }
|
|
175
|
+
catch (e) { try { Object.defineProperty(window, 'webkit', { value: { messageHandlers: handlers }, configurable: true }); } catch (e) {} }
|
|
176
|
+
|
|
177
|
+
// The page's log, sent on a few lines at a time.
|
|
178
|
+
let lines = [], timer = null;
|
|
179
|
+
const say = (level, parts) => {
|
|
180
|
+
let text;
|
|
181
|
+
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]); }
|
|
182
|
+
lines.push({ level, text: String(text).slice(0, 4000) });
|
|
183
|
+
if (!timer) timer = later(() => { timer = null; const batch = lines; lines = []; post('log', { lines: batch }); }, 250);
|
|
184
|
+
};
|
|
185
|
+
for (const level of ['log', 'info', 'warn', 'error']) {
|
|
186
|
+
const real = console[level];
|
|
187
|
+
console[level] = (...parts) => { real.apply(console, parts); say(level === 'info' ? 'log' : level, parts); };
|
|
188
|
+
}
|
|
189
|
+
// Whether the page runs on the game runtime (lamp-game.js), which says so. A game's page without it has `__lampLive`
|
|
190
|
+
// too (lamp-live.js, given after this script), so that alone doesn't tell; a runtime from before it said so does have
|
|
191
|
+
// its own `__lampLive`, never lamp-live.js's.
|
|
192
|
+
const runtime = () => !!window.__lampRuntime || (!!window.__lampLive && !window.__lampLive.lampHelper);
|
|
193
|
+
// A page with the game runtime logs its own errors; any other page's are logged here.
|
|
194
|
+
addEventListener('error', e => { if (!runtime()) say('error', [e.message + ' (' + (String(e.filename || '').split('/').pop() || 'the page') + ':' + e.lineno + ')']); });
|
|
195
|
+
addEventListener('unhandledrejection', e => { if (!runtime()) say('error', [String(e.reason && e.reason.message || e.reason)]); });
|
|
196
|
+
// A page without the game runtime is on screen once it has loaded.
|
|
197
|
+
addEventListener('load', () => { said({ state: 'page-loaded' }); if (!runtime()) light.reveal(); });
|
|
198
|
+
|
|
199
|
+
// Changes. The line says which change this copy's files had, so one saved while the page was opening comes as soon
|
|
200
|
+
// as the line is open; the browser says the last it heard when the line is opened again after a break.
|
|
201
|
+
// Opened again from where the player is, or from the game's beginning when that's asked: the page that opens next
|
|
202
|
+
// is told so before the runtime loads (see `FRESH` above). Any other page always starts over.
|
|
203
|
+
const reopen = e => {
|
|
204
|
+
if (e && e.data === 'fresh') {
|
|
205
|
+
try { sessionStorage.setItem(FRESH, '1'); } catch (e2) {}
|
|
206
|
+
return location.reload();
|
|
207
|
+
}
|
|
208
|
+
(window.__lampReloadNow || (() => location.reload()))();
|
|
209
|
+
};
|
|
210
|
+
const events = new EventSource('/__lamp/events?page=' + id + '&tab=' + tab + '&since=' + LAMP.gen);
|
|
211
|
+
// A server started again doesn't know what changed while it was away: the page opens again from the files as they are.
|
|
212
|
+
events.addEventListener('hello', e => { if (e.data !== LAMP.run) location.reload(); });
|
|
213
|
+
events.addEventListener('change', e => {
|
|
214
|
+
const change = JSON.parse(e.data);
|
|
215
|
+
light.glow(typeof change.words === 'string' ? change.words : '');
|
|
216
|
+
// A game takes the change and keeps the player's place (on the runtime, or where lamp-live.js can); any other page
|
|
217
|
+
// opens again.
|
|
218
|
+
let taken = false;
|
|
219
|
+
try { taken = !!(window.__lampLive && window.__lampLive(change)); } catch (e) {}
|
|
220
|
+
if (!taken) { light.turn(); turnAcross(); said({ gen: change.gen, state: 'reloading' }); location.reload(); }
|
|
221
|
+
});
|
|
222
|
+
events.addEventListener('reload', reopen);
|
|
223
|
+
// With no browser lamp can look into, the page says what's on it itself.
|
|
224
|
+
events.addEventListener('look', e => post('looked', { ask: e.data, seen: window.__lampLook() }));
|
|
225
|
+
|
|
226
|
+
// What's on the page, as text: each control and canvas with the point at its middle, and the words shown.
|
|
227
|
+
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]';
|
|
228
|
+
const words = text => String(text || '').replace(/\s+/g, ' ').trim().slice(0, 90);
|
|
229
|
+
const kind = el => {
|
|
230
|
+
const tag = el.tagName.toLowerCase(), role = el.getAttribute('role');
|
|
231
|
+
if (role) return role;
|
|
232
|
+
if (tag === 'a') return 'link';
|
|
233
|
+
if (tag === 'select') return 'choice';
|
|
234
|
+
if (tag === 'textarea' || el.isContentEditable) return 'field';
|
|
235
|
+
if (tag === 'input') return ({ checkbox: 'checkbox', radio: 'radio', range: 'slider', button: 'button', submit: 'button' })[el.type] || 'field';
|
|
236
|
+
return 'button';
|
|
237
|
+
};
|
|
238
|
+
const named = el => `${kind(el)} "${words(el.getAttribute('aria-label') || el.innerText || el.getAttribute('title') || el.getAttribute('placeholder') || el.getAttribute('alt') || el.name)}"`;
|
|
239
|
+
|
|
240
|
+
window.__lampLook = () => {
|
|
241
|
+
const lines = [], width = innerWidth, height = innerHeight;
|
|
242
|
+
const box = el => {
|
|
243
|
+
const r = el.getBoundingClientRect();
|
|
244
|
+
return r.width < 2 || r.height < 2 || r.bottom <= 0 || r.right <= 0 || r.top >= height || r.left >= width ? null : r;
|
|
245
|
+
};
|
|
246
|
+
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));
|
|
247
|
+
const walk = parent => {
|
|
248
|
+
for (const el of parent.children) {
|
|
249
|
+
if (lines.length > 160) return;
|
|
250
|
+
const tag = el.tagName.toLowerCase();
|
|
251
|
+
if (tag === 'script' || tag === 'style' || tag === 'template' || tag === 'noscript') continue;
|
|
252
|
+
const look = getComputedStyle(el);
|
|
253
|
+
// What's faded out, or nearly (a loading screen on its way out), isn't on the screen.
|
|
254
|
+
if (look.display === 'none' || Number(look.opacity) < 0.1) continue;
|
|
255
|
+
const hidden = look.visibility === 'hidden', r = hidden ? null : box(el);
|
|
256
|
+
if (r && tag === 'canvas') {
|
|
257
|
+
// A canvas drawn at another size than it's shown (a game's stage) says how a point of what's drawn is found
|
|
258
|
+
// in the picture.
|
|
259
|
+
const scale = el.width ? r.width / el.width : 1, round = n => Math.round(n * 100) / 100;
|
|
260
|
+
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)` : '';
|
|
261
|
+
lines.push(`canvas ${Math.round(r.width)}×${Math.round(r.height)} at ${middle(r)}${drawn}`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (r && el.matches(CONTROLS)) {
|
|
265
|
+
const holds = el.type === 'checkbox' || el.type === 'radio' ? (el.checked ? ' (on)' : ' (off)')
|
|
266
|
+
: 'value' in el && typeof el.value === 'string' && el.value && tag !== 'button' ? ` = "${words(el.value)}"` : '';
|
|
267
|
+
lines.push(`${named(el)}${holds}${el.disabled ? ' (off for now)' : ''} at ${middle(r)}`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (r && tag === 'img') { const alt = words(el.alt); if (alt) lines.push(`picture "${alt}"`); continue; }
|
|
271
|
+
if (r) {
|
|
272
|
+
// Words set in pieces (a number in a span of its own, a heading in two colors) are one line, as they read.
|
|
273
|
+
const pieces = [...el.children];
|
|
274
|
+
const whole = pieces.length && (/^h[1-6]$/.test(tag) ? !el.querySelector(CONTROLS + ', canvas')
|
|
275
|
+
: pieces.every(c => getComputedStyle(c).display.startsWith('inline') && !c.children.length && !c.matches(CONTROLS) && c.tagName.toLowerCase() !== 'canvas'));
|
|
276
|
+
const own = words(whole ? el.innerText : [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join(' '));
|
|
277
|
+
if (own) lines.push(`${/^h[1-6]$/.test(tag) ? 'heading' : 'text'} "${own}"`);
|
|
278
|
+
if (whole) continue;
|
|
279
|
+
}
|
|
280
|
+
walk(el);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
if (document.body) walk(document.body);
|
|
284
|
+
if (lines.length > 160) lines.push('… and more');
|
|
285
|
+
// A game drawn on a stage of its own shape, fitted in the window with bars beside or above it: the stage's size.
|
|
286
|
+
// One that fills the window, whatever its shape, has none.
|
|
287
|
+
let stage = null;
|
|
288
|
+
if (runtime()) {
|
|
289
|
+
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];
|
|
290
|
+
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];
|
|
291
|
+
}
|
|
292
|
+
return { tab, stage, title: document.title, path: location.pathname + location.search, width, height, dpr: devicePixelRatio || 1, game: runtime(), screen: lines.join('\n') };
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// The point of the control called this in the screen as text (`button "Add"`, or just `Add`), for a click by name.
|
|
296
|
+
window.__lampPoint = called => {
|
|
297
|
+
const wanted = String(called).trim();
|
|
298
|
+
for (const el of document.querySelectorAll(CONTROLS)) {
|
|
299
|
+
const r = el.getBoundingClientRect(), look = getComputedStyle(el);
|
|
300
|
+
if (r.width < 2 || r.height < 2 || look.display === 'none' || look.visibility === 'hidden') continue;
|
|
301
|
+
const name = named(el), label = name.slice(name.indexOf('"') + 1, -1);
|
|
302
|
+
if (name === wanted || label === wanted) return [Math.round(r.left + r.width / 2), Math.round(r.top + r.height / 2)];
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// What's at a point of the page, in the screen's own words, so a click says what it landed on.
|
|
308
|
+
window.__lampAt = (x, y) => {
|
|
309
|
+
const el = document.elementFromPoint(x, y);
|
|
310
|
+
if (!el || el === document.documentElement || el === document.body) return 'nothing: the page\'s background';
|
|
311
|
+
const control = el.closest(CONTROLS);
|
|
312
|
+
if (control) return named(control);
|
|
313
|
+
if (el.tagName.toLowerCase() === 'canvas') return 'the canvas';
|
|
314
|
+
const text = words(el.innerText).slice(0, 50);
|
|
315
|
+
return text ? `the text "${text}", which isn't a control` : 'nothing that\'s a control';
|
|
316
|
+
};
|
|
317
|
+
})();
|
package/cli/place.mjs
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// A page on one of the person's devices: packaged for the device's own host, put there, opened, looked at, stopped
|
|
2
|
+
// and taken off again, each through what the device's integration says (devices.mjs). An Xbox is the first: the page
|
|
3
|
+
// goes in Lamp's host for the console's own Chromium (Resources/WebHost), in a Windows package lamp makes and signs
|
|
4
|
+
// itself (msix.mjs), installed through the console's Device Portal. It's `DeviceApps` in Lamp's app, for a page.
|
|
5
|
+
//
|
|
6
|
+
// The package is made in lamp's own folder for the app, never in the app's: what goes to a device is what lamp read
|
|
7
|
+
// of the page as it packaged it.
|
|
8
|
+
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { Problem, RESOURCES, stateOf } from "./state.mjs";
|
|
14
|
+
import { androidDeviceNamed, emulatorNamed, knowsAndroid, lookOnAndroid, putOnAndroid, removeFromAndroid, stopOnAndroid, useOnAndroid } from "./android.mjs";
|
|
15
|
+
import { Away, Runner, StillWorking, allKept, answersOn, devicesHome, integrationOf, isReady, kept, look, takesPages } from "./devices.mjs";
|
|
16
|
+
import { PUBLISHER, WEB_HOST, identityName, keptSigner, namesOf, packageOf, versionAfter } from "./msix.mjs";
|
|
17
|
+
import { sizeOf, tile } from "./png.mjs";
|
|
18
|
+
import { nameOf, pageFiles } from "./share.mjs";
|
|
19
|
+
|
|
20
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const HOST = join(RESOURCES, "WebHost");
|
|
22
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
23
|
+
// The most lamp sends a device.
|
|
24
|
+
const PACKAGE_LIMIT = 1 << 30;
|
|
25
|
+
|
|
26
|
+
function readJSON(file) { try { return JSON.parse(readFileSync(file, "utf8")); } catch { return null; } }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The device a word names, among those lamp set up that take a page: by its own name ("Living Room Xbox"), its kind
|
|
30
|
+
* ("xbox") or its id. One that's only found on the network is never taken for the person's: it's set up first.
|
|
31
|
+
*/
|
|
32
|
+
export function deviceNamed(word, { command = "lamp" } = {}) {
|
|
33
|
+
const wanted = String(word).toLowerCase();
|
|
34
|
+
const mine = allKept().filter((device) => takesPages(integrationOf(device.integration)));
|
|
35
|
+
const setUp = `${command} devices add sets one up: run it in a terminal yourself, since it asks for what only you should type`;
|
|
36
|
+
if (!mine.length) throw new Problem(`lamp hasn't set up a device that takes a page yet. ${setUp}.`);
|
|
37
|
+
const named = mine.filter((device) => {
|
|
38
|
+
const integration = integrationOf(device.integration);
|
|
39
|
+
return device.id.toLowerCase() === wanted || device.name.toLowerCase() === wanted || integration.id.startsWith(wanted) || integration.name.toLowerCase() === wanted;
|
|
40
|
+
});
|
|
41
|
+
const found = named.length ? named : mine.filter((device) => device.name.toLowerCase().includes(wanted));
|
|
42
|
+
if (!found.length) throw new Problem(`lamp has no device called "${word}" set up. It has: ${mine.map((device) => device.name).join(", ")}.`);
|
|
43
|
+
if (found.length > 1) throw new Problem(`"${word}" could be ${found.map((device) => `"${device.name}"`).join(" or ")}: say which by its name.`);
|
|
44
|
+
if (!isReady(found[0], integrationOf(found[0].integration))) throw new Problem(`${found[0].name} isn't all set up yet. ${setUp}.`);
|
|
45
|
+
return found[0];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Where the word after --on says a page goes, each put on, looked at, stopped and taken off the same way: lamp's own
|
|
50
|
+
* Android emulator ("android", "android-tv"), a device lamp set up ("xbox", or its name), or an Android device of the
|
|
51
|
+
* person's that adb sees (its name, or adb's serial for it). Only an Android place can be tried (`use`): adb sends it
|
|
52
|
+
* taps and keys, where a console takes none.
|
|
53
|
+
*/
|
|
54
|
+
export async function placeNamed(word, { command = "lamp" } = {}) {
|
|
55
|
+
const android = (place) => ({
|
|
56
|
+
name: place.name, word: place.word,
|
|
57
|
+
put: (layout, options = {}) => putOnAndroid(layout, place, { ...options, command }),
|
|
58
|
+
look: (layout) => lookOnAndroid(layout, place, { command }),
|
|
59
|
+
use: (layout, moves) => useOnAndroid(layout, place, moves, { command }),
|
|
60
|
+
stop: (layout) => stopOnAndroid(layout, place),
|
|
61
|
+
remove: (layout) => removeFromAndroid(layout, place, { command }),
|
|
62
|
+
});
|
|
63
|
+
const emulator = emulatorNamed(word);
|
|
64
|
+
if (emulator) {
|
|
65
|
+
if (!knowsAndroid()) throw new Problem(process.platform === "darwin" ? "Lamp's Android app for a page isn't beside this copy of lamp: scripts/cli-files.sh writes it" : "lamp builds for Android on a Mac, so far");
|
|
66
|
+
return android(emulator);
|
|
67
|
+
}
|
|
68
|
+
let device, missing;
|
|
69
|
+
try { device = deviceNamed(word, { command }); } catch (error) { if (!(error instanceof Problem)) throw error; missing = error; }
|
|
70
|
+
if (device) {
|
|
71
|
+
return {
|
|
72
|
+
name: device.name, word,
|
|
73
|
+
put: async (layout, options = {}) => {
|
|
74
|
+
const put = await putOn(layout, device, options);
|
|
75
|
+
return { ...put, line: `${put.name} is open on ${device.name}${put.game ? ", installed as a game" : ""}: ${put.files} files, version ${put.identity.version}.` };
|
|
76
|
+
},
|
|
77
|
+
look: (layout) => lookOn(layout, device),
|
|
78
|
+
stop: (layout) => stopOn(layout, device),
|
|
79
|
+
remove: (layout) => removeFrom(layout, device),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const own = await androidDeviceNamed(word);
|
|
83
|
+
if (own) return android(own);
|
|
84
|
+
throw new Problem(`${missing.message}${knowsAndroid() ? ` lamp's Android emulator is --on android, and an Android device of the person's goes by its name once it's plugged in with USB debugging on (${command} devices lists them).` : ""}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Makes sure the device answers where lamp last found it, before anything is made for it. One that doesn't may have
|
|
89
|
+
* been given another address since: lamp looks for it once, and goes on at the address it answers from now.
|
|
90
|
+
*/
|
|
91
|
+
async function reached(runner) {
|
|
92
|
+
const device = runner.device, port = runner.integration.bridge?.install?.find((action) => action.http)?.http.port;
|
|
93
|
+
if (!port || (await answersOn(device.address, port, { reach: runner.reach }))) return;
|
|
94
|
+
await look().catch(() => []);
|
|
95
|
+
const now = kept(device.id);
|
|
96
|
+
if (now?.address && now.address !== device.address && (await answersOn(now.address, port, { reach: runner.reach }))) { device.address = now.address; return; }
|
|
97
|
+
throw new Problem(`${device.name} isn't answering (last at ${device.address}). Make sure it's on and in Developer Mode, then try again.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Where lamp keeps what it made of an app for one device: the package, its names, and which copy the device has. */
|
|
101
|
+
const buildFolder = (layout, device) => join(stateOf(layout.folder), "device", createHash("sha256").update(device.id).digest("hex").slice(0, 8));
|
|
102
|
+
|
|
103
|
+
/** The app's name to lamp, which its package is named for: its folder's own, so two apps never share a package. */
|
|
104
|
+
const appID = (layout) => stateOf(layout.folder).split(/[\\/]/).pop();
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The package's name on the Xbox: the folder's name, cut so its hash always fits after it (`identityName` keeps 40
|
|
108
|
+
* characters), so two folders with the same long name are two apps there.
|
|
109
|
+
*/
|
|
110
|
+
const xboxName = (layout) => {
|
|
111
|
+
const id = appID(layout), hash = id.slice(id.lastIndexOf("-") + 1);
|
|
112
|
+
const name = [...id.slice(0, id.lastIndexOf("-"))].filter((c) => /[A-Za-z0-9-]/.test(c)).join("");
|
|
113
|
+
return identityName(`${name.slice(0, 40 - hash.length - 1)}-${hash}`);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** What a game's page is told about the device it's on (`GamePage.descriptor`): its controller and what its keys are called. */
|
|
117
|
+
export function deviceFile(integration) {
|
|
118
|
+
const input = integration.input ?? {}, facts = {};
|
|
119
|
+
if (input.controller) facts.controller = input.controller;
|
|
120
|
+
facts.keys = Object.fromEntries(Object.entries(input.keys ?? {}).sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
121
|
+
if (input.layout) facts.layout = input.layout;
|
|
122
|
+
return `// Written by Lamp for the device this copy of the game is on (${integration.id}); don't edit.\nexport default ${JSON.stringify(facts)};\n`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Whether the app is installed as a game: a device may give a game more than an app (an Xbox, about five times the memory and its whole GPU). */
|
|
126
|
+
const isGame = (layout) => layout.game || readJSON(join(layout.folder, "capabilities.json"))?.game === true;
|
|
127
|
+
|
|
128
|
+
/** The app's icon, where it has one lamp can draw from: Lamp's own for the app, or a page's. */
|
|
129
|
+
function iconOf(layout) {
|
|
130
|
+
const candidates = [join(layout.folder, ".lamp", "AppIcon-square.png"), ...["apple-touch-icon.png", "icon.png", "favicon.png"].map((name) => join(layout.root, name))];
|
|
131
|
+
for (const file of candidates) {
|
|
132
|
+
try { const bytes = readFileSync(file); if (sizeOf(bytes)) return bytes; } catch {}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Makes the package of the page for a device, in lamp's folder: the page's files as they are now, with what a game is
|
|
139
|
+
* told of the device, in the device's host. Each one is a newer version than the last, so it replaces it.
|
|
140
|
+
*/
|
|
141
|
+
export function packageFor(layout, device, integration = integrationOf(device.integration)) {
|
|
142
|
+
if (!takesPages(integration)) throw new Problem(`lamp doesn't put a page on a ${integration?.name ?? "device like that"} yet`);
|
|
143
|
+
let host, library;
|
|
144
|
+
try { host = readFileSync(join(HOST, WEB_HOST.program)); library = readFileSync(join(HOST, WEB_HOST.library)); }
|
|
145
|
+
catch { throw new Problem(`Lamp's web host for the ${integration.name} isn't beside this copy of lamp`); }
|
|
146
|
+
const files = pageFiles(layout, { device: deviceFile(integration) });
|
|
147
|
+
if (files.reduce((sum, [, bytes]) => sum + bytes.length, 0) > PACKAGE_LIMIT) throw new Problem(`the page is bigger than lamp sends to a device (${PACKAGE_LIMIT >> 30} GB)`);
|
|
148
|
+
const payload = [[WEB_HOST.program, host], [WEB_HOST.library, library], ...files.map(([path, bytes]) => [`${WEB_HOST.folder}/${path}`, bytes])];
|
|
149
|
+
const folder = buildFolder(layout, device);
|
|
150
|
+
mkdirSync(folder, { recursive: true });
|
|
151
|
+
const identity = { name: xboxName(layout), publisher: PUBLISHER, version: versionAfter(readJSON(join(folder, "identity.json"))?.version), architecture: "x64" };
|
|
152
|
+
const signer = keptSigner(join(devicesHome(), "msix")), icon = iconOf(layout);
|
|
153
|
+
const made = packageOf({ payload, displayName: nameOf(layout), identity, entry: "webHost", logo: (width, height) => tile(icon, width, height), signer });
|
|
154
|
+
writeFileSync(join(folder, "package.msix"), made);
|
|
155
|
+
writeFileSync(join(folder, "certificate.cer"), signer.certificate);
|
|
156
|
+
writeFileSync(join(folder, "identity.json"), JSON.stringify(identity));
|
|
157
|
+
return { folder, identity, files: files.length, bytes: made.length, game: isGame(layout) };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** lamp's values for the bridge: the app's id, whether it's a game, and the names Windows gives its package. */
|
|
161
|
+
function valuesOf(layout, identity) {
|
|
162
|
+
const values = { appID: appID(layout), isGame: isGame(layout) ? "true" : "false" };
|
|
163
|
+
if (identity) { const names = namesOf(identity); values.packageFullName = names.fullName; values.appUserModelID = names.appUserModelID; }
|
|
164
|
+
return values;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The copy lamp last put on the device, by the record its install left; null when lamp never put one there. */
|
|
168
|
+
const installed = (layout, device) => readJSON(join(buildFolder(layout, device), "installed.json"));
|
|
169
|
+
|
|
170
|
+
// A device busy closing the app it's replacing can drop a connection, the upload's or the launch's just after it.
|
|
171
|
+
const dropped = (error) => error instanceof Away && ["ECONNRESET", "EPIPE", "ECONNABORTED"].includes(error.code);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Packages the page and puts it on the device: the copy there is ended, the new one installed and, unless `opens` is
|
|
175
|
+
* false, opened. `said` is told each stage as it starts.
|
|
176
|
+
*/
|
|
177
|
+
export async function putOn(layout, device, { opens = true, said = () => {}, runner } = {}) {
|
|
178
|
+
runner ??= new Runner({ integration: integrationOf(device.integration), device });
|
|
179
|
+
const integration = runner.integration, bridge = integration.bridge ?? {};
|
|
180
|
+
if (!bridge.install) throw new Problem(`${device.name}'s integration doesn't say how an app goes on it`);
|
|
181
|
+
await reached(runner);
|
|
182
|
+
said(`Packaging ${nameOf(layout)} for ${device.name}…`);
|
|
183
|
+
const made = packageFor(layout, device, integration), folder = made.folder;
|
|
184
|
+
let waited = false;
|
|
185
|
+
runner.onWaiting = () => { if (!waited) said(`Waiting for ${device.name} to finish with it…`); waited = true; };
|
|
186
|
+
const values = valuesOf(layout, made.identity), files = { package: join(folder, "package.msix"), certificate: join(folder, "certificate.cer") };
|
|
187
|
+
// The copy on the device now is ended first: a device can fail to open a package installed over its running copy.
|
|
188
|
+
const before = installed(layout, device);
|
|
189
|
+
if (opens && bridge.stop && before) await runner.run(bridge.stop, { extra: valuesOf(layout, before) }).catch(() => {});
|
|
190
|
+
// A setting the install changes for itself (an Xbox's "treat apps as games") stays changed until the app has been
|
|
191
|
+
// opened: the console settles the package's type then.
|
|
192
|
+
const changed = [];
|
|
193
|
+
const stillInstalling = `The ${integration.name} is still finishing installing ${nameOf(layout)}. Try again in a moment.`;
|
|
194
|
+
said(`Installing it on ${device.name} (${(made.bytes / 1_000_000).toFixed(1)} MB)…`);
|
|
195
|
+
for (let tries = 0; ; tries++) {
|
|
196
|
+
try {
|
|
197
|
+
// Noted as on its way before the upload, and as the installed one only once the device took it: an install cut
|
|
198
|
+
// short may still land on the device, and a removal then names both.
|
|
199
|
+
writeFileSync(join(folder, "pending.json"), JSON.stringify(made.identity));
|
|
200
|
+
await runner.run(bridge.install, { extra: values, files, changed });
|
|
201
|
+
writeFileSync(join(folder, "installed.json"), JSON.stringify(made.identity));
|
|
202
|
+
rmSync(join(folder, "pending.json"), { force: true });
|
|
203
|
+
if (opens && bridge.launch?.length) {
|
|
204
|
+
said(`Opening it…`);
|
|
205
|
+
waited = false;
|
|
206
|
+
const opened = await runner.open(bridge.launch, bridge.running, { extra: values, changed });
|
|
207
|
+
if (!opened.opened) {
|
|
208
|
+
throw new Problem(opened.launched
|
|
209
|
+
? `The ${integration.name} is still finishing installing ${nameOf(layout)}, or it closed as it opened. Try again in a moment.`
|
|
210
|
+
: stillInstalling + (opened.refused ? `\n${opened.refused}` : ""));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (dropped(error) && tries < 2) { await sleep(3000); continue; }
|
|
216
|
+
await runner.putBack(changed);
|
|
217
|
+
if (error instanceof StillWorking) throw new Problem(stillInstalling);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
await runner.putBack(changed);
|
|
222
|
+
return { ...made, opened: opens && !!bridge.launch?.length, name: nameOf(layout) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The device's screen now, kept as a picture in lamp's folder, with whether the app is the one open there and what
|
|
227
|
+
* it last logged: the only word from the device of how the page runs on it.
|
|
228
|
+
*/
|
|
229
|
+
export async function lookOn(layout, device, { runner } = {}) {
|
|
230
|
+
runner ??= new Runner({ integration: integrationOf(device.integration), device });
|
|
231
|
+
const bridge = runner.integration.bridge ?? {};
|
|
232
|
+
await reached(runner);
|
|
233
|
+
const mine = installed(layout, device), values = valuesOf(layout, mine);
|
|
234
|
+
const seen = { device: device.name, installed: !!mine, running: null, picture: null, log: null };
|
|
235
|
+
if (mine && bridge.running) seen.running = await openNow(runner, values);
|
|
236
|
+
if (bridge.look) {
|
|
237
|
+
const picture = (await runner.run(bridge.look, { extra: values })).picture;
|
|
238
|
+
if (picture?.length) {
|
|
239
|
+
const kind = picture.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff])) ? "jpg" : "png";
|
|
240
|
+
seen.picture = join(buildFolder(layout, device), `look.${kind}`);
|
|
241
|
+
mkdirSync(dirname(seen.picture), { recursive: true });
|
|
242
|
+
writeFileSync(seen.picture, picture);
|
|
243
|
+
seen.size = kind === "png" ? sizeOf(picture) : null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (mine && bridge.log) seen.log = await runner.run(bridge.log, { extra: values }).then((outcome) => outcome.log ?? "", (error) => { if (error instanceof Problem) return null; throw error; });
|
|
247
|
+
return seen;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Whether the app is open on the device now, asked once: not whether it opens in a while. */
|
|
251
|
+
function openNow(runner, values) {
|
|
252
|
+
const once = runner.integration.bridge.running.map((action) => (action.http ? { http: { ...action.http, until: undefined } } : action));
|
|
253
|
+
return runner.run(once, { extra: values }).then(() => true, (error) => { if (error instanceof Problem && !(error instanceof Away)) return false; throw error; });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Ends the app on the device; false when lamp never put it there. */
|
|
257
|
+
export async function stopOn(layout, device, { runner } = {}) {
|
|
258
|
+
runner ??= new Runner({ integration: integrationOf(device.integration), device });
|
|
259
|
+
const bridge = runner.integration.bridge ?? {}, mine = installed(layout, device);
|
|
260
|
+
if (!mine || !bridge.stop) return false;
|
|
261
|
+
await reached(runner);
|
|
262
|
+
const values = valuesOf(layout, mine);
|
|
263
|
+
try {
|
|
264
|
+
await runner.run(bridge.stop, { extra: values });
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// A device refuses to end an app that isn't open (an Xbox answers that it failed to): that's stopped already.
|
|
267
|
+
if (!(error instanceof Problem) || error instanceof Away || !bridge.running || (await openNow(runner, values))) throw error;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
// A device goes on listing an app for a while after it's told to end it (an Xbox, about fifteen seconds): waited
|
|
271
|
+
// for, so a look right after says what's so.
|
|
272
|
+
for (let tries = 0; bridge.running && tries < 25 && (await openNow(runner, values)); tries++) await runner.pause(1000);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Takes the app off the device: the copy it took, and one that was on its way and may have landed. False when lamp never put it there. */
|
|
277
|
+
export async function removeFrom(layout, device, { runner } = {}) {
|
|
278
|
+
runner ??= new Runner({ integration: integrationOf(device.integration), device });
|
|
279
|
+
const bridge = runner.integration.bridge ?? {}, folder = buildFolder(layout, device);
|
|
280
|
+
if (!bridge.remove) throw new Problem(`${device.name}'s integration doesn't say how an app comes off it`);
|
|
281
|
+
if (!readJSON(join(folder, "installed.json")) && !readJSON(join(folder, "pending.json"))) return false;
|
|
282
|
+
await reached(runner);
|
|
283
|
+
const asked = new Set();
|
|
284
|
+
let removed = false;
|
|
285
|
+
for (const record of ["installed.json", "pending.json"]) {
|
|
286
|
+
const identity = readJSON(join(folder, record));
|
|
287
|
+
if (!identity || asked.has(namesOf(identity).fullName)) continue;
|
|
288
|
+
asked.add(namesOf(identity).fullName);
|
|
289
|
+
const values = valuesOf(layout, identity);
|
|
290
|
+
if (bridge.stop) await runner.run(bridge.stop, { extra: values }).catch(() => {});
|
|
291
|
+
// The copy that was only on its way may never have landed: the device saying so is no failure.
|
|
292
|
+
if (record === "installed.json") await runner.run(bridge.remove, { extra: values });
|
|
293
|
+
else await runner.run(bridge.remove, { extra: values }).catch(() => {});
|
|
294
|
+
removed = true;
|
|
295
|
+
rmSync(join(folder, record), { force: true });
|
|
296
|
+
}
|
|
297
|
+
return removed;
|
|
298
|
+
}
|