valence-cli 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +56 -53
  2. package/README.original.md +56 -53
  3. package/dist/README.md +56 -53
  4. package/dist/commands/add.d.ts.map +1 -1
  5. package/dist/commands/add.js +39 -1
  6. package/dist/commands/add.js.map +1 -1
  7. package/dist/commands/features/audio.d.ts +2 -0
  8. package/dist/commands/features/audio.d.ts.map +1 -0
  9. package/dist/commands/features/audio.js +17 -0
  10. package/dist/commands/features/audio.js.map +1 -0
  11. package/dist/commands/features/input.d.ts +2 -0
  12. package/dist/commands/features/input.d.ts.map +1 -0
  13. package/dist/commands/features/input.js +13 -0
  14. package/dist/commands/features/input.js.map +1 -0
  15. package/dist/commands/features/settings.d.ts +2 -0
  16. package/dist/commands/features/settings.d.ts.map +1 -0
  17. package/dist/commands/features/settings.js +13 -0
  18. package/dist/commands/features/settings.js.map +1 -0
  19. package/dist/commands/features/state.d.ts +2 -0
  20. package/dist/commands/features/state.d.ts.map +1 -0
  21. package/dist/commands/features/state.js +12 -0
  22. package/dist/commands/features/state.js.map +1 -0
  23. package/dist/commands/features/storage.d.ts +2 -0
  24. package/dist/commands/features/storage.d.ts.map +1 -0
  25. package/dist/commands/features/storage.js +33 -0
  26. package/dist/commands/features/storage.js.map +1 -0
  27. package/dist/commands/generate.d.ts.map +1 -1
  28. package/dist/commands/generate.js +19 -0
  29. package/dist/commands/generate.js.map +1 -1
  30. package/dist/commands/new-helpers.d.ts +2 -1
  31. package/dist/commands/new-helpers.d.ts.map +1 -1
  32. package/dist/commands/new-helpers.js +28 -76
  33. package/dist/commands/new-helpers.js.map +1 -1
  34. package/dist/commands/new.d.ts +1 -0
  35. package/dist/commands/new.d.ts.map +1 -1
  36. package/dist/commands/new.js +58 -38
  37. package/dist/commands/new.js.map +1 -1
  38. package/dist/constants/versions.d.ts +3 -0
  39. package/dist/constants/versions.d.ts.map +1 -1
  40. package/dist/constants/versions.js +3 -0
  41. package/dist/constants/versions.js.map +1 -1
  42. package/dist/index.js +1 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/templates/angular.d.ts +2 -2
  45. package/dist/templates/angular.d.ts.map +1 -1
  46. package/dist/templates/angular.js +4 -5
  47. package/dist/templates/angular.js.map +1 -1
  48. package/dist/templates/audio.d.ts +2 -0
  49. package/dist/templates/audio.d.ts.map +1 -0
  50. package/dist/templates/audio.js +95 -0
  51. package/dist/templates/audio.js.map +1 -0
  52. package/dist/templates/index.d.ts +5 -0
  53. package/dist/templates/index.d.ts.map +1 -1
  54. package/dist/templates/index.js +5 -0
  55. package/dist/templates/index.js.map +1 -1
  56. package/dist/templates/input.d.ts +2 -0
  57. package/dist/templates/input.d.ts.map +1 -0
  58. package/dist/templates/input.js +159 -0
  59. package/dist/templates/input.js.map +1 -0
  60. package/dist/templates/phaser-scene.d.ts +2 -0
  61. package/dist/templates/phaser-scene.d.ts.map +1 -0
  62. package/dist/templates/phaser-scene.js +27 -0
  63. package/dist/templates/phaser-scene.js.map +1 -0
  64. package/dist/templates/phaser.d.ts +2 -0
  65. package/dist/templates/phaser.d.ts.map +1 -0
  66. package/dist/templates/phaser.js +147 -0
  67. package/dist/templates/phaser.js.map +1 -0
  68. package/dist/templates/settings.d.ts +2 -0
  69. package/dist/templates/settings.d.ts.map +1 -0
  70. package/dist/templates/settings.js +66 -0
  71. package/dist/templates/settings.js.map +1 -0
  72. package/dist/templates/state.d.ts +2 -0
  73. package/dist/templates/state.d.ts.map +1 -0
  74. package/dist/templates/state.js +35 -0
  75. package/dist/templates/state.js.map +1 -0
  76. package/dist/templates/storage.d.ts +4 -0
  77. package/dist/templates/storage.d.ts.map +1 -0
  78. package/dist/templates/storage.js +121 -0
  79. package/dist/templates/storage.js.map +1 -0
  80. package/dist/templates.d.ts +2 -0
  81. package/dist/templates.d.ts.map +1 -1
  82. package/dist/templates.js +2 -0
  83. package/dist/templates.js.map +1 -1
  84. package/dist/utils/ast/modify-app-component.d.ts +2 -0
  85. package/dist/utils/ast/modify-app-component.d.ts.map +1 -0
  86. package/dist/utils/ast/modify-app-component.js +136 -0
  87. package/dist/utils/ast/modify-app-component.js.map +1 -0
  88. package/dist/utils/configurators/angular.d.ts +2 -0
  89. package/dist/utils/configurators/angular.d.ts.map +1 -0
  90. package/dist/utils/configurators/angular.js +28 -0
  91. package/dist/utils/configurators/angular.js.map +1 -0
  92. package/dist/utils/configurators/electron.d.ts +2 -0
  93. package/dist/utils/configurators/electron.d.ts.map +1 -0
  94. package/dist/utils/configurators/electron.js +12 -0
  95. package/dist/utils/configurators/electron.js.map +1 -0
  96. package/dist/utils/configurators/meta.d.ts +5 -0
  97. package/dist/utils/configurators/meta.d.ts.map +1 -0
  98. package/dist/utils/configurators/meta.js +14 -0
  99. package/dist/utils/configurators/meta.js.map +1 -0
  100. package/dist/utils/configurators/package.d.ts +5 -0
  101. package/dist/utils/configurators/package.d.ts.map +1 -0
  102. package/dist/utils/configurators/package.js +23 -0
  103. package/dist/utils/configurators/package.js.map +1 -0
  104. package/dist/utils/files.d.ts +17 -0
  105. package/dist/utils/files.d.ts.map +1 -0
  106. package/dist/utils/files.js +41 -0
  107. package/dist/utils/files.js.map +1 -0
  108. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  export declare const stylesScssTemplate = "\nhtml, body {\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background-color: #0d0d0d;\n font-family: 'Segoe UI', sans-serif;\n color: #fff;\n}\n";
2
2
  export declare const appComponentTsTemplate = "\nimport { Component, NgZone, AfterViewInit, OnDestroy } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { RouterOutlet } from '@angular/router';\nimport { ValenceEngineService } from './valence-engine.service';\nimport { TopnavComponent } from './core/topnav/topnav.component';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [CommonModule, RouterOutlet, TopnavComponent],\n templateUrl: './app.component.html',\n styleUrl: './app.component.scss'\n})\nexport class AppComponent implements AfterViewInit, OnDestroy {\n public title: string = 'Valence Project';\n\n constructor(public engineService: ValenceEngineService, private ngZone: NgZone) {}\n\n ngAfterViewInit(): void {\n const canvas = document.getElementById('renderCanvas') as HTMLCanvasElement;\n if (canvas) {\n this.ngZone.runOutsideAngular(() => {\n this.engineService.initEngine(canvas);\n });\n }\n }\n\n ngOnDestroy(): void {\n this.engineService.dispose();\n }\n}\n";
