Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
116eb05
First sketch
FadiShawki Aug 2, 2026
bdba5e8
Pre-XOR-space setup
FadiShawki Aug 2, 2026
8ee5bc4
Attempt 1 XOR Space
FadiShawki Aug 2, 2026
97403bc
First working toy model of XOR space
FadiShawki Aug 4, 2026
13c16cf
Experimenting with XOR space
FadiShawki Aug 5, 2026
5cb5bc9
Gravitational waves in XOR space
FadiShawki Aug 5, 2026
69ea522
More examples of XOR space
FadiShawki Aug 6, 2026
5387558
First attempt at 3D
FadiShawki Aug 6, 2026
01a557c
First attempt at 3D
FadiShawki Aug 6, 2026
6f2e6be
Playing with rendering options, spinnning magnet
FadiShawki Aug 6, 2026
1f41768
Trying to stabalize rendering, 2D & optimizations
FadiShawki Aug 7, 2026
7c67913
First attempt at a continous implementation
FadiShawki Aug 7, 2026
9f03605
Orbiting examples
FadiShawki Aug 7, 2026
12cea76
Break apart the file
FadiShawki Aug 8, 2026
c68802c
Attempt at compressing the continous model
FadiShawki Aug 8, 2026
b65087a
First attempt at recovering Newtonian gravity at a large scale
FadiShawki Aug 8, 2026
0b8a075
Increase trail
FadiShawki Aug 8, 2026
f6e6c57
Solar system examples
FadiShawki Aug 8, 2026
88a42da
Change dependence on free
FadiShawki Aug 9, 2026
29642f9
Reproducing Newtonian gravity
FadiShawki Aug 9, 2026
a3e60be
Sketch of what writing about the law would look like (temporary) - ti…
FadiShawki Aug 9, 2026
43ae489
Partially assuming GR. Space creation from neutral points.
FadiShawki Aug 10, 2026
68e6794
Trying to work out conclusions of the model
FadiShawki Aug 11, 2026
1bd55c5
Trying to work out conclusions of the model
FadiShawki Aug 11, 2026
509679c
Reproducing results from GR
FadiShawki Aug 11, 2026
ab20142
Black holes
FadiShawki Aug 11, 2026
385d5f2
Thinking cosmology
FadiShawki Aug 11, 2026
c907c79
Thinking about matter
FadiShawki Aug 11, 2026
4e14742
More thinking on dark matter
FadiShawki Aug 12, 2026
f4ce8be
Rotational curves in galaxies dont need dark matter / MOND falls out
FadiShawki Aug 12, 2026
eff5cb2
Magnetism
FadiShawki Aug 12, 2026
2edf9a3
Convert into a booklet
FadiShawki Aug 12, 2026
07d24cb
Make a start to writing the article
FadiShawki Aug 13, 2026
581003d
Bookkeeping, and writing the first sections for the physics booklet
FadiShawki Aug 13, 2026
5912458
Bookkeeping, and writing the first sections for the physics booklet
FadiShawki Aug 13, 2026
eba626c
Move a few things around
FadiShawki Aug 13, 2026
9569894
Generate derivation steps
FadiShawki Aug 13, 2026
5adb4c0
Thinking inverse square law
FadiShawki Aug 13, 2026
68ecc01
Thinking about discrete model: movement
FadiShawki Aug 14, 2026
4dba380
Vacuum dynamics + gravity movement
FadiShawki Aug 14, 2026
21f3447
First gravity section
FadiShawki Aug 14, 2026
5fce54e
First section on gravity
FadiShawki Aug 14, 2026
cb02c76
Generated notes for spherical influence
FadiShawki Aug 14, 2026
7f5125b
QM Layer 2 & Matter - separate into a separate tabs TODO
FadiShawki Aug 14, 2026
3fe9154
FLuctuation + matter
FadiShawki Aug 14, 2026
24f5a3c
Working on the wobble derevation
FadiShawki Aug 14, 2026
b9777f2
Magnetism, efficiency, thinking visualizations
FadiShawki Aug 15, 2026
9f3d308
Thinking magnetism
FadiShawki Aug 16, 2026
d3c4c30
Magnetism & matter
FadiShawki Aug 16, 2026
49044cc
Matter
FadiShawki Aug 17, 2026
a7a9be9
Electromagnetism
FadiShawki Aug 17, 2026
6412bee
Electromagnetism
FadiShawki Aug 17, 2026
270e7e5
Electromagnetism
FadiShawki Aug 18, 2026
271e5dc
Further thoughts on Electromagnetism
FadiShawki Aug 18, 2026
5d887f4
Refactoring and unifying the different implementations
FadiShawki Aug 18, 2026
bd81b8a
Refactor
FadiShawki Aug 19, 2026
ed80c60
Refactor
FadiShawki Aug 19, 2026
287e5ab
Fix visualizations
FadiShawki Aug 19, 2026
e0c68d5
Refactor
FadiShawki Aug 19, 2026
2a86542
Refactor
FadiShawki Aug 19, 2026
c5d10c4
Refactor
FadiShawki Aug 20, 2026
57d47d1
Refactor
FadiShawki Aug 20, 2026
590282f
Autogen a temporary section for galaxy rotation curves
FadiShawki Sep 3, 2026
fa2fb53
Start the actual article. Gravity.
FadiShawki Sep 3, 2026
0951c6f
Move stuff into the @orbitmines/physics package, continue with the gr…
FadiShawki Sep 4, 2026
34b4e8c
Small changes to the gravity sections
FadiShawki Sep 5, 2026
20791c2
Add visuals
FadiShawki Sep 5, 2026
79e67d3
Typo
FadiShawki Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Miscellaneous
./external

