Files
rocketry/docs/CONTRIBUTING.md
2026-03-04 20:29:19 +00:00

10 KiB

Contributing Guide

Getting Started

Prerequisites

  • Node.js 18+
  • npm or yarn
  • Basic understanding of React, JavaScript

Setup

# 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 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:

{
  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
  }
}
  1. Test manually
    • Go to Solver page
    • Drag variables onto workspace
    • Set constraints, verify solver computes correct values

For Transcendental Equations (Bisection)

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:

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:

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:

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):
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>
  )
}
  1. Add route in src/App.jsx:
import NewPage from './pages/NewPage.jsx'

// In Routes:
<Route path="/new-feature" element={<NewPage />} />
  1. Add navigation link in header if needed:
<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):

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

// 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
<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
// ✅ 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)

// 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)

// 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:

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
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:

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:

// 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

// In React component
console.time('calculation')
const result = expensiveFunction()
console.timeEnd('calculation')  // logs elapsed time

Common Pitfalls

Don't: Mutate state directly

state.value = 100  // Wrong!
setState({...state, value: 100})  // Correct

Don't: Create objects in dependencies

const obj = {x: 1}
useMemo(() => calc(obj), [obj])  // Runs every render!

Do: Memoize dependencies

const obj = useMemo(() => ({x: 1}), [])
useMemo(() => calc(obj), [obj])  // Stable

Don't: Forget dependencies

useEffect(() => {
  doSomething(dep)
}, [])  // Missing 'dep'!

Do: Include all dependencies

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


Last Updated: 2025-02 | Status: Current