3
- export declare const appComponentHtmlTemplate = "\n<div class=\"app-container\">\n <app-topnav [title]=\"title\"></app-topnav>\n <div class=\"canvas-wrapper\">\n <canvas #renderCanvas id=\"renderCanvas\"></canvas>\n \n <!-- UI Overlay -->\n <div class=\"ui-overlay\">\n <div class=\"logo-container\">\n <svg width=\"200\" height=\"200\" viewBox=\"0 0 200 200\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <filter id=\"bloom\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"/>\n <feMerge>\n <feMergeNode in=\"coloredBlur\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n </filter>\n <linearGradient id=\"lineGradient\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n <stop offset=\"0%\" style=\"stop-color:#47848F;stop-opacity:0.2\" />\n <stop offset=\"50%\" style=\"stop-color:#47848F;stop-opacity:0.8\" />\n <stop offset=\"100%\" style=\"stop-color:#DD0031;stop-opacity:0.2\" />\n </linearGradient>\n </defs>\n \n <!-- Neural Connections (Axons) -->\n <g stroke=\"url(#lineGradient)\" stroke-width=\"2\" filter=\"url(#bloom)\">\n <!-- Main V Structure -->\n <line x1=\"50\" y1=\"50\" x2=\"100\" y2=\"150\" />\n <line x1=\"150\" y1=\"50\" x2=\"100\" y2=\"150\" />\n \n <!-- Network Interconnects -->\n <line x1=\"50\" y1=\"50\" x2=\"80\" y2=\"90\" />\n <line x1=\"150\" y1=\"50\" x2=\"120\" y2=\"90\" />\n <line x1=\"80\" y1=\"90\" x2=\"120\" y2=\"90\" opacity=\"0.5\"/>\n <line x1=\"80\" y1=\"90\" x2=\"100\" y2=\"150\" />\n <line x1=\"120\" y1=\"90\" x2=\"100\" y2=\"150\" />\n </g>\n\n <!-- Data Flow Particles -->\n <circle r=\"2\" fill=\"#fff\" filter=\"url(#bloom)\">\n <animateMotion dur=\"3s\" repeatCount=\"indefinite\" path=\"M50,50 L80,90 L100,150\" />\n </circle>\n <circle r=\"2\" fill=\"#fff\" filter=\"url(#bloom)\">\n <animateMotion dur=\"3s\" begin=\"1.5s\" repeatCount=\"indefinite\" path=\"M150,50 L120,90 L100,150\" />\n </circle>\n <circle r=\"2\" fill=\"#DD0031\" filter=\"url(#bloom)\">\n <animateMotion dur=\"4s\" begin=\"0.5s\" repeatCount=\"indefinite\" path=\"M50,50 L100,150\" />\n </circle>\n\n <!-- Nodes -->\n <g fill=\"#47848F\" filter=\"url(#bloom)\">\n <!-- Top Nodes -->\n <circle cx=\"50\" cy=\"50\" r=\"6\">\n <animate attributeName=\"r\" values=\"6;8;6\" dur=\"2s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.6;1;0.6\" dur=\"2s\" repeatCount=\"indefinite\" />\n </circle>\n <circle cx=\"150\" cy=\"50\" r=\"6\">\n <animate attributeName=\"r\" values=\"6;8;6\" dur=\"2.2s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.6;1;0.6\" dur=\"2.2s\" repeatCount=\"indefinite\" />\n </circle>\n \n <!-- Mid Nodes -->\n <circle cx=\"80\" cy=\"90\" r=\"5\">\n <animate attributeName=\"r\" values=\"5;7;5\" dur=\"1.8s\" repeatCount=\"indefinite\" />\n </circle>\n <circle cx=\"120\" cy=\"90\" r=\"5\">\n <animate attributeName=\"r\" values=\"5;7;5\" dur=\"1.9s\" repeatCount=\"indefinite\" />\n </circle>\n\n <!-- Bottom Node (The Combine) -->\n <circle cx=\"100\" cy=\"150\" r=\"8\" fill=\"#DD0031\">\n <animate attributeName=\"r\" values=\"8;12;8\" dur=\"1s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.8;1;0.8\" dur=\"1s\" repeatCount=\"indefinite\" />\n </circle>\n </g>\n \n <text x=\"100\" y=\"190\" font-family=\"Segoe UI, Arial\" font-size=\"20\" fill=\"#fff\" text-anchor=\"middle\" letter-spacing=\"8\" font-weight=\"300\" opacity=\"0.8\">VALENCE</text>\n </svg>\n </div>\n\n <div class=\"debug-panel\">\n <div class=\"stat-row\"><span class=\"label\">FPS:</span> <span class=\"value\">{{ engineService.fps() }}</span></div>\n <div class=\"stat-row\"><span class=\"label\">CAM:</span> <span class=\"value\">{{ engineService.rotation() }}</span></div>\n <div class=\"stat-row info\"><small>Angular + Electron + Babylon</small></div>\n </div>\n </div>\n </div>\n</div>\n";
3
+ export declare const appComponentHtmlTemplate = "\n<div class=\"app-container\">\n <app-topnav [title]=\"title\"></app-topnav>\n <div class=\"canvas-wrapper\">\n <canvas #renderCanvas id=\"renderCanvas\"></canvas>\n \n <!-- UI Overlay -->\n <div class=\"ui-overlay\">\n <div class=\"logo-container\">\n <svg width=\"200\" height=\"200\" viewBox=\"0 0 200 200\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <filter id=\"bloom\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"/>\n <feMerge>\n <feMergeNode in=\"coloredBlur\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n </filter>\n <linearGradient id=\"lineGradient\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n <stop offset=\"0%\" style=\"stop-color:#47848F;stop-opacity:0.2\" />\n <stop offset=\"50%\" style=\"stop-color:#47848F;stop-opacity:0.8\" />\n <stop offset=\"100%\" style=\"stop-color:#DD0031;stop-opacity:0.2\" />\n </linearGradient>\n </defs>\n \n <!-- Neural Connections (Axons) -->\n <g stroke=\"url(#lineGradient)\" stroke-width=\"2\" filter=\"url(#bloom)\">\n <!-- Main V Structure -->\n <line x1=\"50\" y1=\"50\" x2=\"100\" y2=\"150\" />\n <line x1=\"150\" y1=\"50\" x2=\"100\" y2=\"150\" />\n \n <!-- Network Interconnects -->\n <line x1=\"50\" y1=\"50\" x2=\"80\" y2=\"90\" />\n <line x1=\"150\" y1=\"50\" x2=\"120\" y2=\"90\" />\n <line x1=\"80\" y1=\"90\" x2=\"120\" y2=\"90\" opacity=\"0.5\"/>\n <line x1=\"80\" y1=\"90\" x2=\"100\" y2=\"150\" />\n <line x1=\"120\" y1=\"90\" x2=\"100\" y2=\"150\" />\n </g>\n\n <!-- Data Flow Particles -->\n <circle r=\"2\" fill=\"#fff\" filter=\"url(#bloom)\">\n <animateMotion dur=\"3s\" repeatCount=\"indefinite\" path=\"M50,50 L80,90 L100,150\" />\n </circle>\n <circle r=\"2\" fill=\"#fff\" filter=\"url(#bloom)\">\n <animateMotion dur=\"3s\" begin=\"1.5s\" repeatCount=\"indefinite\" path=\"M150,50 L120,90 L100,150\" />\n </circle>\n <circle r=\"2\" fill=\"#DD0031\" filter=\"url(#bloom)\">\n <animateMotion dur=\"4s\" begin=\"0.5s\" repeatCount=\"indefinite\" path=\"M50,50 L100,150\" />\n </circle>\n\n <!-- Nodes -->\n <g fill=\"#47848F\" filter=\"url(#bloom)\">\n <!-- Top Nodes -->\n <circle cx=\"50\" cy=\"50\" r=\"6\">\n <animate attributeName=\"r\" values=\"6;8;6\" dur=\"2s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.6;1;0.6\" dur=\"2s\" repeatCount=\"indefinite\" />\n </circle>\n <circle cx=\"150\" cy=\"50\" r=\"6\">\n <animate attributeName=\"r\" values=\"6;8;6\" dur=\"2.2s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.6;1;0.6\" dur=\"2.2s\" repeatCount=\"indefinite\" />\n </circle>\n \n <!-- Mid Nodes -->\n <circle cx=\"80\" cy=\"90\" r=\"5\">\n <animate attributeName=\"r\" values=\"5;7;5\" dur=\"1.8s\" repeatCount=\"indefinite\" />\n </circle>\n <circle cx=\"120\" cy=\"90\" r=\"5\">\n <animate attributeName=\"r\" values=\"5;7;5\" dur=\"1.9s\" repeatCount=\"indefinite\" />\n </circle>\n\n <!-- Bottom Node (The Combine) -->\n <circle cx=\"100\" cy=\"150\" r=\"8\" fill=\"#DD0031\">\n <animate attributeName=\"r\" values=\"8;12;8\" dur=\"1s\" repeatCount=\"indefinite\" />\n <animate attributeName=\"fill-opacity\" values=\"0.8;1;0.8\" dur=\"1s\" repeatCount=\"indefinite\" />\n </circle>\n </g>\n \n <text x=\"100\" y=\"190\" font-family=\"Segoe UI, Arial\" font-size=\"20\" fill=\"#fff\" text-anchor=\"middle\" letter-spacing=\"8\" font-weight=\"300\" opacity=\"0.8\">VALENCE</text>\n </svg>\n </div>\n\n <div class=\"debug-panel\">\n <div class=\"stat-row\"><span class=\"label\">FPS:</span> <span class=\"value\">{{ engineService.fps() }}</span></div>\n <div class=\"stat-row\"><span class=\"label\">OBJ:</span> <span class=\"value vec3\">{{ engineService.rotation() }}</span></div>\n <div class=\"stat-row info\"><small>Angular + Electron + Babylon</small></div>\n </div>\n </div>\n </div>\n</div>\n";
4
4
  export declare const appComponentScssTemplate = "\n.app-container {\n width: 100vw;\n height: 100vh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: #000;\n}\n\n.canvas-wrapper {\n flex: 1;\n position: relative;\n overflow: hidden;\n background: radial-gradient(circle at center, #1a1a1a 0%, #000 100%);\n}\n\n#renderCanvas {\n width: 100%;\n height: 100%;\n touch-action: none;\n outline: none;\n}\n\n.ui-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none; // Let clicks pass to canvas\n \n display: flex;\n flex-direction: column;\n justify-content: space-between;\n}\n\n.logo-container {\n padding: 2rem;\n opacity: 0.8;\n animation: float 6s ease-in-out infinite;\n filter: drop-shadow(0 0 20px rgba(71, 132, 143, 0.3));\n}\n\n.debug-panel {\n position: absolute;\n top: 2rem;\n right: 2rem;\n \n // Glassmorphism\n background: rgba(13, 13, 13, 0.65);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n border: 1px solid rgba(255, 255, 255, 0.08);\n box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4);\n \n // Accent\n border-left: 3px solid #47848F;\n \n padding: 1.25rem;\n border-radius: 6px;\n color: #fff;\n font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;\n pointer-events: auto;\n min-width: 220px;\n \n transition: all 0.3s ease;\n \n &:hover {\n background: rgba(13, 13, 13, 0.8);\n border-color: rgba(255, 255, 255, 0.15);\n box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);\n }\n \n .stat-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.75rem;\n \n &:last-of-type {\n margin-bottom: 0;\n }\n \n .label {\n color: #888;\n font-size: 0.85rem;\n letter-spacing: 1px;\n font-weight: 500;\n }\n \n .value {\n color: #4dbf00;\n font-weight: 600;\n font-size: 1rem;\n text-shadow: 0 0 10px rgba(77, 191, 0, 0.3);\n \n // Fixed width for alignment of rotation numbers\n &.vec3 {\n font-size: 0.9rem;\n }\n }\n }\n\n .info {\n margin-top: 1.25rem;\n padding-top: 1rem;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n text-align: right;\n opacity: 0.6;\n \n small {\n font-size: 0.75rem;\n letter-spacing: 0.5px;\n font-family: 'Segoe UI', sans-serif;\n text-transform: uppercase;\n }\n }\n}\n\n@keyframes float {\n 0% { transform: translateY(0px); }\n 50% { transform: translateY(-10px); }\n 100% { transform: translateY(0px); }\n}\n";