.panels
_*.*

# orbitmines.com
orbitmines.com/.next
orbitmines.com/node_modules
orbitmines.com/build
orbitmines.com/tsconfig.tsbuildinfo

# Environment
**/.idea
Expand Down
14 changes: 12 additions & 2 deletions orbitmines.com/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
'use client';

import EtherOrMinimap from '../src/@ether/UI/router/EtherOrMinimap';
import React from 'react';

// Cloudflare Pages routes unknown URLs to /index.html with 200 via the
// _redirects rule, so this 404.html is rarely hit. We still wire it up to
// the same SPA-routing component as a defensive fallback.
//
// Lazily, and that is not about this page. The App Router treats the root
// not-found as part of every page's segment tree, so whatever this file names
// statically is downloaded by every URL on the site — and what it names is the
// minimap, which reaches the whole archive and, through it, three.js. An
// article was fetching a WebGL renderer and a paper index in order to render a
// 404 nobody was looking at. Behind a lazy import the fallback still works and
// costs only the page that actually falls back to it.
const EtherOrMinimap = React.lazy(() => import('../src/@ether/UI/router/EtherOrMinimap'));

export default function NotFound() {
return <EtherOrMinimap />;
return <React.Suspense fallback={<></>}><EtherOrMinimap /></React.Suspense>;
}
7 changes: 7 additions & 0 deletions orbitmines.com/app/physics/[[...section]]/PhysicsClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import Physics from '../../../src/routes/Physics';

