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
a617a72
feat(onboarding): onboarding components
Dhruv-0-Arora Jul 8, 2026
b5299c7
feat(onboarding): adding initial onboarding ui
Dhruv-0-Arora Jul 8, 2026
4f9e17e
ci(preferences): onboarding preferences tests
Dhruv-0-Arora Jul 8, 2026
3ac1180
fix(onboarding): card ui
Dhruv-0-Arora Jul 13, 2026
99bc0cd
feat(mirabuf): add year and thumbnail metadata to asset pipeline
Dhruv-0-Arora Jul 13, 2026
05f74f2
feat(ui): replace spawn asset panel with year-grouped Library modal
Dhruv-0-Arora Jul 13, 2026
aab72a7
refactor(onboarding): code quality
Dhruv-0-Arora Jul 14, 2026
2c5aca4
refactor(library): extract Autodesk Hub accordion into its own component
Dhruv-0-Arora Jul 14, 2026
ad56d11
fix(library): populate asset year metadata so the modal groups by year
Dhruv-0-Arora Jul 14, 2026
6b66c57
feat(library): use the thumbnail embedded in mira metadata for asset …
Dhruv-0-Arora Jul 14, 2026
65def65
fix(onboarding): onboarding handling for library modal
Dhruv-0-Arora Jul 15, 2026
51b1577
feat(onboarding): added informational tourcards
Dhruv-0-Arora Jul 15, 2026
ccd7dfa
feat(onboarding): add assembly setup input-scheme tour step
Dhruv-0-Arora Jul 15, 2026
08d3c51
chore(librarymodal): cleaning savedassets accordian
Dhruv-0-Arora Jul 21, 2026
f7908b6
fix!(onboarding): auto-advancing despite minification
Dhruv-0-Arora Jul 21, 2026
25fd398
fix(onboarding): field precedes robots & double scroll bug
Dhruv-0-Arora Jul 22, 2026
22ee90b
Merge remote-tracking branch 'origin/feat/63/ui-redesign' into darora…
Dhruv-0-Arora Jul 22, 2026
b1b3858
feat(thumbnail): setting up scene capture logic
Dhruv-0-Arora Jul 23, 2026
c18f803
feat(thumbnail): computing framing
Dhruv-0-Arora Jul 23, 2026
4fe5bde
feat(thumbnail): computing bounds
Dhruv-0-Arora Jul 23, 2026
9df7aac
feat(thumbnail): encoding pixels for rendered scene
Dhruv-0-Arora Jul 23, 2026
975a6b5
fix(thumbnail): size constant usage & rendering
Dhruv-0-Arora Jul 23, 2026
1cc500f
feat(thumbnail): rendering target
Dhruv-0-Arora Jul 24, 2026
d1f3510
ci(thumbnail): tests and working implementation to generate thumbnail…
Dhruv-0-Arora Jul 24, 2026
f71c922
feat(thumbnail): embedding thumbnail into mira data
Dhruv-0-Arora Jul 25, 2026
7f7b7b1
feat(thumbnail): APS hub integration
Dhruv-0-Arora Jul 28, 2026
f007602
Merge remote-tracking branch 'origin/dev' into darora1/218/ui-tour
Dhruv-0-Arora Jul 30, 2026
1cadd0b
Merge remote-tracking branch 'origin/darora1/218/ui-tour' into darora…
Dhruv-0-Arora Jul 30, 2026
778b438
ci(thumbnail): making tests more robust
Dhruv-0-Arora Jul 31, 2026
6a0683c
feat(thumbnail): walking mirabuf assembly for thumbnail
Dhruv-0-Arora Jul 31, 2026
6faa873
ci(thumbnails): adding snapshot
Dhruv-0-Arora Jul 31, 2026
71fae62
fix(onboarding): assetpack test
Dhruv-0-Arora Aug 3, 2026
390753a
Merge remote-tracking branch 'origin/dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 5, 2026
aca5a2b
Merge remote-tracking branch 'origin/darora1/218/ui-tour' into darora…
Dhruv-0-Arora Aug 5, 2026
9f3bbe5
fix(thumbnail): edge case for failure
Dhruv-0-Arora Aug 5, 2026
d82df35
docs(thumbnail): wiretype docs
Dhruv-0-Arora Aug 5, 2026
b0fd299
Taking thumbnail screenshots for assemblies in the Simulator `SYNTH-2…
Dhruv-0-Arora Aug 5, 2026
0f4d69f
chore(thumbnails): biome lint error
Dhruv-0-Arora Aug 6, 2026
72a1561
Merge remote-tracking branch 'origin/dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 6, 2026
3b24d55
chore(cleanup): break config import cycle and remove useless fragment
Dhruv-0-Arora Aug 13, 2026
7d54465
refactor(input): register escape key handlers with priorities
Dhruv-0-Arora Aug 13, 2026
0e716d8
feat(onboarding): drive tour progress from app state conditions
Dhruv-0-Arora Aug 13, 2026
4c6cf8c
feat(onboarding): spotlight the active step and clarify card navigation
Dhruv-0-Arora Aug 13, 2026
c94445c
test(onboarding): cover tour advancement and recovery
Dhruv-0-Arora Aug 13, 2026
8ab054e
feat(settings): add replay onboarding tour button
Dhruv-0-Arora Aug 13, 2026
5864aa0
fix(onboarding): clear stale focus when assembly setup opens
Dhruv-0-Arora Aug 13, 2026
9a252c9
Merge remote-tracking branch 'origin/dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 13, 2026
0d96bb9
fix(onboarding): removing stale configurePanel blocker
Dhruv-0-Arora Aug 13, 2026
24d6683
refactor(onboarding): colocate useTourAnchor with the tour context
Dhruv-0-Arora Aug 13, 2026
622d59b
refactor(mirabuf): drop unused cache field and simplify cacheRemote
Dhruv-0-Arora Aug 13, 2026
c4c171d
fix(ui): weird spacing in inputschemeselection
Dhruv-0-Arora Aug 13, 2026
68297ee
fix(onboarding): tour would rewind when spawning a 2nd robot
Dhruv-0-Arora Aug 13, 2026
ff0cea3
fix(onboarding): remove overlay over blocking panel
Dhruv-0-Arora Aug 13, 2026
df75ff8
feat(spawn): event fire on spawn start / finish
Dhruv-0-Arora Aug 13, 2026
2c7b54a
fix(onboarding): allow 'next' when condition met
Dhruv-0-Arora Aug 13, 2026
df5fc2d
chore(cleanup): remove dead unconfirmedImport
Dhruv-0-Arora Aug 13, 2026
9f9a96d
chore(onboarding): tour copy and extra code cleanup
Dhruv-0-Arora Aug 13, 2026
d0c85c4
refactor(onboarding): redesigning the back button to look more like t…
Dhruv-0-Arora Aug 13, 2026
f6f8b0b
refactor(cleanup): name escape priorities & code cleanup
Dhruv-0-Arora Aug 13, 2026
9d9c3f3
docs(cleanup): adding clarifying comments
Dhruv-0-Arora Aug 13, 2026
15b961b
refactor(onboarding): renaming modules to PascalCase
Dhruv-0-Arora Aug 13, 2026
f268b76
fix(onboarding): exit tour on esc
Dhruv-0-Arora Aug 13, 2026
8c30c73
fix(onboarding): advance past already completed tour steps
Dhruv-0-Arora Aug 13, 2026
da8483d
test(onboarding): use stable ids for tour steps and scrims
Dhruv-0-Arora Aug 13, 2026
aa852ff
Merge remote-tracking branch 'origin/dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 13, 2026
da4312c
Merge branch 'dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 13, 2026
d234f91
fix(onboarding): skip spawn steps for assets already in scene
Dhruv-0-Arora Aug 14, 2026
ca12619
Merge branch 'dev' into darora1/218/ui-tour
Dhruv-0-Arora Aug 14, 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
9 changes: 9 additions & 0 deletions fission/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@
"linter": {
"enabled": false
}
},
{
"includes": ["src/systems/multiplayer/bindings/**/*"],
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
}
}
],
"assist": {
Expand Down
3 changes: 2 additions & 1 deletion fission/manifest.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type ManifestFileType = Record<"robots" | "private" | "fields", { filename: string; hash: string }[]>
export type ManifestFileEntry = { filename: string; hash: string; year?: number; thumbnail?: string }
export type ManifestFileType = Record<"robots" | "private" | "fields", ManifestFileEntry[]>
4 changes: 2 additions & 2 deletions fission/public/assetpack.zip
Git LFS file not shown
37 changes: 21 additions & 16 deletions fission/src/Synthesis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { StateProvider } from "./ui/StateProvider.tsx"
import { ThemeProvider } from "./ui/ThemeProvider.tsx"
import { UIProvider } from "./ui/UIProvider.tsx"
import CommandPalette from "@/ui/components/CommandPalette.tsx"
import { TourProvider } from "./ui/tour/TourProvider.tsx"
import TourOverlay from "./ui/tour/TourOverlay.tsx"
import SessionStorage, { applyAutoToast } from "@/util/SessionStorage.ts"
import { globalOpenModal } from "@/components/GlobalUIControls.ts"
import { startMultiplayerWorld } from "@/ui/helpers/StartMultiplayerWorld.ts"
Expand Down Expand Up @@ -100,23 +102,26 @@ const Synthesis = () => {
>
<StateProvider>
<UIProvider>
<Scene useStats={import.meta.env.DEV} key="scene-in-toast-provider" />
<TouchControls />
<SceneOverlay />
<ContextMenu />
<MainHUD key={"main-hud"} />
<UIRenderer />
<CommandPalette />
<ProgressNotifications key={"progress-notifications"} />
<Stack direction={"column"} gap={1} position={"absolute"} bottom={0} left={0}>
<DragModeIndicator />
<MultiplayerHUD />
</Stack>
<PortraitOverlay />
<TourProvider>
<Scene useStats={import.meta.env.DEV} key="scene-in-toast-provider" />
<TouchControls />
<SceneOverlay />
<ContextMenu />
<MainHUD key={"main-hud"} />
<UIRenderer />
<CommandPalette />
<ProgressNotifications key={"progress-notifications"} />
<Stack direction={"column"} gap={1} position={"absolute"} bottom={0} left={0}>
<DragModeIndicator />
<MultiplayerHUD />
</Stack>
<PortraitOverlay />
<TourOverlay />

{!consentPopupDisable && (
<AnalyticsConsent onClose={onDisableConsent} onConsent={onConsent} />
)}
{!consentPopupDisable && (
<AnalyticsConsent onClose={onDisableConsent} onConsent={onConsent} />
)}
</TourProvider>
</UIProvider>
</StateProvider>
</SnackbarProvider>
Expand Down
7 changes: 6 additions & 1 deletion fission/src/mirabuf/DefaultAssetLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { type MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader.ts"
import type { ManifestFileType } from "../../manifest.d.ts"
import { API_URL } from "@/util/Consts.ts"

export type DefaultAssetInfo = Required<Pick<MirabufCacheInfo, "hash" | "remotePath" | "miraType" | "name">>
export type DefaultAssetInfo = Required<Pick<MirabufCacheInfo, "hash" | "remotePath" | "miraType" | "name">> & {
year?: number
thumbnail?: string
}

class DefaultAssetLoader {
private static _assets: DefaultAssetInfo[] = []
Expand Down Expand Up @@ -34,6 +37,8 @@ class DefaultAssetLoader {
hash: obj.hash,
miraType,
name: obj.filename,
year: obj.year,
thumbnail: obj.thumbnail ? `${baseUrl}/${dir}/${obj.thumbnail}` : undefined,
})
})
})
Expand Down
14 changes: 14 additions & 0 deletions fission/src/mirabuf/MiraType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Kind of Mirabuf assembly.
*
* Kept in its own leaf module: `MirabufLoader` initializes its storage backend with a
* top-level `await`, so importing this enum from there makes the importer wait on that
* async module. Modules that need only the enum at evaluation time (e.g. the tour step
* table) would otherwise observe it as `undefined` when an import cycle is involved.
*
* The numeric values are persisted in the localStorage asset cache - do not renumber.
*/
export enum MiraType {
ROBOT = 1,
FIELD,
}
117 changes: 25 additions & 92 deletions fission/src/mirabuf/MirabufLoader.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { type Data, downloadData } from "@/aps/APSDataManagement"
import { globalAddToast, globalOpenPanel } from "@/components/GlobalUIControls"
import { globalAddToast } from "@/components/GlobalUIControls"
import { mirabuf } from "@/proto/mirabuf"
import World from "@/systems/World"
import { type MirabufStorageBackend, initStorageBackend } from "@/mirabuf/MirabufStorageBackend"
import { MiraType } from "@/mirabuf/MiraType"
import { hashBuffer, unzipMira } from "@/util/Utility.ts"
import InitialConfigPanel from "@/panels/configuring/initial-config/InitialConfigPanel.tsx"
import { PAUSE_REF_ASSEMBLY_SPAWNING } from "@/systems/physics/PhysicsTypes.ts"
import { createMirabuf } from "@/mirabuf/MirabufSceneObject.ts"
import { getTargetControls } from "@/systems/scene/CameraControls.ts"
import { ProgressHandle } from "@/components/ProgressNotificationData.ts"
import type { EncodedAssembly, Message } from "@/systems/multiplayer/MultiplayerTypes"
import { consolePrefixer } from "console-prefixer"
import { detectAndTagWheels } from "@/systems/simulation/synthesis_brain/WheelDetector"
import { consolePrefixer } from "console-prefixer"

const console = consolePrefixer({
defaultPrefix: {
Expand All @@ -28,7 +23,15 @@ export interface MirabufCacheInfo {
name: string
miraType: MiraType
remotePath?: string
thumbnailStorageID?: string
year?: number
thumbnail?: string
}

export interface CacheRemoteOptions {
name?: string
expectedHash?: string
year?: number
thumbnail?: string
}

export interface MirabufRemoteInfo {
Expand Down Expand Up @@ -165,23 +168,23 @@ class MirabufCachingService {
*
* @param {string} fetchLocation Location of Mirabuf file.
* @param {MiraType} miraType Type of Mirabuf Assembly.
* @param {string} name Optional display name for the cached file.
* @param {CacheRemoteOptions} options Optional metadata to store alongside the cached file.
*
* @returns {Promise<MirabufCacheInfo | undefined>} Promise with the result of the promise. Metadata on the mirabuf file if successful, undefined if not.
*/
public static async cacheRemote(
fetchLocation: string,
miraType: MiraType,
name?: string,
expectedHash?: string
options: CacheRemoteOptions = {}
): Promise<MirabufCacheInfo | undefined> {
const { expectedHash, year, thumbnail } = options
try {
// grab file remote
const resp = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined)
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`)

