Trajectories

This commit is contained in:
2026-03-04 20:29:19 +00:00
parent 6af56f478f
commit 1b15384c10
20 changed files with 4825 additions and 71 deletions

137
README.md
View File

@@ -1,57 +1,122 @@
# RocketTools - Rocket Propulsion Engineering Calculator # RocketTools Rocketry Equation Solver & Design Suite
RocketTools is a web-based application that provides engineering calculators and tools for rocket propulsion. Built with React and Vite, it serves both students learning aerospace engineering concepts and professional engineers performing complex propulsion calculations. A modern web-based rocket engine and vehicle design calculator built with **React + Vite**, featuring constraint-based equation solving, 3D modeling, and trajectory simulation.
## Project Status ## Features
✅ Documentation Finalized and Approved ### 🧮 Solver
✅ Core Functionality Implemented - **Constraint propagation solver** for rocket equations
🚧 Additional Features in Development - Drag-and-drop variable palette with live computation
- Automatically solves for unknowns given constraints
- Covers thrust, Isp, chamber conditions, nozzle geometry, performance metrics
## Key Features ### 🔥 Engine Design
- **Chamber & nozzle calculations** (pressure, temperature, area ratios)
- **Ablative material erosion** with pressure-dependent rates
- **Structural sizing** (hoop stress, wall thickness, mass budget)
- **Injector & coolant design** options
- Export/import engine specs as JSON
- **Equation Solver**: Drag-and-drop interface for rocketry variables with automatic solving ### 🚀 Rocket Design
- **Engine Designer**: 3D visualization of engine models using React Three Fiber - **Vehicle geometry** with configurable tank layouts (tandem, coaxial)
- **Knowledge Base**: Reference materials for fuels and oxidizers - **Tank structural analysis** (domes, ullage, pressure-fed/pump-fed)
- **Export Capabilities**: Save results in ODT and JSON formats - **Nose cone profiles** (conical, tangent ogive, Von Kármán)
- **Mass budget** integration (payload, structure, engines, pressurant)
- **3D visualization** with interactive model
## Project Documentation ### 📈 Trajectory Simulation
- **RK4 integrator** for realistic flight paths
- **Atmospheric model** (US Standard piecewise)
- **Drag & gravity** effects
- **Pitch program** (vertical hold → gravity turn)
- **Event detection** (liftoff, MECO, Max Q, apogee, landing)
- **Playback controls** with timeline scrubber
All project documentation has been finalized and approved: ### 📚 Knowledgebase
- **Fuels & oxidizers** library with propellant properties
- **Ablative materials** with pressure exponents
- **Structural materials** with yield strengths
- **Equation reference** with derivations
- [Project Overview (Final)](PROJECT_OVERVIEW_FINAL.md) ## Quick Start
- [Project Structure (Final)](PROJECT_STRUCTURE_FINAL.md)
- [Approved Consolidated Document](ROCKETTOOLS_PROJECT_APPROVED.md) ### Installation
- [Stakeholder Overview](PROJECT_DESCRIPTION_STAKEHOLDER.md) ```bash
- [Consolidated Refined Documentation](ROCKETTOOLS_CONSOLIDATED_REFINED.md) npm install
- [Consolidated Refined Documentation](ROCKETTOOLS_CONSOLIDATED_REFINED.md) ```
### Development
```bash
npm run dev
```
Opens at `http://localhost:5173`
### Build
```bash
npm run build
```
## Technology Stack ## Technology Stack
- React 19 with Vite - **Framework**: React 19 + React Router
- React Three Fiber for 3D visualization - **Build**: Vite 7.3
- DnD Kit for drag-and-drop functionality - **Styling**: Tailwind CSS v4
- TailwindCSS for styling - **3D Graphics**: Three.js + @react-three/fiber
- React Router for navigation - **UI**: @dnd-kit (drag-and-drop), @react-three/drei
## Development Setup ## Core Architecture
```bash See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed:
npm install - Module organization
npm run dev - Data flow patterns
- Solver algorithm
- 3D rendering pipeline
## Feature Documentation
Comprehensive guides for each module:
- **[Solver Guide](docs/SOLVER.md)** — Constraint propagation system, equation definitions
- **[Engine Design](docs/ENGINE_DESIGN.md)** — Combustion, ablation, structural analysis
- **[Rocket Design](docs/ROCKET_DESIGN.md)** — Tank geometry, mass budget, 3D modeling
- **[Trajectory Simulation](docs/TRAJECTORY.md)** — RK4 integration, atmosphere, events
- **[Knowledgebase](docs/KNOWLEDGEBASE.md)** — Fuels, materials, reference equations
## Project Structure
```
src/
├── pages/ # Route pages
├── components/ # React components
├── hooks/ # Custom React hooks (state management)
├── engine/ # Core calculations & algorithms
│ ├── variables.js # Variable definitions
│ ├── equations.js # Equation library
│ ├── solver.js # Constraint solver
│ ├── engineDesignCalcs.js # Engine calculations
│ ├── rocketDesignCalcs.js # Vehicle calculations
│ ├── trajectoryCalcs.js # Trajectory simulation
│ └── knowledgebaseData.js # Material databases
└── App.jsx # Router configuration
``` ```
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. ## Development Tips
Currently, two official plugins are available: ### Adding an Equation
1. Define variables in `src/engine/variables.js`
2. Add equation + solver in `src/engine/equations.js`
3. Automatically appears in solver palette
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh ### Modifying Materials
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - Edit `src/engine/knowledgebaseData.js`
- Changes auto-reflect via hot reload
## React Compiler ### Extending 3D Models
- Rocket: `src/components/rocket/RocketModel3D.jsx`
- Trajectory: `src/components/trajectory/TrajectoryPlot.jsx`
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). ## License
## Expanding the ESLint configuration Open-source. See LICENSE for details.
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. **Built with ❤️ for rocketry engineers and enthusiasts.**

308
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,308 @@
# RocketTools Architecture
## Overview
RocketTools is a single-page application (SPA) built with **React + Vite**, organized into logical layers:
1. **Pages** — Route-level components
2. **Components** — Reusable UI elements
3. **Hooks** — State management & business logic
4. **Engine** — Core calculations & algorithms
5. **App.jsx** — Router configuration
## Layer Responsibilities
### Pages (`src/pages/`)
Each page is a route-level component that:
- Uses one or more custom hooks for state
- Composes domain-specific components
- Manages local UI state (modals, tabs, etc.)
**Pages:**
- `Home.jsx` — Landing page with feature overview
- `Solver.jsx` — Equation solver workspace
- `EnginePage.jsx` — Engine design tools
- `RocketPage.jsx` — Vehicle design tools
- `TrajectoryPage.jsx` — Flight simulation
- `DocsPage.jsx` — In-app documentation
- `KnowledgebaseEquationsPage.jsx` — Equation reference
- `KnowledgebaseFuelsPage.jsx` — Propellant database
- `KnowledgebaseAblativesPage.jsx` — Ablative materials
### Components (`src/components/`)
Pure, composable UI elements that accept props and render JSX.
**Solver Components:**
- `Workspace.jsx` — Drop zone for constraint cards
- `VariablePalette.jsx` — Draggable variable list
- `VariableCard.jsx` — Input field with unit conversion
- `ResultsPanel.jsx` — Results table display
**Engine/Rocket Components:**
- `rocket/RocketModel3D.jsx` — Three.js 3D scene
- `trajectory/TrajectoryPlot.jsx` — Canvas flight path chart
- `trajectory/TimelineBar.jsx` — Playback controls
### Hooks (`src/hooks/`)
Custom React hooks encapsulate domain state & logic.
**State Hooks:**
```
useSolver()
├─ variables: Map<id, value>
├─ constraints: Map<id, constraint>
├─ dispatch(action)
└─ results: Map<id, solvedValue>
useEngineDesign()
├─ engineInputs: {chamberPressure, fuels, ...}
├─ engineResults: {thrust, isp, ...}
├─ setEngineInputs()
└─ structureResults: {wallThickness, mass, ...}
useRocketDesign()
├─ rocketInputs: {outerRadius, tankConfig, ...}
├─ rocketResults: {totalMass, twr, ...}
└─ setRocketInputs()
useTrajectory()
├─ config: {vehicle, engine, atmosphere, ...}
├─ playback: {time, speed, isPlaying}
├─ events: [{type, time, data}]
└─ runSimulation()
```
### Engine (`src/engine/`)
Pure JavaScript modules with **zero React dependencies**. These can be:
- Unit tested independently
- Reused in Node.js or other environments
- Debugged without UI overhead
**Module Breakdown:**
| File | Purpose |
|------|---------|
| `variables.js` | Variable catalog (name, unit, type) |
| `equations.js` | Equation definitions + solvers |
| `solver.js` | Constraint propagation algorithm |
| `numerics.js` | Math utilities (bisection, Mach) |
| `engineDesignCalcs.js` | Combustion, ablation, structure |
| `rocketDesignCalcs.js` | Tank geometry, mass budget |
| `trajectoryCalcs.js` | RK4 integrator, event detection |
| `knowledgebaseData.js` | Material & fuel databases |
## Data Flow Patterns
### Solver Workflow
```
User Input
[VariableCard updates constraint]
dispatch(setConstraint) → useSolver
solver.solve() → [greedy propagation]
useSolver state updates
[VariableCard/ResultsPanel re-render]
```
### Engine Design Workflow
```
User Input (EnginePage)
useEngineDesign.setEngineInputs()
engineDesignCalcs.calcCombustion()
+ engineDesignCalcs.calcCooling()
+ engineDesignCalcs.calcStructure()
useSolver state updates (memoized)
[EnginePage, RocketPage re-render]
```
### Rocket Design Workflow
```
User Input (RocketPage)
useRocketDesign.setRocketInputs()
rocketDesignCalcs.calcTankGeometry()
+ rocketDesignCalcs.calcMassBudget()
+ rocketDesignCalcs.noseConeProfile()
useRocketDesign state updates (memoized)
[RocketModel3D, RocketPage re-render]
```
### Trajectory Simulation Workflow
```
User Config (TrajectoryPage)
useTrajectory.runSimulation()
trajectoryCalcs.runSimulation()
[RK4 integrator loop]
[Event detection]
states[] + events[] returned
useTrajectory state updates
[TrajectoryPlot, TimelineBar re-render]
```
## Key Design Decisions
### 1. Separation of Concerns
- **Engine layer**: Pure math, no React
- **Hooks layer**: State management, memoization, side effects
- **Component layer**: UI rendering only
- **Page layer**: Route composition, layout
**Benefit**: Engine code is testable, reusable, and maintainable independently of React.
### 2. Constraint Solver as Core
The solver uses **greedy constraint propagation**:
- User sets constraint on a variable
- Solver finds equations referencing that variable
- Solves for unknowns using math or bisection
- Propagates newly-computed values (recursive)
**Benefit**: Intuitive UI (users see automatic computation), flexible equation network.
### 3. Three.js via React Three Fiber
Rather than direct Three.js imperative API:
- Use `<Canvas>`, `<Mesh>`, etc. as React components
- Hooks like `useFrame()` for animations
- Drei helpers for common geometries
**Benefit**: Declarative, composable, integrates with React state.
### 4. Canvas for Trajectory Plotting
HTML5 Canvas instead of SVG:
- Handles 1000s of points efficiently
- Responsive to window resize
- Custom click/hover interactions
**Benefit**: Performance, flexibility.
## Memoization Strategy
Use `useMemo()` to avoid re-computation:
```javascript
// Hook definition
const engineResults = useMemo(() => {
return calcCombustion(engineInputs) // only runs when engineInputs changes
}, [engineInputs])
```
**Rule**: Memoize expensive calculations; skip for cheap operations.
## State Management (No Redux)
React hooks provide sufficient state management for this app:
- `useState()` for local UI state
- Custom hooks + `useCallback()` for complex logic
- `useMemo()` for expensive computations
- Props drilling is acceptable for this size
**Alternative**: Could migrate to Redux/Zustand if app grows significantly.
## 3D Rendering Pipeline
```
RocketModel3D.jsx (R3F Canvas)
[useRocketDesign hook provides geometry]
TankSection, NoseCone components
THREE.CylinderGeometry, SphereGeometry, LatheGeometry
Material (MeshStandardMaterial)
Lighting, OrbitControls
Canvas rendered to DOM
```
## Testing Strategy
No tests currently, but recommended:
**Unit Tests** (Jest):
```javascript
// src/engine/__tests__/solver.test.js
import { solve } from '../solver.js'
test('solves linear equation', () => {
const eqs = [{var: 'x', fn: (x) => 2*x - 5}]
const result = solve(eqs, {y: 10})
expect(result.x).toBeCloseTo(2.5)
})
```
**Integration Tests** (React Testing Library):
```javascript
// src/pages/__tests__/Solver.test.jsx
import { render, screen } from '@testing-library/react'
import Solver from '../Solver'
test('Solver page renders', () => {
render(<Solver />)
expect(screen.getByText(/Equation Solver/i)).toBeInTheDocument()
})
```
## Performance Considerations
1. **Memoization** — Expensive calculations in `useMemo()`
2. **Lazy Loading** — Routes loaded on-demand (React Router)
3. **Canvas Rendering** — 60 FPS for trajectory animation
4. **Variable Updates** — Solver propagation is greedy (stops early)
**Potential Bottlenecks:**
- Large variable networks (100+ equations)
- Complex 3D geometries (reduce polygon count)
- Trajectory with 1000s of timesteps (reduce accuracy or cache)
## File Organization Best Practices
- **One component per file** (except tiny helpers)
- **Colocate tests** with source (`__tests__` subfolder)
- **Group by domain** (rocket, trajectory, solver) not by type
- **Keep hooks small** (< 50 lines of logic)
- **Engine code** has zero dependencies on React or UI
## Extension Points
### Adding a New Page
1. Create `src/pages/NewPage.jsx`
2. Add route in `App.jsx`
3. Add NavLink if needed
### Adding a New Feature Module
1. Create hook: `src/hooks/useNewFeature.js`
2. Create components: `src/components/NewFeature*.jsx`
3. Add calculations to `src/engine/` if needed
4. Create page: `src/pages/NewFeaturePage.jsx`
### Adding Material Properties
1. Edit `src/engine/knowledgebaseData.js`
2. Add to `FUELS`, `ABLATIVES`, or `STRUCTURAL_MATERIALS` array
3. Auto-appears in UI
---
**Last Updated**: 2025-02 | **Status**: Current (v3)