5
- export declare const valenceEngineServiceTemplate = "\nimport { Injectable, NgZone, OnDestroy, signal, WritableSignal } from '@angular/core';\nimport { Engine, WebGPUEngine, Scene, FreeCamera, Vector3, HemisphericLight, MeshBuilder, StandardMaterial, Color3, Color4, Mesh } from 'babylonjs';\n// VALENCE: IMPORTS\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ValenceEngineService implements OnDestroy {\n private engine?: Engine | WebGPUEngine;\n private scene?: Scene;\n private plane?: Mesh;\n // VALENCE: PROPERTIES\n\n public fps: WritableSignal<string> = signal('0');\n public rotation: WritableSignal<string> = signal('X: 0.00 Y: 0.00 Z: 0.00');\n\n constructor(private ngZone: NgZone) {}\n\n public async initEngine(canvas: HTMLCanvasElement): Promise<void> {\n // Attempt WebGPU, fall back to WebGL\n try {\n if (await WebGPUEngine.IsSupportedAsync) {\n this.engine = new WebGPUEngine(canvas);\n await (this.engine as WebGPUEngine).initAsync();\n } else {\n this.engine = new Engine(canvas, true);\n }\n } catch (e) {\n console.warn('WebGPU failed, falling back to WebGL', e);\n this.engine = new Engine(canvas, true);\n }\n \n // VALENCE: PRE_SCENE_INIT\n\n this.scene = this.createScene();\n\n // VALENCE: POST_SCENE_INIT\n\n this.ngZone.runOutsideAngular(() => {\n if (!this.engine) return;\n \n this.engine.runRenderLoop(() => {\n if (this.scene) {\n this.scene.render();\n // Update specific meshes\n if (this.plane) {\n this.plane.rotation.y += 0.01;\n this.plane.rotation.x += 0.005;\n }\n \n // Update Camera Stats\n const cam = this.scene.activeCamera as FreeCamera;\n if (cam && cam.rotation) {\n const { x, y, z } = cam.rotation;\n this.rotation.set(`X: ${x.toFixed(2)} Y: ${y.toFixed(2)} Z: ${z.toFixed(2)}`);\n }\n\n // VALENCE: RENDER_LOOP\n }\n \n // Update FPS rarely to avoid UI thrashing, or just every frame if signal handles it efficient?\n // actually better to throttle it slightly for UI, but signal is fine.\n this.fps.set(this.engine?.getFps().toFixed(0) || '0');\n });\n });\n\n window.addEventListener('resize', () => {\n this.engine?.resize();\n });\n }\n\n private createScene(): Scene {\n if (!this.engine) throw new Error('Engine not initialized');\n const scene = new Scene(this.engine);\n scene.clearColor = new Color4(0.1, 0.1, 0.1, 1);\n \n // Camera\n const camera = new FreeCamera('camera1', new Vector3(0, 0, -5), scene);\n camera.setTarget(Vector3.Zero());\n \n // Attach control needs to happen on the canvas. \n // Since we created engine with canvas, we can attach to it.\n // Note: In WebGPU engine.getRenderingCanvas() might differ slightly but usually works.\n const canvas = this.engine.getRenderingCanvas();\n if (canvas) {\n camera.attachControl(canvas, true);\n }\n\n // Light\n const light = new HemisphericLight('light1', new Vector3(0, 1, 0), scene);\n light.intensity = 0.7;\n\n // Rotating Plane\n this.plane = MeshBuilder.CreatePlane('plane', { size: 2 }, scene);\n this.plane.material = new StandardMaterial('mat', scene);\n (this.plane.material as StandardMaterial).backFaceCulling = false;\n (this.plane.material as StandardMaterial).diffuseColor = new Color3(0.4, 0.8, 1);\n (this.plane.material as StandardMaterial).wireframe = true;\n\n return scene;\n }\n\n public dispose(): void {\n if (this.engine) {\n this.engine.dispose();\n }\n }\n\n ngOnDestroy(): void {\n this.dispose();\n }\n}\n";
5
+ export declare const valenceEngineServiceTemplate = "\nimport { Injectable, NgZone, OnDestroy, signal, WritableSignal } from '@angular/core';\nimport { Engine, WebGPUEngine, Scene, FreeCamera, Vector3, HemisphericLight, MeshBuilder, StandardMaterial, Color3, Color4, Mesh } from 'babylonjs';\n// VALENCE: IMPORTS\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ValenceEngineService implements OnDestroy {\n private engine?: Engine | WebGPUEngine;\n private scene?: Scene;\n private plane?: Mesh;\n // VALENCE: PROPERTIES\n\n public fps: WritableSignal<string> = signal('0');\n public rotation: WritableSignal<string> = signal('X: 0.00 Y: 0.00 Z: 0.00');\n\n constructor(private ngZone: NgZone) {}\n\n public async initEngine(canvas: HTMLCanvasElement): Promise<void> {\n // Attempt WebGPU, fall back to WebGL\n try {\n if (await WebGPUEngine.IsSupportedAsync) {\n this.engine = new WebGPUEngine(canvas);\n await (this.engine as WebGPUEngine).initAsync();\n } else {\n this.engine = new Engine(canvas, true);\n }\n } catch (e) {\n console.warn('WebGPU failed, falling back to WebGL', e);\n this.engine = new Engine(canvas, true);\n }\n \n // VALENCE: PRE_SCENE_INIT\n\n this.scene = this.createScene();\n\n // VALENCE: POST_SCENE_INIT\n\n this.ngZone.runOutsideAngular(() => {\n if (!this.engine) return;\n \n this.engine.runRenderLoop(() => {\n if (this.scene) {\n this.scene.render();\n // Update specific meshes\n if (this.plane) {\n this.plane.rotation.y += 0.01;\n this.plane.rotation.x += 0.005;\n }\n \n // Update Object Stats\n if (this.plane) {\n const { x, y, z } = this.plane.rotation;\n this.rotation.set(`X: ${x.toFixed(2)} Y: ${y.toFixed(2)} Z: ${z.toFixed(2)}`);\n }\n\n // VALENCE: RENDER_LOOP\n }\n \n // Update FPS rarely to avoid UI thrashing, or just every frame if signal handles it efficient?\n // actually better to throttle it slightly for UI, but signal is fine.\n this.fps.set(this.engine?.getFps().toFixed(0) || '0');\n });\n });\n\n window.addEventListener('resize', () => {\n this.engine?.resize();\n });\n }\n\n private createScene(): Scene {\n if (!this.engine) throw new Error('Engine not initialized');\n const scene = new Scene(this.engine);\n scene.clearColor = new Color4(0.1, 0.1, 0.1, 1);\n \n // Camera\n const camera = new FreeCamera('camera1', new Vector3(0, 0, -5), scene);\n camera.setTarget(Vector3.Zero());\n \n // Attach control needs to happen on the canvas. \n // Since we created engine with canvas, we can attach to it.\n // Note: In WebGPU engine.getRenderingCanvas() might differ slightly but usually works.\n const canvas = this.engine.getRenderingCanvas();\n if (canvas) {\n camera.attachControl(canvas, true);\n }\n\n // Light\n const light = new HemisphericLight('light1', new Vector3(0, 1, 0), scene);\n light.intensity = 0.7;\n\n // Rotating Plane\n this.plane = MeshBuilder.CreatePlane('plane', { size: 2 }, scene);\n this.plane.material = new StandardMaterial('mat', scene);\n (this.plane.material as StandardMaterial).backFaceCulling = false;\n (this.plane.material as StandardMaterial).diffuseColor = new Color3(0.4, 0.8, 1);\n (this.plane.material as StandardMaterial).wireframe = true;\n\n return scene;\n }\n\n public dispose(): void {\n if (this.engine) {\n this.engine.dispose();\n }\n }\n\n ngOnDestroy(): void {\n this.dispose();\n }\n}\n";
6
6
  //# sourceMappingURL=angular.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"angular.d.ts","sourceRoot":"","sources":["../../src/templates/angular.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,iNAW9B,CAAC;AAEF,eAAO,MAAM,sBAAsB,mkCAgClC,CAAC;AAEF,eAAO,MAAM,wBAAwB,44KAyFpC,CAAC;AAEF,eAAO,MAAM,wBAAwB,i2FA6HpC,CAAC;AAEF,eAAO,MAAM,4BAA4B,wxHAiHxC,CAAC"}
