Code edit (1 edits merged)
Please save this source code
User prompt
update with: // Reduce spawn ahead time even more var NOTE_SPAWN_AHEAD_MS = 400; // Reduced from 800ms to 400ms // Fix the tap feedback positioning in game.down function game.down = function (x, y, obj) { if (playerHealth <= 0 || !gameStartTime) { return; } var currentTime = Date.now() - gameStartTime; // Show tap feedback AT THE TAP LOCATION, not on scanner var feedback = game.attachAsset('tapFeedback', { anchorX: 0.5, anchorY: 0.5 }); feedback.x = x; feedback.y = y; // Changed from scanner.y to actual tap y position feedback.alpha = 0.7; tween(feedback, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, onFinish: function onFinish() { if (feedback && feedback.destroy) { feedback.destroy(); } } }); // Rest of the hit detection code stays the same... var hitOccurred = false; var tapRadius = 100; for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note || !note.active || note.isHit || note.isSpawning) { continue; } // Check distance from tap var dx = x - note.x; var dy = y - note.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < tapRadius) { // Check timing - note should be near scanner var scannerDiff = Math.abs(scanner.y - note.y); if (scannerDiff < HIT_TOLERANCE_PX) { note.isHit = true; note.showHitEffect(); var timeDiff = Math.abs(currentTime - note.hitTime); var points = 10; if (timeDiff < 50) points = 20; // Perfect hit else if (timeDiff < 100) points = 15; // Great hit LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); hitOccurred = true; if (LK.getScore() >= TARGET_SCORE_TO_WIN) { LK.showYouWin(); gameStartTime = null; tween.stop(scanner); return; } break; } } } }; // Also improve the spawn timing logic function spawnNotesForCurrentTime(currentTime) { if (!currentSongData) { return; } currentSongData.notes.forEach(function (noteData, index) { var noteKey = index + '_' + noteData.time; if (spawnedNotes.indexOf(noteKey) === -1) { var timeUntilHit = noteData.time - currentTime; // More precise spawning - only spawn when really needed if (timeUntilHit <= NOTE_SPAWN_AHEAD_MS && timeUntilHit > -200) { if (!noteData.color) { noteData.color = NOTE_COLORS[Math.floor(Math.random() * NOTE_COLORS.length)]; } var properY = calculateNoteYFromTime(noteData.time, currentTime); var correctedNoteData = { time: noteData.time, type: noteData.type, x: noteData.x, y: properY, color: noteData.color, duration: noteData.duration, path: noteData.path }; var note = new Note(correctedNoteData, currentTime); notes.push(note); game.addChild(note); note.spawnIn(); spawnedNotes.push(noteKey); } } }); } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
update with: // Game Constants (update these) var NOTE_SPAWN_AHEAD_MS = 800; // Reduce from 2000 to 800ms - notes appear closer to hit time var SCANNER_CYCLE_DURATION = 4000; // 4 seconds total cycle var SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2; // 2 seconds each direction // Modified spawnNotesForCurrentTime function function spawnNotesForCurrentTime(currentTime) { if (!currentSongData) { return; } currentSongData.notes.forEach(function (noteData, index) { var noteKey = index + '_' + noteData.time; // Only spawn if not already spawned and within spawn window if (spawnedNotes.indexOf(noteKey) === -1) { // Calculate when this note should spawn based on scanner position var timeUntilHit = noteData.time - currentTime; // Spawn note when scanner is approaching (not all at once) if (timeUntilHit <= NOTE_SPAWN_AHEAD_MS && timeUntilHit > -500) { // Assign random color if not specified if (!noteData.color) { noteData.color = NOTE_COLORS[Math.floor(Math.random() * NOTE_COLORS.length)]; } // Calculate proper Y position based on scanner cycle var properY = calculateNoteYFromTime(noteData.time, currentTime); // Override the Y position with calculated value var correctedNoteData = { time: noteData.time, type: noteData.type, x: noteData.x, y: properY, // Use calculated Y position color: noteData.color, duration: noteData.duration, path: noteData.path }; var note = new Note(correctedNoteData, currentTime); notes.push(note); game.addChild(note); note.spawnIn(); spawnedNotes.push(noteKey); } } }); } // Fixed calculateNoteYFromTime function function calculateNoteYFromTime(noteTime, currentTime) { // Determine where in the scanner cycle this note should be hit var cycleTime = noteTime % SCANNER_CYCLE_DURATION; if (cycleTime < SCANNER_HALF_CYCLE) { // First half of cycle: scanner moving down var progressInPhase = cycleTime / SCANNER_HALF_CYCLE; return SCANNER_Y_MIN + (progressInPhase * PLAY_AREA_HEIGHT); } else { // Second half of cycle: scanner moving up var progressInPhase = (cycleTime - SCANNER_HALF_CYCLE) / SCANNER_HALF_CYCLE; return SCANNER_Y_MAX - (progressInPhase * PLAY_AREA_HEIGHT); } } // Update the default song data with better timing spread var defaultSongData = { bpm: 120, scannerCycleDuration: 4000, notes: [ // First cycle (0-4000ms) - spread across scanner movement { time: 500, type: 'tap', x: 500, y: 0 }, // Early in down movement { time: 1200, type: 'tap', x: 800, y: 0 }, // Mid down movement { time: 2000, type: 'tap', x: 1200, y: 0 }, // Bottom turn { time: 2800, type: 'hold', x: 600, y: 0, duration: 500 }, // Mid up movement { time: 3500, type: 'tap', x: 900, y: 0 }, // Near top // Second cycle (4000-8000ms) { time: 4500, type: 'tap', x: 400, y: 0 }, { time: 5200, type: 'drag', x: 1000, y: 0, path: [{ x: 1200, y: 0 }] }, { time: 6000, type: 'tap', x: 700, y: 0 }, { time: 6800, type: 'hold', x: 800, y: 0, duration: 400 }, { time: 7500, type: 'tap', x: 500, y: 0 }, // Third cycle (8000-12000ms) { time: 8500, type: 'tap', x: 600, y: 0 }, { time: 9200, type: 'tap', x: 1100, y: 0 }, { time: 10000, type: 'drag', x: 400, y: 0, path: [{ x: 600, y: 0 }] }, { time: 10800, type: 'tap', x: 900, y: 0 }, { time: 11500, type: 'tap', x: 800, y: 0 } ] };
User prompt
the scanline should start at the bottom and move upwards
User prompt
Please fix the bug: 'TypeError: self.createHoldTrail is not a function' in or related to this line: 'self.createHoldTrail();' Line Number: 68
User prompt
Please fix the bug: 'Set is not a constructor' in or related to this line: 'var spawnedNotes = new Set(); // Track which notes have been spawned' Line Number: 216
Code edit (1 edits merged)
Please save this source code
User prompt
Do not remove player health if enemy hit is succrssful.
User prompt
Do not spawn enemies at the very top and bottom 10%.
User prompt
Please fix the bug: 'LK.gui.add is not a function. (In 'LK.gui.add(pip)', 'LK.gui.add' is undefined)' in or related to this line: 'LK.gui.add(pip); // Add to general GUI layer' Line Number: 327
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Lane Defender
Initial prompt
**RHYTHM TOWER DEFENSE PROTOTYPE - GAME DESIGN SPECIFICATION** Create a single-screen rhythm tower defense game with these specifications: **Core Layout:** - 3 vertical lanes of equal width spanning the screen - Horizontal timing bar that spans full screen width - Timing bar continuously travels up and down the screen in smooth, deliberate motion - Enemies spawn as stationary objects positioned throughout the lane areas **Game Mechanics:** - Timing bar completes one full up-down cycle over 8 beats at 100-110 BPM (slower, more strategic pace) - Enemies appear as stationary targets in lanes, aligned to beat timing - **Maximum 2 enemies can share the same horizontal position (row) across all lanes** - respecting two-thumb input limitation - Different enemy patterns can appear on upstroke vs downstroke passes - Player presses lane key when timing bar intersects stationary enemy AND on the beat - Successful hits destroy enemies with visual destruction effect - Score system tracks successful hits with end-game rating **Enemy System:** - Simple colored shapes positioned at various heights within lanes - Spawn timing aligned to beat structure with strategic positioning - Stationary once spawned - timing bar does the moving - Visual destruction effect when successfully eliminated - Spawn logic ensures never more than 2 enemies at same horizontal level **Controls & Feedback:** - 3 lane input keys (suggest A, S, D) - Visual feedback for key presses - Clear indication when timing bar aligns with enemies - Beat-synchronized visual cues throughout game **UI Elements:** - Score display - Health indicator (reduced when enemies are missed) - Lane key indicators at bottom - End-game rating system based on performance **Technical Focus:** - Smooth timing bar animation at deliberate pace (8-beat cycles) - Precise hit detection when bar intersects stationary enemies on beat - Enemy spawn system that respects 2-enemy-per-row maximum - Clean visual feedback for successful hits and misses
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Note = Container.expand(function (noteData, spawnTime) { var self = Container.call(this); // Note properties self.noteData = noteData; self.type = noteData.type; // 'tap', 'hold', 'drag' self.targetX = noteData.x; self.targetY = noteData.y; self.color = noteData.color || 0x00ffff; self.hitTime = noteData.time; self.duration = noteData.duration || 0; // For hold notes self.path = noteData.path || []; // For drag notes self.spawnTime = spawnTime; // State self.active = true; self.isHit = false; self.isMissed = false; self.isSpawning = true; self.isHolding = false; // For hold notes self.holdStarted = false; // Visual components var assetName = self.type === 'hold' ? 'holdNoteCore' : self.type === 'drag' ? 'dragNoteCore' : 'noteCore'; self.noteGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.noteGraphics.tint = self.color; self.glowRing = self.attachAsset('glowRing', { anchorX: 0.5, anchorY: 0.5 }); self.glowRing.tint = self.color; self.glowRing.alpha = 0.3; // Position self.x = self.targetX; self.y = self.targetY; // Start invisible and scale up self.alpha = 0; self.scaleX = 0.1; self.scaleY = 0.1; // Hold trail for hold notes self.holdTrails = []; if (self.type === 'hold' && self.duration > 0) { self.createHoldTrail(); } self.createHoldTrail = function () { var endY = self.targetY + self.duration / 4 * 100; // Rough calculation var trailLength = Math.abs(endY - self.targetY); var segments = Math.max(1, Math.floor(trailLength / 20)); for (var i = 0; i < segments; i++) { var trail = self.attachAsset('holdTrail', { anchorX: 0.5, anchorY: 0 }); trail.tint = self.color; trail.alpha = 0.6; trail.y = i * 20; trail.height = 20; self.holdTrails.push(trail); } }; self.spawnIn = function () { self.isSpawning = true; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.isSpawning = false; } }); }; self.showHitEffect = function () { LK.getSound('hitSound').play(); self.explodeIntoParticles(); // Scale and fade effect tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.active = false; } }); }; self.showMissEffect = function () { LK.getSound('missSound').play(); // Flash red then fade tween(self.noteGraphics, { tint: 0xff0000 }, { duration: 150, onFinish: function onFinish() { tween(self, { alpha: 0.3 }, { duration: 300, onFinish: function onFinish() { self.active = false; } }); } }); }; self.explodeIntoParticles = function () { var particleCount = 12; for (var i = 0; i < particleCount; i++) { var particle = game.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.tint = self.color; particle.x = self.x; particle.y = self.y; var angle = i / particleCount * Math.PI * 2; var speed = 150 + Math.random() * 100; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.3, scaleY: 0.3 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (particle && particle.destroy) { particle.destroy(); } } }); } }; return self; }); var Scanner = Container.expand(function () { var self = Container.call(this); // Scanner glow (background) self.glow = self.attachAsset('scannerGlow', { anchorX: 0, anchorY: 0.5 }); self.glow.alpha = 0.3; // Scanner line (foreground) self.line = self.attachAsset('scannerLine', { anchorX: 0, anchorY: 0.5 }); self.x = 0; self.isMovingDown = true; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Pure black background }); /**** * Game Code ****/ // Game Constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BPM = 120; var BEAT_DURATION_MS = 60 / BPM * 1000; var SCANNER_Y_MIN = 200; var SCANNER_Y_MAX = GAME_HEIGHT - 300; var PLAY_AREA_HEIGHT = SCANNER_Y_MAX - SCANNER_Y_MIN; var INITIAL_PLAYER_HEALTH = 5; var HIT_TOLERANCE_PX = 80; var TARGET_SCORE_TO_WIN = 1000; var NOTE_SPAWN_AHEAD_MS = 2000; // Notes appear 2 seconds early // Game State Variables var playerHealth; var healthPips = []; var scoreTxt; var scanner; var scannerIsMovingDown = true; var gameStartTime; var notes = []; var currentSongData; var spawnedNotes = []; // Track which notes have been spawned // Color palette for notes var NOTE_COLORS = [0x00ffff, // Cyan 0xff00ff, // Magenta 0xffff00, // Yellow 0x00ff00, // Green 0xff8800, // Orange 0x8800ff // Purple ]; // Default test song data var defaultSongData = { bpm: 120, scannerCycleDuration: 4000, // 4 seconds per cycle notes: [ // Beat 1 { time: 1000, type: 'tap', x: 500, y: 400 }, { time: 1500, type: 'tap', x: 800, y: 500 }, // Beat 2 { time: 2000, type: 'tap', x: 1200, y: 350 }, { time: 2500, type: 'hold', x: 600, y: 600, duration: 500 }, // Beat 3 { time: 3000, type: 'tap', x: 400, y: 300 }, { time: 3200, type: 'tap', x: 900, y: 650 }, { time: 3500, type: 'drag', x: 1000, y: 400, path: [{ x: 1200, y: 500 }] }, // Beat 4 { time: 4000, type: 'tap', x: 700, y: 250 }, // Repeat pattern with variations { time: 5000, type: 'tap', x: 600, y: 500 }, { time: 5300, type: 'tap', x: 1100, y: 400 }, { time: 5600, type: 'hold', x: 800, y: 600, duration: 600 }, { time: 6500, type: 'tap', x: 500, y: 350 }, { time: 6800, type: 'tap', x: 1000, y: 550 }, { time: 7000, type: 'drag', x: 400, y: 300, path: [{ x: 600, y: 450 }] }, { time: 8000, type: 'tap', x: 800, y: 400 }] }; function initHealthDisplay() { healthPips.forEach(function (pip) { if (pip && pip.destroy) { pip.destroy(); } }); healthPips = []; var pipSpacing = 70; var totalPipsWidth = (INITIAL_PLAYER_HEALTH - 1) * pipSpacing + 60; var startX = GAME_WIDTH - totalPipsWidth - 30; for (var i = 0; i < INITIAL_PLAYER_HEALTH; i++) { var pip = LK.getAsset('healthPip', { anchorX: 0.5, anchorY: 0.5 }); pip.x = startX + i * pipSpacing; pip.y = 70; LK.gui.top.addChild(pip); healthPips.push(pip); } } function updateHealthDisplay() { for (var i = 0; i < healthPips.length; i++) { if (healthPips[i]) { healthPips[i].visible = i < playerHealth; } } if (playerHealth <= 0) { LK.showGameOver(); gameStartTime = null; tween.stop(scanner); } } function moveScanner() { if (!scanner) return; tween.stop(scanner); var targetY = scannerIsMovingDown ? SCANNER_Y_MAX : SCANNER_Y_MIN; scanner.y = scannerIsMovingDown ? SCANNER_Y_MIN : SCANNER_Y_MAX; tween(scanner, { y: targetY }, { duration: currentSongData.scannerCycleDuration / 2, easing: tween.linear, onFinish: function onFinish() { scannerIsMovingDown = !scannerIsMovingDown; moveScanner(); } }); } function spawnNotesForCurrentTime(currentTime) { if (!currentSongData) return; currentSongData.notes.forEach(function (noteData, index) { var noteKey = index + '_' + noteData.time; if (spawnedNotes.indexOf(noteKey) === -1 && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime < noteData.time + 1000) { // Grace period for late spawns // Assign random color if not specified if (!noteData.color) { noteData.color = NOTE_COLORS[Math.floor(Math.random() * NOTE_COLORS.length)]; } var note = new Note(noteData, currentTime); notes.push(note); game.addChild(note); note.spawnIn(); spawnedNotes.push(noteKey); } }); } function setupGame() { LK.setScore(0); playerHealth = INITIAL_PLAYER_HEALTH; gameStartTime = Date.now(); // Score Text if (scoreTxt && scoreTxt.destroy) { scoreTxt.destroy(); } scoreTxt = new Text2('0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.setText(LK.getScore()); // Health Display initHealthDisplay(); updateHealthDisplay(); // Scanner if (scanner && scanner.destroy) { scanner.destroy(); } scanner = new Scanner(); scanner.y = SCANNER_Y_MIN; game.addChild(scanner); // Clear previous notes notes.forEach(function (note) { if (note && note.destroy) { note.destroy(); } }); notes = []; spawnedNotes = []; // Load song data currentSongData = defaultSongData; BPM = currentSongData.bpm; BEAT_DURATION_MS = 60 / BPM * 1000; // Start scanner movement scannerIsMovingDown = true; moveScanner(); LK.playMusic('gameMusic', { loop: true }); } function getScannerProgress() { if (!scanner) return 0; var totalRange = SCANNER_Y_MAX - SCANNER_Y_MIN; var currentPos = scanner.y - SCANNER_Y_MIN; return currentPos / totalRange; } function calculateNoteYFromTime(noteTime, currentTime, scannerProgress) { // Calculate where note should be positioned based on when scanner will reach it var timeUntilHit = noteTime - currentTime; var cycleDuration = currentSongData.scannerCycleDuration; var halfCycle = cycleDuration / 2; // Determine if we're in down or up phase when note should be hit var cycleTime = noteTime % cycleDuration; var isDownPhase = cycleTime < halfCycle; if (isDownPhase) { var progressInPhase = cycleTime / halfCycle; return SCANNER_Y_MIN + progressInPhase * PLAY_AREA_HEIGHT; } else { var progressInPhase = (cycleTime - halfCycle) / halfCycle; return SCANNER_Y_MAX - progressInPhase * PLAY_AREA_HEIGHT; } } // Initial setup call setupGame(); game.down = function (x, y, obj) { if (playerHealth <= 0 || !gameStartTime) return; var currentTime = Date.now() - gameStartTime; // Show tap feedback var feedback = game.attachAsset('tapFeedback', { anchorX: 0.5, anchorY: 0.5 }); feedback.x = x; feedback.y = scanner.y; feedback.alpha = 0.7; tween(feedback, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, onFinish: function onFinish() { if (feedback && feedback.destroy) { feedback.destroy(); } } }); // Check for note hits var hitOccurred = false; var tapRadius = 100; // Radius for tap detection for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note || !note.active || note.isHit || note.isSpawning) continue; // Check distance from tap var dx = x - note.x; var dy = y - note.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < tapRadius) { // Check timing var timeDiff = Math.abs(currentTime - note.hitTime); var scannerDiff = Math.abs(scanner.y - note.y); if (scannerDiff < HIT_TOLERANCE_PX) { note.isHit = true; note.showHitEffect(); var points = 10; if (timeDiff < 50) points = 20; // Perfect hit else if (timeDiff < 100) points = 15; // Great hit LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); hitOccurred = true; if (LK.getScore() >= TARGET_SCORE_TO_WIN) { LK.showYouWin(); gameStartTime = null; tween.stop(scanner); return; } break; } } } }; game.update = function () { if (playerHealth <= 0 || !gameStartTime) return; var currentTime = Date.now() - gameStartTime; // Spawn new notes spawnNotesForCurrentTime(currentTime); // Update existing notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note) { notes.splice(i, 1); continue; } // Check for missed notes if (note.active && !note.isHit && !note.isMissed && !note.isSpawning) { var timeDiff = currentTime - note.hitTime; var scannerPassed = false; if (scannerIsMovingDown && scanner.y > note.y + 50) { scannerPassed = true; } else if (!scannerIsMovingDown && scanner.y < note.y - 50) { scannerPassed = true; } if (scannerPassed && timeDiff > 200) { // Grace period note.isMissed = true; note.showMissEffect(); playerHealth--; updateHealthDisplay(); if (playerHealth <= 0) { return; } } } // Cleanup inactive notes if (!note.active) { notes.splice(i, 1); if (game.children.indexOf(note) > -1) { game.removeChild(note); } if (note.destroy) { note.destroy(); } } } };
===================================================================
--- original.js
+++ change.js
@@ -195,9 +195,9 @@
var scannerIsMovingDown = true;
var gameStartTime;
var notes = [];
var currentSongData;
-var spawnedNotes = new Set(); // Track which notes have been spawned
+var spawnedNotes = []; // Track which notes have been spawned
// Color palette for notes
var NOTE_COLORS = [0x00ffff,
// Cyan
0xff00ff,
@@ -364,9 +364,9 @@
function spawnNotesForCurrentTime(currentTime) {
if (!currentSongData) return;
currentSongData.notes.forEach(function (noteData, index) {
var noteKey = index + '_' + noteData.time;
- if (!spawnedNotes.has(noteKey) && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime < noteData.time + 1000) {
+ if (spawnedNotes.indexOf(noteKey) === -1 && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime < noteData.time + 1000) {
// Grace period for late spawns
// Assign random color if not specified
if (!noteData.color) {
noteData.color = NOTE_COLORS[Math.floor(Math.random() * NOTE_COLORS.length)];
@@ -374,9 +374,9 @@
var note = new Note(noteData, currentTime);
notes.push(note);
game.addChild(note);
note.spawnIn();
- spawnedNotes.add(noteKey);
+ spawnedNotes.push(noteKey);
}
});
}
function setupGame() {
@@ -410,9 +410,9 @@
note.destroy();
}
});
notes = [];
- spawnedNotes.clear();
+ spawnedNotes = [];
// Load song data
currentSongData = defaultSongData;
BPM = currentSongData.bpm;
BEAT_DURATION_MS = 60 / BPM * 1000;
The word 'Pulsar' in a glowing neon SVG in futuristic font. The word is half blue on the left and half red on the right. In-Game asset. 2d. High contrast. No shadows
Remove the background.
A thin expanding ring with energy distortion ``` - Outer ring: 4-6 pixels thick, bright cyan (#00FFFF) - Inner ring: 2-3 pixels thick, white (#FFFFFF) - Ring thickness: Tapers from thick to thin as it expands - Transparency: Ring itself at 80% opacity - Background: Completely transparent - Edge treatment: Soft anti-aliased edges, slight glow effect - Optional: Subtle "energy crackle" texture within the ring. In-Game asset. 2d. High contrast. No shadows
Soft, lingering light effect ``` - Center: Warm orange (#FF6600) at 40% opacity - Middle: Yellow (#FFAA00) at 25% opacity - Edge: Transparent - Shape: Perfect circle with very soft, wide falloff. In-Game asset. 2d. High contrast. No shadows