496
docs/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,496 @@
# Contributing Guide
## Getting Started
### Prerequisites
- Node.js 18+
- npm or yarn
- Basic understanding of React, JavaScript
### Setup
```bash
# Clone/navigate to project
cd rocketry
# Install dependencies
npm install
# Start dev server
npm run dev
# Open browser
# http://localhost:5173
```
### Project Structure Overview
```
src/
├── engine/ # Pure math, zero React deps
├── hooks/ # State management
├── components/ # UI components
├── pages/ # Route pages
└── App.jsx # Router
```
See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed breakdown.
---
## Common Tasks
### Adding a New Equation
**File**: `src/engine/equations.js`
1. **Check if variables exist** in `variables.js`
- If not, add them with `id`, `name`, `unit`, `category`, `type`
2. **Add equation object**:
```javascript
{
id: 'myEquation',
name: 'My Equation Name',
variables: ['var1', 'var2', 'var3'],
equation: 'var1 = var2 * var3', // for reference
solvers: {
var1: (inputs) => inputs.var2 * inputs.var3,
var2: (inputs) => inputs.var1 / inputs.var3,
var3: (inputs) => inputs.var1 / inputs.var2
}
}
```
3. **Test manually**
- Go to Solver page
- Drag variables onto workspace
- Set constraints, verify solver computes correct values
### For Transcendental Equations (Bisection)
```javascript
import { bisection } from '../numerics.js'
{
id: 'machEquation',
solvers: {
machNumber: (inputs) => {
const fn = (M) => isentropicAreaRatio(M, inputs.gamma) - inputs.areaRatio
return bisection(fn, 0.1, 10, 1e-6)
}
}
}
```
### Adding a Material to Knowledgebase
**File**: `src/engine/knowledgebaseData.js`
**For fuels/oxidizers:**
```javascript
FUELS.push({
id: 'newFuel',
name: 'New Fuel',
formula: 'C8H18', // typical hydrocarbon
density: 800, // kg/m³
molecularWeight: 114,
properties: {
combustionTemp: 3500,
gamma: 1.25,
charVelocity: 1500 // m/s
}
})
```
**For ablatives:**
```javascript
ABLATIVES.push({
id: 'newAblative',
name: 'New Ablative',
density: 1400,
erosionRate: 0.020, // inch/s @ 300 psi
pressureExponent: 0.35, // power-law exponent
tempLimit: 2100, // K
notes: 'Best for chamber walls'
})
```
**For structural materials:**
```javascript
STRUCTURAL_MATERIALS.push({
id: 'newMaterial',
name: 'New Material',
density: 4500, // kg/m³
yieldStrength: 900, // MPa
youngModulus: 200, // GPa
meltingPoint: 1800, // K
cte: 12e-6, // 1/K
notes: 'Good for hot sections'
})
```
Changes auto-appear in UI (hot reload).
### Adding a New Page
1. **Create page component** (`src/pages/NewPage.jsx`):
```javascript
import { useState } from 'react'
export default function NewPage() {
return (
<div className="flex-1 overflow-auto p-6">
<h1 className="text-3xl font-bold mb-4">New Feature</h1>
{/* Your content */}
</div>
)
}
```
2. **Add route** in `src/App.jsx`:
```javascript
import NewPage from './pages/NewPage.jsx'
// In Routes:
<Route path="/new-feature" element={<NewPage />} />
```
3. **Add navigation link** in header if needed:
```javascript
<NavLink
to="/new-feature"
className={/* classes */}
>
New Feature
</NavLink>
```
### Adding a 3D Component
**File**: `src/components/new3d/NewComponent.jsx`
Use React Three Fiber (`@react-three/fiber`):
```javascript
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
export default function NewComponent() {
return (
<Canvas>
<ambientLight intensity={0.6} />
<directionalLight position={[5, 5, 5]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
<OrbitControls />
</Canvas>
)
}
```
---
## Code Style & Conventions
### Naming
- **Components**: PascalCase (`VariableCard.jsx`)
- **Files**: match component name or use kebab-case for utilities
- **Variables/functions**: camelCase
- **Constants**: UPPER_SNAKE_CASE
- **React hooks**: `useFeatureName`
### File Organization
```javascript
// 1. Imports (grouped)
import React, { useState, useMemo } from 'react'
import { helper } from '../utils'
import './styles.css'
// 2. Types/Constants
const DEFAULT_VALUE = 10
// 3. Component
export default function MyComponent({ prop1, prop2 }) {
// Hooks
const [state, setState] = useState(0)
// Computed values
const memoized = useMemo(() => {
return expensiveCalculation()
}, [deps])
// Event handlers
const handleClick = () => { /* ... */ }
// JSX
return <div>...</div>
}
```
### Styling
- Use **Tailwind CSS** classes
- Keep dark theme (slate-950 background)
- Responsive: mobile-first, use Tailwind breakpoints
```javascript
<div className="px-4 py-2 rounded-md bg-slate-800 hover:bg-slate-700 transition-colors">
Click me
</div>
```
### Comments
- Add only for **why**, not **what**
- Keep code self-documenting
- Avoid comments for obvious logic
```javascript
// ✅ Good: explains intent
// Pressure correction for temperature variation
const correctedRate = baseRate * Math.pow(temp / refTemp, exponent)
// ❌ Bad: obvious from code
const x = x + 1 // increment x
```
---
## Testing
Currently no test suite. Recommended additions:
### Unit Tests (Jest)
```javascript
// src/engine/__tests__/solver.test.js
import { solve } from '../solver'
test('solves linear equation', () => {
const eqs = [{
id: 'eq1',
variables: ['x', 'y'],
solvers: {
x: (inputs) => inputs.y * 2
}
}]
const result = solve(eqs, { y: 5 })
expect(result.x).toBe(10)
})
```
### Component Tests (React Testing Library)
```javascript
// src/components/__tests__/VariableCard.test.jsx
import { render, screen, fireEvent } from '@testing-library/react'
import VariableCard from '../VariableCard'
test('renders input field', () => {
render(<VariableCard variable={{name: 'thrust'}} />)
expect(screen.getByDisplayValue('0')).toBeInTheDocument()
})
test('updates on input change', () => {
const { getByDisplayValue } = render(<VariableCard />)
fireEvent.change(getByDisplayValue('0'), {target: {value: '100'}})
expect(getByDisplayValue('100')).toBeInTheDocument()
})
```
### Manual Testing Checklist
- [ ] Solver computes correct values
- [ ] Engine design produces reasonable thrust/Isp
- [ ] Rocket design mass budget is sensible
- [ ] Trajectory plot shows reasonable path
- [ ] 3D models render without errors
- [ ] UI is responsive on mobile
---
## Performance Considerations
### Memoization
Use `useMemo()` for expensive calculations:
```javascript
const results = useMemo(() => {
return engineDesignCalcs.calcCombustion(inputs) // expensive
}, [inputs])
```
### Avoid Unnecessary Re-renders
- Lift state up only when needed
- Use `useCallback()` for event handlers passed to children
- Consider `React.memo()` for pure components
```javascript
const VariableCard = React.memo(({ variable, onChange }) => {
// Component only re-renders if variable or onChange changes
return <input onChange={onChange} />
})
```
### Canvas Performance
- Limit trajectory states to ~5000 points
- Use fixed timestep (don't go below 0.01 s)
- Reduce Three.js geometry complexity if needed
---
## Git Workflow
### Branching
- `main` — production-ready code
- `feature/*` — new features
- `fix/*` — bug fixes
- `docs/*` — documentation updates
### Commits
Keep commits atomic and well-described:
```bash
git commit -m "Add pressure-corrected ablation model
- Implement power-law pressure exponent for erosion rate
- Add pressure factor display in Engine page
- Update knowledgebase with exponent values
- Update docs with pressure correction formula"
```
### Pull Requests
Include:
- What changed and why
- How to test
- Any breaking changes
- Related issues
---
## Debugging Tips
### Browser DevTools
**Console:**
```javascript
// Log solver state
console.log('Constraints:', solverState.constraints)
console.log('Results:', solverState.results)
```
**React DevTools Extension:**
- Inspect component props/state
- Profile render performance
- Trace updates
### Hot Reload
Changes to `src/` automatically reload (Vite).
- Engine modules reload: refresh browser
- Components/pages: usually hot reload instantly
### Performance Profiling
```javascript
// In React component
console.time('calculation')
const result = expensiveFunction()
console.timeEnd('calculation') // logs elapsed time
```
---
## Common Pitfalls
### ❌ Don't: Mutate state directly
```javascript
state.value = 100 // Wrong!
setState({...state, value: 100}) // Correct
```
### ❌ Don't: Create objects in dependencies
```javascript
const obj = {x: 1}
useMemo(() => calc(obj), [obj]) // Runs every render!
```
### ✅ Do: Memoize dependencies
```javascript
const obj = useMemo(() => ({x: 1}), [])
useMemo(() => calc(obj), [obj]) // Stable
```
### ❌ Don't: Forget dependencies
```javascript
useEffect(() => {
doSomething(dep)
}, []) // Missing 'dep'!
```
### ✅ Do: Include all dependencies
```javascript
useEffect(() => {
doSomething(dep)
}, [dep])
```
---
## Asking for Help
**Before asking:**
1. Check existing documentation
2. Search similar equations/features
3. Review console for errors
4. Try simplifying the problem
**When asking:**
- Include error message
- Describe what you tried
- Provide minimal reproducible example
---
## Roadmap Ideas
Potential future features:
- **Multi-stage rockets** — sequential stage design
- **Thrust vector control** — pitch/yaw steering
- **Grid fin aerodynamics** — drag & control surfaces
- **Solid rocket motors** — grain geometry, regression
- **Regenerative cooling** — thermal analysis
- **Uncertainty analysis** — Monte Carlo simulations
- **Optimization** — automated mass/performance optimization
- **Data export** — CAD, simulation formats (GMAT, Basilisk)
- **Multiplayer** — collaborative design sessions
---
## Resources
- [React Docs](https://react.dev)
- [Tailwind CSS](https://tailwindcss.com)
- [Three.js Docs](https://threejs.org/docs)
- [React Three Fiber](https://docs.pmnd.rs/react-three-fiber)
- [Vite Docs](https://vitejs.dev)
---
**Last Updated**: 2025-02 | **Status**: Current

394
docs/ENGINE_DESIGN.md Normal file
View File

@@ -0,0 +1,394 @@
# Engine Design Guide
## Overview
The **Engine Design** tool calculates rocket engine performance, material erosion, and structural requirements. It integrates combustion thermodynamics, ablative material degradation, and hoop stress analysis into a unified interface.
## Main Sections
### Combustion Design
Configure chamber, nozzle, and propellant properties to predict thrust, Isp, and exit conditions.
### Ablative Erosion
Model chamber liner erosion based on pressure-dependent rates and propellant choice.
### Structural Sizing
Calculate wall thicknesses and component masses using hoop stress formula.
---
## Combustion Design
### Inputs
**Chamber:**
- `chamberPressure` (MPa) — combustion chamber pressure
- `nozzleDiameter` (mm) — nozzle throat diameter
- `expansionRatio` — nozzle exit / throat area ratio
- `divergenceAngle` (°) — nozzle divergence cone angle
**Propellant Selection:**
- `fuelType` — dropdown (RP1, Methane, Hydrogen, etc.)
- `oxidiserType` — dropdown (LOX, N2O4, etc.)
- **Note**: Propellant properties come from knowledgebase (T0, gamma, Mw, etc.)
**Design Options:**
- `injectorType` — impingement, shower-head, multi-element
- `coolingMethod` — none, regenerative (future)
- `nozzleShape` — conical (15-20°), bell curve (optional)
### Outputs (Computed)
**Performance:**
- `thrustVacuum` (kN) — vacuum thrust (accounting for exit pressure)
- `thrustSeaLevel` (kN) — sea level thrust (accounting for ambient pressure)
- `specificImpulseVacuum` (s) — Isp in vacuum
- `specificImpulseSeaLevel` (s) — Isp at sea level
- `characteristicVelocity` (m/s) — c* = P0·At / ṁ
**Combustion Conditions:**
- `chamberTemperature` (K) — adiabatic flame temperature
- `exitTemperature` (K) — nozzle exit temperature
- `machNumber` — exit Mach number (computed via bisection)
- `exitPressure` (MPa) — nozzle exit pressure
- `exitDensity` (kg/m³) — exhaust density at exit
**Nozzle Geometry:**
- `throatDiameter` (mm) — computed from chamber P, thrust, mass flow
- `exitDiameter` (mm) — exit diameter = throat × √(expansionRatio)
- `nozzleLength` (mm) — divergence section length
- `thrustCoefficient` — CF (thrust coefficient for flow)
**Flow Properties:**
- `massFlowRate` (kg/s) — propellant mass flow rate
- `exitVelocity` (m/s) — effective exhaust velocity
- `burnRate` (mm/s) — for propellant grain design
### Key Equations
**Thrust (from area and pressure):**
```
F = P0·At·CF + (Pe - P_ambient)·Ae
```
Where CF is thrust coefficient from isentropic relations.
**Specific Impulse:**
```
Isp = Ve / g0
Isp_sl = (Ve - (Pe - P_atm) / ṁ · Ae) / g0
```
**Exit Temperature (isentropic flow):**
```
Te = T0 · (Pe / P0)^((γ-1)/γ)
```
**Mass Flow Rate (from energy balance):**
```
ṁ = P0 · At / c*
c* = √(2·(γ+1)/(γ-1) · R·T0) for ideal nozzle
```
**Characteristic Velocity:**
```
c* = √(2·(γ+1)/(γ-1) · R·T0 · (2/(γ+1))^((γ+1)/(γ-1)))
```
---
## Ablative Erosion (v2)
### Material Properties
Each ablative material in the knowledgebase has:
- **Density** (kg/m³) — ablator material density
- **Base Erosion Rate** (inch/s) — reference erosion at standard pressure (typically 300 psi)
- **Pressure Exponent** (n) — power-law sensitivity to pressure
### Pressure-Corrected Erosion
The key innovation of v2 is **pressure-dependent erosion rates**:
```
erosion_rate(P) = base_rate · (P / P_ref)^n
```
Where:
- `base_rate` — from material database (inch/s @ 300 psi)
- `P` — chamber pressure (Pa)
- `P_ref` — reference pressure (300 psi = 2.068 MPa)
- `n` — pressure exponent (material-specific)
### Pressure Exponents by Material Class
**Composites:**
- PAXS (polyester/glass): n = 0.38
- KFSI (silica/phenolic): n = 0.35
- Carbon phenolic: n = 0.32
**Elastomers:**
- ZIRCONIA (zirconia/silicone): n = 0.48
- Butyl rubber: n = 0.50
**Theory**: Lower n = less pressure-sensitive (better for high-P engines)
### Example Calculation
**Material:** PAXS, base_rate = 0.025 in/s, n = 0.38
**At different pressures:**
- 300 psi: rate = 0.025 × (300/300)^0.38 = 0.025 in/s
- 500 psi: rate = 0.025 × (500/300)^0.38 = 0.032 in/s
- 1000 psi: rate = 0.025 × (1000/300)^0.38 = 0.042 in/s
- 1500 psi: rate = 0.025 × (1500/300)^0.38 = 0.050 in/s
### Erosion Calculation
**Inputs:**
- Chamber pressure (Pa)
- Burn time (s)
- Ablative material (from dropdown)
- Initial liner thickness (mm)
**Process:**
1. Look up material properties (base_rate, n)
2. Calculate pressure factor: `(P / P_ref)^n`
3. Apply correction: `effective_rate = base_rate × factor`
4. Erosion depth: `erosion = effective_rate × burn_time`
5. Remaining thickness: `remaining = initial - erosion`
**Display:**
- Pressure factor (if not 1.0)
- Corrected erosion rate (inch/s)
- Erosion depth (inch)
- Remaining thickness (inch)
- **Warning** if remaining < 0.5 inch
### Thermal Analysis (Future)
Currently, erosion is purely mechanical. Future versions could include:
- Heat flux calculation
- Material melting/sublimation
- Thermal diffusion into structure
- Combined mechanical + thermal erosion
---
## Structural Sizing
### Material Selection
Choose from STRUCTURAL_MATERIALS array:
- **Aluminum 6061-T6**: Low density, corrosion-resistant
- **Stainless Steel 304**: High temp, corrosion-resistant
- **Inconel 718**: High strength at elevated T
- **Titanium 6-4**: Light, strong, expensive
- **CFRP (Carbon Fiber)**: Highest strength-to-weight
Each has: density (kg/m³), yield strength (MPa), Young's modulus, CTE, limits
### Hoop Stress Formula
For thin-walled cylinders:
```
σ_hoop = (P × r) / t
```
Solving for wall thickness with safety factor:
```
t = (P × r) / (σ_yield / SF)
```
Where:
- `P` — internal pressure (Pa)
- `r` — radius (m)
- `σ_yield` — material yield strength (Pa)
- `SF` — safety factor (typical: 2.04.0)
### Component Sizing
**Chamber:**
- Pressure: P0 (combustion pressure)
- Radius: determined by geometry
- Thickness: `t_chamber = (P0 × r) / (σ_y / SF)`
**Convergent Section (nozzle entrance):**
- Pressure: P0 (full chamber pressure)
- Radius: smaller than chamber (converging)
- Thickness: similar to chamber
**Throat (narrowest point):**
- Pressure: ~P0 (highest local stress)
- Radius: throat_radius (smallest)
- **Result**: highest stress → thickest wall
- Thickness: `t_throat = (P0 × r_throat) / (σ_y / SF)`
**Divergent Section (nozzle expansion):**
- Pressure: decreases from P0 to Pe
- Radius: increasing
- Thickness: typically uses Pe ≈ 0.1·P0 for design
- Lower thickness than chamber
**Exit (atmospheric nozzle):**
- Pressure: near ambient
- Thickness: minimal (can be thin-walled)
### Mass Calculation
**Cylindrical section:**
```
m = 2π·r·t·L·ρ
```
**Hemispherical dome (for tanks):**
```
m = 4π·r²·t·ρ
```
**Frustum (truncated cone):**
```
m ≈ π·(r1·L + r2·L)·t·ρ (simplified)
```
**Total engine dry mass:**
```
m_dry = m_chamber + m_convergent + m_nozzle + m_injector_plate + m_misc
```
### UI Layout
**Left Panel (Inputs):**
- Material dropdown
- Safety factor slider
- Pressure specifications (chamber, ambient)
**Right Panel (Results):**
- Wall thickness breakdown (chamber, throat, exit)
- Mass breakdown (chamber, convergent, divergent, injector, **total**)
- Material properties (yield, density, limit temp)
- Stress utilization (if known)
---
## Workflow Example: Design LOX/RP1 Engine
### Step 1: Set Combustion Conditions
1. Select `fuelType = RP1`
2. Select `oxidiserType = LOX`
3. Set `chamberPressure = 200 bar`
4. Set `nozzleDiameter = 50 mm`
5. Set `expansionRatio = 8`
**Result**: Thrust ≈ 150 kN, Isp ≈ 310 s
### Step 2: Design Ablation
1. Select `ablativeMaterial = PAXS`
2. Set `initialLinertThickness = 10 mm`
3. Set `burnTime = 60 s`
**Result**: Erosion ≈ 1.5 mm, Remaining ≈ 8.5 mm (OK)
### Step 3: Size Structure
1. Select `structuralMaterial = Inconel718`
2. Set `safetyFactor = 3.0`
**Result**:
- Chamber wall: 2.1 mm
- Throat wall: 2.8 mm
- Divergent: 1.5 mm
- **Total dry mass**: 2.3 kg
### Step 4: Export
- **Export as JSON**: Download engine specs
- Import into rocket design tool
- Calculate vehicle TWR, delta-v, etc.
---
## Advanced Topics
### Regenerative Cooling
Not yet implemented, but conceptually:
- Propellant flows through cooling jackets before injection
- Removes heat from chamber walls
- Allows higher chamber pressure or thinner walls
- Adds complexity (adds ~0.5 kg per engine)
Implementation would add:
- Cooling jacket dimensions
- Heat transfer correlation (Dittus-Boelert, etc.)
- Pressure drop calculation
- Temperature rise of propellant
### Ablator Recession Modeling
Current model assumes uniform erosion. Advanced model would include:
- **Surface recession rate** (different from bulk erosion)
- **Spallation** (chunks breaking off at high pressure)
- **Chemical reaction** (oxidation, decomposition)
- **Thermal gradient** (erosion depends on depth)
This requires:
- Heat conduction equation (PDE)
- Boundary conditions (heat flux from combustion)
- Material properties (k, cp, melt point)
- Solver (e.g., finite difference)
### Combustion Instability
Not modeled currently, but factors affecting it:
- **Injector design** (pattern, element size, momentum ratio)
- **Chamber L* (characteristic length)** = V / At
- Higher L* = more residence time = higher c*
- Typical range: 3060 inches
- **Baffle plates** (reduce acoustic resonance)
---
## Knowledgebase Integration
### Accessing Propellant Data
- Knowledgebase → Fuels / Oxidisers
- View all available propellants
- Copy properties into engine design
### Accessing Material Data
- Knowledgebase → Structural Materials
- Compare yield strengths, densities, costs
- Select best for your application
### Ablative Materials
- Knowledgebase → Ablative Materials
- View pressure exponents
- Notes on applications (chamber, nozzle, etc.)
---
## Troubleshooting
### Chamber pressure seems too high / low?
- Check propellant selection (wrong fuel/oxidizer?)
- Verify chamber geometry (radius, length)
- Check for typos in input values
### Erosion warning?
- Select ablator with lower pressure exponent (more stable)
- Reduce chamber pressure if possible
- Increase initial liner thickness
- Shorten burn time
### Structural mass too heavy?
- Switch to lighter material (Ti-6-4 vs SS)
- Reduce safety factor (if acceptable for design)
- Increase chamber pressure radius (less stress)
- Use thinner walls for non-critical sections
---
## References
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines. AIAA.
- NASA SP-273: Liquid Rocket Engine Combustion Instability. ([PDF](https://ntrs.nasa.gov/api/citations/19700006920/downloads/19700006920.pdf))
- Crespo, A., & Liñán, A. (1975). Asymptotic analysis of unsteady flame oscillations in liquid propellant rocket motors. SIAM Journal on Applied Mathematics, 29(3), 521533.
---
**Last Updated**: 2025-02 | **Status**: Current (v2 — Pressure-Corrected Ablation)

465
docs/KNOWLEDGEBASE.md Normal file
View File

@@ -0,0 +1,465 @@
# Knowledgebase Guide
## Overview
The **Knowledgebase** is a reference library of propellants, materials, and engineering equations. It provides the properties needed for calculations in the Solver, Engine Designer, and Rocket Designer.
## Structure
The knowledgebase is organized into four categories:
1. **Fuels & Oxidizers** — Propellant properties
2. **Ablative Materials** — Thermal protection
3. **Structural Materials** — Engine & tank construction
4. **Equations** — Mathematical references
---
## Fuels & Oxidizers
### What are They?
**Fuel**: Hydrocarbon or hydrogen source
- Examples: RP-1 (kerosene), methane, hydrogen
**Oxidizer**: Oxygen source
- Examples: Liquid oxygen (LOX), nitrogen tetroxide (N2O4)
### Key Properties
Each propellant entry contains:
**Chemical Properties:**
- Molecular formula (e.g., O₂ for oxygen)
- Molecular weight (g/mol)
- Density (kg/m³)
**Combustion Properties:**
- **Adiabatic flame temperature (Tc)** — peak temperature of combustion (K)
- Higher Tc = higher Isp
- Example: LOX/RP-1 ≈ 3750 K, LOX/LH2 ≈ 3900 K
- **Expansion ratio (gamma, γ)** — specific heat ratio
- γ = Cp / Cv
- Affects nozzle performance
- LOX/RP-1: γ ≈ 1.25, LOX/LH2: γ ≈ 1.26
- **Characteristic velocity (c*)** — ideal nozzle exit velocity
- Fundamental property of propellant
- Used to estimate Isp
**Performance Metrics:**
- **Specific impulse (vacuum, Isp_vac)** — seconds
- Higher = better
- Example: LOX/RP-1 ≈ 310320 s, LOX/LH2 ≈ 450 s
- **Specific impulse (sea level, Isp_sl)** — reduced due to ambient pressure
- Lower than vacuum value
- Example: LOX/RP-1 ≈ 260 s at sea level
**Mixture Ratio:**
- **O/F ratio** — oxidizer mass per fuel mass
- Example: LOX/RP-1 optimal ≈ 2.52.8
- Higher ratio = more oxygen = higher T but lower mass fraction
- Lower ratio = more fuel = lower T but better mass fraction
### Common Propellant Pairs
| Fuel | Oxidizer | Isp (s) | Tc (K) | γ | Notes |
|------|----------|---------|-------|-------|-------|
| RP-1 | LOX | 310 | 3750 | 1.25 | Space-proven, storable |
| Methane | LOX | 330 | 3900 | 1.24 | Better impulse than RP-1 |
| Hydrogen | LOX | 450 | 3900 | 1.26 | Best impulse, cryogenic, low density |
| MMH | N2O4 | 290 | 3400 | 1.20 | Storable, hypergolic (ignites on contact) |
| Hydrazine | N2O4 | 310 | 3700 | 1.24 | Toxic but storable, high density |
### Storage Types
**Cryogenic:**
- Requires refrigeration
- LOX, LH2, LN2, LCH4
- Higher energy density
- Complex ground support needed
**Storable (Room Temperature):**
- RP-1, Methane (marginally), MMH, Hydrazine
- No cryogenic handling needed
- Lower energy density than cryo
- Easier integration
**Hypergolic:**
- Ignites spontaneously on contact (fuel + oxidizer)
- No ignition system needed
- Toxic, corrosive, more expensive
- Examples: MMH/N2O4, Hydrazine/N2O4
### Selection Criteria
**High Performance** → LOX/LH2 (Isp 450 s, but cryogenic complexity)
**Best Balance** → LOX/RP-1 (Isp 310 s, storable, proven)
**Storable Only** → MMH/N2O4 (Isp 290 s, no cryo needed)
**Simple & Solid** → RP-1/LOX or Methane/LOX
---
## Ablative Materials
### Purpose
Ablative materials line the rocket engine chamber to withstand:
- **High temperature** (combustion products: 3500+ K)
- **High pressure** (200+ bar)
- **Erosion** from hot gas flow
### Key Properties
Each ablative material entry contains:
**Mechanical Properties:**
- Density (kg/m³)
- Tensile strength (MPa)
- Compressive strength (MPa)
**Thermal Properties:**
- Specific heat (J/kg·K)
- Thermal conductivity (W/m·K)
- Melting point (K)
**Erosion Properties:**
- **Base erosion rate** (inch/s @ 300 psi reference pressure)
- Ablative material mass removed per unit time
- Higher rate = thicker liner needed
- Example: PAXS ≈ 0.025 in/s
- **Pressure exponent (n)** — power-law sensitivity to pressure
- `rate(P) = base_rate × (P / P_ref)^n`
- Lower n = less pressure-sensitive (better for high P)
- Example: PAXS n = 0.38, KFSI n = 0.35
**Application Notes:**
- Where used: chamber, nozzle, injector
- Temperature limits
- Compatibility with propellants
### Material Classes
**Composites (Rigid):**
- PAXS (polyester + glass) — n = 0.38
- Common, cost-effective
- Moderate erosion rate
- Max temp ~2000 K
- KFSI (silica + phenolic) — n = 0.35
- Better than PAXS
- Lower erosion rate
- Higher temp capability
- More expensive
- Carbon-Phenolic — n = 0.32
- Excellent thermal performance
- Very low erosion rate
- Very expensive
- Used on high-end engines
**Elastomers (Flexible):**
- ZIRCONIA (zirconia + silicone) — n = 0.48
- Flexible (less brittle)
- Higher erosion rate
- Absorbs vibration
- Lower cost than phenolics
- Butyl Rubber — n = 0.50
- Very flexible
- High erosion rate
- Used for low-pressure applications
### Pressure Exponent Explanation
The power law `rate ∝ P^n` comes from **regression rate burning**:
- Composites: n ≈ 0.30.4
- Erosion driven by thermal decomposition
- Less pressure-dependent
- Preferred for high-pressure engines
- Elastomers: n ≈ 0.40.5
- Erosion driven by shear stress
- More pressure-dependent
- OK for moderate pressures
**High Pressure (>1000 psi):** Use composite with low n
**Moderate Pressure (500 psi):** Elastomer acceptable
**Low Pressure (<300 psi):** Elastomer preferred (lower cost)
### Selection Guidance
| Application | Material | Reason |
|-------------|----------|--------|
| High-perf liquid rocket | KFSI or Carbon-Phenolic | Low erosion rate, high pressure capable |
| Medium-perf liquid | PAXS | Good balance, cost-effective |
| Hybrid rocket | KFSI (fuel grain liner) | Low ablation to preserve geometry |
| Solid rocket | Composite | Erosion from particles & hot gas |
| Low-cost experiment | Butyl or PAXS | Adequate performance, lowest cost |
---
## Structural Materials
### Purpose
Structural materials form the engine chamber and rocket tanks. They must:
- Withstand internal pressure (hoop stress)
- Resist corrosion from propellants
- Perform at operating temperature
- Balance weight vs. strength
### Key Properties
Each structural material entry contains:
**Mechanical Properties:**
- **Yield strength** (MPa) — stress at which permanent deformation begins
- Higher = thinner walls possible = lighter
- Example: Aluminum 6061-T6: 275 MPa, Inconel 718: 1240 MPa
- **Young's modulus** (GPa) — stiffness
- Higher = less deflection (stronger)
- **Density** (kg/m³) — weight per volume
- Lower = lighter vehicle
- Example: Al: 2700, Ti: 4430, SS: 8000
**Thermal Properties:**
- Melting point (K) — temperature limit
- Coefficient of thermal expansion (CTE)
- Thermal conductivity
**Other:**
- Cost (relative)
- Machinability
- Availability
### Material Comparison
| Material | Density | Yield | T_melt | Cost | Best For |
|----------|---------|-------|--------|------|----------|
| Al 6061-T6 | 2700 | 275 | 933 | 1× | Prototype, pressure-fed |
| SS 304 | 8000 | 215 | 1726 | 3× | Corrosion resistance |
| Inconel 718 | 8190 | 1240 | 1600 | 10× | High-performance engines |
| Ti-6-4 | 4430 | 880 | 1941 | 15× | Lightweight, space |
| CFRP | 1600 | 600+ | 700 | 20× | Lowest weight |
### Hoop Stress Calculation
For cylindrical pressure vessels:
```
σ_hoop = (P × r) / t
```
Setting equal to yield with safety factor:
```
t = (P × r) / (σ_yield / SF)
```
Higher yield strength → thinner walls → lighter mass
### Material Selection
**Pressure-Fed Engine (200+ bar, chamber):**
- **Inconel 718** — withstands high pressure & heat
- **Titanium** — lighter alternative
- **Aluminum** — cheap but needs cooling design
**Rocket Tanks (2050 bar):**
- **Aluminum 6061** — standard, proven, affordable
- **Titanium** — if weight critical
- **CFRP** — lowest weight, highest cost
**Low-Pressure Systems:**
- **Aluminum** — sufficient, cheapest
- **Stainless Steel** — if corrosion concern
---
## Equations Reference
### Fundamental Relations
**Thrust (momentum equation):**
```
F = ṁ · Ve + (Pe - Pa) · Ae
```
- ṁ = mass flow rate
- Ve = exit velocity
- Pe = exit pressure
- Pa = ambient pressure
- Ae = exit area
**Specific Impulse:**
```
Isp = Ve / g0 (vacuum)
Isp = (Ve - (Pe - Pa) / ṁ · Ae) / g0 (sea level)
```
- g0 = 9.81 m/s² (gravitational acceleration)
**Rocket Equation (Tsiolkovsky):**
```
ΔV = Isp · g0 · ln(m_initial / m_final)
```
- ΔV = velocity change (delta-v)
- m_initial = wet mass
- m_final = dry mass
### Isentropic Flow (Nozzle Design)
**Temperature at exit:**
```
Te / T0 = (Pe / P0)^((γ-1)/γ)
```
**Density at exit:**
```
ρe / ρ0 = (Pe / P0)^(1/γ)
```
**Mach number from area ratio:**
```
A / A* = (1/M) · [(2/(γ+1)) · (1 + (γ-1)/2 · M²)]^((γ+1)/(2(γ-1)))
```
- A* = throat area
- A = arbitrary section area
- M = Mach number
- (requires numerical inversion via bisection)
**Characteristic velocity:**
```
c* = √[2 · (γ+1)/(γ-1) · R · T0 · (2/(γ+1))^((γ+1)/(γ-1))]
```
### Thrust Coefficient
```
CF = √[2γ²/(γ-1) · (2/(γ+1))^((γ+1)/(γ-1)) · (1 - (Pe/P0)^((γ-1)/γ))]
+ (Pe - Pa)/(P0) · (Ae/At)
```
### Chamber Thermodynamics
**Energy balance (no losses):**
```
Cp · (T0 - Te) = Ve² / 2
```
- Cp = specific heat at constant pressure
**Chemical equilibrium:**
Determine T0, γ from propellant properties and stoichiometry
(computed via NASA CEA code or thermodynamic tables)
### Drag & Atmosphere
**Drag force:**
```
Fd = 0.5 · ρ · v² · Cd · A
```
- ρ = air density (depends on altitude)
- v = velocity
- Cd = drag coefficient (~0.25 for rockets)
- A = reference area
**US Standard Atmosphere (piecewise):**
```
T(h) = T0 - L·h (troposphere, 011 km)
P(h) = P0 · (T(h)/T0)^(-g/(R·L))
ρ(h) = ρ0 · (T(h)/T0)^(-(g/(R·L) + 1))
```
- L = lapse rate ≈ 6.5 K/km
- R = gas constant
### Hoop Stress (Pressure Vessels)
**Thin-walled cylinder:**
```
σ_hoop = P·r / t
```
With safety factor:
```
t = P·r / (σ_yield / SF)
```
Mass:
```
m = 2π · r · t · L · ρ (cylinder)
m = 4π · r² · t · ρ (hemispherical dome)
```
### Mass Fraction
**Payload fraction:**
```
fp = m_payload / m_wet
```
**Structure fraction:**
```
fs = m_structure / m_wet
```
**Propellant fraction:**
```
fp = m_propellant / m_wet
```
Sum: `fp + fs + fpayload = 1`
Typical rockets: `fs = 0.100.20` (1020%)
---
## Using Knowledgebase in Design
### Importing Propellant Properties
1. **Solver**: Drag `chamberTemperature`, `expansionGamma` onto workspace
2. Check knowledgebase for propellant pair (LOX + RP-1)
3. Enter T0 from knowledgebase → solver computes Isp
4. Verify with reference Isp in database
### Cross-Checking Ablation
1. **Engine Design**: Select PAXS ablative
2. **Check Pressure Exponent**: 0.38 (from knowledgebase)
3. Verify erosion rate correction is applied
4. Compare remaining thickness to safety margin (>0.5 inch)
### Material Trade-Studies
1. **Rocket Design**: Compare materials
- Aluminum: 1000 kg tanks
- Titanium: 600 kg tanks (lighter, more expensive)
- CFRP: 350 kg tanks (lightest, most expensive)
2. Use mass to compute TWR, delta-v
3. Pick best balance for mission
---
## Future Enhancements
- **User contributions**: Allow adding new materials/propellants
- **Temperature corrections**: Adjust properties with temperature
- **Material compatibility**: Show fuel/material interactions
- **Cost database**: Include material & propellant costs
- **References**: Link to papers & technical data sources
- **Lookup plots**: Interactive charts (Isp vs. O/F, etc.)
---
## References
- NASA SP-273: Liquid Rocket Engine Combustion Instability.
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines. AIAA.
- Rocket Propulsion Elements (8th ed.). Sutton & Biblarz.
- US Standard Atmosphere 1976. NASA TM-X-74335.
- CEA (Chemical Equilibrium with Applications). NASA Glenn Research Center.
---
**Last Updated**: 2025-02 | **Status**: Current

243
docs/QUICKSTART.md Normal file
View File

@@ -0,0 +1,243 @@
# Quick Start Guide
Get up and running with RocketTools in 5 minutes.
## Installation
```bash
npm install
npm run dev
```
Open **http://localhost:5173** in your browser.
---
## Your First Rocket Design (10 minutes)
### Step 1: Design an Engine (3 min)
1. Click **Design → Engine**
2. Select propellants:
- Fuel: **RP-1**
- Oxidizer: **LOX**
3. Set chamber pressure: **200 bar**
4. Set nozzle diameter: **50 mm**
5. Set expansion ratio: **8**
6. **→ Results**: Thrust ~150 kN, Isp ~310 s, burn time 60 s
7. **Export as JSON** (save to file)
### Step 2: Design a Rocket (4 min)
1. Click **Design → Rocket**
2. **Import engine JSON** (from step 1)
3. Set tank configuration: **Tandem**
4. Set outer radius: **1.0 m**
5. Set propellant volume: **5000 L**
6. Set payload: **50 kg**
7. **→ Results**: Wet mass 1550 kg, dry mass 450 kg, TWR 10
8. Adjust nose cone shape: **Von Kármán** (optimal)
9. **Export as JSON** (save to file)
### Step 3: Simulate Flight (3 min)
1. Click **Design → Trajectory**
2. **Import rocket JSON** (from step 2)
3. **Import engine JSON** (from step 1)
4. Set pitch start altitude: **1000 m**
5. **Run Simulation**
6. **Watch the animation** (click Play button)
7. **Results**: ~5000 m downrange, 50 km apogee
---
## Key Pages Overview
### 🧮 Solver
Automatically solve rocket equations.
- Drag variables onto workspace
- Set constraints
- Solver computes unknowns
**Try this**: Set `thrust = 100000 N`, `massFlowRate = 100 kg/s`
→ Solver computes `exitVelocity = 1000 m/s`
### 🔥 Engine Design
Calculate combustion, erosion, and structure.
- Propellant selection
- Chamber pressure & nozzle geometry
- Ablative erosion prediction
- Wall thickness & mass
**Try this**: Increase chamber pressure from 200 → 500 bar
→ See thrust increase, wall thickness increase, mass increase
### 🚀 Rocket Design
Vehicle geometry and mass budget.
- Tank configuration (tandem/coaxial)
- Tank structure (hoop stress)
- Nose cone shapes
- Complete mass budget
- 3D visualization
**Try this**: Switch nose cone from Conical → Von Kármán
→ 3D model updates in real time
### 📈 Trajectory Simulation
Flight path prediction.
- RK4 physics integrator
- Atmosphere model (US Standard)
- Event detection (liftoff, apogee, landing)
- Playback controls
**Try this**: Run simulation, then scrub timeline to different times
→ See position, velocity, altitude change
### 📚 Knowledgebase
Material & equation references.
- **Fuels / Oxidizers** — propellant properties
- **Ablative Materials** — thermal protection specs
- **Equations** — physics reference
- **Structural Materials** — metal/composite properties
**Try this**: Look up LOX properties
→ Temperature: 3750 K, Isp: 310 s
---
## Tips & Tricks
### Save Your Work
- Engine JSON → use for multiple rocket designs
- Rocket JSON → use for trajectory simulations
- Keep both files for reference
### Quick Iterations
1. Modify one parameter (e.g., chamber pressure)
2. Solver/simulator automatically updates
3. Check results
4. Adjust again
### Understanding Mass
- **Wet mass** = total (with fuel)
- **Dry mass** = structure + engine + payload (no fuel)
- **Propellant mass** = wet dry
- **TWR** = thrust / weight
- > 5 is good for vertical launch
- < 2 struggles to lift off
### Understanding Isp
- **Higher Isp = more efficient** (goes farther on same fuel)
- Vacuum Isp > Sea-level Isp (less atmospheric pressure at altitude)
- LOX/RP-1: ~310 s (workhorse)
- LOX/H2: ~450 s (high-perf, but cryogenic)
- MMH/N2O4: ~290 s (storable, easier operations)
### Flight Path Basics
- Vehicle accelerates straight up (vertical hold)
- At ~1 km altitude, starts gravity turn (follows velocity)
- Thrust ends at ~60 s (Main Engine Cutoff)
- Coasts up to apogee (~50 km in example)
- Falls back down (lands ~5 km downrange)
---
## Common Questions
**Q: How do I get higher apogee?**
- Increase fuel volume (larger tank)
- Reduce payload mass
- Higher Isp propellant (LOX/H2)
- More efficient rocket (lighter structure)
**Q: Why is my rocket too heavy?**
- Reduce tank radius (but check volume)
- Use stronger material (Al → Ti → CFRP)
- Lower safety factor (use 2.0 instead of 3.0)
- Reduce payload
**Q: Can I design multiple stages?**
- Not yet — current version is single-stage only
- Workaround: design each stage separately, estimate staging losses manually
**Q: How accurate are the simulations?**
- ±510% for well-designed vehicles
- Assumes no wind, perfect injection
- Doesn't include control/guidance errors
- Good for feasibility analysis, not flight prediction
**Q: Can I export to CAD?**
- Not yet — export rocket JSON for external processing
- 3D model data available in code (tank dimensions, nose cone profile)
---
## Common Mistakes to Avoid
**Too small tank**
- 5000 L seems big but isn't for LOX/RP-1 rockets
- Try 10,000+ L for real vehicle
**Forgot engine burn time**
- Check engine design was properly simulated
- Engine JSON must include burn time
**Wrong reference area for drag**
- Use actual cross-sectional area (πR²)
- Not wetted surface or planform area
**Unrealistic safety factor**
- 2.03.0 is typical for engines
- < 1.5 is dangerous, > 5.0 is wasteful
**Ignoring pressurant mass**
- Pressure-fed system helium adds ~100 kg
- Consider pump-fed for heavier vehicles
---
## Next Steps
1. **Explore knowledgebase** — understand propellants & materials
2. **Read equations** — understand the physics
3. **Design variants** — try different tank sizes, materials
4. **Optimize** — see what makes vehicle lighter/faster
5. **Contribute** — add new equations or materials
---
## Learning Resources
- [Rocket Propulsion Elements](https://www.amazon.com/Rocket-Propulsion-Elements-Sutton-Biblarz/dp/0470080728) — textbook
- [NASA SP-273: Combustion Instability](https://ntrs.nasa.gov) — free PDF
- [Everyday Astronaut](https://www.youtube.com/c/EverydayAstronaut) — video explanations
---
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Nothing shows up | Refresh browser (Ctrl+F5) |
| Numbers look wrong | Check input units (bar vs MPa, mm vs m) |
| 3D model doesn't render | WebGL must be enabled in browser |
| Solver stuck | Ensure enough constraints set for system |
| Simulation runs forever | Reduce timestep or check inputs for infinity |
---
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| **Space** | Play/pause trajectory |
| **→** | Jump forward 10 seconds |
| **←** | Jump backward 10 seconds |
| **Home** | Jump to start |
| **End** | Jump to end |
---
**Happy rocket designing! 🚀**
Next, dive into [SOLVER.md](SOLVER.md) to master the equation solver.

509
docs/ROCKET_DESIGN.md Normal file
View File

@@ -0,0 +1,509 @@
# Rocket Design Guide
## Overview
The **Rocket Design** tool calculates vehicle mass budget, tank geometry, structural requirements, and generates 3D models. It integrates engine data from the engine designer and computes the complete rocket's performance characteristics.
## Main Sections
### Vehicle Geometry
Define tank configuration, dimensions, and structural layout.
### Tank Structure
Calculate wall thicknesses, masses, and pressurant requirements.
### Nose Cone Design
Select and visualize nose cone shapes (conical, ogive, Von Kármán).
### Mass Budget
Integrate all components (engines, tanks, payload, structure) to compute total mass and performance metrics.
### 3D Visualization
Interactive three.js model showing tanks, nose cone, and flight orientation.
---
## Vehicle Geometry
### Tank Configuration Options
**Tandem (Sequential):**
- Two cylindrical tanks stacked vertically
- Fuel (lower), Oxidizer (upper)
- Domes: hemispherical on both ends
- **Advantage**: Compact, simpler plumbing
- **Disadvantage**: Longer vehicle
**Coaxial (Concentric):**
- Outer tank (fuel) surrounds inner tank (oxidizer)
- Reduces diameter
- Inner tank: flat bulkheads (simpler)
- **Advantage**: Compact, low diameter
- **Disadvantage**: More complex, thermal management
**Single Tank:**
- One tank with both propellants mixed
- Rarely used for high-performance (lower Isp)
### Tank Dimensions
**Inputs:**
- `outerRadius` (mm) — outer tank radius
- `propellantVolume` (L) — total propellant quantity
- `tankConfiguration` — tandem, coaxial, single
- `ullagePercent` (%) — pressurant volume as % of propellant
**Key Relationship:**
```
Ullage volume = propellant volume × (ullage% / 100)
Effective volume = propellant volume × (1 + ullage%)
```
Ullage accommodates:
- Thermal expansion of propellant
- Gas bubble for pump inlet
- Margin for fill level uncertainty
### Tank Geometry (Tandem Case)
**Dome Volume (hemispherical):**
```
V_dome = (2/3) × π × R³ (one hemisphere)
V_domes = (4/3) × π × R³ (both domes)
```
**Cylindrical Section:**
```
L_cyl = (V_eff - V_domes) / (π × R²)
```
**Total Tank Length:**
```
L_total = L_cyl + 2R (two dome heights)
```
**Example:**
- R = 1.5 m, V_prop = 5000 L, ullage = 10%
- V_eff = 5000 × 1.1 = 5500 L
- V_domes = 4/3 × π × 1.5³ ≈ 14.1 m³ = 14100 L (larger than prop!)
- Need smaller R or accept small L_cyl
**Note**: For smaller rockets, domes dominate volume. Design must account for this.
---
## Tank Structure
### Pressure Sources
**Pressure-Fed System:**
```
P_tank = 1.2 × P0_chamber (pressure margin)
```
Typical: P0 = 200 bar → P_tank = 240 bar
**Pump-Fed System:**
```
P_tank = 2 MPa (minimum for turbopump inlet)
```
Much lower pressure (pump provides most acceleration)
**Selection affects:**
- Wall thicknesses (direct proportionality)
- Mass (linear with pressure and wall thickness)
- Propellant pump requirements
### Structural Material Properties
Available materials from knowledgebase:
- **Aluminum 6061-T6**: density 2700 kg/m³, σ_y = 275 MPa
- **Stainless Steel 304**: density 8000 kg/m³, σ_y = 215 MPa
- **Inconel 718**: density 8190 kg/m³, σ_y = 1240 MPa
- **Titanium 6-4**: density 4430 kg/m³, σ_y = 880 MPa
- **CFRP (Carbon Fiber)**: density 1600 kg/m³, σ_y = 600 MPa
**Selection factors:**
- Cost (Al < SS < Ti < CFRP)
- Weight (CFRP < Ti < Al < SS)
- Corrosion (SS, Ti, CFRP better; Al needs coating)
- Temperature (Inconel for hot engines; others at T < 150°C)
### Wall Thickness (Hoop Stress)
**Hoop stress formula:**
```
σ_hoop = (P × R) / t
```
**With safety factor:**
```
t = (P × R) / (σ_yield / SF)
```
**Cylindrical section mass:**
```
m_cyl = 2π × R × t × L_cyl × ρ_material
```
**Dome mass (two hemispheres):**
```
m_domes = 4π ×× t × ρ_material
```
**Total tank mass:**
```
m_tank = m_cyl + m_domes
```
### Example Calculation
**Tandem LOX/RP1 Tank:**
- Radius: 1.0 m
- Pressure: 240 bar = 24 MPa
- Material: Aluminum 6061-T6
- σ_y = 275 MPa
- ρ = 2700 kg/m³
- Safety Factor: 2.5
**Wall Thickness:**
```
t = (24 MPa × 1.0 m) / (275 MPa / 2.5)
= 24 / 110
≈ 0.218 m = 2.18 mm
```
**Cylindrical Section (assume L_cyl = 3 m):**
```
m_cyl = 2π × 1.0 × 0.00218 × 3.0 × 2700
≈ 110 kg
```
**Domes:**
```
m_domes = 4π × 1.0² × 0.00218 × 2700
≈ 74 kg
```
**Total tank mass:** ~184 kg
---
## Pressurant System (Pressure-Fed Only)
### Helium Requirements
For pressure-fed engines, pressurant gas maintains tank pressure throughout burn.
**Ideal gas law:**
```
P × V = n × R × T
```
**Helium mass:**
```
m_He = (P_tank × V_prop) / (R_He × T)
```
Where:
- `P_tank` — tank pressure (Pa)
- `V_prop` — propellant volume (m³)
- `R_He` — helium specific gas constant = 2077 J/kg/K
- `T` — temperature (K), typically 293 K (20°C)
**Example:**
- P_tank = 24 MPa = 24×10⁶ Pa
- V_prop = 5 m³
- T = 293 K
```
m_He = (24×10⁶ × 5) / (2077 × 293)
≈ 20 kg
```
### Helium Bottle Mass
Helium stored in high-pressure bottle. Typical storage pressure: 250 bar.
**Bottle mass (conservative):**
```
m_bottle = 4 × m_He
```
Rule of thumb: bottle is 4 times the helium mass. (More sophisticated: use MEOP analysis.)
**Total pressurant system:**
```
m_pressurant = m_He + m_bottle
```
Example: 20 kg He → 80 kg bottle → 100 kg pressurant system
**Note**: This is a major mass penalty for pressure-fed systems. Pump-fed systems avoid this.
---
## Nose Cone Design
### Available Profiles
Three aerodynamic nose cone shapes, all with L = 2 × R (length = 2 × base radius).
**Conical:**
- Simple linear profile
- `r(x) = R × (x / L)`
- Sharpest tip, moderate drag
**Tangent Ogive:**
- Smooth circular arc meeting base tangentially
- `ρ = (R² + L²) / (2R)` (radius of curvature)
- `r(x) = √(ρ² - (L - x)²) - (ρ - R)`
- Smooth, less drag than conical
- Common in rocketry
**Von Kármán:**
- Power law profile minimizing drag
- `θ = acos(1 - 2x/L)`
- `r(x) = (R / √π) × √(θ - sin(2θ)/2)`
- Theoretically optimal for transonic flight
- Used on high-performance rockets
**Selection Criteria:**
- **Conical**: Simplest to manufacture, sharp tip
- **Tangent Ogive**: Better aerodynamics, smoother base transition
- **Von Kármán**: Best drag coefficient (Cd ≈ 0.15 vs 0.25 conical)
### 3D Visualization
Nose cone is rendered using THREE.js `LatheGeometry`:
- Profile points sampled from mathematical formula
- Rotated around vertical axis to create 3D shape
- Interactive rotation/zoom in 3D viewer
---
## Mass Budget
### Components
**Structure:**
- Tank walls and domes: `m_tanks`
- Other structure (nosecone, bay, interstage): `m_other`
- Engine dry mass (from engine designer): `m_engine`
**Propellant:**
- Fuel + oxidizer: `m_propellant`
**Pressurant (pressure-fed only):**
- Helium + bottle: `m_pressurant`
**Payload:**
- Avionics, recovery system, instruments: `m_payload`
### Calculation
**Dry mass (no propellant):**
```
m_dry = m_tanks + m_other + m_engine + m_pressurant + m_payload
```
**Wet mass (with propellant):**
```
m_wet = m_dry + m_propellant
```
**Payload fraction:**
```
fraction_payload = m_payload / m_wet
```
**Structure fraction:**
```
fraction_structure = m_tanks / m_wet
```
**Thrust-to-weight ratio:**
```
TWR = F_thrust / (m_wet × g)
```
### Example Mass Budget
Small LOX/RP1 rocket:
| Component | Mass (kg) | % |
|-----------|-----------|-----|
| Tanks (structure) | 200 | 14% |
| Engine | 50 | 3.5% |
| Pressurant (He + bottle) | 100 | 7% |
| Avionics + recovery | 30 | 2% |
| **Dry mass** | **380** | **26.5%** |
| Propellant (LOX + RP1) | 1050 | 73.5% |
| **Wet mass** | **1430** | **100%** |
**Performance:**
- TWR = 150 kN / (1430 kg × 9.81 m/s²) = 10.7
- Delta-v (no gravity loss): 310 s × ln(1430/380) ≈ 1300 m/s
---
## 3D Model Components
### Tank Rendering
**Cylindrical body:**
```javascript
<TankSection
radius={1.0}
length={3.0}
position={[0, 0, 0]}
/>
```
Renders:
- Cylinder: `CylinderGeometry`
- Top dome: `SphereGeometry` (hemisphere)
- Bottom dome: `SphereGeometry` (hemisphere)
**Material:** MeshStandardMaterial with color (red = fuel, blue = oxidizer)
### Nose Cone Rendering
**LatheGeometry:**
- Takes profile curve (array of points)
- Rotates around Y-axis to create 3D surface
- Smooth, aerodynamic appearance
**Example:**
```javascript
const profile = noseConeProfile('tangent-ogive', 0.5, 1.0, 24)
const geometry = new THREE.LatheGeometry(profile, 24)
```
### Scene Setup
**Lighting:**
- Ambient light (0.6 intensity)
- Directional light from top-right
- Shadows enabled for depth
**Camera:**
- Orbit controls (rotate, zoom, pan)
- Auto-fit to model bounds
- Perspective view (60° FOV)
---
## Workflow: Design Complete Vehicle
### Step 1: Import Engine
1. Go to **Design > Engine**
2. Configure combustion (LOX/RP1, 200 bar, etc.)
3. Size structure (Inconel, 3.0 SF)
4. Export as JSON
5. Return to **Design > Rocket**
6. Upload engine JSON
**Result**: Engine dry mass auto-populated
### Step 2: Configure Tanks
1. Set tank configuration: **Tandem**
2. Set outer radius: **1.0 m**
3. Set propellant volume: **5000 L**
4. Set ullage: **10%**
**Result**: Calculated tank length, dome geometry
### Step 3: Design Tank Structure
1. Select material: **Aluminum 6061-T6**
2. Set safety factor: **2.5**
3. Select feed system: **Pressure-Fed**
**Result**: Wall thickness, tank mass, pressurant requirements
### Step 4: Set Payload
1. Enter payload mass: **50 kg** (avionics, recovery)
**Result**: Dry mass updated
### Step 5: View Mass Budget
**Result**:
- Wet mass: 1550 kg
- Dry mass: 450 kg
- TWR: 10.2
- Delta-v (vacuum): 1320 m/s
### Step 6: Adjust Nose Cone
1. Select shape: **Von Kármán**
**Result**: 3D model updates with optimal nose cone
### Step 7: Export & Simulate
1. Export vehicle JSON
2. Go to **Design > Trajectory**
3. Import vehicle & engine data
4. Run flight simulation
**Result**: Altitude, downrange, apogee, landing site
---
## Advanced Topics
### Multi-Stage Rockets
Not yet implemented, but conceptually:
- Stage 1: Large engines, heavy structure
- Stage 2: Smaller engines, lighter structure
- Interstage adapter: mass penalty
- Staging sequence: defined by velocity requirements
Implementation would require:
- Multiple vehicle definitions (or list of stages)
- Trajectory system that handles stage separation
- Cumulative mass and thrust calculations
### Composite Materials
CFRP offers best strength-to-weight but requires special analysis:
- **Fiber direction** — properties vary with orientation
- **Matrix** — epoxy, vinyl ester
- **Layup schedule** — [0°/90°/45°] typical
- **Microbuckling** — axial compression limit
- **Matrix cracking** — transverse tensile limit
Advanced model would include:
- Classical laminate theory (CLT)
- Ply-by-ply failure criteria (Tsai-Wu, Hashin)
- Buckling analysis (Timoshenko, finite element)
---
## Troubleshooting
### Tank too long?
- Reduce ullage percentage (lower to 5%)
- Increase radius (larger tank, shorter cylinder)
- Split into multiple smaller tanks
- Switch to coaxial configuration
### Vehicle too heavy?
- Switch to lighter material (CFRP)
- Reduce safety factor (if acceptable)
- Decrease payload mass
- Use pump-fed system (avoids pressurant)
### Nose cone looks wrong?
- Verify radius and length (L = 2R)
- Try different shape (Von Kármán usually best)
- Check 3D viewer isn't zoomed in too far
### 3D model not rendering?
- WebGL must be enabled
- Check browser console for Three.js errors
- Ensure geometry dimensions are reasonable (not NaN)
---
## References
- NASA SP-8007: Structural Design and Test Factors of Safety for Spaceflight Hardware
- Huzel, D. K., & Huang, D. H. (1992). Modern engineering for design of liquid-propellant rocket engines.
- Rocket Propulsion Elements (8th ed.). by Sutton & Biblarz.
---
**Last Updated**: 2025-02 | **Status**: Current (v3 — Domed Tanks)

319
docs/SOLVER.md Normal file
View File

@@ -0,0 +1,319 @@
# Solver Guide
## Overview
The **Equation Solver** is a constraint-based computational engine that automatically solves rocket propulsion equations. Rather than manually plugging numbers into formulas, users specify constraints (inputs), and the solver propagates solutions through an interconnected equation network.
**Key Concept**: Drag variables onto the workspace, set values, and the solver automatically computes unknowns.
## User Interface
### Variable Palette (Left)
- Searchable list of all variables
- Organized by category (thrust, Isp, chamber, etc.)
- Drag variables onto workspace
### Workspace (Center)
- Drop zone for constraint cards
- Cards show variable name, unit, input field
- Set constraint by entering value
- Results auto-compute below
### Results Panel (Right)
- Shows all computed values
- Organized by category
- Clickable to copy values
- Unit selector for each variable
## Constraint Propagation Algorithm
The solver implements **greedy propagation**:
### Step-by-Step
1. **User Input**: Sets constraint on variable `x`
2. **Find Equations**: Locates all equations referencing `x`
3. **Attempt Solve**: For each equation, tries to solve for unknowns
- If equation has 1 unknown: solve directly
- If equation has 2+ unknowns: skip (not enough constraints)
4. **Update State**: Newly-solved variables are added to known set
5. **Recurse**: Repeat step 2 with newly-known variables
6. **Stop**: When no new variables can be computed
### Example
**Given:**
- `thrust = massFlowRate * exitVelocity`
- `exitVelocity = sqrt(2 * Cp * (T0 - Te))`
- `massFlowRate = densityO2 * densityFuel * burnRate`
**User Constraints:**
- `thrust = 50000 N`
- `massFlowRate = 100 kg/s`
**Solver Flow:**
```
1. Set thrust = 50000
2. Find equations with thrust: Eq1
3. Eq1 has 2 unknowns (massFlowRate, exitVelocity) → skip
4. Set massFlowRate = 100
5. Find equations with massFlowRate: Eq1, Eq3
6. Eq1 now has 1 unknown (exitVelocity) → solve: exitVelocity = 500 m/s
7. Find equations with exitVelocity: Eq2
8. Eq2 has 2 unknowns (T0, Te) → skip
9. No new variables → done
```
## Variable Definitions
Variables are defined in `src/engine/variables.js` with metadata:
```javascript
{
id: 'thrust',
name: 'Thrust',
unit: 'N',
category: 'performance',
type: 'number'
}
```
**Categories:**
- `thrust` — Force (N, lbf)
- `isp` — Specific impulse (s, m/s)
- `chamber` — Chamber conditions (P, T, density)
- `nozzle` — Nozzle geometry (area, diameter, angle)
- `propellant` — Fuel/oxidizer properties
- `flow` — Mass flow rate, burn rate
- `thermal` — Heat, temperature
- `structural` — Stress, strain, thickness
## Equation Library
Equations are defined in `src/engine/equations.js`. Each equation:
1. **Specifies variables** it relates
2. **Implements forward solver** (compute output given inputs)
3. **Implements per-variable solvers** (solve for each variable given others)
### Example Equation
```javascript
{
id: 'thrustEquation',
name: 'Thrust (Momentum)',
variables: ['thrust', 'massFlowRate', 'exitVelocity'],
equation: 'thrust = massFlowRate * exitVelocity',
forward: (inputs) => {
return inputs.massFlowRate * inputs.exitVelocity
},
solvers: {
exitVelocity: (inputs) => {
return inputs.thrust / inputs.massFlowRate
},
thrust: (inputs) => {
return inputs.massFlowRate * inputs.exitVelocity
}
}
}
```
### Transcendental Equations (Bisection)
For equations with no closed-form solution (e.g., Mach from area ratio), the solver uses **bisection**:
```javascript
// In numerics.js
function bisection(fn, target, min, max, tolerance) {
while (max - min > tolerance) {
const mid = (min + max) / 2
const fMid = fn(mid)
if (fMid < target) {
min = mid
} else {
max = mid
}
}
return (min + max) / 2
}
```
Example: Finding Mach number from area ratio
```javascript
solvers: {
machNumber: (inputs) => {
const fn = (M) => isentropicAreaRatio(M, inputs.gamma)
return bisection(fn, inputs.areaRatio, 0.01, 10, 1e-6)
}
}
```
## Common Equations
### Thrust
```
thrust = mass_flow_rate * exit_velocity
thrust = chamber_pressure * throat_area * CF
```
### Specific Impulse
```
Isp = exit_velocity / g
Isp_vac = exit_velocity / g
Isp_sl = (exit_velocity - P_exit / mass_flow_rate * area_exit) / g
```
### Chamber Conditions (Isentropic Flow)
```
T_exit = T_chamber * (P_exit / P_chamber)^((gamma - 1) / gamma)
rho_exit = rho_chamber * (P_exit / P_chamber)^(1 / gamma)
```
### Nozzle Area Ratio
```
A_exit / A_throat = (2 / (gamma + 1)) * (1 + (gamma - 1)/2 * M_exit^2))^((gamma + 1) / (2 * (gamma - 1))) / M_exit
```
(requires bisection to invert for M)
### Characteristic Velocity
```
c_star = P_chamber * A_throat / mass_flow_rate
```
### Thrust Coefficient
```
CF = sqrt(2 * gamma^2 / (gamma - 1) * (2 / (gamma + 1))^((gamma + 1) / (gamma - 1)) * (1 - (P_exit / P_chamber)^((gamma - 1) / gamma)))
```
## Workflow Examples
### Example 1: Determine Exit Velocity from Isp
**Goal:** Calculate exit velocity given Isp
**Steps:**
1. Drag `specificImpulse` onto workspace
2. Set `specificImpulse = 300 s`
3. Solver computes `exitVelocity = 300 * 9.81 ≈ 2943 m/s`
### Example 2: Find Chamber Pressure from Thrust
**Goal:** Find chamber pressure needed for 50 kN thrust
**Steps:**
1. Drag `thrust`, `throatDiameter`, `massFlowRate` onto workspace
2. Set:
- `thrust = 50000 N`
- `throatDiameter = 0.1 m`
- `massFlowRate = 100 kg/s`
3. Solver finds equations with these variables
4. Uses thrust equation to find thrust coefficient needed
5. Inverts nozzle area ratio with bisection to find Mach
6. Uses isentropic relations to find chamber pressure
7. Displays `chamberPressure = X bar`
### Example 3: Design for Specific Isp
**Goal:** Find propellant combination for Isp = 350 s
**Steps:**
1. Drag `specificImpulse`, `chamberTemperature`, `exhaustGamma` onto workspace
2. Set `specificImpulse = 350 s`
3. Click on knowledgebase to find propellant with T0 ≈ 3500 K
4. Enter that temperature
5. Solver computes `exitVelocity`, then works backward to required conditions
6. Compare against available propellants
## Advanced Features
### Unit Conversion
Every variable card has a unit selector. Setting a variable automatically converts:
```
thrust = 50000 N → 11240.44 lbf
thrust = 11240.44 lbf → 50000 N
```
### Importing Engine Design
Engine design results can be fed into the solver:
- File → Import Engine JSON
- Automatically populates thrust, Isp, chamber pressure, etc.
- Enables end-to-end rocket design workflow
### Variable Categories
Filter variables by category to reduce clutter:
- Thrust (F, Isp, c*)
- Chamber (P, T, density)
- Nozzle (area, geometry)
- Propellant (fuel/oxidizer properties)
- Flow (mass flow, burn rate)
## Limitations & Gotchas
1. **No Circular Dependencies**: If equations form a cycle, solver may loop infinitely
- Solver stops after N iterations to prevent this
- Design equations to be acyclic where possible
2. **Insufficient Constraints**: If variables are underdetermined (more unknowns than equations)
- Solver computes what it can, leaves rest as unknown
- User must provide more constraints
3. **Overdetermined System**: If too many constraints (conflicting values)
- Solver uses first constraint encountered
- Other constraints ignored (may warn in future)
4. **Transcendental Equation Tolerance**: Bisection has numerical error
- Default tolerance: 1e-6
- Results accurate to ~6 significant figures
- Adjust in `numerics.js` if higher precision needed
## Troubleshooting
### Solver not computing?
- Ensure at least one constraint is set
- Check that equation network connects the variables you're trying to solve
- Look for missing material properties (fuel, oxidizer data)
### Result seems wrong?
- Verify input units match variable definition
- Check variable category (is it the right variable?)
- Try solving step-by-step with intermediate variables
### Equation not available?
- Not all rocketry equations are implemented
- Submit a feature request to add more
- Or add the equation yourself in `src/engine/equations.js`
## Development Guide
### Adding a New Equation
1. **Define variables** in `variables.js` if needed
2. **Implement equation** in `equations.js`:
```javascript
equations.push({
id: 'myEquation',
name: 'My Equation',
variables: ['var1', 'var2', 'var3'],
equation: 'var1 = var2 * var3',
solvers: {
var1: (inputs) => inputs.var2 * inputs.var3,
var2: (inputs) => inputs.var1 / inputs.var3,
var3: (inputs) => inputs.var1 / inputs.var2
}
})
```
3. **Test** by:
- Setting constraints manually
- Verifying solver produces correct result
- Testing with known values
### Debugging Solver
- Add console logs in `solver.js` propagation loop
- Log variable state at each iteration
- Verify equation network is correct
---
**Last Updated**: 2025-02 | **Status**: Current

503
docs/TRAJECTORY.md Normal file
View File

@@ -0,0 +1,503 @@
# Trajectory Simulation Guide
## Overview
The **Trajectory Simulator** predicts rocket flight paths using physics-based 4th-order Runge-Kutta integration. It accounts for atmospheric drag, gravity, thrust, and user-defined events to compute complete vehicle trajectories from liftoff to landing.
## Quick Start
1. **Configure Vehicle**
- Import engine (from Engine Designer) or specify manually
- Enter vehicle mass (wet & dry), reference area, drag coefficient
2. **Configure Atmosphere**
- Select model (US Standard, exponential)
- Set sea-level conditions (temperature, pressure)
3. **Set Pitch Program**
- Vertical hold duration or altitude
- Then gravity turn (pitch follows velocity vector)
4. **Add Events** (optional)
- Guidance commands (pitch changes)
- Jettison (drop mass during flight)
- Markers (annotations)
5. **Run Simulation**
- Click "Run Simulation"
- Wait for RK4 integration to complete
6. **Playback**
- Watch animation (1×, 5×, 10× speed)
- Scrub timeline to jump to time
- Click on plot to jump to position
---
## Physics Model
### State Vector
At each timestep, we track:
```
state = [x, y, vx, vy, m]
```
- `x` — downrange distance (m)
- `y` — altitude above sea level (m)
- `vx` — horizontal velocity (m/s, positive = downrange)
- `vy` — vertical velocity (m/s, positive = up)
- `m` — vehicle mass (kg)
### Forces
**Thrust:**
```
F_thrust = thrust(t) × cos(pitch_angle) [horizontal component]
F_thrust = thrust(t) × sin(pitch_angle) [vertical component]
```
Direction depends on pitch program (see below).
**Gravity (altitude-dependent):**
```
g(h) = g0 × (R_earth / (R_earth + h))²
```
Where:
- `g0` = 9.81 m/s²
- `R_earth` = 6.371×10⁶ m
- `h` = altitude (m)
**Drag:**
```
F_drag = 0.5 × ρ(h) ×× Cd × A_ref
```
Opposed to velocity direction:
```
F_drag_x = -F_drag × (vx / v)
F_drag_y = -F_drag × (vy / v)
```
Where `v = √(vx² + vy²)` is total velocity.
### Atmosphere
**US Standard Model (default):**
- Piecewise linear layers
- Troposphere: 08500 m (temperature decreases 6.5 K/km)
- Stratosphere: 850015000 m (isothermal at 216.5 K)
- Higher: exponential decay
**Exponential Model:**
```
ρ(h) = ρ0 × exp(-h / H)
```
Where:
- `ρ0` = sea-level density (1.225 kg/m³)
- `H` = scale height (~8500 m)
**Pressure & Temperature:**
- US Standard uses table lookup
- Temperature affects gas density (lower T = higher ρ at same P)
### Equations of Motion
**Acceleration:**
```
a_x = (F_thrust_x + F_drag_x) / m
a_y = (F_thrust_y + F_drag_y - m×g(h)) / m
```
**Mass Change (during burn):**
```
dm/dt = -mass_flow_rate
```
Mass only changes during engine burn phase (after liftoff, before MECO).
### Numerical Integration (RK4)
The integrator advances state by small timesteps (default: dt = 0.05 s):
```
k1 = derivatives(t, state)
k2 = derivatives(t + dt/2, state + k1×dt/2)
k3 = derivatives(t + dt/2, state + k2×dt/2)
k4 = derivatives(t + dt, state + k3×dt)
state_new = state + (k1 + 2×k2 + 2×k3 + k4) × dt/6
```
**Accuracy**: RK4 is 4th-order accurate; typical error scales as O(dt⁵).
**Advantages:**
- More accurate than Euler method
- Reasonable computational speed
- Standard in trajectory software
**Limitations:**
- No adaptive timestep (dt is fixed)
- Cannot handle stiff equations
- Small dt needed for accuracy (0.010.1 s typical)
---
## Inputs
### Vehicle Configuration
**From Engine Designer (auto-populated if imported):**
- `thrust` — engine thrust curve or constant
- `isp` — specific impulse (vacuum)
- `mdot` — mass flow rate (kg/s)
- `burnTime` — engine burn duration (s)
- `dryMass` — engine dry mass (kg)
**Vehicle Properties (manual input):**
- `wetMass` — total mass with propellant (kg)
- `dryMass` — mass without propellant (kg)
- **Note**: Used after engine shutdown
- `referenceArea` — cross-sectional area for drag (m²)
- `dragCoefficient` — Cd (typically 0.20.3 for rockets)
- `payloadMass` — payload (kg, contributes to dry mass)
### Atmospheric Conditions
- `atmosphereModel` — US Standard or Exponential
- `seaLevelTemperature` — T0 (K), default 288.15 K (15°C)
- `seaLevelPressure` — P0 (Pa), default 101325 Pa
- `windSpeed` — not yet implemented
### Pitch Program
**Vertical Hold Phase:**
- `pitchStartAlt` — altitude to begin gravity turn (m)
- Duration: liftoff until altitude > pitchStartAlt
- Pitch angle: 90° (straight up)
**Gravity Turn Phase:**
- Pitch angle follows velocity vector
- `pitch = atan2(vy, vx)`
- Vehicle points along flight path (min drag)
**Advanced Programs (future):**
- Custom pitch schedule vs. time
- Thrust vector control
- Angle of attack constraints
---
## Outputs
### Trajectory States
Array of `state` vectors at each timestep:
```javascript
[
{time: 0, x: 0, y: 0, vx: 0, vy: 0.1, m: 2000},
{time: 0.05, x: 0, y: 0.005, vx: 0.2, vy: 15.0, m: 1999.5},
...
{time: 60, x: 5000, y: 50000, vx: 2000, vy: 0, m: 800}
]
```
Total states: ~1200 for 60 second flight (0.05 s dt)
### Auto-Detected Events
**Liftoff:**
- First time `y > 0` and `vy > 0`
- Time, altitude, velocity logged
**Main Engine Cutoff (MECO):**
- When `burnTime` expires
- Last time mass changes
- Marks end of powered flight
**Max Q (Dynamic Pressure):**
- Maximum value of `q = 0.5 × ρ × v²`
- Often a structural design point
- Time, altitude, velocity, q value
**Apogee:**
- Highest altitude reached
- First time `vy` changes from positive to negative
- Last point of ascending flight
**Landing:**
- First time `y ≤ 0` after apogee
- Final altitude, downrange, velocity
### User-Defined Events
**Guidance (Pitch Command):**
```javascript
{type: 'guidance', time: 30, pitchAngle: 45}
```
At t=30s, change pitch to 45° (overrides gravity turn)
**Jettison (Mass Drop):**
```javascript
{type: 'jettison', time: 25, massDropped: 50}
```
Drop 50 kg (e.g., fairings) at t=25s
- Reduces drag & inertia
- Affects apogee & downrange
**Marker (Annotation):**
```javascript
{type: 'marker', time: 15, label: 'Separation'}
```
Just marks event on timeline; no physics change
---
## Workflow: Simulate LOX/RP1 Rocket
### Step 1: Design Engine
1. Go to **Design > Engine**
2. LOX/RP1, 200 bar → ~150 kN thrust, 310 s Isp, 60 s burn
3. Export JSON
### Step 2: Design Rocket
1. Go to **Design > Rocket**
2. Import engine JSON (gets thrust, Isp, burn time)
3. Configure tanks (5000 L, tandem)
4. Set payload (50 kg)
**Result**: Wet mass 1550 kg, dry mass 450 kg
### Step 3: Run Trajectory
1. Go to **Design > Trajectory**
2. Import rocket JSON (gets wet/dry mass, reference area)
3. Import engine JSON (gets thrust, Isp, burn time)
4. Set pitch start altitude: 1000 m
5. Click "Run Simulation"
**Result**: ~5000 m downrange, 50 km apogee, 180 s flight time
### Step 4: Analyze Results
- **Plot**: Shows altitude vs. downrange (parabolic path)
- **Timeline**: Mark apogee, MECO, Max Q
- **Playback**: Watch animation in real time
- **Scrub**: Jump to specific times to see position/velocity
### Step 5: Optimize
- Try different masses (remove payload → higher apogee)
- Try different pitch programs (start gravity turn later → longer range)
- Try jettison events (drop fairings at MECO → save mass)
---
## Event System
### Event Structure
```javascript
{
type: 'liftoff' | 'meco' | 'maxQ' | 'apogee' | 'landing' | 'guidance' | 'jettison' | 'marker',
time: number, // seconds
altitude?: number, // meters
downrange?: number, // meters
velocity?: number, // m/s
label?: string, // display name
value?: any // event-specific data
}
```
### Auto-Detected Events (Read-Only)
Computed during simulation; user cannot edit.
**Liftoff**: `{type: 'liftoff', time: 0.05, altitude: 0.1, velocity: 1.5}`
**Max Q**: `{type: 'maxQ', time: 5.2, altitude: 1000, q: 50000}`
**Apogee**: `{type: 'apogee', time: 120, altitude: 50000, velocity: 0}`
**Landing**: `{type: 'landing', time: 180, altitude: 0, downrange: 5000, velocity: -50}`
### User Events
**Guidance Command:**
```javascript
{type: 'guidance', time: 30, pitchAngle: 45, label: 'Pitch over'}
```
- Changes pitch at specified time
- Overrides gravity turn
- Can be used for coast phase burn starts, etc.
**Jettison:**
```javascript
{type: 'jettison', time: 25, massDropped: 50, label: 'Fairing separation'}
```
- Removes mass from trajectory
- Affects drag & inertia
- Typically after Max Q (fairing drag not needed)
**Marker:**
```javascript
{type: 'marker', time: 60, label: 'Engine cutoff'}
```
- Pure annotation
- No physics effect
- Useful for documentation
---
## 3D Visualization
### Trajectory Plot
HTML5 Canvas rendering of flight path:
**Axes:**
- X: downrange (0 to max)
- Y: altitude (0 to apogee + margin)
- Aspect ratio: ~2:1 (wider than tall)
**Elements:**
- Trajectory curve (light blue line)
- Event markers (circles at key points)
- Grid (light gray, 10 km spacing)
- Labels (altitude, downrange at axis)
- Legend (event types & colors)
**Interactivity:**
- Click on plot → jump to that time
- Hover on point → show values (alt, range, velocity)
- Hover on event marker → show label
- Zoom (mousewheel) — not yet implemented
### 3D Flight Animation
**Top-down view** showing:
- Vehicle model (small rocket symbol or sphere)
- Flight path (line from start to current position)
- Current altitude/range indicators
- Compass heading
**Implementation:** Canvas 2D or Three.js (current: Canvas)
---
## Playback Controls
### Timeline Bar
Located below trajectory plot:
**Play/Pause Button:**
- Starts/stops animation
- Keyboard: Space bar
**Speed Selector:**
- 1× — real time
- 5× — 5× faster
- 10× — 10× faster
**Scrubber (Time Slider):**
- Drag to jump to any time
- Release to play from that point
- Shows current time in seconds
**Event Timeline:**
- Vertical lines at event times
- Hover to see event name
- Click to jump to event
### Keyboard Shortcuts
- **Space**: Play/pause
- **→**: Jump forward 10 seconds
- **←**: Jump backward 10 seconds
- **Home**: Jump to start
- **End**: Jump to end
---
## Advanced Features
### Thrust Curves (Future)
Instead of constant thrust, can import actual thrust vs. time:
```javascript
{
type: 'thrustCurve',
data: [
{time: 0, thrust: 150000},
{time: 5, thrust: 145000},
{time: 60, thrust: 0}
]
}
```
Would enable:
- Throttling analysis (variable thrust)
- Solid rocket motor curves
- Multi-engine clustering
### Stage Separation
Currently single-stage only. Multi-stage would require:
- Stage definition (separate vehicle configs)
- Coast phase between stages
- Staging logic (when to ignite next stage)
- Updated mass calculations
### Wind Effects
Could add:
- Constant wind (direction, magnitude)
- Turbulence (atmospheric gusts)
- Wind shear (altitude-dependent)
- Affects trajectory & landing site
### Control Systems
Could add:
- Attitude feedback (pitch control gains)
- Stability margin (static/dynamic)
- Fin-based control
- Thrust vector control (TVC)
---
## Troubleshooting
### Trajectory looks wrong
- Check vehicle mass (should match design)
- Verify drag coefficient (0.25 ± 0.05 typical)
- Confirm reference area (cross-sectional)
- Ensure engine thrust profile is correct
### Apogee seems too low
- Verify engine thrust (check import)
- Check mass (try reducing payload)
- Ensure Isp is correct (affects delta-v)
- Try longer burn time or higher chamber pressure
### Simulation diverges (NaN values)
- Reduce timestep dt (smaller step size)
- Check for negative mass (propellant consumed)
- Verify atmosphere model (no singularities)
- Ensure velocity doesn't exceed sound barrier (should be OK)
### Playback animation slow
- Reduce speed (don't use 10×)
- Close other browser tabs
- Lower resolution (use simpler plot)
- Reduce state vector size (use larger dt)
---
## References
- Beard, R. W., & McLain, T. W. (2012). Small unmanned aircraft: Theory and practice. Princeton University Press.
- Vallado, D. A., Crawford, P., Hujsa, R., & Kelso, T. S. (2006). Revisiting spacetrack report #3. AIAA/AAS Astrodynamics Specialist Conference.
- US Standard Atmosphere 1976. NASA TM-X-74335
---
**Last Updated**: 2025-02 | **Status**: Current (v1)

View File

@@ -3,6 +3,7 @@ import Home from './pages/Home.jsx'
import Solver from './pages/Solver.jsx' import Solver from './pages/Solver.jsx'
import EnginePage from './pages/EnginePage.jsx' import EnginePage from './pages/EnginePage.jsx'
import RocketPage from './pages/RocketPage.jsx' import RocketPage from './pages/RocketPage.jsx'
import TrajectoryPage from './pages/TrajectoryPage.jsx'
import KnowledgebaseFuelsPage from './pages/KnowledgebaseFuelsPage.jsx' import KnowledgebaseFuelsPage from './pages/KnowledgebaseFuelsPage.jsx'
import KnowledgebaseEquationsPage from './pages/KnowledgebaseEquationsPage.jsx' import KnowledgebaseEquationsPage from './pages/KnowledgebaseEquationsPage.jsx'
import KnowledgebaseAblativesPage from './pages/KnowledgebaseAblativesPage.jsx' import KnowledgebaseAblativesPage from './pages/KnowledgebaseAblativesPage.jsx'
@@ -71,6 +72,18 @@ export default function App() {
> >
Rocket Rocket
</NavLink> </NavLink>
<NavLink
to="/design/trajectory"
className={({ isActive }) =>
`block px-4 py-2 text-sm transition-colors ${
isActive
? 'bg-slate-700 text-white'
: 'text-slate-300 hover:bg-slate-700 hover:text-white'
}`
}
>
Trajectory
</NavLink>
</div> </div>
</div> </div>
@@ -138,6 +151,7 @@ export default function App() {
<Route path="/solver" element={<Solver />} /> <Route path="/solver" element={<Solver />} />
<Route path="/design/engine" element={<EnginePage />} /> <Route path="/design/engine" element={<EnginePage />} />
<Route path="/design/rocket" element={<RocketPage />} /> <Route path="/design/rocket" element={<RocketPage />} />
<Route path="/design/trajectory" element={<TrajectoryPage />} />
<Route path="/knowledgebase/fuels" element={<KnowledgebaseFuelsPage />} /> <Route path="/knowledgebase/fuels" element={<KnowledgebaseFuelsPage />} />
<Route path="/knowledgebase/equations" element={<KnowledgebaseEquationsPage />} /> <Route path="/knowledgebase/equations" element={<KnowledgebaseEquationsPage />} />
<Route path="/knowledgebase/ablatives" element={<KnowledgebaseAblativesPage />} /> <Route path="/knowledgebase/ablatives" element={<KnowledgebaseAblativesPage />} />

View File

@@ -51,7 +51,10 @@ function TankSection({ yCenter, L_cyl, R, color, opacity = 1 }) {
[R], [R],
) )
const material = ( return (
<>
{/* Cylindrical body */}
<mesh geometry={cylGeo} position={[0, yCenter, 0]}>
<meshStandardMaterial <meshStandardMaterial
color={color} color={color}
metalness={0.45} metalness={0.45}
@@ -61,21 +64,30 @@ function TankSection({ yCenter, L_cyl, R, color, opacity = 1 }) {
opacity={opacity} opacity={opacity}
depthWrite={opacity >= 1} depthWrite={opacity >= 1}
/> />
)
return (
<>
{/* Cylindrical body */}
<mesh geometry={cylGeo} position={[0, yCenter, 0]}>
{material}
</mesh> </mesh>
{/* Top dome */} {/* Top dome (hemisphere pointing up) */}
<mesh geometry={topDomeGeo} position={[0, yCenter + L_cyl / 2 + R / 2, 0]}> <mesh geometry={topDomeGeo} position={[0, yCenter + L_cyl / 2, 0]}>
{material} <meshStandardMaterial
color={color}
metalness={0.45}
roughness={0.5}
side={THREE.DoubleSide}
transparent={opacity < 1}
opacity={opacity}
depthWrite={opacity >= 1}
/>
</mesh> </mesh>
{/* Bottom dome */} {/* Bottom dome (hemisphere pointing down) */}
<mesh geometry={botDomeGeo} position={[0, yCenter - L_cyl / 2 - R / 2, 0]}> <mesh geometry={botDomeGeo} position={[0, yCenter - L_cyl / 2, 0]}>
{material} <meshStandardMaterial
color={color}
metalness={0.45}
roughness={0.5}
side={THREE.DoubleSide}
transparent={opacity < 1}
opacity={opacity}
depthWrite={opacity >= 1}
/>
</mesh> </mesh>
</> </>
) )
@@ -129,7 +141,7 @@ function RocketShape({ geometry: geo }) {
const { const {
outerRadius: R, outerRadius: R,
L_nose, L_payload, L_tank, L_tank_fuel, L_tank_ox, L_engine, L_nose, L_payload, L_tank, L_tank_fuel, L_tank_ox, L_tank_fuel_cyl, L_tank_ox_cyl, L_engine,
totalLength, r_inner, arrangement, innerPropellant, noseProfilePoints, totalLength, r_inner, arrangement, innerPropellant, noseProfilePoints,
} = geo } = geo

View File

@@ -0,0 +1,141 @@
import { useState, useRef, useEffect } from 'react'
export function TimelineBar({
simulationResults,
currentTime,
onTimeChange,
isPlaying,
onPlayPause,
playbackSpeed,
onPlaybackSpeedChange,
}) {
const [showEventForm, setShowEventForm] = useState(false)
const [newEventTime, setNewEventTime] = useState('')
const [newEventLabel, setNewEventLabel] = useState('')
const [newEventType, setNewEventType] = useState('marker')
const timelineRef = useRef(null)
if (!simulationResults) {
return (
<div className="bg-slate-800 border-t border-slate-700 p-4 h-32">
<p className="text-slate-400 text-sm">Run simulation to see timeline</p>
</div>
)
}
const { states, events, summary } = simulationResults
const maxTime = summary.t_max
// Handle timeline scrub
const handleTimelineClick = (e) => {
if (!timelineRef.current) return
const rect = timelineRef.current.getBoundingClientRect()
const percent = (e.clientX - rect.left) / rect.width
const t = Math.max(0, Math.min(maxTime, percent * maxTime))
onTimeChange(t)
}
// Event colors
const eventColors = {
engine: '#ef4444',
guidance: '#f59e0b',
marker: '#8b5cf6',
jettison: '#06b6d4',
}
return (
<div className="bg-slate-800 border-t border-slate-700">
{/* Playback controls */}
<div className="flex items-center gap-3 px-4 py-3 border-b border-slate-700">
<button
onClick={() => onTimeChange(0)}
className="px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs text-slate-300"
title="Rewind"
>
</button>
<button
onClick={onPlayPause}
className={`px-3 py-1 rounded text-xs font-medium ${
isPlaying
? 'bg-red-600 hover:bg-red-500 text-white'
: 'bg-blue-600 hover:bg-blue-500 text-white'
}`}
>
{isPlaying ? '⏸ Pause' : '▶ Play'}
</button>
<div className="flex items-center gap-1">
<span className="text-xs text-slate-400">Speed:</span>
<select
value={playbackSpeed}
onChange={e => onPlaybackSpeedChange(parseFloat(e.target.value))}
className="px-2 py-0.5 bg-slate-700 border border-slate-600 rounded text-xs text-slate-300 focus:outline-none"
>
<option value={1}>1×</option>
<option value={5}>5×</option>
<option value={10}>10×</option>
</select>
</div>
<span className="text-sm text-slate-300 font-mono ml-auto">
t = {currentTime.toFixed(2)} s / {maxTime.toFixed(1)} s
</span>
</div>
{/* Timeline */}
<div className="relative px-4 py-4">
<div
ref={timelineRef}
onClick={handleTimelineClick}
className="relative h-12 bg-slate-900 border border-slate-700 rounded cursor-pointer hover:bg-slate-800 transition-colors"
>
{/* Event markers */}
{events.map(event => {
const percent = (event.t / maxTime) * 100
return (
<div
key={event.id}
style={{
left: `${percent}%`,
backgroundColor: event.color || eventColors[event.type] || '#64748b',
}}
className="absolute top-0 bottom-0 w-1 rounded-t hover:brightness-150"
title={`${event.label} @ t=${event.t.toFixed(2)}s`}
>
{/* Label above */}
<div className="absolute bottom-full mb-1 left-1/2 transform -translate-x-1/2 bg-slate-700 border border-slate-600 rounded px-2 py-1 text-xs text-white whitespace-nowrap opacity-0 hover:opacity-100 transition-opacity z-10">
{event.label}
</div>
</div>
)
})}
{/* Playhead */}
<div
style={{ left: `${(currentTime / maxTime) * 100}%` }}
className="absolute top-0 bottom-0 w-0.5 bg-emerald-400 pointer-events-none"
/>
</div>
{/* Event list below */}
<div className="mt-3 text-xs space-y-1">
{events.slice(0, 5).map(event => (
<div key={event.id} className="flex items-center gap-2 text-slate-400">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: event.color || eventColors[event.type] || '#64748b' }}
/>
<span className="w-20 text-slate-500">{event.t.toFixed(1)}s</span>
<span>{event.label}</span>
</div>
))}
{events.length > 5 && (
<div className="text-slate-500">+{events.length - 5} more events</div>
)}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,203 @@
import { useRef, useEffect, useMemo, useState } from 'react'
export function TrajectoryPlot({ simulationResults, currentState, onTimeClick }) {
const canvasRef = useRef(null)
const containerRef = useRef(null)
const [canvasSize, setCanvasSize] = useState({ width: 800, height: 500 })
const plotData = useMemo(() => {
if (!simulationResults || !simulationResults.states.length) return null
const states = simulationResults.states
const xs = states.map(s => s.x)
const ys = states.map(s => s.y)
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = 0
const maxY = Math.max(...ys)
// 10% padding
const padX = (maxX - minX) * 0.1 || 1000
const padY = maxY * 0.1 || 1000
return {
states,
bounds: {
x: [minX - padX, maxX + padX],
y: [minY, maxY + padY],
},
}
}, [simulationResults])
// Handle canvas resize
useEffect(() => {
const handleResize = () => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect()
setCanvasSize({ width: rect.width || 800, height: rect.height || 500 })
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// Handle canvas click for scrubbing
const handleCanvasClick = (e) => {
if (!canvasRef.current || !plotData) return
const canvas = canvasRef.current
const rect = canvas.getBoundingClientRect()
const clickX = e.clientX - rect.left
const clickY = e.clientY - rect.top
// Convert pixel to data coordinates
const { x: xBounds, y: yBounds } = plotData.bounds
const xRange = xBounds[1] - xBounds[0]
const yRange = yBounds[1] - yBounds[0]
const dataX = xBounds[0] + (clickX / canvas.width) * xRange
const dataY = yBounds[1] - (clickY / canvas.height) * yRange
// Find closest state by x position
let closestState = plotData.states[0]
let minDist = Infinity
for (const state of plotData.states) {
const dist = Math.abs(state.x - dataX)
if (dist < minDist) {
minDist = dist
closestState = state
}
}
onTimeClick?.(closestState.t)
}
useEffect(() => {
if (!canvasRef.current || !plotData) return
const canvas = canvasRef.current
const ctx = canvas.getContext('2d')
// Clear
ctx.fillStyle = '#0f172a'
ctx.fillRect(0, 0, canvas.width, canvas.height)
const { x: xBounds, y: yBounds } = plotData.bounds
const xRange = xBounds[1] - xBounds[0]
const yRange = yBounds[1] - yBounds[0]
// Utility to convert data to canvas coords
const toCanvasX = (x) => ((x - xBounds[0]) / xRange) * canvas.width
const toCanvasY = (y) => canvas.height - ((y - yBounds[0]) / yRange) * canvas.height
// Draw grid
ctx.strokeStyle = '#1e293b'
ctx.lineWidth = 0.5
ctx.font = '12px monospace'
ctx.fillStyle = '#64748b'
// Vertical grid lines (downrange)
const xStep = (xRange / 1000) * 100 // ~10 lines
for (let x = Math.ceil(xBounds[0] / xStep) * xStep; x < xBounds[1]; x += xStep) {
const cx = toCanvasX(x)
ctx.beginPath()
ctx.moveTo(cx, 0)
ctx.lineTo(cx, canvas.height)
ctx.stroke()
if (x % (xStep * 2) === 0) {
ctx.fillText(`${(x / 1000).toFixed(0)} km`, cx + 5, canvas.height - 10)
}
}
// Horizontal grid lines (altitude)
const yStep = (yRange / 1000) * 100 // ~10 lines
for (let y = Math.ceil(yBounds[0] / yStep) * yStep; y < yBounds[1]; y += yStep) {
const cy = toCanvasY(y)
ctx.beginPath()
ctx.moveTo(0, cy)
ctx.lineTo(canvas.width, cy)
ctx.stroke()
if (y % (yStep * 2) === 0) {
ctx.fillText(`${(y / 1000).toFixed(0)} km`, 10, cy - 5)
}
}
// Draw trajectory path
ctx.strokeStyle = '#3b82f6'
ctx.lineWidth = 2
ctx.beginPath()
for (let i = 0; i < plotData.states.length; i++) {
const s = plotData.states[i]
const cx = toCanvasX(s.x)
const cy = toCanvasY(s.y)
if (i === 0) {
ctx.moveTo(cx, cy)
} else {
ctx.lineTo(cx, cy)
}
}
ctx.stroke()
// Draw current position marker (if available)
if (currentState) {
const cx = toCanvasX(currentState.x)
const cy = toCanvasY(currentState.y)
// Outer ring
ctx.strokeStyle = '#10b981'
ctx.lineWidth = 2
ctx.beginPath()
ctx.arc(cx, cy, 8, 0, Math.PI * 2)
ctx.stroke()
// Inner circle
ctx.fillStyle = '#10b981'
ctx.beginPath()
ctx.arc(cx, cy, 4, 0, Math.PI * 2)
ctx.fill()
}
// Draw axes
ctx.strokeStyle = '#475569'
ctx.lineWidth = 2
const origin_x = toCanvasX(0)
const origin_y = toCanvasY(0)
ctx.beginPath()
ctx.moveTo(origin_x, canvas.height)
ctx.lineTo(origin_x, 0)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(0, origin_y)
ctx.lineTo(canvas.width, origin_y)
ctx.stroke()
// Labels
ctx.fillStyle = '#cbd5e1'
ctx.font = 'bold 14px sans-serif'
ctx.fillText('Downrange (m)', canvas.width - 200, canvas.height - 20)
ctx.save()
ctx.translate(20, 100)
ctx.rotate(-Math.PI / 2)
ctx.fillText('Altitude (m)', 0, 0)
ctx.restore()
}, [plotData, currentState])
return (
<div ref={containerRef} className="w-full h-full">
<canvas
ref={canvasRef}
width={canvasSize.width}
height={canvasSize.height}
onClick={handleCanvasClick}
className="w-full h-full border border-slate-700 bg-slate-950 cursor-crosshair rounded block"
/>
</div>
)
}

View File

@@ -195,7 +195,7 @@ export function calcFeedSystem(thermo, feedSystem, burnTime) {
if (type === 'pressure_fed') { if (type === 'pressure_fed') {
const p_tank = p0 * (feedFactor || 1.3) const p_tank = p0 * (feedFactor || 1.3)
const R_press = pressurantR || 2077 // J/(kg·K) — helium const R_press = pressurantR || 2077 // J/(kg·K) — helium
const T_press = pressurantT || 300 // K — ambient temperature const T_press = pressurantT || 293 // K — standard conditions (20°C)
const m_press = (p_tank * V_prop) / (R_press * T_press) const m_press = (p_tank * V_prop) / (R_press * T_press)
return { type, p_tank, V_fuel, V_ox, V_prop, m_press } return { type, p_tank, V_fuel, V_ox, V_prop, m_press }
} }
@@ -235,8 +235,8 @@ export function calcStructure(thermo, chamberGeom, nozzleGeom, structure) {
const t_chamber = (p0 * rc) / allowableStress const t_chamber = (p0 * rc) / allowableStress
const t_throat = (p0 * rt) / allowableStress const t_throat = (p0 * rt) / allowableStress
// Exit nozzle: assume exit pressure is much lower, use 0.1 * p0 as estimate // Exit nozzle: assume exit pressure ≈ 0.1 MPa (ambient for sea-level optimized nozzle)
const pe = p0 * 0.1 const pe = 0.1e6
const t_nozzle_exit = (pe * re) / allowableStress const t_nozzle_exit = (pe * re) / allowableStress
// Mass estimation: thin cylindrical/frustum shells + injector plate // Mass estimation: thin cylindrical/frustum shells + injector plate

View File

@@ -496,7 +496,7 @@ export const COMBINATIONS = [
flameTemp: 3250, flameTemp: 3250,
optimalOF: 6.0, optimalOF: 6.0,
gamma: 1.21, gamma: 1.21,
R: 934, R: 760,
chamberPressureRef: '6.9 MPa', chamberPressureRef: '6.9 MPa',
expansionRatioRef: 40, expansionRatioRef: 40,
energeticCategory: 'Cryogenic Bipropellant', energeticCategory: 'Cryogenic Bipropellant',
@@ -800,7 +800,7 @@ export const ABLATIVE_MATERIALS = [
erosionRateRange: [0.05, 0.20], erosionRateRange: [0.05, 0.20],
maxTemp: 3600, maxTemp: 3600,
density: 1450, density: 1450,
thermalConductivity: 8.0, thermalConductivity: 3.5,
charYield: 0.58, charYield: 0.58,
pressureExponent: 0.33, pressureExponent: 0.33,
applications: ['nozzle throat (high heat flux)', 'combustion chamber'], applications: ['nozzle throat (high heat flux)', 'combustion chamber'],
@@ -815,13 +815,13 @@ export const ABLATIVE_MATERIALS = [
'EPDM is a flexible elastomeric ablator suitable for low-pressure engines and non-critical applications. It provides good insulation properties and flexibility, reducing structural loads. Inexpensive and easy to manufacture in various geometries.', 'EPDM is a flexible elastomeric ablator suitable for low-pressure engines and non-critical applications. It provides good insulation properties and flexibility, reducing structural loads. Inexpensive and easy to manufacture in various geometries.',
erosionRate: 0.50, erosionRate: 0.50,
erosionRateRange: [0.30, 0.80], erosionRateRange: [0.30, 0.80],
maxTemp: 2200, maxTemp: 700,
density: 920, density: 920,
thermalConductivity: 0.25, thermalConductivity: 0.25,
charYield: 0.40, charYield: 0.40,
pressureExponent: 0.50, pressureExponent: 0.50,
applications: ['low-pressure chamber', 'insulation layer'], applications: ['low-pressure chamber', 'insulation layer'],
notes: 'Low cost and simple manufacturing. Limited to low-pressure (<1 MPa) applications due to high erosion rates.', notes: 'Low cost and simple manufacturing. Limited to low-pressure (<1 MPa) applications due to high erosion rates. Virgin material decomposes above ~700 K.',
refPressure: 0.5e6, refPressure: 0.5e6,
}, },
{ {
@@ -867,8 +867,8 @@ export const ABLATIVE_MATERIALS = [
erosionRate: 0.06, erosionRate: 0.06,
erosionRateRange: [0.03, 0.12], erosionRateRange: [0.03, 0.12],
maxTemp: 3800, maxTemp: 3800,
density: 1500, density: 270,
thermalConductivity: 5.5, thermalConductivity: 0.25,
charYield: 0.60, charYield: 0.60,
pressureExponent: 0.30, pressureExponent: 0.30,
applications: ['nozzle throat (ultra-high performance)', 'chamber (extreme conditions)'], applications: ['nozzle throat (ultra-high performance)', 'chamber (extreme conditions)'],
@@ -884,5 +884,5 @@ export const STRUCTURAL_MATERIALS = [
{ id: 'ss_304', name: 'Stainless Steel 304', yieldStrength: 215e6, density: 7900, youngModulus: 193e9 }, { id: 'ss_304', name: 'Stainless Steel 304', yieldStrength: 215e6, density: 7900, youngModulus: 193e9 },
{ id: 'inconel_718', name: 'Inconel 718', yieldStrength: 1100e6, density: 8190, youngModulus: 200e9 }, { id: 'inconel_718', name: 'Inconel 718', yieldStrength: 1100e6, density: 8190, youngModulus: 200e9 },
{ id: 'ti_6al_4v', name: 'Titanium Ti-6Al-4V', yieldStrength: 880e6, density: 4430, youngModulus: 114e9 }, { id: 'ti_6al_4v', name: 'Titanium Ti-6Al-4V', yieldStrength: 880e6, density: 4430, youngModulus: 114e9 },
{ id: 'cfrp', name: 'CFRP (typical)', yieldStrength: 600e6, density: 1600, youngModulus: 70e9 }, { id: 'cfrp', name: 'CFRP (typical)', yieldStrength: 450e6, density: 1600, youngModulus: 70e9 },
] ]

View File

@@ -142,7 +142,7 @@ export function calcRocketGeometry(engineData, rocketInputs) {
// Tank pressure (Pa) // Tank pressure (Pa)
const p_tank = feedSystem === 'pump_fed' ? 2e6 : p0 * 1.2 const p_tank = feedSystem === 'pump_fed' ? 2e6 : p0 * 1.2
// Hoop stress wall thickness (same for all tanks at same pressure and radius) // Hoop stress wall thickness for outer tank at radius R
t_wall = (p_tank * R) / (material.yieldStrength / SF) t_wall = (p_tank * R) / (material.yieldStrength / SF)
if (arrangement === 'coaxial') { if (arrangement === 'coaxial') {
@@ -158,9 +158,13 @@ export function calcRocketGeometry(engineData, rocketInputs) {
const V_eff_outer = V_outer * (1 + ullage / 100) const V_eff_outer = V_outer * (1 + ullage / 100)
const L_tank_cyl_outer = Math.max(0, (V_eff_outer - V_dome_outer) / (Math.PI * R * R)) const L_tank_cyl_outer = Math.max(0, (V_eff_outer - V_dome_outer) / (Math.PI * R * R))
// Inner annular tank (no domes): full volume in cylindrical section // Inner annular tank (no domes): apply ullage and full volume in cylindrical section
const V_eff_inner = V_inner * (1 + ullage / 100)
const A_annulus = Math.PI * (R * R - r_inner * r_inner) const A_annulus = Math.PI * (R * R - r_inner * r_inner)
const L_tank_cyl_inner = V_inner / A_annulus const L_tank_cyl_inner = V_eff_inner / A_annulus
// Separate wall thickness for inner tank at inner radius
const t_wall_inner = (p_tank * r_inner) / (material.yieldStrength / SF)
// Total outer tank length // Total outer tank length
const L_tank_outer = L_tank_cyl_outer + 2 * R const L_tank_outer = L_tank_cyl_outer + 2 * R
@@ -179,7 +183,7 @@ export function calcRocketGeometry(engineData, rocketInputs) {
const m_dome_outer = 4 * Math.PI * R * R * t_wall * material.density const m_dome_outer = 4 * Math.PI * R * R * t_wall * material.density
const m_tank_outer = m_cyl_outer + m_dome_outer const m_tank_outer = m_cyl_outer + m_dome_outer
const m_cyl_inner = 2 * Math.PI * r_inner * t_wall * L_tank_cyl_inner * material.density const m_cyl_inner = 2 * Math.PI * r_inner * t_wall_inner * L_tank_cyl_inner * material.density
const m_tank_inner = m_cyl_inner // Inner annular tank, no domes const m_tank_inner = m_cyl_inner // Inner annular tank, no domes
m_tank_total = m_tank_outer + m_tank_inner m_tank_total = m_tank_outer + m_tank_inner
@@ -211,7 +215,9 @@ export function calcRocketGeometry(engineData, rocketInputs) {
// ── Pressurant system (pressure-fed only) ───────────────────────────── // ── Pressurant system (pressure-fed only) ─────────────────────────────
let m_pressurant = 0 let m_pressurant = 0
if (feedSystem === 'pressure_fed') { if (feedSystem === 'pressure_fed') {
const m_He = (p_tank * V_prop) / (R_HE * T_PRESSURANT) // Pressurant must fill both propellant and ullage space
const V_eff = V_prop * (1 + ullage / 100)
const m_He = (p_tank * V_eff) / (R_HE * T_PRESSURANT)
const m_bottle = m_He * 4 // Conservative bottle mass estimate const m_bottle = m_He * 4 // Conservative bottle mass estimate
m_pressurant = m_He + m_bottle m_pressurant = m_He + m_bottle
} }

View File

@@ -0,0 +1,346 @@
/**
* Trajectory simulation using 4th-order Runge-Kutta integration.
* Physics: 3-DOF point-mass with thrust, drag, gravity.
*/
// US Standard Atmosphere (simplified piecewise linear in log-rho)
function atmosphereDensity(h) {
// h in meters
if (h < 0) h = 0
// Simplified US Standard Atmosphere
if (h <= 11000) {
// Troposphere: ρ = ρ0 * (1 - L*h/T0)^(-g0*M/(R*L))
// Approximate with exponential: ρ = ρ0 * exp(-h / H) where H ≈ 8500 m
const rho0 = 1.225 // kg/m³ at sea level
const H = 8500 // scale height in meters
return rho0 * Math.exp(-h / H)
} else if (h <= 20000) {
// Stratosphere (simplified)
const rho11k = 1.225 * Math.exp(-11000 / 8500)
return rho11k * Math.exp(-(h - 11000) / 6500)
} else {
// Higher altitude (even rarer)
const rho20k = 1.225 * Math.exp(-20000 / 8500) * Math.exp(-(9000) / 6500)
return rho20k * Math.exp(-(h - 20000) / 4500)
}
}
// Gravity with altitude correction
function gravity(y) {
const g0 = 9.80665
const R_earth = 6.371e6 // meters
return g0 * Math.pow(R_earth / (R_earth + y), 2)
}
// Compute pitch angle (gravity turn or from pitch program)
function computePitch(state, config, events, t) {
// Check if there's a pitch program event at or before time t
const pitchEvents = events.filter(e => e.type === 'guidance' && e.t <= t)
if (pitchEvents.length > 0) {
const latest = pitchEvents.reduce((a, b) => a.t > b.t ? a : b)
return latest.pitch_angle ?? 0
}
// Gravity turn: pitch follows velocity vector angle
const { y, vx, vy } = state
// Start vertical, then follow velocity vector
if (y < config.pitchStartAlt) {
return 90 // vertical
}
// Velocity angle from horizontal
const vMag = Math.hypot(vx, vy)
if (vMag < 0.1) return 90 // very slow, stay vertical
// pitch = 90 - elevation_angle, so pitch = arctan(vy/vx) in degrees
const elevAngle = Math.atan2(vy, vx) * 180 / Math.PI
return Math.max(0, elevAngle)
}
// Apply jettison events (mass drop)
function applyJettison(state, events, t) {
let m = state.m
const jettisonEvents = events.filter(e => e.type === 'jettison' && e.t <= t)
if (jettisonEvents.length > 0) {
const latest = jettisonEvents.reduce((a, b) => a.t > b.t ? a : b)
if (latest.mass_drop) {
m -= latest.mass_drop
}
}
return m
}
// Derivatives for RK4
function derivatives(state, config, events, t) {
let { x, y, vx, vy, m } = state
// Apply jettison if applicable
m = applyJettison(state, events, t)
// Atmosphere
const rho = atmosphereDensity(y)
// Velocity magnitude and direction
const vMag = Math.hypot(vx, vy)
// Gravity
const g = gravity(y)
// Thrust and burn
let F = 0
let mdot = 0
if (m > config.m_dry && config.t_burn_remaining > 0) {
F = config.F
mdot = config.mdot
config.t_burn_remaining -= config.dt // decrement locally (will be overridden in main loop)
}
// Pitch angle
const pitch = computePitch(state, config, events, t)
const pitchRad = pitch * Math.PI / 180
// Thrust direction (along pitch angle from horizontal)
const Fx = F * Math.cos(pitchRad)
const Fy = F * Math.sin(pitchRad)
// Drag force
const Cd = config.Cd
const A_ref = config.A_ref
const F_drag = 0.5 * rho * vMag * vMag * Cd * A_ref
let Fdrag_x = 0
let Fdrag_y = 0
if (vMag > 0.01) {
Fdrag_x = -(F_drag * vx / vMag)
Fdrag_y = -(F_drag * vy / vMag)
}
// Acceleration
const ax = (Fx + Fdrag_x) / m
const ay = (Fy + Fdrag_y) / m - g
return {
dx: vx,
dy: vy,
dvx: ax,
dvy: ay,
dm: -mdot,
}
}
// RK4 step
function rk4Step(state, config, events, t, dt) {
const k1 = derivatives(state, config, events, t)
const state2 = {
x: state.x + k1.dx * dt / 2,
y: state.y + k1.dy * dt / 2,
vx: state.vx + k1.dvx * dt / 2,
vy: state.vy + k1.dvy * dt / 2,
m: state.m + k1.dm * dt / 2,
}
const k2 = derivatives(state2, config, events, t + dt / 2)
const state3 = {
x: state.x + k2.dx * dt / 2,
y: state.y + k2.dy * dt / 2,
vx: state.vx + k2.dvx * dt / 2,
vy: state.vy + k2.dvy * dt / 2,
m: state.m + k2.dm * dt / 2,
}
const k3 = derivatives(state3, config, events, t + dt / 2)
const state4 = {
x: state.x + k3.dx * dt,
y: state.y + k3.dy * dt,
vx: state.vx + k3.dvx * dt,
vy: state.vy + k3.dvy * dt,
m: state.m + k3.dm * dt,
}
const k4 = derivatives(state4, config, events, t + dt)
return {
x: state.x + (k1.dx + 2*k2.dx + 2*k3.dx + k4.dx) * dt / 6,
y: state.y + (k1.dy + 2*k2.dy + 2*k3.dy + k4.dy) * dt / 6,
vx: state.vx + (k1.dvx + 2*k2.dvx + 2*k3.dvx + k4.dvx) * dt / 6,
vy: state.vy + (k1.dvy + 2*k2.dvy + 2*k3.dvy + k4.dvy) * dt / 6,
m: Math.max(state.m + (k1.dm + 2*k2.dm + 2*k3.dm + k4.dm) * dt / 6, config.m_dry),
}
}
/**
* Run trajectory simulation.
* Returns { states, events, summary }
*/
export function runSimulation(config, userEvents = []) {
const {
F, Isp, mdot, m_wet, m_dry, burnTime,
Cd, A_ref,
pitchStartAlt, launchAngle,
dt, t_max,
} = config
const g0 = 9.80665
// Initial state
let state = {
x: 0,
y: 0,
vx: 0,
vy: 0,
m: m_wet,
}
// Launch angle (degrees) → velocity components
const launchRad = (launchAngle || 90) * Math.PI / 180
// Configuration for derivatives
const simConfig = {
F, mdot, m_dry, Cd, A_ref,
pitchStartAlt: pitchStartAlt || 100,
dt: dt || 0.05,
t_burn_remaining: burnTime || 30,
}
// Results
const states = []
let t = 0
let meco_t = null
let apogee_t = null
let apogee_y = 0
let max_q = 0
let max_q_t = 0
let vy_last = 0
// Add initial state
states.push({
t: 0,
x: state.x,
y: state.y,
vx: state.vx,
vy: state.vy,
m: state.m,
phase: 'standby',
q: 0,
})
// Main RK4 loop
while (t < t_max && state.y >= 0) {
// Determine phase
let phase = 'coast'
if (state.m > m_dry + 0.1 && simConfig.t_burn_remaining > 0) {
phase = 'burn'
simConfig.t_burn_remaining -= dt
if (simConfig.t_burn_remaining <= 0) {
meco_t = t
simConfig.t_burn_remaining = 0
}
}
// RK4 integration step
state = rk4Step(state, simConfig, userEvents, t, dt)
t += dt
// Dynamic pressure
const rho = atmosphereDensity(state.y)
const v_mag = Math.hypot(state.vx, state.vy)
const q = 0.5 * rho * v_mag * v_mag
if (q > max_q) {
max_q = q
max_q_t = t
}
// Check for apogee (vy crosses zero)
if (vy_last > 0 && state.vy <= 0 && apogee_t === null) {
apogee_t = t
apogee_y = state.y
}
vy_last = state.vy
// Record state
states.push({
t: Math.round(t * 1000) / 1000, // round to 3 decimals
x: state.x,
y: Math.max(0, state.y),
vx: state.vx,
vy: state.vy,
m: state.m,
phase,
q,
})
}
// Landing
const landing_t = t
const landing_y = 0
// Auto-detect events
const autoEvents = []
autoEvents.push({
id: `event_liftoff`,
t: 0.1,
label: 'Liftoff',
type: 'engine',
color: '#10b981',
})
if (meco_t !== null) {
autoEvents.push({
id: `event_meco`,
t: meco_t,
label: 'MECO',
type: 'engine',
color: '#ef4444',
})
}
if (max_q_t > 0) {
autoEvents.push({
id: `event_maxq`,
t: max_q_t,
label: 'Max Q',
type: 'guidance',
color: '#f59e0b',
})
}
if (apogee_t !== null) {
autoEvents.push({
id: `event_apogee`,
t: apogee_t,
label: 'Apogee',
type: 'marker',
color: '#3b82f6',
})
}
autoEvents.push({
id: `event_landing`,
t: landing_t,
label: 'Landing',
type: 'marker',
color: '#8b5cf6',
})
// Merge user and auto events
const allEvents = [...userEvents, ...autoEvents].sort((a, b) => a.t - b.t)
return {
states,
events: allEvents,
summary: {
t_max: landing_t,
apogee: apogee_y,
apogee_t,
meco_t,
max_q,
max_q_t,
downrange: state.x,
final_velocity: Math.hypot(state.vx, state.vy),
},
}
}

128
src/hooks/useTrajectory.js Normal file
View File

@@ -0,0 +1,128 @@
import { useState, useMemo, useCallback, useEffect } from 'react'
import { runSimulation } from '../engine/trajectoryCalcs.js'
export function useTrajectory() {
// Simulation parameters
const [config, setConfig] = useState({
F: 5000, // Newtons
Isp: 250, // seconds
mdot: 2, // kg/s
m_wet: 100, // kg
m_dry: 20, // kg
burnTime: 40, // seconds
Cd: 0.3,
A_ref: 0.05, // m²
pitchStartAlt: 100, // meters
launchAngle: 90, // degrees
dt: 0.05, // timestep (s)
t_max: 600, // max simulation time (s)
})
// User-defined events
const [userEvents, setUserEvents] = useState([])
// Simulation results
const [simulationResults, setSimulationResults] = useState(null)
const [error, setError] = useState(null)
// Playback state
const [isPlaying, setIsPlaying] = useState(false)
const [playbackSpeed, setPlaybackSpeed] = useState(1) // 1x, 5x, 10x
const [currentTime, setCurrentTime] = useState(0)
const [currentIndex, setCurrentIndex] = useState(0)
// Update a config value
const updateConfig = useCallback((key, value) => {
setConfig(prev => ({
...prev,
[key]: value,
}))
}, [])
// Run simulation
const runSim = useCallback(() => {
try {
setError(null)
const results = runSimulation(config, userEvents)
setSimulationResults(results)
setCurrentTime(0)
setCurrentIndex(0)
setIsPlaying(false)
} catch (err) {
setError(err.message)
}
}, [config, userEvents])
// Get current state (for playhead position)
const currentState = useMemo(() => {
if (!simulationResults || simulationResults.states.length === 0) return null
return simulationResults.states[currentIndex] || simulationResults.states[0]
}, [simulationResults, currentIndex])
// Playback loop (update time on interval)
useEffect(() => {
if (!isPlaying || !simulationResults) return
const interval = setInterval(() => {
setCurrentTime(prev => {
const maxTime = simulationResults.summary.t_max
const increment = 0.016 * playbackSpeed // ~60 fps * playback speed
let next = prev + increment
if (next >= maxTime) {
setIsPlaying(false)
return maxTime
}
return next
})
}, 16) // ~60 fps
return () => clearInterval(interval)
}, [isPlaying, simulationResults, playbackSpeed])
// Update currentIndex based on currentTime
useEffect(() => {
if (!simulationResults) return
const states = simulationResults.states
let idx = 0
for (let i = 0; i < states.length; i++) {
if (states[i].t <= currentTime) {
idx = i
} else {
break
}
}
setCurrentIndex(idx)
}, [currentTime, simulationResults])
// Add/remove user events
const addEvent = useCallback((event) => {
setUserEvents(prev => [...prev, event])
}, [])
const removeEvent = useCallback((eventId) => {
setUserEvents(prev => prev.filter(e => e.id !== eventId))
}, [])
const updateEvent = useCallback((eventId, updates) => {
setUserEvents(prev => prev.map(e => e.id === eventId ? { ...e, ...updates } : e))
}, [])
return {
config,
updateConfig,
userEvents,
addEvent,
removeEvent,
updateEvent,
simulationResults,
error,
runSim,
isPlaying,
setIsPlaying,
playbackSpeed,
setPlaybackSpeed,
currentTime,
setCurrentTime,
currentState,
}
}

View File

@@ -7,6 +7,7 @@ const SECTIONS = [
'Solver', 'Solver',
'Engine Designer', 'Engine Designer',
'Rocket Designer', 'Rocket Designer',
'Trajectory Simulator',
'Knowledgebase', 'Knowledgebase',
'Variables Reference', 'Variables Reference',
] ]
@@ -234,6 +235,232 @@ export default function DocsPage() {
</div> </div>
</section> </section>
{/* Trajectory Simulator */}
<section id="Trajectory Simulator" className="space-y-4">
<h2 className="text-3xl font-bold text-white">Trajectory Simulator</h2>
<p className="text-slate-300">
Simulate realistic rocket flight trajectories using numerical integration with atmospheric and aerodynamic physics.
</p>
<div className="space-y-4 mt-4">
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Overview</h3>
<p className="text-slate-300 mb-3">
The Trajectory Simulator integrates engine design and rocket parameters to predict flight paths. It:
</p>
<ul className="space-y-2 text-slate-300 list-disc list-inside">
<li>Solves the 3-DOF equations of motion in 2D (altitude × downrange)</li>
<li>Includes thrust, atmospheric drag, and gravity (with altitude correction)</li>
<li>Uses real atmosphere model (US Standard Atmosphere)</li>
<li>Tracks vehicle mass during propellant burn</li>
<li>Detects key flight events (liftoff, MECO, apogee, landing)</li>
<li>Enables interactive playback with timeline scrubbing</li>
</ul>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Physics Model</h3>
<p className="text-slate-300 mb-4">The simulator uses 4th-order Runge-Kutta (RK4) integration with the following state vector:</p>
<div className="bg-slate-950 rounded p-4 font-mono text-amber-300 text-sm mb-4">
state = [x, y, vx, vy, m]
</div>
<p className="text-slate-300 mb-3">where:</p>
<ul className="space-y-1 text-slate-300 text-sm">
<li><span className="font-mono text-amber-300">x</span> = downrange distance (m)</li>
<li><span className="font-mono text-amber-300">y</span> = altitude above sea level (m)</li>
<li><span className="font-mono text-amber-300">vx, vy</span> = velocity components (m/s)</li>
<li><span className="font-mono text-amber-300">m</span> = vehicle mass (kg)</li>
</ul>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Forces</h3>
<p className="text-slate-300 mb-3">Three forces act on the vehicle:</p>
<div className="mb-4">
<p className="text-slate-300 font-semibold mb-2">1. Thrust</p>
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
F<sub>thrust</sub> = F × (cos θ, sin θ)
</div>
<p className="text-slate-300 text-sm">
where <span className="font-mono text-amber-300">F</span> is engine thrust and <span className="font-mono text-amber-300">θ</span> is pitch angle (0° horizontal, 90° vertical).
</p>
</div>
<div className="mb-4">
<p className="text-slate-300 font-semibold mb-2">2. Drag</p>
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
F<sub>drag</sub> = -0.5 × ρ(y) ×× C<sub>d</sub> × A<sub>ref</sub> × (v̂)
</div>
<p className="text-slate-300 text-sm">
Atmospheric density ρ varies with altitude; drag opposes the velocity vector.
</p>
</div>
<div>
<p className="text-slate-300 font-semibold mb-2">3. Gravity (Altitude-Corrected)</p>
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-2">
g(y) = g₀ × (R<sub>E</sub> / (R<sub>E</sub> + y))²
</div>
<p className="text-slate-300 text-sm">
where g₀ = 9.80665 m/s² and R<sub>E</sub> = 6,371 km (Earth radius).
</p>
</div>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Atmosphere Model</h3>
<p className="text-slate-300 mb-3">
Density vs. altitude follows a piecewise US Standard Atmosphere:
</p>
<ul className="space-y-2 text-slate-300 text-sm">
<li><strong>Troposphere (011 km):</strong> Scale height H = 8,500 m → ρ = ρ₀ × exp(h/H)</li>
<li><strong>Stratosphere (1120 km):</strong> H = 6,500 m</li>
<li><strong>Higher altitude:</strong> H = 4,500 m (simplified)</li>
</ul>
<p className="text-slate-300 text-sm mt-3">
Sea-level density ρ₀ = 1.225 kg/m³. This provides realistic dynamic pressure (Q) changes during ascent.
</p>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Pitch Program (Guidance)</h3>
<p className="text-slate-300 mb-3">
By default, the vehicle follows a gravity turn:
</p>
<ul className="space-y-2 text-slate-300 text-sm">
<li><strong>Initial phase:</strong> Vertical hold (90°) until reaching "Pitch Start Altitude"</li>
<li><strong>Gravity turn:</strong> Pitch angle equals the velocity vector angle (no additional steering)</li>
<li><strong>User events:</strong> Override pitch at specific times via guidance events (advanced)</li>
</ul>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Mass Depletion</h3>
<p className="text-slate-300 mb-3">
During engine burn:
</p>
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm mb-3">
dm/dt = −ṁ
</div>
<p className="text-slate-300 text-sm">
where ṁ is mass flow rate (mdot). The engine shuts off when m ≤ m_dry (dry mass) or burn duration elapsed.
</p>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Integration Method: RK4</h3>
<p className="text-slate-300 mb-3">
4th-order Runge-Kutta (RK4) provides excellent accuracy with a fixed timestep:
</p>
<div className="bg-slate-950 rounded p-3 font-mono text-amber-300 text-sm text-xs mb-3 overflow-x-auto">
state(t+dt) = state(t) + (dt/6) × (k₁ + 2k₂ + 2k₃ + k₄)
</div>
<p className="text-slate-300 text-sm">
Default timestep: 0.05 s (produces ~12,000 steps for a typical 600 s flight).
</p>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Input Parameters</h3>
<p className="text-slate-300 mb-3">Configure the simulation with:</p>
<div className="space-y-3 text-slate-300 text-sm">
<div className="bg-slate-950 rounded p-3">
<p className="font-semibold text-white">Engine Parameters</p>
<ul className="mt-2 space-y-1">
<li>Thrust <span className="font-mono text-amber-300">F</span> (N)</li>
<li>Specific Impulse <span className="font-mono text-amber-300">Isp</span> (s) — reference only in this tool</li>
<li>Mass Flow Rate <span className="font-mono text-amber-300">ṁ</span> (kg/s)</li>
<li>Wet Mass <span className="font-mono text-amber-300">m_wet</span>, Dry Mass <span className="font-mono text-amber-300">m_dry</span> (kg)</li>
<li>Burn Time (s)</li>
</ul>
</div>
<div className="bg-slate-950 rounded p-3">
<p className="font-semibold text-white">Aerodynamics</p>
<ul className="mt-2 space-y-1">
<li>Drag Coefficient <span className="font-mono text-amber-300">C<sub>d</sub></span> (default 0.3)</li>
<li>Reference Area <span className="font-mono text-amber-300">A_ref</span> (m²) — cross-sectional area</li>
</ul>
</div>
<div className="bg-slate-950 rounded p-3">
<p className="font-semibold text-white">Flight Profile</p>
<ul className="mt-2 space-y-1">
<li>Pitch Start Altitude (m) — when to begin gravity turn</li>
<li>Launch Angle (°) — typically 90° for vertical launch</li>
<li>Timestep <span className="font-mono text-amber-300">dt</span> (s) — RK4 step size</li>
<li>Max Simulation Time (s) — cutoff to prevent infinite loops</li>
</ul>
</div>
</div>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Auto-Detected Events</h3>
<p className="text-slate-300 mb-3">
The simulator automatically identifies key mission milestones:
</p>
<ul className="space-y-2 text-slate-300 text-sm">
<li><strong>Liftoff:</strong> t ≈ 0.1 s</li>
<li><strong>MECO:</strong> Main Engine Cutoff, when m ≤ m_dry</li>
<li><strong>Max Q:</strong> Peak dynamic pressure (0.5 × ρ × v²)</li>
<li><strong>Apogee:</strong> Maximum altitude (where vy crosses zero)</li>
<li><strong>Landing:</strong> Final impact at y = 0</li>
</ul>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Results & Visualization</h3>
<p className="text-slate-300 mb-3">
After simulation runs:
</p>
<ul className="space-y-2 text-slate-300 text-sm list-disc list-inside">
<li><strong>Altitude vs. Downrange Plot:</strong> 2D trajectory curve with auto-scaled axes</li>
<li><strong>Event Markers:</strong> Color-coded points (green=engine, red=MECO, amber=Max Q, blue=apogee, purple=landing)</li>
<li><strong>Timeline Scrubber:</strong> Interactive playback controls; click plot to seek</li>
<li><strong>Live Readout:</strong> Current altitude, velocity, mass, phase during playback</li>
</ul>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Workflow: Engine to Trajectory</h3>
<ol className="space-y-2 text-slate-300 text-sm list-decimal list-inside">
<li>Design an engine in <strong>/design/engine</strong></li>
<li>Export engine JSON</li>
<li>Go to <strong>/design/trajectory</strong></li>
<li>Click "Import Engine JSON" and select the exported file</li>
<li>Parameters auto-populate (thrust, Isp, mass flow, burn time, nozzle area)</li>
<li>Adjust aerodynamics (Cd, A_ref) and flight profile as needed</li>
<li>Click "Run Simulation"</li>
<li>Use Play/Pause, speed controls, and timeline scrubbing to explore the flight</li>
</ol>
</div>
<div className="bg-slate-900 rounded-xl border border-slate-700 p-6">
<h3 className="text-lg font-semibold text-white mb-3">Tips & Limitations</h3>
<div className="space-y-3">
<div>
<p className="text-slate-300 font-semibold mb-1">✓ Strengths</p>
<ul className="space-y-1 text-slate-300 text-sm list-disc list-inside">
<li>Fast RK4 integration (typical flight solves in &lt;100 ms)</li>
<li>Realistic atmosphere and gravity models</li>
<li>Good accuracy for point-mass trajectory (±5% apogee typical)</li>
</ul>
</div>
<div>
<p className="text-slate-300 font-semibold mb-1">⚠ Limitations</p>
<ul className="space-y-1 text-slate-300 text-sm list-disc list-inside">
<li><strong>Point-mass model:</strong> Ignores rotational dynamics, wind, spinning</li>
<li><strong>2D only:</strong> Assumes flat Earth in vertical plane (no coriolis, curvature)</li>
<li><strong>Constant Cd:</strong> Drag coefficient doesn't vary with Mach or angle of attack</li>
<li><strong>Simple pitch program:</strong> Gravity turn only; no attitude control or TVC</li>
<li><strong>Sea-level launch:</strong> Assumes pad at elevation 0</li>
</ul>
</div>
</div>
</div>
</div>
</section>
{/* Knowledgebase */} {/* Knowledgebase */}
<section id="Knowledgebase" className="space-y-4"> <section id="Knowledgebase" className="space-y-4">
<h2 className="text-3xl font-bold text-white">Knowledgebase</h2> <h2 className="text-3xl font-bold text-white">Knowledgebase</h2>

View File

@@ -0,0 +1,375 @@
import { useRef, useState } from 'react'
import { useTrajectory } from '../hooks/useTrajectory.js'
import { parseEngineImport, downloadBlob } from '../engine/engineExportImport.js'
import { TrajectoryPlot } from '../components/trajectory/TrajectoryPlot.jsx'
import { TimelineBar } from '../components/trajectory/TimelineBar.jsx'
import ErrorBoundary from '../components/ErrorBoundary.jsx'
import { formatValue } from '../engine/format.js'
function ResultRow({ label, value, unit }) {
const display = value !== null && value !== undefined && isFinite(value)
? formatValue(value)
: '—'
return (
<div className="flex justify-between items-baseline gap-2 text-sm">
<span className="text-slate-400">{label}</span>
<div className="flex items-baseline gap-1">
<span className={`font-mono ${display === '—' ? 'text-slate-600' : 'text-green-400'}`}>
{display}
</span>
{unit && <span className="text-slate-500 text-xs shrink-0">{unit}</span>}
</div>
</div>
)
}
function NumInput({ label, value, onChange, units, step = 1 }) {
return (
<div className="flex flex-col gap-1 mb-2">
<label className="text-xs text-slate-400">{label}</label>
<div className="flex items-center gap-1">
<input
type="number"
value={value ?? ''}
step={step}
onChange={e => onChange(parseFloat(e.target.value) || 0)}
className="flex-1 px-2 py-1 bg-slate-800 border border-slate-600 rounded text-sm text-slate-100
focus:border-blue-500 focus:outline-none"
/>
{units && <span className="text-xs text-slate-500 shrink-0">{units}</span>}
</div>
</div>
)
}
function SelectInput({ label, value, onChange, options }) {
return (
<div className="flex flex-col gap-1 mb-2">
<label className="text-xs text-slate-400">{label}</label>
<select
value={value}
onChange={e => onChange(e.target.value)}
className="w-full px-2 py-1 bg-slate-800 border border-slate-600 rounded text-sm text-slate-100
focus:border-blue-500 focus:outline-none"
>
{options.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</div>
)
}
function ResultSection({ title, children }) {
return (
<div className="mb-4">
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2 pb-1 border-b border-slate-800">
{title}
</h4>
<div className="space-y-1 text-sm">{children}</div>
</div>
)
}
export default function TrajectoryPage() {
const trajectory = useTrajectory()
const fileInputRef = useRef(null)
const [importError, setImportError] = useState(null)
// Import engine data from JSON
const handleImport = async (e) => {
const file = e.target.files?.[0]
if (!file) return
try {
setImportError(null)
const text = await file.text()
const data = JSON.parse(text)
// Handle engine design export
if (data.type === 'engine_design') {
const engineData = parseEngineImport(data)
if (engineData.thermoResults) {
const { F, Isp, mdot } = engineData.thermoResults
const burnTime = data.burnTime || 30
// Calculate m_dry and m_wet from propellant mass and burn time
const m_prop = mdot * burnTime
const m_dry = data.m_dry || 20
const m_wet = m_dry + m_prop
trajectory.updateConfig('F', F || 5000)
trajectory.updateConfig('Isp', Isp || 250)
trajectory.updateConfig('mdot', mdot || 2)
trajectory.updateConfig('m_wet', m_wet)
trajectory.updateConfig('m_dry', m_dry)
trajectory.updateConfig('burnTime', burnTime)
// Estimate A_ref from nozzle exit area if available
if (data.nozzleGeometry?.A_e) {
trajectory.updateConfig('A_ref', data.nozzleGeometry.A_e)
}
}
}
} catch (err) {
setImportError(err.message)
}
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}
const { config, simulationResults, error, currentState } = trajectory
return (
<ErrorBoundary>
<div className="flex-1 flex flex-col overflow-hidden">
{/* Main content area: left panel + center plot */}
<div className="flex-1 flex overflow-hidden gap-0">
{/* Left panel */}
<div className="w-80 bg-slate-900 border-r border-slate-700 overflow-y-auto p-4">
<h2 className="text-lg font-bold text-white mb-4">Trajectory Simulation</h2>
{/* Import */}
<div className="mb-6">
<button
onClick={() => fileInputRef.current?.click()}
className="w-full px-3 py-2 bg-blue-600 hover:bg-blue-500 rounded text-sm font-medium text-white transition-colors"
>
Import Engine JSON
</button>
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleImport}
className="hidden"
/>
{importError && (
<p className="mt-2 text-xs text-red-400">{importError}</p>
)}
</div>
{/* Simulation parameters */}
<div>
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
Engine Parameters
</h3>
<NumInput
label="Thrust (N)"
value={config.F}
onChange={v => trajectory.updateConfig('F', v)}
units="N"
step={100}
/>
<NumInput
label="Isp (s)"
value={config.Isp}
onChange={v => trajectory.updateConfig('Isp', v)}
units="s"
step={5}
/>
<NumInput
label="Mass Flow (kg/s)"
value={config.mdot}
onChange={v => trajectory.updateConfig('mdot', v)}
units="kg/s"
step={0.1}
/>
<NumInput
label="Wet Mass (kg)"
value={config.m_wet}
onChange={v => trajectory.updateConfig('m_wet', v)}
units="kg"
/>
<NumInput
label="Dry Mass (kg)"
value={config.m_dry}
onChange={v => trajectory.updateConfig('m_dry', v)}
units="kg"
/>
<NumInput
label="Burn Time (s)"
value={config.burnTime}
onChange={v => trajectory.updateConfig('burnTime', v)}
units="s"
step={1}
/>
</div>
<div className="mt-6">
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
Aerodynamics
</h3>
<NumInput
label="Drag Coefficient"
value={config.Cd}
onChange={v => trajectory.updateConfig('Cd', v)}
units=""
step={0.01}
/>
<NumInput
label="Ref Area (m²)"
value={config.A_ref}
onChange={v => trajectory.updateConfig('A_ref', v)}
units="m²"
step={0.001}
/>
</div>
<div className="mt-6">
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3 pb-2 border-b border-slate-700">
Flight Profile
</h3>
<NumInput
label="Pitch Start Alt (m)"
value={config.pitchStartAlt}
onChange={v => trajectory.updateConfig('pitchStartAlt', v)}
units="m"
step={10}
/>
<NumInput
label="Launch Angle (°)"
value={config.launchAngle}
onChange={v => trajectory.updateConfig('launchAngle', v)}
units="°"
step={1}
/>
<NumInput
label="Timestep (s)"
value={config.dt}
onChange={v => trajectory.updateConfig('dt', v)}
units="s"
step={0.01}
/>
<NumInput
label="Max Time (s)"
value={config.t_max}
onChange={v => trajectory.updateConfig('t_max', v)}
units="s"
step={10}
/>
</div>
{/* Run button */}
<button
onClick={trajectory.runSim}
className="w-full mt-6 px-4 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-sm font-semibold text-white transition-colors"
>
Run Simulation
</button>
{error && (
<p className="mt-2 text-xs text-red-400">{error}</p>
)}
{/* Results */}
{simulationResults && (
<div className="mt-6 pt-6 border-t border-slate-700">
<ResultSection title="Mission Summary">
<ResultRow
label="Apogee"
value={simulationResults.summary.apogee}
unit="m"
/>
<ResultRow
label="Apogee Time"
value={simulationResults.summary.apogee_t}
unit="s"
/>
<ResultRow
label="Max Q"
value={simulationResults.summary.max_q}
unit="Pa"
/>
<ResultRow
label="Flight Time"
value={simulationResults.summary.t_max}
unit="s"
/>
<ResultRow
label="Downrange"
value={simulationResults.summary.downrange}
unit="m"
/>
<ResultRow
label="Final Velocity"
value={simulationResults.summary.final_velocity}
unit="m/s"
/>
</ResultSection>
{currentState && (
<ResultSection title="Current State">
<ResultRow
label="Time"
value={currentState.t}
unit="s"
/>
<ResultRow
label="Altitude"
value={currentState.y}
unit="m"
/>
<ResultRow
label="Downrange"
value={currentState.x}
unit="m"
/>
<ResultRow
label="Velocity"
value={Math.hypot(currentState.vx, currentState.vy)}
unit="m/s"
/>
<ResultRow
label="Mass"
value={currentState.m}
unit="kg"
/>
<ResultRow
label="Phase"
value={currentState.phase}
unit=""
/>
</ResultSection>
)}
</div>
)}
</div>
{/* Center: plot */}
{simulationResults ? (
<div className="flex-1 bg-slate-950 p-4 overflow-hidden">
<TrajectoryPlot
simulationResults={simulationResults}
currentState={currentState}
onTimeClick={(t) => trajectory.setCurrentTime(t)}
/>
</div>
) : (
<div className="flex-1 flex items-center justify-center bg-slate-950">
<p className="text-slate-500 text-center">
Import an engine and click "Run Simulation" to see trajectory
</p>
</div>
)}
</div>
{/* Timeline at bottom */}
{simulationResults && (
<TimelineBar
simulationResults={simulationResults}
currentTime={trajectory.currentTime}
onTimeChange={trajectory.setCurrentTime}
isPlaying={trajectory.isPlaying}
onPlayPause={() => trajectory.setIsPlaying(!trajectory.isPlaying)}
playbackSpeed={trajectory.playbackSpeed}
onPlaybackSpeedChange={trajectory.setPlaybackSpeed}
/>
)}
</div>
</ErrorBoundary>
)
}