Phase 0: fix typecheck, format, build; remove stray emit; pin port via env
CI / Lint, typecheck, test, build (push) Failing after 22s
CI / Lint, typecheck, test, build (push) Failing after 22s
This commit is contained in:
@@ -45,3 +45,10 @@ coverage/
|
|||||||
# KSP game (if you ever mount the install into the repo for dev)
|
# KSP game (if you ever mount the install into the repo for dev)
|
||||||
KSP/
|
KSP/
|
||||||
saves/
|
saves/
|
||||||
|
|
||||||
|
# accidentally-emitted tsbuild outputs
|
||||||
|
**/*.js.map
|
||||||
|
**/*.d.ts.map
|
||||||
|
apps/live-map/src/*.js
|
||||||
|
apps/live-map/src/*.d.ts
|
||||||
|
apps/live-map/dist/
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ full implementation plan.
|
|||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
| Layer | Tech |
|
| Layer | Tech |
|
||||||
|---|---|
|
| ------------------ | --------------------------------------------------------------------- |
|
||||||
| **Hub** | Next.js 14 (App Router) + TypeScript |
|
| **Hub** | Next.js 14 (App Router) + TypeScript |
|
||||||
| **Live map** | Vite + React + Three.js + TypeScript |
|
| **Live map** | Vite + React + Three.js + TypeScript |
|
||||||
| **API** | Hono on Node 22 + WebSocket (`ws`) + Zod |
|
| **API** | Hono on Node 22 + WebSocket (`ws`) + Zod |
|
||||||
| **Shared** | `@kerbal-rt/shared-types`, `@kerbal-rt/orbital-math`, `@kerbal-rt/ui` |
|
| **Shared** | `@kerbal-rt/shared-types`, `@kerbal-rt/orbital-math`, `@kerbal-rt/ui` |
|
||||||
| **Data (Phase 1)** | Postgres 16 + TimescaleDB, Redis 7, MinIO (S3) |
|
| **Data (Phase 1)** | Postgres 16 + TimescaleDB, Redis 7, MinIO (S3) |
|
||||||
| **Package mgr** | pnpm workspaces |
|
| **Package mgr** | pnpm workspaces |
|
||||||
| **Linter** | Prettier (ESLint coming per-app) |
|
| **Linter** | Prettier (ESLint coming per-app) |
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
@@ -83,6 +83,7 @@ docker compose -f infra/docker-compose.yml up -d
|
|||||||
```
|
```
|
||||||
|
|
||||||
This brings up:
|
This brings up:
|
||||||
|
|
||||||
- **TimescaleDB** on `:5432` (with the full KSP body catalog pre-seeded)
|
- **TimescaleDB** on `:5432` (with the full KSP body catalog pre-seeded)
|
||||||
- **Redis** on `:6379`
|
- **Redis** on `:6379`
|
||||||
- **MinIO** on `:9000` (S3-compatible object store for media)
|
- **MinIO** on `:9000` (S3-compatible object store for media)
|
||||||
@@ -98,7 +99,7 @@ push / PR to `main`. See `.github/workflows/ci.yml`.
|
|||||||
See [`kerbalrealtime-clone-plan.md`](./kerbalrealtime-clone-plan.md) for the
|
See [`kerbalrealtime-clone-plan.md`](./kerbalrealtime-clone-plan.md) for the
|
||||||
roadmap. Quick map:
|
roadmap. Quick map:
|
||||||
|
|
||||||
- [x] **Phase 0** — monorepo, skeletons, docker-compose, CI *(this commit)*
|
- [x] **Phase 0** — monorepo, skeletons, docker-compose, CI _(this commit)_
|
||||||
- [ ] **Phase 1** — KSP telemetry bridge + ingest pipeline (Postgres + Redis)
|
- [ ] **Phase 1** — KSP telemetry bridge + ingest pipeline (Postgres + Redis)
|
||||||
- [ ] **Phase 2** — Live map 3D scene (time controls, focus, eclipses, overpass)
|
- [ ] **Phase 2** — Live map 3D scene (time controls, focus, eclipses, overpass)
|
||||||
- [ ] **Phase 3** — Mission calendar, media, crew, YouTube/Twitch embeds
|
- [ ] **Phase 3** — Mission calendar, media, crew, YouTube/Twitch embeds
|
||||||
|
|||||||
+2
-3
@@ -5,7 +5,7 @@
|
|||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { cors } from 'hono/cors';
|
import { cors } from 'hono/cors';
|
||||||
import { logger } from 'hono/logger';
|
import { logger } from 'hono/logger';
|
||||||
import { UniverseSnapshotSchema, type AppType as _ } from '@kerbal-rt/shared-types';
|
import { UniverseSnapshotSchema } from '@kerbal-rt/shared-types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
|
|
||||||
@@ -58,8 +58,7 @@ export function buildApp() {
|
|||||||
// ─── read-only endpoints ───────────────────────────────────────────────
|
// ─── read-only endpoints ───────────────────────────────────────────────
|
||||||
app.get('/api/v1/state', (c) => {
|
app.get('/api/v1/state', (c) => {
|
||||||
const snap = state.latestSnapshot();
|
const snap = state.latestSnapshot();
|
||||||
if (!snap)
|
if (!snap) return c.json({ error: true, code: 'NO_DATA', message: 'No snapshot yet' }, 503);
|
||||||
return c.json({ error: true, code: 'NO_DATA', message: 'No snapshot yet' }, 503);
|
|
||||||
return c.json({ error: false, data: snap });
|
return c.json({ error: false, data: snap });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ app.get(
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Suppress unused-import warning when no upgrade happens
|
||||||
|
void upgradeWebSocket;
|
||||||
|
|
||||||
const port = Number(process.env.PORT ?? 4000);
|
const port = Number(process.env.PORT ?? 4000);
|
||||||
|
|
||||||
const server: ServerType = serve({ fetch: app.fetch, port }, (info) => {
|
const server: ServerType = serve({ fetch: app.fetch, port }, (info) => {
|
||||||
|
|||||||
+22
-7
@@ -6,33 +6,48 @@
|
|||||||
*
|
*
|
||||||
* Phase 1 will wire this to a Redis pubsub channel so multiple API
|
* Phase 1 will wire this to a Redis pubsub channel so multiple API
|
||||||
* instances can share the fan-out load.
|
* instances can share the fan-out load.
|
||||||
|
*
|
||||||
|
* Hono's @hono/node-ws gives us a `WSContext` that wraps a `ws` WebSocket
|
||||||
|
* with extra Hono-specific methods. We use a thin adapter type so this
|
||||||
|
* module doesn't need to import from hono.
|
||||||
*/
|
*/
|
||||||
import type { LiveMessage } from '@kerbal-rt/shared-types';
|
import type { LiveMessage } from '@kerbal-rt/shared-types';
|
||||||
import type { WebSocket } from 'ws';
|
|
||||||
|
|
||||||
const clients = new Set<WebSocket>();
|
/** Anything with .send(string) and a .readyState we can inspect. */
|
||||||
|
export interface WsLike {
|
||||||
|
send(data: string): void;
|
||||||
|
readonly readyState: number;
|
||||||
|
}
|
||||||
|
|
||||||
function send(ws: WebSocket, msg: LiveMessage): void {
|
const READY_STATE_OPEN = 1;
|
||||||
if (ws.readyState === 1 /* OPEN */) {
|
const clients = new Set<WsLike>();
|
||||||
|
|
||||||
|
function send(ws: WsLike, msg: LiveMessage): void {
|
||||||
|
if (ws.readyState === READY_STATE_OPEN) {
|
||||||
ws.send(JSON.stringify(msg));
|
ws.send(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleLiveSocket = {
|
export const handleLiveSocket = {
|
||||||
onOpen(ws: WebSocket): void {
|
onOpen(ws: WsLike): void {
|
||||||
clients.add(ws);
|
clients.add(ws);
|
||||||
},
|
},
|
||||||
|
|
||||||
onMessage(_evt: { data: unknown }, _ws: WebSocket): void {
|
onMessage(_evt: { data: unknown }, _ws: WsLike): void {
|
||||||
// Phase 0: clients are read-only, ignore incoming.
|
// Phase 0: clients are read-only, ignore incoming.
|
||||||
// Phase 2: accept subscriptions like { type: 'subscribe', vesselId: '...' }
|
// Phase 2: accept subscriptions like { type: 'subscribe', vesselId: '...' }
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose(ws: WebSocket): void {
|
onClose(ws: WsLike): void {
|
||||||
clients.delete(ws);
|
clients.delete(ws);
|
||||||
},
|
},
|
||||||
|
|
||||||
broadcast(msg: LiveMessage): void {
|
broadcast(msg: LiveMessage): void {
|
||||||
for (const ws of clients) send(ws, msg);
|
for (const ws of clients) send(ws, msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** For tests / introspection. */
|
||||||
|
get clientCount(): number {
|
||||||
|
return clients.size;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "Marketing/operations hub: mission calendar, crew, media, Spacenomicon",
|
"description": "Marketing/operations hub: mission calendar, crew, media, Spacenomicon",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --port 3000",
|
"dev": "next dev --port ${PORT:-3000}",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start --port 3000",
|
"start": "next start --port ${PORT:-3000}",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "echo 'no tests yet'"
|
"test": "echo 'no tests yet'"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Kerbal RT — Real-time KSP server',
|
title: 'Kerbal RT — Real-time KSP server',
|
||||||
description: 'A no-warp Kerbal Space Program multiplayer server, mirrored to the web in real time.',
|
description:
|
||||||
|
'A no-warp Kerbal Space Program multiplayer server, mirrored to the web in real time.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { Card, Pill } from '@kerbal-rt/ui';
|
|||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<main style={{ maxWidth: 960, margin: '0 auto', padding: '2rem 1rem' }}>
|
<main style={{ maxWidth: 960, margin: '0 auto', padding: '2rem 1rem' }}>
|
||||||
<header style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1.5rem' }}>
|
<header
|
||||||
|
style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1.5rem' }}
|
||||||
|
>
|
||||||
<h1 style={{ fontSize: '1.75rem', margin: 0 }}>Kerbal RT</h1>
|
<h1 style={{ fontSize: '1.75rem', margin: 0 }}>Kerbal RT</h1>
|
||||||
<Pill tone="success">Phase 0 — skeleton</Pill>
|
<Pill tone="success">Phase 0 — skeleton</Pill>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -7,7 +7,12 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"plugins": [{ "name": "next" }]
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"description": "3D real-time solar system viewer",
|
"description": "3D real-time solar system viewer",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 3001",
|
"dev": "vite --port 3001",
|
||||||
"build": "tsc -p tsconfig.build.json && vite build",
|
"build": "tsc --noEmit && vite build",
|
||||||
"preview": "vite preview --port 3001",
|
"preview": "vite preview --port 3001",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "echo 'no linter yet'",
|
"lint": "echo 'no linter yet'",
|
||||||
|
|||||||
@@ -148,11 +148,7 @@ function Scene({ bodies, ut }: { bodies: CelestialBody[]; ut: number }) {
|
|||||||
if (body.sphereOfInfluence === 0 || body.sphereOfInfluence === Infinity) {
|
if (body.sphereOfInfluence === 0 || body.sphereOfInfluence === Infinity) {
|
||||||
// Star: render but skip orbit
|
// Star: render but skip orbit
|
||||||
if (body.kind === 'star') {
|
if (body.kind === 'star') {
|
||||||
const geo = new THREE.SphereGeometry(
|
const geo = new THREE.SphereGeometry(Math.max(body.radius, 1e8), 32, 16);
|
||||||
Math.max(body.radius, 1e8),
|
|
||||||
32,
|
|
||||||
16,
|
|
||||||
);
|
|
||||||
const mat = new THREE.MeshBasicMaterial({ color: 0xffcc33 });
|
const mat = new THREE.MeshBasicMaterial({ color: 0xffcc33 });
|
||||||
const mesh = new THREE.Mesh(geo, mat);
|
const mesh = new THREE.Mesh(geo, mat);
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
@@ -287,9 +283,7 @@ export function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 4 }}>UT {formatUtAsKspDate(ut)}</div>
|
<div style={{ marginTop: 4 }}>UT {formatUtAsKspDate(ut)}</div>
|
||||||
<div style={{ display: 'flex', gap: '0.25rem', marginTop: 8 }}>
|
<div style={{ display: 'flex', gap: '0.25rem', marginTop: 8 }}>
|
||||||
<button onClick={() => setPlaying((p) => !p)}>
|
<button onClick={() => setPlaying((p) => !p)}>{playing ? '⏸' : '▶'}</button>
|
||||||
{playing ? '⏸' : '▶'}
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setUt(0)}>Reset</button>
|
<button onClick={() => setUt(0)}>Reset</button>
|
||||||
{[1, 60, 3600, 86400].map((s) => (
|
{[1, 60, 3600, 86400].map((s) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"noEmit": false
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"]
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,6 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler"
|
"moduleResolution": "Bundler"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*", "src/**/*.d.ts"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -20,12 +20,12 @@ services:
|
|||||||
POSTGRES_PASSWORD: kerbal
|
POSTGRES_PASSWORD: kerbal
|
||||||
POSTGRES_DB: kerbal_rt
|
POSTGRES_DB: kerbal_rt
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- '5432:5432'
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data
|
- postgres-data:/var/lib/postgresql/data
|
||||||
- ./init-sql:/docker-entrypoint-initdb.d
|
- ./init-sql:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U kerbal -d kerbal_rt"]
|
test: ['CMD-SHELL', 'pg_isready -U kerbal -d kerbal_rt']
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -33,13 +33,13 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:7.4-alpine
|
image: redis:7.4-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["redis-server", "--appendonly", "yes"]
|
command: ['redis-server', '--appendonly', 'yes']
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- '6379:6379'
|
||||||
volumes:
|
volumes:
|
||||||
- redis-data:/data
|
- redis-data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: ['CMD', 'redis-cli', 'ping']
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -47,17 +47,17 @@ services:
|
|||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2024-09-13T20-26-02Z
|
image: minio/minio:RELEASE.2024-09-13T20-26-02Z
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["server", "/data", "--console-address", ":9001"]
|
command: ['server', '/data', '--console-address', ':9001']
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: kerbal
|
MINIO_ROOT_USER: kerbal
|
||||||
MINIO_ROOT_PASSWORD: kerbal-secret-change-me
|
MINIO_ROOT_PASSWORD: kerbal-secret-change-me
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000" # S3 API
|
- '9000:9000' # S3 API
|
||||||
- "9001:9001" # Web console
|
- '9001:9001' # Web console
|
||||||
volumes:
|
volumes:
|
||||||
- minio-data:/data
|
- minio-data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -66,12 +66,12 @@ services:
|
|||||||
pgadmin:
|
pgadmin:
|
||||||
image: dpage/pgadmin4:8
|
image: dpage/pgadmin4:8
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
profiles: ["dev"]
|
profiles: ['dev']
|
||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: admin@kerbal.local
|
PGADMIN_DEFAULT_EMAIL: admin@kerbal.local
|
||||||
PGADMIN_DEFAULT_PASSWORD: admin
|
PGADMIN_DEFAULT_PASSWORD: admin
|
||||||
ports:
|
ports:
|
||||||
- "5050:80"
|
- '5050:80'
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
export { solveKepler } from './kepler.js';
|
export { solveKepler } from './kepler.js';
|
||||||
export {
|
export { meanMotion, propagateToEpoch, positionAt, sampleOrbit } from './propagate.js';
|
||||||
meanMotion,
|
|
||||||
propagateToEpoch,
|
|
||||||
positionAt,
|
|
||||||
sampleOrbit,
|
|
||||||
} from './propagate.js';
|
|
||||||
export { shadowFraction } from './occultation.js';
|
export { shadowFraction } from './occultation.js';
|
||||||
export { phaseAngle, hohmannDeltaV, findTransferWindows } from './transfer.js';
|
export { phaseAngle, hohmannDeltaV, findTransferWindows } from './transfer.js';
|
||||||
|
|||||||
@@ -41,13 +41,8 @@ export function shadowFraction(
|
|||||||
const perpDist = Math.hypot(px, py, pz);
|
const perpDist = Math.hypot(px, py, pz);
|
||||||
|
|
||||||
if (perpDist >= occluderRadius) return 0;
|
if (perpDist >= occluderRadius) return 0;
|
||||||
// Approximate chord length through the occluder disc
|
|
||||||
const halfChord = Math.sqrt(occluderRadius * occluderRadius - perpDist * perpDist);
|
|
||||||
// Approximate the angular size of the sun as seen from the occluder
|
// Approximate the angular size of the sun as seen from the occluder
|
||||||
// vs the angular size of the occluder; we use 1.0 for the sun
|
// vs the angular size of the occluder; we use 1.0 for the sun
|
||||||
// (i.e. effectively point source) — good enough for visualization.
|
// (i.e. effectively point source) — good enough for visualization.
|
||||||
// For a "fraction in shadow" treat the occluder disc as fully shadowing
|
|
||||||
// when perpDist + halfChord reaches the observer; that simplifies to
|
|
||||||
// perpDist < occluderRadius which we already check.
|
|
||||||
return Math.min(1, 1 - perpDist / occluderRadius);
|
return Math.min(1, 1 - perpDist / occluderRadius);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { KeplerianElements, CelestialBody } from '@kerbal-rt/shared-types';
|
import type { KeplerianElements } from '@kerbal-rt/shared-types';
|
||||||
import { solveKepler } from './kepler.js';
|
import { solveKepler } from './kepler.js';
|
||||||
|
|
||||||
const TWO_PI = Math.PI * 2;
|
const TWO_PI = Math.PI * 2;
|
||||||
@@ -89,10 +89,8 @@ export function positionAt(
|
|||||||
const cosw = Math.cos(w);
|
const cosw = Math.cos(w);
|
||||||
const sinw = Math.sin(w);
|
const sinw = Math.sin(w);
|
||||||
|
|
||||||
const x =
|
const x = (cosO * cosw - sinO * sinw * cosi) * xP + (-cosO * sinw - sinO * cosw * cosi) * yQ;
|
||||||
(cosO * cosw - sinO * sinw * cosi) * xP + (-cosO * sinw - sinO * cosw * cosi) * yQ;
|
const y = (sinO * cosw + cosO * sinw * cosi) * xP + (-sinO * sinw + cosO * cosw * cosi) * yQ;
|
||||||
const y =
|
|
||||||
(sinO * cosw + cosO * sinw * cosi) * xP + (-sinO * sinw + cosO * cosw * cosi) * yQ;
|
|
||||||
const z = sinw * sini * xP + cosw * sini * yQ;
|
const z = sinw * sini * xP + cosw * sini * yQ;
|
||||||
|
|
||||||
return { x, y, z };
|
return { x, y, z };
|
||||||
@@ -105,7 +103,7 @@ export function positionAt(
|
|||||||
*/
|
*/
|
||||||
export function sampleOrbit(
|
export function sampleOrbit(
|
||||||
elements: KeplerianElements,
|
elements: KeplerianElements,
|
||||||
mu: number,
|
_mu: number,
|
||||||
steps: number = 128,
|
steps: number = 128,
|
||||||
): { x: number; y: number; z: number }[] {
|
): { x: number; y: number; z: number }[] {
|
||||||
const points: { x: number; y: number; z: number }[] = [];
|
const points: { x: number; y: number; z: number }[] = [];
|
||||||
@@ -130,10 +128,8 @@ export function sampleOrbit(
|
|||||||
const r = (a * (1 - e * e)) / (1 + e * cosNu);
|
const r = (a * (1 - e * e)) / (1 + e * cosNu);
|
||||||
const xP = r * cosNu;
|
const xP = r * cosNu;
|
||||||
const yQ = r * sinNu;
|
const yQ = r * sinNu;
|
||||||
const x =
|
const x = (cosO * cosw - sinO * sinw * cosi) * xP + (-cosO * sinw - sinO * cosw * cosi) * yQ;
|
||||||
(cosO * cosw - sinO * sinw * cosi) * xP + (-cosO * sinw - sinO * cosw * cosi) * yQ;
|
const y = (sinO * cosw + cosO * sinw * cosi) * xP + (-sinO * sinw + cosO * cosw * cosi) * yQ;
|
||||||
const y =
|
|
||||||
(sinO * cosw + cosO * sinw * cosi) * xP + (-sinO * sinw + cosO * cosw * cosi) * yQ;
|
|
||||||
const z = sinw * sini * xP + cosw * sini * yQ;
|
const z = sinw * sini * xP + cosw * sini * yQ;
|
||||||
points.push({ x, y, z });
|
points.push({ x, y, z });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { KeplerianElements, CelestialBody } from '@kerbal-rt/shared-types';
|
import type { KeplerianElements } from '@kerbal-rt/shared-types';
|
||||||
import { meanMotion } from './propagate.js';
|
import { meanMotion } from './propagate.js';
|
||||||
|
|
||||||
const TWO_PI = Math.PI * 2;
|
const TWO_PI = Math.PI * 2;
|
||||||
@@ -79,7 +79,6 @@ export function findTransferWindows(
|
|||||||
|
|
||||||
const results: { ut: number; phaseAngle: number }[] = [];
|
const results: { ut: number; phaseAngle: number }[] = [];
|
||||||
const stepSec = 3600; // 1h steps for the coarse scan
|
const stepSec = 3600; // 1h steps for the coarse scan
|
||||||
const nA = meanMotion(from.elements.semiMajorAxis, from.mu);
|
|
||||||
|
|
||||||
for (let t = ut; t < ut + maxSearchTime && results.length < count; t += stepSec) {
|
for (let t = ut; t < ut + maxSearchTime && results.length < count; t += stepSec) {
|
||||||
const phase = phaseAngle(from.elements, from.mu, to.elements, to.mu, t);
|
const phase = phaseAngle(from.elements, from.mu, to.elements, to.mu, t);
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "Shared React components for hub + live-map",
|
"description": "Shared React components for hub + live-map",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.tsx",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.tsx",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.tsx"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
|
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
|
|
||||||
export function Pill({ children, tone = 'neutral' }: { children: ReactNode; tone?: 'neutral' | 'success' | 'warn' | 'danger' }) {
|
export function Pill({
|
||||||
|
children,
|
||||||
|
tone = 'neutral',
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
tone?: 'neutral' | 'success' | 'warn' | 'danger';
|
||||||
|
}) {
|
||||||
const colorMap = {
|
const colorMap = {
|
||||||
neutral: 'var(--pill-neutral, #ddd)',
|
neutral: 'var(--pill-neutral, #ddd)',
|
||||||
success: 'var(--pill-success, #2c5)',
|
success: 'var(--pill-success, #2c5)',
|
||||||
Generated
+6599
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
packages:
|
packages:
|
||||||
- "apps/*"
|
- 'apps/*'
|
||||||
- "packages/*"
|
- 'packages/*'
|
||||||
|
|||||||
Reference in New Issue
Block a user