Create a custom Project Gravity shape plugin in Roblox Luau from the brief at the end. Return one complete .lua module, its filename, and a short controls note. It must work on desktop and mobile with the current runtime. Do not invent APIs or install dependencies. INSTALLATION Save as GravityShapes/.lua in the executor workspace, then run/re-run Project Gravity. Return a module table, not a standalone loop or another UI. MODULE CONTRACT local M = {} local NAME = "Your Shape Name" function M.px(t, c, x6, x9, x1) -- optional, once per formation frame x6.pre = x6.pre or {} -- Own shared state only at x6.pre[NAME]. end function M.f2(p, cen, d, t, c, x1, x6, x9) -- required, per held part local target = cen + Vector3.new(0, 20, 0) return (target - p.Position) * (x1.k10 * x9.c1), target end function M.cleanup(x6, x1) -- optional, safe to repeat if x6.pre then x6.pre[NAME] = nil end end M.Controls = {} return M p is a BasePart; cen is the current center (which can move or follow a target); d is its persistent record; c contains this shape's control values by Key. x1 is global settings, x6 is runtime state, and x9 contains runtime constants. The first f2 result is a velocity correction, not a force. Return the exact target position as the second result for moving-target tracking. Do not use arbitrary huge feedback gains. Use d.slot or d.id or 1 for deterministic placement. d.slot_n is the optional formation slot count; x6.n is the held count. Parts arrive late and are processed in buckets: f2 is not necessarily called every frame. Advance shared clocks in px. For timed per-part actions, capture the shared clock and subtract it later instead of adding one frame's dt per f2 call. Account for mixed part dimensions. t is formation time and can pause, slow down or reverse. d.claim_t is wall time: do not subtract it from t. Initialize timestamps on first px without a huge startup delta. Keep per-part data in d.my_shape and give it an owner reference to x6.pre[NAME], so cleanup/re-entry invalidates stale records. Cache shared trig, paths and frames in px. Do not reroll assignments every frame. Keep results finite at zero size/speed, vertical directions, path turnarounds and all control limits. CONTROLS M.Controls supports Slider, Toggle, TextBox and Button. Name is the visible label; Key identifies a setting or action. Desc is optional explanatory text for settings. { Type = "Slider", Name = "Radius", Key = "radius", Min = 0, Max = 150, Default = 40 } { Type = "Slider", Name = "Speed", Key = "speed", Min = 0, Max = 40, Default = 8, ExactMax = true } { Type = "Slider", Name = "Duration", Key = "duration", Min = 1, Max = 100, Div = 10, Default = 1.6 } { Type = "Toggle", Name = "Enabled", Key = "enabled", Default = true } { Type = "TextBox", Name = "Message", Key = "message", Default = "HELLO", MaxChars = 80 } { Type = "Button", Name = "Restart", Key = "restart", Callback = function(c, x6, x1) local st = x6.pre and x6.pre[NAME] if st then st.restart = true end end } Buttons run once per click/tap, never during UI construction. Their Key is optional; there is no button value in c or saved settings. Use real Buttons for actions rather than flipping Toggles. Callbacks should finish quickly without yielding or spawning permanent loops. Errors are caught and reported. A second click is ignored while a callback is still running. Slider Min/Max are display units; Default is already in stored units. Div=10 and Default=1.6 display 16, and the shape reads 1.6. IntOnly rounds the display. Labels containing "speed" get an extended maximum unless ExactMax=true. Clamp your math to supported ranges. Missing Toggle/TextBox defaults become false/empty string. Built-in shapes need matching non-button defaults in config.lua; local imports get defaults from Controls. OPTIONAL FLAGS AND PHYSICS ACTIONS M.ContinuousMotion=true opts into the shared real-time drift. Add x6.motion_offset to the final target when present; the drift continues when formation time is frozen. M.AlwaysProcess=true bypasses the distance cull, but not processing buckets. M.NoBlend=true opts interactive/physics-mutating modules out of blending. Use it for release or impulse actions. M.FrameTracking=true evaluates every part every physics frame and follows the exact target through its velocity constraint, honoring global/per-part speed limits and bypassing ordinary smoothing and integral feedback. Damping softens recovery from displacement without slowing the target's own motion; recovery is consistent across frame rates. Use it with NoBlend, continuous targets and capture from the live part position for fast rigid orbits. It costs a full part sweep each frame; cache shared geometry in px. Release, collisions and pause still apply normally. Set d.free_physics=true in f2 to disable both actuators and restore physical properties while keeping the record available for regrab. Roblox gravity and momentum take over. Set it to nil/false to resume control and clear stale tracking. Do not fake gravity by subtracting it every frame while a LinearVelocity is still driving the part. Set d.launch_velocity=Vector3.new(...) with free_physics=true for a one-time impulse. The engine consumes and clears it. Set d.collisions=false for local noclip, true for the original collision setting, or nil for normal engine policy (original collisions while free). Preserve Collisions or Disabled always restores the captured collision value, overriding shape requests. Leave d.collisions nil while gathering to honor global policy. Set d.angular_velocity=Vector3.new(x,y,z) to drive the held part's angular motor in world-space radians/second. Clear it to nil to restore normal angular behavior. Pause stops this motor; release disables it. Convert user-facing degree rates with math.rad. For an immediate button action on a tracked real part, set these fields and call x6.apply_shape_physics(p,d) if available. Normal f2 calls do not need the helper. Do not use it on preview markers or alter Part Control overrides (d.pc_mode). Set d.unclaim=true to restore properties, remove actuators AND forget the record. Velocity becomes zero by default. Set d.keep_velocity=true to preserve momentum, OR provide d.release_velocity and optional d.release_spin for explicit velocities. Unclaimed parts are no longer available for a Regrab action. MOBILE INPUT Opt in with M.MobileControls = { Action = "FIRE", Range = 600 }. The runtime shows a circular stick and separate held action while this shape runs. The module must read their state; it needs no custom UI or global touch listener. Read local input = x6.mobile_input, only when input and input.active and input.shape == NAME. input.direction: unit world direction. input.aim: world point. input.base_aim: camera-forward world point. input.x/input.y: normalized stick axes. input.yaw/input.pitch: accumulated steering angles in radians. input.held: action finger is held. input.presses: counter for one-shot actions. input.generation: advances on every reset, including UI replacement. For one-shot actions, check input.presses > 0 and (st.input_generation ~= input.generation or st.input_presses ~= input.presses), then record both fields. Counters reset on shape changes, stop, pause and disable; generation distinguishes the first press after reset even if shape processing never saw zero. Keep a desktop mouse/keyboard fallback. Ignore raw touch listeners while this interface is active, so movement/camera touches do not trigger the shape. Disconnect owned listeners in cleanup. Focus loss cancels gestures; unload removes the controls. LIFECYCLE AND GEOMETRY Q is Stop/Reset: release parts and remove the core, retaining UI and bindings for restart. The main UI X fully unloads: disconnect listeners, unbind actions, remove core/UI and restore captured properties. Implement cleanup for every connection or instance the plugin creates. Clean up owned objects only; never delete unrelated objects by a name search. Record and restore any appearance/property changes. Make a readable silhouette with mixed debris and low part counts. For creatures, use smooth path tangents and spatially delayed frames: head leads, torso bends, tail follows. Phase wing motion across the span rather than turning every feather at once. SHAPE BRIEF [Describe the shape, motion, scale, controls, and what a mobile player should be able to do.]