const miraBuff = await resp.arrayBuffer()
name ??= this.assemblyFromBuffer(miraBuff).info?.name ?? fetchLocation
const name = options.name ?? this.assemblyFromBuffer(miraBuff).info?.name ?? fetchLocation

World.analyticsSystem?.event("Remote Download", {
assemblyName: name,
Expand All @@ -195,6 +198,8 @@ class MirabufCachingService {
miraType,
name,
remotePath: fetchLocation,
year,
thumbnail,
},
expectedHash
)
Expand All @@ -214,6 +219,8 @@ class MirabufCachingService {
hash: await hashBuffer(miraBuff),
miraType: miraType,
name: name,
year,
thumbnail,
}
} catch (e) {
console.warn("Caching failed", e)
Expand Down Expand Up @@ -360,6 +367,10 @@ class MirabufCachingService {
return this._cacheMap.getAll(miraType)
}

public static has(hash: string): boolean {
return this._cacheMap.get(hash) != null
}

/**
* Removes a given Mirabuf item from the cache
*/
Expand Down Expand Up @@ -463,84 +474,6 @@ class MirabufCachingService {
}
}

export enum MiraType {
ROBOT = 1,
FIELD,
}
export { MiraType }

export default MirabufCachingService

export async function spawnCachedMira(
info: MirabufCacheInfo,
progressHandle: ProgressHandle = new ProgressHandle(info.name)
) {
// If spawning a field, then remove all other fields
if (info.miraType === MiraType.FIELD) {
if (World.multiplayerSystem != null && World.sceneRenderer.mirabufSceneObjects.getField() != null) {
globalAddToast("warning", "Cannot spawn a second field!")
progressHandle.fail("Cannot spawn a second field")
return
}
World.sceneRenderer.removeAllFields()
}

World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_SPAWNING)
await MirabufCachingService.get(info.hash)
.then(async assembly => {
if (!assembly) {
progressHandle.fail()
console.error("Failed to spawn robot")

return
}

await createMirabuf(info.hash, assembly, progressHandle).then(async mirabufSceneObject => {
if (!mirabufSceneObject) {
progressHandle.fail("No object!")
return
}

World.sceneRenderer.registerSceneObject(mirabufSceneObject)

const targetControls = getTargetControls()

if (World.multiplayerSystem != null) {
const encodedAssembly =
mirabufSceneObject.miraType !== MiraType.FIELD
? (mirabuf.Assembly.encode(assembly).finish() as EncodedAssembly)
: undefined

const message: Message = {
type: "newObject",
timestamp: Date.now(),
data: {
sceneObjectId: mirabufSceneObject.id,
assembly: encodedAssembly,
assemblyHash: info.hash,
miraType: info.miraType,
initialPreferences: mirabufSceneObject.getPreferenceData(),
},
}
World.multiplayerSystem?.broadcast(message)
World.multiplayerSystem?.registerOwnSceneObject(mirabufSceneObject.id)
}

if (targetControls && (info.miraType === MiraType.ROBOT || !targetControls.focusProvider)) {
targetControls.focusProvider = mirabufSceneObject
}

progressHandle.done()
World.physicsSystem.deactivateGamepieces()
if (mirabufSceneObject.miraType == MiraType.ROBOT) {
globalOpenPanel(InitialConfigPanel, undefined)
}
})
})
.catch(e => {
console.error(e)
progressHandle.fail()
})
.finally(() => {
setTimeout(() => World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_SPAWNING), 500)
})
}
16 changes: 11 additions & 5 deletions fission/src/mirabuf/MirabufSceneObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier {
public readonly mirabufInstance: MirabufInstance
public readonly mechanism: Mechanism

public assemblyHash?: string

private _brain: Brain | undefined
public alliance: Alliance | undefined
public station: Station | undefined
Expand Down Expand Up @@ -1443,9 +1445,10 @@ export async function createMirabuf(
): Promise<MirabufSceneObject | undefined> {
const parser = new MirabufParser(assembly, progressHandle)

if (!parser.assembly.info?.GUID?.match(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/)) {
await migrateUUID(parser, hash)
}
const resolvedHash = parser.assembly.info?.GUID?.match(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/)
? hash
: ((await migrateUUID(parser, hash)) ?? hash)

if (parser.maxErrorSeverity >= ParseErrorSeverity.UNIMPORTABLE) {
console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`)
return
Expand All @@ -1456,10 +1459,12 @@ export async function createMirabuf(
progressHandle?.update("Created Mirabuf Instance", URDFImportProgressBar.MIRABUF_INSTANCE)
await yieldToMain()

return new MirabufSceneObject(mirabufInstance, progressHandle, multiplayerOwnerId)
const sceneObject = new MirabufSceneObject(mirabufInstance, progressHandle, multiplayerOwnerId)
sceneObject.assemblyHash = MirabufCachingService.has(resolvedHash) ? resolvedHash : undefined
return sceneObject
}

async function migrateUUID(parser: MirabufParser, hash: string) {
async function migrateUUID(parser: MirabufParser, hash: string): Promise<string | undefined> {
parser.assembly.info ??= {}
const newGUID = uuidV4({ random: hexStringToUint8Array(hash).slice(0, 16) }) // using deterministic random to prevent the same model from being assigned different uuids after being imported multiple times. Once initially set, uuid will be persistent across hash changes
console.warn("Migrating UUID", parser.assembly.info.GUID, "->", newGUID)
Expand All @@ -1477,6 +1482,7 @@ async function migrateUUID(parser: MirabufParser, hash: string) {
if (cacheInfo == null) {
globalAddToast("warning", "Migration Error", "Importing failed to save")
}
return cacheInfo?.hash
}
/**
* Body association to a rigid node with a given mirabuf scene object.
Expand Down
Loading
Loading