1
+ {"version":3,"file":"angular.d.ts","sourceRoot":"","sources":["../../src/templates/angular.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,iNAW9B,CAAC;AAEF,eAAO,MAAM,sBAAsB,mkCAgClC,CAAC;AAEF,eAAO,MAAM,wBAAwB,i5KAyFpC,CAAC;AAEF,eAAO,MAAM,wBAAwB,i2FA6HpC,CAAC;AAEF,eAAO,MAAM,4BAA4B,ktHAgHxC,CAAC"}
@@ -126,7 +126,7 @@ export const appComponentHtmlTemplate = `
126
126
 
127
127
  <div class="debug-panel">
128
128
  <div class="stat-row"><span class="label">FPS:</span> <span class="value">{{ engineService.fps() }}</span></div>
129
- <div class="stat-row"><span class="label">CAM:</span> <span class="value">{{ engineService.rotation() }}</span></div>
129
+ <div class="stat-row"><span class="label">OBJ:</span> <span class="value vec3">{{ engineService.rotation() }}</span></div>
130
130
  <div class="stat-row info"><small>Angular + Electron + Babylon</small></div>
131
131
  </div>
132
132
  </div>
@@ -310,10 +310,9 @@ export class ValenceEngineService implements OnDestroy {
310
310
  this.plane.rotation.x += 0.005;
311
311
  }
312
312
 
313
- // Update Camera Stats
314
- const cam = this.scene.activeCamera as FreeCamera;
315
- if (cam && cam.rotation) {
316
- const { x, y, z } = cam.rotation;
313
+ // Update Object Stats
314
+ if (this.plane) {
315
+ const { x, y, z } = this.plane.rotation;
317
316
  this.rotation.set(\`X: \${x.toFixed(2)} Y: \${y.toFixed(2)} Z: \${z.toFixed(2)}\`);
318
317
  }
319
318
 
@@ -1 +1 @@
1
- {"version":3,"file":"angular.js","sourceRoot":"","sources":["../../src/templates/angular.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;CAWjC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCrC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFvC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6HvC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiH3C,CAAC"}
1
+ {"version":3,"file":"angular.js","sourceRoot":"","sources":["../../src/templates/angular.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;CAWjC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCrC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFvC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6HvC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgH3C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const audioServiceTemplate = "\nimport { Injectable, signal } from '@angular/core';\nimport { Howl, Howler } from 'howler';\n\nexport enum SoundChannel {\n MASTER = 'master',\n MUSIC = 'music',\n SFX = 'sfx',\n VOICE = 'voice'\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AudioService {\n \n // Volume Signals (0.0 - 1.0)\n public masterVolume = signal(1.0);\n public musicVolume = signal(0.8);\n public sfxVolume = signal(1.0);\n\n // Asset Registry\n private sounds = new Map<string, Howl>();\n private activeMusic: Howl | null = null;\n\n constructor() {\n this.initVolume();\n }\n\n // --- API ---\n\n public play(key: string, channel: SoundChannel = SoundChannel.SFX): number | null {\n const sound = this.sounds.get(key);\n if (!sound) {\n console.warn(`Sound not found: ${key}`);\n return null;\n }\n\n const id = sound.play();\n sound.volume(this.getChannelVolume(channel), id);\n return id;\n }\n\n public playMusic(key: string, fadeDuration = 1000) {\n if (this.activeMusic) {\n this.activeMusic.fade(this.activeMusic.volume(), 0, fadeDuration);\n setTimeout(() => this.activeMusic?.stop(), fadeDuration);\n }\n\n const sound = this.sounds.get(key);\n if (sound) {\n this.activeMusic = sound;\n sound.loop(true);\n sound.volume(0);\n sound.play();\n sound.fade(0, this.musicVolume(), fadeDuration);\n }\n }\n\n public load(key: string, src: string[]) {\n const sound = new Howl({ src });\n this.sounds.set(key, sound);\n }\n\n public setVolume(channel: SoundChannel, value: number) {\n switch(channel) {\n case SoundChannel.MASTER: \n this.masterVolume.set(value); \n Howler.volume(value);\n break;\n case SoundChannel.MUSIC: \n this.musicVolume.set(value);\n if (this.activeMusic) this.activeMusic.volume(value);\n break;\n case SoundChannel.SFX: \n this.sfxVolume.set(value);\n // Updating active SFX is harder without tracking IDs, usually play-time is enough.\n break;\n }\n }\n\n private getChannelVolume(channel: SoundChannel): number {\n switch(channel) {\n case SoundChannel.MUSIC: return this.musicVolume();\n case SoundChannel.SFX: return this.sfxVolume();\n default: return 1.0;\n }\n }\n\n private initVolume() {\n Howler.volume(this.masterVolume());\n }\n}\n";
2
+ //# sourceMappingURL=audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../src/templates/audio.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,41EA6FhC,CAAC"}
@@ -0,0 +1,95 @@
1
+ export const audioServiceTemplate = `
2
+ import { Injectable, signal } from '@angular/core';
3
+ import { Howl, Howler } from 'howler';
4
+
5
+ export enum SoundChannel {
6
+ MASTER = 'master',
7
+ MUSIC = 'music',
8
+ SFX = 'sfx',
9
+ VOICE = 'voice'
10
+ }
11
+
12
+ @Injectable({
13
+ providedIn: 'root'
14
+ })
15
+ export class AudioService {
16
+
17
+ // Volume Signals (0.0 - 1.0)
18
+ public masterVolume = signal(1.0);
19
+ public musicVolume = signal(0.8);
20
+ public sfxVolume = signal(1.0);
21
+
22
+ // Asset Registry
23
+ private sounds = new Map<string, Howl>();
24
+ private activeMusic: Howl | null = null;
25
+
26
+ constructor() {
27
+ this.initVolume();
28
+ }
29
+
30
+ // --- API ---
31
+
32
+ public play(key: string, channel: SoundChannel = SoundChannel.SFX): number | null {
33
+ const sound = this.sounds.get(key);
34
+ if (!sound) {
35
+ console.warn(\`Sound not found: \${key}\`);
36
+ return null;
37
+ }
38
+
39
+ const id = sound.play();
40
+ sound.volume(this.getChannelVolume(channel), id);
41
+ return id;
42
+ }
43
+
44
+ public playMusic(key: string, fadeDuration = 1000) {
45
+ if (this.activeMusic) {
46
+ this.activeMusic.fade(this.activeMusic.volume(), 0, fadeDuration);
47
+ setTimeout(() => this.activeMusic?.stop(), fadeDuration);
48
+ }
49
+
50
+ const sound = this.sounds.get(key);
51
+ if (sound) {
52
+ this.activeMusic = sound;
53
+ sound.loop(true);
54
+ sound.volume(0);
55
+ sound.play();
56
+ sound.fade(0, this.musicVolume(), fadeDuration);
57
+ }
58
+ }
59
+
60
+ public load(key: string, src: string[]) {
61
+ const sound = new Howl({ src });
62
+ this.sounds.set(key, sound);
63
+ }
64
+
65
+ public setVolume(channel: SoundChannel, value: number) {
66
+ switch(channel) {
67
+ case SoundChannel.MASTER:
68
+ this.masterVolume.set(value);
69
+ Howler.volume(value);
70
+ break;
71
+ case SoundChannel.MUSIC:
72
+ this.musicVolume.set(value);
73
+ if (this.activeMusic) this.activeMusic.volume(value);
74
+ break;
75
+ case SoundChannel.SFX:
76
+ this.sfxVolume.set(value);
77
+ // Updating active SFX is harder without tracking IDs, usually play-time is enough.
78
+ break;
79
+ }
80
+ }
81
+
82
+ private getChannelVolume(channel: SoundChannel): number {
83
+ switch(channel) {
84
+ case SoundChannel.MUSIC: return this.musicVolume();
85
+ case SoundChannel.SFX: return this.sfxVolume();
86
+ default: return 1.0;
87
+ }
88
+ }
89
+
90
+ private initVolume() {
91
+ Howler.volume(this.masterVolume());
92
+ }
93
+ }
94
+ `;
95
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../../src/templates/audio.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FnC,CAAC"}
@@ -4,4 +4,9 @@ export * from './components.js';
4
4
  export * from './config.js';
5
5
  export * from './multiplayer.js';
6
6
  export * from './native.js';
7
+ export * from './input.js';
8
+ export * from './audio.js';
9
+ export * from './storage.js';
10
+ export * from './settings.js';
11
+ export * from './state.js';
7
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
@@ -4,4 +4,9 @@ export * from './components.js';
4
4
  export * from './config.js';
5
5
  export * from './multiplayer.js';
6
6
  export * from './native.js';
7
+ export * from './input.js';
8
+ export * from './audio.js';
9
+ export * from './storage.js';
10
+ export * from './settings.js';
11
+ export * from './state.js';
7
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const inputServiceTemplate = "\nimport { Injectable, signal, WritableSignal } from '@angular/core';\n\nexport enum InputState {\n PRESSED,\n HELD,\n RELEASED,\n IDLE\n}\n\nexport interface InputAction {\n name: string;\n keys?: string[]; // KeyboardEvent.code (e.g., 'Space', 'KeyW')\n buttons?: number[]; // Gamepad Button Indices\n axis?: {\n index: number; // Gamepad Axis Index\n threshold: number; // Trigger threshold\n };\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class InputService {\n \n // State Map: Action Name -> Current State\n private actions = new Map<string, WritableSignal<InputState>>();\n private values = new Map<string, WritableSignal<number>>(); // For analog axes\n\n // Configuration\n private config: InputAction[] = [\n { name: 'MoveForward', keys: ['KeyW', 'ArrowUp'], axis: { index: 1, threshold: -0.5 } },\n { name: 'MoveBackward', keys: ['KeyS', 'ArrowDown'], axis: { index: 1, threshold: 0.5 } },\n { name: 'MoveLeft', keys: ['KeyA', 'ArrowLeft'], axis: { index: 0, threshold: -0.5 } },\n { name: 'MoveRight', keys: ['KeyD', 'ArrowRight'], axis: { index: 0, threshold: 0.5 } },\n { name: 'Jump', keys: ['Space'], buttons: [0] },\n { name: 'Run', keys: ['ShiftLeft', 'ShiftRight'], buttons: [1] },\n ];\n\n constructor() {\n this.initSignals();\n this.bindEvents();\n }\n\n // --- Public API ---\n\n public getState(action: string): InputState {\n return this.actions.get(action)?.() ?? InputState.IDLE;\n }\n\n public getValue(action: string): number {\n return this.values.get(action)?.() ?? 0;\n }\n\n public isPressed(action: string): boolean {\n const s = this.getState(action);\n return s === InputState.PRESSED || s === InputState.HELD;\n }\n\n public getActionSignal(action: string) {\n if (!this.actions.has(action)) this.initAction(action);\n return this.actions.get(action)!;\n }\n\n // --- Internal Logic ---\n\n private initSignals() {\n this.config.forEach(c => this.initAction(c.name));\n }\n\n private initAction(name: string) {\n if (!this.actions.has(name)) {\n this.actions.set(name, signal(InputState.IDLE));\n this.values.set(name, signal(0));\n }\n }\n\n private bindEvents() {\n window.addEventListener('keydown', (e) => this.handleKey(e.code, true));\n window.addEventListener('keyup', (e) => this.handleKey(e.code, false));\n \n // Poll Gamepad every frame\n this.pollGamepad();\n }\n\n private handleKey(code: string, pressed: boolean) {\n this.config.forEach(action => {\n if (action.keys?.includes(code)) {\n this.updateActionState(action.name, pressed ? 1 : 0);\n }\n });\n }\n\n private updateActionState(name: string, value: number) {\n const signalState = this.actions.get(name)!;\n const signalValue = this.values.get(name)!;\n\n const currentState = signalState();\n const isActive = Math.abs(value) > 0.1;\n\n signalValue.set(value);\n\n if (isActive) {\n if (currentState === InputState.IDLE || currentState === InputState.RELEASED) {\n signalState.set(InputState.PRESSED);\n // Auto-switch to HELD after next tick? \n // For simplicity, we keep as PRESSED for one frame logic usually handled by game loop.\n // Here we just toggle active.\n } else {\n signalState.set(InputState.HELD);\n }\n } else {\n if (currentState === InputState.PRESSED || currentState === InputState.HELD) {\n signalState.set(InputState.RELEASED);\n // Reset to IDLE shortly after\n setTimeout(() => signalState.set(InputState.IDLE), 50); \n }\n }\n }\n\n private pollGamepad() {\n // Simple rAF loop for gamepad polling\n const loop = () => {\n const pads = navigator.getGamepads();\n const gp = pads[0]; // Assuming P1\n \n if (gp) {\n this.config.forEach(action => {\n let maxVal = 0;\n\n // Check Buttons\n if (action.buttons) {\n for (const btnIdx of action.buttons) {\n if (gp.buttons[btnIdx]?.pressed) maxVal = 1;\n }\n }\n\n // Check Axis\n if (action.axis) {\n const val = gp.axes[action.axis.index];\n if (Math.abs(val) > 0.1) { // Deadzone\n // Direction check\n if (action.axis.threshold < 0 && val < action.axis.threshold) maxVal = Math.abs(val);\n if (action.axis.threshold > 0 && val > action.axis.threshold) maxVal = Math.abs(val);\n }\n }\n\n if (maxVal > 0) this.updateActionState(action.name, maxVal);\n // Note: Logic needs to handle \"release\" if gamepad stops\n // Implementation simplified for brevity\n });\n }\n requestAnimationFrame(loop);\n };\n loop();\n }\n}\n";
2
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/templates/input.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,+uJA6JhC,CAAC"}
@@ -0,0 +1,159 @@
1
+ export const inputServiceTemplate = `
2
+ import { Injectable, signal, WritableSignal } from '@angular/core';
3
+
4
+ export enum InputState {
5
+ PRESSED,
6
+ HELD,
7
+ RELEASED,
8
+ IDLE
9
+ }
10
+
11
+ export interface InputAction {
12
+ name: string;
13
+ keys?: string[]; // KeyboardEvent.code (e.g., 'Space', 'KeyW')
14
+ buttons?: number[]; // Gamepad Button Indices
15
+ axis?: {
16
+ index: number; // Gamepad Axis Index
17
+ threshold: number; // Trigger threshold
18
+ };
19
+ }
20
+
21
+ @Injectable({
22
+ providedIn: 'root'
23
+ })
24
+ export class InputService {
25
+
26
+ // State Map: Action Name -> Current State
27
+ private actions = new Map<string, WritableSignal<InputState>>();
28
+ private values = new Map<string, WritableSignal<number>>(); // For analog axes
29
+
30
+ // Configuration
31
+ private config: InputAction[] = [
32
+ { name: 'MoveForward', keys: ['KeyW', 'ArrowUp'], axis: { index: 1, threshold: -0.5 } },
33
+ { name: 'MoveBackward', keys: ['KeyS', 'ArrowDown'], axis: { index: 1, threshold: 0.5 } },
34
+ { name: 'MoveLeft', keys: ['KeyA', 'ArrowLeft'], axis: { index: 0, threshold: -0.5 } },
35
+ { name: 'MoveRight', keys: ['KeyD', 'ArrowRight'], axis: { index: 0, threshold: 0.5 } },
36
+ { name: 'Jump', keys: ['Space'], buttons: [0] },
37
+ { name: 'Run', keys: ['ShiftLeft', 'ShiftRight'], buttons: [1] },
38
+ ];
39
+
40
+ constructor() {
41
+ this.initSignals();
42
+ this.bindEvents();
43
+ }
44
+
45
+ // --- Public API ---
46
+
47
+ public getState(action: string): InputState {
48
+ return this.actions.get(action)?.() ?? InputState.IDLE;
49
+ }
50
+
51
+ public getValue(action: string): number {
52
+ return this.values.get(action)?.() ?? 0;
53
+ }
54
+
55
+ public isPressed(action: string): boolean {
56
+ const s = this.getState(action);
57
+ return s === InputState.PRESSED || s === InputState.HELD;
58
+ }
59
+
60
+ public getActionSignal(action: string) {
61
+ if (!this.actions.has(action)) this.initAction(action);
62
+ return this.actions.get(action)!;
63
+ }
64
+
65
+ // --- Internal Logic ---
66
+
67
+ private initSignals() {
68
+ this.config.forEach(c => this.initAction(c.name));
69
+ }
70
+
71
+ private initAction(name: string) {
72
+ if (!this.actions.has(name)) {
73
+ this.actions.set(name, signal(InputState.IDLE));
74
+ this.values.set(name, signal(0));
75
+ }
76
+ }
77
+
78
+ private bindEvents() {
79
+ window.addEventListener('keydown', (e) => this.handleKey(e.code, true));
80
+ window.addEventListener('keyup', (e) => this.handleKey(e.code, false));
81
+
82
+ // Poll Gamepad every frame
83
+ this.pollGamepad();
84
+ }
85
+
86
+ private handleKey(code: string, pressed: boolean) {
87
+ this.config.forEach(action => {
88
+ if (action.keys?.includes(code)) {
89
+ this.updateActionState(action.name, pressed ? 1 : 0);
90
+ }
91
+ });
92
+ }
93
+
94
+ private updateActionState(name: string, value: number) {
95
+ const signalState = this.actions.get(name)!;
96
+ const signalValue = this.values.get(name)!;
97
+
98
+ const currentState = signalState();
99
+ const isActive = Math.abs(value) > 0.1;
100
+
101
+ signalValue.set(value);
102
+
103
+ if (isActive) {
104
+ if (currentState === InputState.IDLE || currentState === InputState.RELEASED) {
105
+ signalState.set(InputState.PRESSED);
106
+ // Auto-switch to HELD after next tick?
107
+ // For simplicity, we keep as PRESSED for one frame logic usually handled by game loop.
108
+ // Here we just toggle active.
109
+ } else {
110
+ signalState.set(InputState.HELD);
111
+ }
112
+ } else {
113
+ if (currentState === InputState.PRESSED || currentState === InputState.HELD) {
114
+ signalState.set(InputState.RELEASED);
115
+ // Reset to IDLE shortly after
116
+ setTimeout(() => signalState.set(InputState.IDLE), 50);
117
+ }
118
+ }
119
+ }
120
+
121
+ private pollGamepad() {
122
+ // Simple rAF loop for gamepad polling
123
+ const loop = () => {
124
+ const pads = navigator.getGamepads();
125
+ const gp = pads[0]; // Assuming P1
126
+
127
+ if (gp) {
128
+ this.config.forEach(action => {
129
+ let maxVal = 0;
130
+
131
+ // Check Buttons
132
+ if (action.buttons) {
133
+ for (const btnIdx of action.buttons) {
134
+ if (gp.buttons[btnIdx]?.pressed) maxVal = 1;
135
+ }
136
+ }
137
+
138
+ // Check Axis
139
+ if (action.axis) {
140
+ const val = gp.axes[action.axis.index];
141
+ if (Math.abs(val) > 0.1) { // Deadzone
142
+ // Direction check
143
+ if (action.axis.threshold < 0 && val < action.axis.threshold) maxVal = Math.abs(val);
144
+ if (action.axis.threshold > 0 && val > action.axis.threshold) maxVal = Math.abs(val);
145
+ }
146
+ }
147
+
148
+ if (maxVal > 0) this.updateActionState(action.name, maxVal);
149
+ // Note: Logic needs to handle "release" if gamepad stops
150
+ // Implementation simplified for brevity
151
+ });
152
+ }
153
+ requestAnimationFrame(loop);
154
+ };
155
+ loop();
156
+ }
157
+ }
158
+ `;
159
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/templates/input.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6JnC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const phaserSceneTemplate: (name: string, className: string) => string;
2
+ //# sourceMappingURL=phaser-scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phaser-scene.d.ts","sourceRoot":"","sources":["../../src/templates/phaser-scene.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,WAAW,MAAM,WAyBlE,CAAC"}
@@ -0,0 +1,27 @@
1
+ export const phaserSceneTemplate = (name, className) => `
2
+ import Phaser from 'phaser';
3
+
4
+ export class ${className} extends Phaser.Scene {
5
+ constructor() {
6
+ super({ key: '${className}' });
7
+ }
8
+
9
+ preload() {
10
+ // Load assets
11
+ }
12
+
13
+ create() {
14
+ const { width, height } = this.scale;
15
+
16
+ this.add.text(width / 2, height / 2, 'Scene: ${name}', {
17
+ fontSize: '32px',
18
+ color: '#ffffff'
19
+ }).setOrigin(0.5);
20
+ }
21
+
22
+ update(time: number, delta: number) {
23
+ // Game loop
24
+ }
25
+ }
26
+ `;
27
+ //# sourceMappingURL=phaser-scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phaser-scene.js","sourceRoot":"","sources":["../../src/templates/phaser-scene.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,SAAiB,EAAE,EAAE,CAAC;;;eAGzD,SAAS;;wBAEA,SAAS;;;;;;;;;;uDAUsB,IAAI;;;;;;;;;;CAU1D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const phaserEngineServiceTemplate = "\nimport { Injectable, NgZone, ElementRef, OnDestroy, signal, WritableSignal } from '@angular/core';\nimport Phaser from 'phaser';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PhaserEngineService implements OnDestroy {\n public game: Phaser.Game | null = null;\n public fps: WritableSignal<string> = signal('0');\n public rotation: WritableSignal<string> = signal('2D Mode');\n \n private canvas: HTMLCanvasElement | null = null;\n\n constructor(private ngZone: NgZone) {}\n\n public initGame(canvas: HTMLCanvasElement) {\n this.canvas = canvas;\n this.ngZone.runOutsideAngular(() => {\n if (!this.game) {\n this.createGame();\n }\n });\n }\n\n private createGame() {\n if (!this.canvas) return;\n\n const config: Phaser.Types.Core.GameConfig = {\n type: Phaser.WEBGL,\n canvas: this.canvas,\n width: window.innerWidth,\n height: window.innerHeight,\n backgroundColor: '#0d0d0d', // Match 3D background\n physics: {\n default: 'arcade',\n arcade: {\n gravity: { x: 0, y: 0 },\n debug: false\n }\n },\n scene: [MainScene],\n scale: {\n mode: Phaser.Scale.RESIZE,\n autoCenter: Phaser.Scale.CENTER_BOTH\n },\n callbacks: {\n postBoot: (game) => {\n // Hook into the loop for stats\n const originalStep = game.loop.step;\n game.loop.step = (time: number, delta: number) => {\n originalStep.call(game.loop, time, delta);\n this.fps.set(game.loop.actualFps.toFixed(0));\n };\n }\n }\n };\n\n this.game = new Phaser.Game(config);\n \n window.addEventListener('resize', this.onResize.bind(this));\n }\n\n private onResize() {\n if (this.game) {\n this.game.scale.resize(window.innerWidth, window.innerHeight);\n }\n }\n\n ngOnDestroy(): void {\n if (this.game) {\n this.game.destroy(true);\n this.game = null;\n }\n window.removeEventListener('resize', this.onResize.bind(this));\n }\n}\n\nclass MainScene extends Phaser.Scene {\n private logoContainer!: Phaser.GameObjects.Container;\n\n constructor() {\n super({ key: 'MainScene' });\n }\n\n create() {\n const centerX = this.scale.width / 2;\n const centerY = this.scale.height / 2;\n\n this.logoContainer = this.add.container(centerX, centerY);\n\n // V Logo Graphics\n const graphics = this.add.graphics();\n \n // Glow effect (simulated with multiple overlapping strokes)\n graphics.lineStyle(4, 0x47848F, 0.3);\n this.drawV(graphics, 6); // Outer glow\n \n graphics.lineStyle(2, 0x47848F, 1);\n this.drawV(graphics, 0); // Main Stroke\n\n // Red Accent\n const accent = this.add.graphics();\n accent.lineStyle(2, 0xDD0031, 1);\n accent.beginPath();\n accent.moveTo(-50, 0);\n accent.lineTo(0, 100);\n accent.strokePath();\n\n this.logoContainer.add([graphics, accent]);\n\n // Text\n const text = this.add.text(0, 140, 'VALENCE 2D', {\n fontFamily: 'Segoe UI',\n fontSize: '32px',\n color: '#ffffff',\n align: 'center'\n }).setOrigin(0.5);\n \n const subtext = this.add.text(0, 180, 'Angular + Electron + Phaser', {\n fontFamily: 'Segoe UI',\n fontSize: '14px',\n color: '#888888',\n align: 'center'\n }).setOrigin(0.5);\n\n this.logoContainer.add([text, subtext]);\n }\n\n private drawV(graphics: Phaser.GameObjects.Graphics, offset: number) {\n // V Shape\n graphics.beginPath();\n graphics.moveTo(-50 - offset, -50 - offset);\n graphics.lineTo(0, 50 + offset);\n graphics.lineTo(50 + offset, -50 - offset);\n graphics.strokePath();\n }\n\n update() {\n if (this.logoContainer) {\n this.logoContainer.rotation = Math.sin(this.time.now / 1000) * 0.05;\n this.logoContainer.scale = 1 + Math.sin(this.time.now / 2000) * 0.02;\n }\n }\n}\n";
2
+ //# sourceMappingURL=phaser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phaser.d.ts","sourceRoot":"","sources":["../../src/templates/phaser.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,8nIAiJvC,CAAC"}
@@ -0,0 +1,147 @@
1
+ export const phaserEngineServiceTemplate = `
2
+ import { Injectable, NgZone, ElementRef, OnDestroy, signal, WritableSignal } from '@angular/core';
3
+ import Phaser from 'phaser';
4
+
5
+ @Injectable({
6
+ providedIn: 'root'
7
+ })
8
+ export class PhaserEngineService implements OnDestroy {
9
+ public game: Phaser.Game | null = null;
10
+ public fps: WritableSignal<string> = signal('0');
11
+ public rotation: WritableSignal<string> = signal('2D Mode');
12
+
13
+ private canvas: HTMLCanvasElement | null = null;
14
+
15
+ constructor(private ngZone: NgZone) {}
16
+
17
+ public initGame(canvas: HTMLCanvasElement) {
18
+ this.canvas = canvas;
19
+ this.ngZone.runOutsideAngular(() => {
20
+ if (!this.game) {
21
+ this.createGame();
22
+ }
23
+ });
24
+ }
25
+
26
+ private createGame() {
27
+ if (!this.canvas) return;
28
+
29
+ const config: Phaser.Types.Core.GameConfig = {
30
+ type: Phaser.WEBGL,
31
+ canvas: this.canvas,
32
+ width: window.innerWidth,
33
+ height: window.innerHeight,
34
+ backgroundColor: '#0d0d0d', // Match 3D background
35
+ physics: {
36
+ default: 'arcade',
37
+ arcade: {
38
+ gravity: { x: 0, y: 0 },
39
+ debug: false
40
+ }
41
+ },
42
+ scene: [MainScene],
43
+ scale: {
44
+ mode: Phaser.Scale.RESIZE,
45
+ autoCenter: Phaser.Scale.CENTER_BOTH
46
+ },
47
+ callbacks: {
48
+ postBoot: (game) => {
49
+ // Hook into the loop for stats
50
+ const originalStep = game.loop.step;
51
+ game.loop.step = (time: number, delta: number) => {
52
+ originalStep.call(game.loop, time, delta);
53
+ this.fps.set(game.loop.actualFps.toFixed(0));
54
+ };
55
+ }
56
+ }
57
+ };
58
+
59
+ this.game = new Phaser.Game(config);
60
+
61
+ window.addEventListener('resize', this.onResize.bind(this));
62
+ }
63
+
64
+ private onResize() {
65
+ if (this.game) {
66
+ this.game.scale.resize(window.innerWidth, window.innerHeight);
67
+ }
68
+ }
69
+
70
+ ngOnDestroy(): void {
71
+ if (this.game) {
72
+ this.game.destroy(true);
73
+ this.game = null;
74
+ }
75
+ window.removeEventListener('resize', this.onResize.bind(this));
76
+ }
77
+ }
78
+
79
+ class MainScene extends Phaser.Scene {
80
+ private logoContainer!: Phaser.GameObjects.Container;
81
+
82
+ constructor() {
83
+ super({ key: 'MainScene' });
84
+ }
85
+
86
+ create() {
87
+ const centerX = this.scale.width / 2;
88
+ const centerY = this.scale.height / 2;
89
+
90
+ this.logoContainer = this.add.container(centerX, centerY);
91
+
92
+ // V Logo Graphics
93
+ const graphics = this.add.graphics();
94
+
95
+ // Glow effect (simulated with multiple overlapping strokes)
96
+ graphics.lineStyle(4, 0x47848F, 0.3);
97
+ this.drawV(graphics, 6); // Outer glow
98
+
99
+ graphics.lineStyle(2, 0x47848F, 1);
100
+ this.drawV(graphics, 0); // Main Stroke
101
+
102
+ // Red Accent
103
+ const accent = this.add.graphics();
104
+ accent.lineStyle(2, 0xDD0031, 1);
105
+ accent.beginPath();
106
+ accent.moveTo(-50, 0);
107
+ accent.lineTo(0, 100);
108
+ accent.strokePath();
109
+
110
+ this.logoContainer.add([graphics, accent]);
111
+
112
+ // Text
113
+ const text = this.add.text(0, 140, 'VALENCE 2D', {
114
+ fontFamily: 'Segoe UI',
115
+ fontSize: '32px',
116
+ color: '#ffffff',
117
+ align: 'center'
118
+ }).setOrigin(0.5);
119
+
120
+ const subtext = this.add.text(0, 180, 'Angular + Electron + Phaser', {
121
+ fontFamily: 'Segoe UI',
122
+ fontSize: '14px',
123
+ color: '#888888',
124
+ align: 'center'
125
+ }).setOrigin(0.5);
126
+
127
+ this.logoContainer.add([text, subtext]);
128
+ }
129
+
130
+ private drawV(graphics: Phaser.GameObjects.Graphics, offset: number) {
131
+ // V Shape
132
+ graphics.beginPath();
133
+ graphics.moveTo(-50 - offset, -50 - offset);
134
+ graphics.lineTo(0, 50 + offset);
135
+ graphics.lineTo(50 + offset, -50 - offset);
136
+ graphics.strokePath();
137
+ }
138
+
139
+ update() {
140
+ if (this.logoContainer) {
141
+ this.logoContainer.rotation = Math.sin(this.time.now / 1000) * 0.05;
142
+ this.logoContainer.scale = 1 + Math.sin(this.time.now / 2000) * 0.02;
143
+ }
144
+ }
145
+ }
146
+ `;
147
+ //# sourceMappingURL=phaser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phaser.js","sourceRoot":"","sources":["../../src/templates/phaser.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiJ1C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const settingsServiceTemplate = "\nimport { Injectable, signal, effect } from '@angular/core';\nimport { StorageService } from '../storage/storage.service';\nimport { AudioService, SoundChannel } from '../audio/audio.service';\n\nexport interface GameSettings {\n audio: {\n master: number;\n music: number;\n sfx: number;\n };\n graphics: {\n resolutionScale: number;\n antialiasing: boolean;\n };\n}\n\nconst DEFAULT_SETTINGS: GameSettings = {\n audio: { master: 1.0, music: 0.8, sfx: 1.0 },\n graphics: { resolutionScale: 1.0, antialiasing: true }\n};\n\n@Injectable({\n providedIn: 'root'\n})\nexport class SettingsService {\n\n public settings = signal<GameSettings>(DEFAULT_SETTINGS);\n\n constructor(\n private storage: StorageService,\n private audio: AudioService\n ) {\n\n // Auto-save on change (debounced effect could be better, but simple for now)\n effect(() => {\n const s = this.settings();\n this.applySettings(s);\n this.storage.save('settings', s);\n });\n }\n\n private async loadSettings() {\n const saved = await this.storage.load<GameSettings>('settings');\n if (saved) {\n this.settings.set({ ...DEFAULT_SETTINGS, ...saved });\n }\n }\n\n private applySettings(s: GameSettings) {\n this.audio.setVolume(SoundChannel.MASTER, s.audio.master);\n this.audio.setVolume(SoundChannel.MUSIC, s.audio.music);\n this.audio.setVolume(SoundChannel.SFX, s.audio.sfx);\n \n // Graphics would typically be pulled by the EngineService from this signal\n }\n\n public updateAudio(channel: keyof GameSettings['audio'], value: number) {\n this.settings.update(s => ({\n ...s,\n audio: { ...s.audio, [channel]: value }\n }));\n }\n}\n";
2
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/templates/settings.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,qrDAgEnC,CAAC"}