export default function PhysicsClient() {
return <Physics />;
}
50 changes: 50 additions & 0 deletions orbitmines.com/app/physics/[[...section]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type {Metadata} from 'next';
import fs from 'fs';
import path from 'path';
import {sectionSlug} from '../../../src/lib/post/sectionSlug';
import PhysicsClient from './PhysicsClient';

const BOOK_TITLE = 'OrbitMines: Notes on Physics';

// The same arrangement the Almanac uses: arcs and sections live in the path
// (/physics/<section-slug>) as client-side shallow routes within the book, and
// every one is prerendered as its own URL so that dev and the static export
// both serve them, and a refresh on a deep link does not 404.
//
// Derived from the source at build time rather than kept by hand, so adding an
// arc is one edit rather than two.
export function physicsSections(): {slug: string; head: string}[] {
const src = fs.readFileSync(
path.join(process.cwd(), 'src/routes/Physics.tsx'),
'utf8',
);
const heads = [...src.matchAll(/<(?:Arc|Section)\s+head="([^"]+)"/g)].map((m) => m[1]);
const bySlug = new Map<string, string>();
for (const head of heads) {
const slug = sectionSlug(head);
if (slug && !bySlug.has(slug)) bySlug.set(slug, head);
}
return [...bySlug].map(([slug, head]) => ({slug, head}));
}

export function generateStaticParams() {
return [
{section: [] as string[]},
...physicsSections().map(({slug}) => ({section: [slug]})),
];
}

export const dynamicParams = false;

export async function generateMetadata(
{params}: {params: Promise<{section?: string[]}>},
): Promise<Metadata> {
const slug = (await params).section?.[0];
if (!slug) return {title: BOOK_TITLE};
const head = physicsSections().find((s) => s.slug === slug)?.head;
return {title: head ? `${BOOK_TITLE} - ${head.trim()}` : BOOK_TITLE};
}

export default function Page() {
return <PhysicsClient />;
}
2 changes: 1 addition & 1 deletion orbitmines.com/app/thumbnail/ThumbnailClient.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { ThumbnailPage } from '../../src/lib/post/Post';
import { ThumbnailPage } from '../../src/lib/post/Thumbnail';

export default function ThumbnailClient() {
return <ThumbnailPage />;
Expand Down
13 changes: 13 additions & 0 deletions orbitmines.com/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const nextConfig = {
images: { unoptimized: true, disableStaticImages: true },
trailingSlash: false,
productionBrowserSourceMaps: true,
// `@orbitmines/physics` ships its TypeScript source rather than a build — the package
// has no dependencies and no build step, which is the point of it — so Next has to
// compile it the way it compiles this repository's own files.
transpilePackages: ['@orbitmines/physics'],

eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },

Expand All @@ -17,6 +22,14 @@ const nextConfig = {
// for browser bundles, so the old webpack rules for those are no longer
// needed.
turbopack: {
// No `root` override. `@orbitmines/physics` used to be a symlink out of this
// directory, which Turbopack will not follow, so the root had to be the folder
// holding both repositories — 322GB across 17 repos, all of which Turbopack then
// watched, until the dev server died with `RangeError: Map maximum size exceeded`
// out of async_hooks. Next has no watch-ignore for Turbopack, so instead
// `scripts/sync-physics.mjs` copies the package into node_modules as real files
// and the watched tree is this repository alone. See that file.

resolveAlias: {
'@blueprintjs/core': BP_LOCAL,
'@blueprintjs/core/src/common': `${BP_LOCAL}/common`,
Expand Down
16 changes: 16 additions & 0 deletions orbitmines.com/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion orbitmines.com/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@orbitmines/physics": "file:../../physics/languages/.ts",
"@react-pdf/renderer": "^4.3.2",
"@react-three/drei": "^10.0.0",
"@react-three/fiber": "^9.0.0",
Expand Down Expand Up @@ -35,7 +36,12 @@
"dev": "next dev",
"start": "next start",
"build": "next build",
"lint": "next lint"
"lint": "next lint",
"sync:physics": "node scripts/sync-physics.mjs",
"dev:physics": "node scripts/sync-physics.mjs --watch",
"predev": "npm run sync:physics",
"prebuild": "npm run sync:physics",
"postinstall": "npm run sync:physics"
},
"browserslist": {
"production": [
Expand Down
65 changes: 65 additions & 0 deletions orbitmines.com/scripts/sync-physics.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* MATERIALISE `@orbitmines/physics` INSIDE THIS REPOSITORY, instead of linking out to it.
*
* The package is a `file:` dependency during development, which npm installs as a SYMLINK
* pointing at `../../physics/languages/.ts`. Turbopack will not follow a symlink out of its
* filesystem root, so the root had to be widened to the folder holding both repositories -
* and that folder is three hundred gigabytes across seventeen repositories, most of it
* archives. Turbopack watches its whole root, so the dev server was opening file handles for
* all of it until Node's async-hooks map hit its ceiling and the process died with
* `RangeError: Map maximum size exceeded`.
*
* NEXT HAS NO WATCH IGNORE for Turbopack - `watchOptions` carries a poll interval and nothing
* else - so the root cannot be kept and narrowed. It has to become this repository, which
* means the package has to BE here rather than point away.
*
* SO THE SYMLINK IS REPLACED BY A COPY, of exactly what the package's own `files` field says
* it ships. Nothing else changes: the import specifier, `transpilePackages` and the type
* resolution all work on a real directory in `node_modules` the way they worked on a link,
* and the watched tree is this repository alone.
*
* IT IS A COPY, SO IT GOES STALE. Run it again after editing the package - `npm run
* sync:physics`, which `predev` and `prebuild` already do - or `npm run dev:physics` to have
* it re-copied whenever a source file there changes.
*/
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, watch } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const from = resolve(here, "../../../physics/languages/.ts");
const to = resolve(here, "../node_modules/@orbitmines/physics");

if (!existsSync(from)) {
console.error(`sync:physics - no package at ${from}`);
process.exit(1);
}

/* what the package says it ships, plus the manifest that says it */
const ships = () => [
...JSON.parse(readFileSync(join(from, "package.json"), "utf8")).files ?? [],
"package.json",
];

const sync = () => {
/* a symlink is removed rather than written through, or the copy lands in the other repo */
if (existsSync(to) || lstatSync(to, { throwIfNoEntry: false })) rmSync(to, { recursive: true, force: true });
mkdirSync(to, { recursive: true });
for (const f of ships()) {
const src = join(from, f);
if (existsSync(src)) cpSync(src, join(to, f), { recursive: true });
}
console.log(`sync:physics - ${ships().length} entries -> node_modules/@orbitmines/physics`);
};

sync();

if (process.argv.includes("--watch")) {
let queued = null;
console.log(`sync:physics - watching ${from}`);
watch(from, { recursive: true }, (_, file) => {
if (file?.startsWith("node_modules") || file?.startsWith(".git")) return;
clearTimeout(queued);
queued = setTimeout(sync, 150);
});
}
9 changes: 8 additions & 1 deletion orbitmines.com/src/@orbitmines/js/react/IEventListener.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import React, {
TouchEventHandler, TransitionEventHandler, UIEventHandler,
useMemo, WheelEventHandler
} from 'react';
import _ from "lodash";
// Three functions, one file each — see the note in `lib/post/Post.tsx`. This
// one matters most: it is reached from the root layout, so whatever it names
// is named by every page on the site.
import entries from "lodash/entries";
import mergeWith from "lodash/mergeWith";
import pickBy from "lodash/pickBy";

const _ = {entries, mergeWith, pickBy};

export type IEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;

Expand Down
7 changes: 6 additions & 1 deletion orbitmines.com/src/@orbitmines/js/react/hooks/useHotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import IModule, {useModule} from "../IModule";
import {HotkeyConfig} from "@blueprintjs/core/src/hooks/hotkeys/hotkeyConfig";
import {useHotkeys as useBlueprintJSHotkeys} from '@blueprintjs/core';
import {useState} from "react";
import _ from "lodash";
// Three functions, one file each — see the note in `lib/post/Post.tsx`.
import compact from "lodash/compact";
import isArray from "lodash/isArray";
import uniq from "lodash/uniq";

const _ = {compact, isArray, uniq};

export type PressedKeys = string[];
export type HotkeyEventOptions = { pressed: PressedKeys };
Expand Down
16 changes: 12 additions & 4 deletions orbitmines.com/src/lib/post/Book.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ export class BookUtil {
nextSection = (reverse: boolean = false) => this.sectionName(this.next(reverse))

sectionName = (element: any) => {
if (typeof element.props.head === "string") return element.props.head
if (element.props.head.props != undefined) return element.props.head.props.children
// Defensive at both levels: `firstSection()` reads `allSections()[0]`,
// which is undefined for a book with no arcs, and a Section may carry no
// head at all. Neither is worth a blank page.
const head = element?.props?.head
if (typeof head === "string") return head
if (head?.props !== undefined) return head.props.children
return ""
}
disabled = (element: any) => typeof element.props.head !== "string"
Expand Down Expand Up @@ -173,12 +177,16 @@ export const Navigation = (props: PaperProps & { hideBorder?: boolean, onNavigat
<a className="bp5-text-muted" data-selected={util.isSelected(arc) || undefined} style={{color: util.isSelected(arc) ? 'orange' : '#abb3bf'}} onClick={() => !util.disabled(arc) ? navigate(util.sectionName(arc)) : undefined}>{arc.props.head}</a>

{React.Children.toArray((arc as any).props.children).filter(child =>
React.isValidElement(child) && child.type === Section
// `props.head` is what makes a Section navigable — see `getSections`.
// Without it there is nothing to name the link after, and `sectionName`
// reads `props.head.props` and throws. A Section used purely to group
// prose is content, not a destination.
React.isValidElement(child) && child.type === Section && (child.props as any).head
).map((section: any) => <Col key={util.sectionName(section)} xs={12} style={{textAlign: 'start'}} className="pt-3">
<a className="bp5-text-muted ml-5" data-selected={util.isSelected(section) || undefined} style={util.isSelected(section) ? {color: 'orange'} : {}} onClick={() => !util.disabled(section) ? navigate(util.sectionName(section)) : undefined}>{section.props.head}</a>

{React.Children.toArray((section as any).props.children).filter(child =>
React.isValidElement(child) && child.type === Section
React.isValidElement(child) && child.type === Section && (child.props as any).head
).map((section: any) => <Col key={util.sectionName(section)} xs={12} style={{textAlign: 'start'}}>
<a className="bp5-text-muted ml-10" data-selected={util.isSelected(section) || undefined} style={util.isSelected(section) ? {color: 'orange'} : {}} onClick={() => !util.disabled(section) ? navigate(util.sectionName(section)) : undefined}>{section.props.head}</a>

Expand Down
Loading
Loading