{"id":51265926,"url":"https://github.com/machbase/neo-humanoid-demo","last_synced_at":"2026-06-29T15:01:50.946Z","repository":{"id":362603810,"uuid":"1259936553","full_name":"machbase/neo-humanoid-demo","owner":"machbase","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-05T02:53:00.000Z","size":9852,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T04:20:32.867Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/machbase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-05T02:19:29.000Z","updated_at":"2026-06-05T02:53:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/machbase/neo-humanoid-demo","commit_stats":null,"previous_names":["machbase/neo-humanoid-demo"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/machbase/neo-humanoid-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machbase%2Fneo-humanoid-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machbase%2Fneo-humanoid-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machbase%2Fneo-humanoid-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machbase%2Fneo-humanoid-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/machbase","download_url":"https://codeload.github.com/machbase/neo-humanoid-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machbase%2Fneo-humanoid-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34931592,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T15:01:48.341Z","updated_at":"2026-06-29T15:01:50.935Z","avatar_url":"https://github.com/machbase.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Unitree Physical AI Simulation by Machbase Neo\n\nHumanoid Everyday 데이터를 Machbase Neo에 저장하고, 브라우저에서 휴머노이드 로봇의 3D 동작, 관절/IMU/손압력/오도메트리 센서 값, 주변 공간 포인트를 재생하는 데모입니다.\n\n데모 목적은 Physical AI 센서 타임라인을 Machbase Neo에서 빠르게 저장/조회하고, 웹에서 시간축을 자유롭게 이동하며 로봇 동작과 센서 변화를 확인하는 것입니다.\n\n## 데이터셋\n\n사용 데이터셋:\n\n```text\nHumanoid Everyday\nhttps://github.com/physical-superintelligence-lab/Humanoid-Everyday\n```\n\nHumanoid Everyday는 Unitree G1/H1 실제 로봇의 작업 수행 데이터를 포함합니다. 주요 센서는 관절 상태, IMU, 오도메트리, 손 센서/action, RGB, depth, LiDAR입니다. 전체 데이터셋은 매우 크므로 이 데모는 `data/catalog/humanoid-category-tasks.json`에 정의된 대표 task 35개를 사용하고, 각 task의 첫 번째 episode 1개만 Machbase Neo에 적재합니다.\n\n## 실행 환경\n\n이 문서는 명령 실행 위치를 분리해서 표기합니다.\n\n| 표기 | 의미 | 사용하는 명령 |\n|---|---|---|\n| Linux shell | OS 터미널 | `git`, `unzip`, `curl`, `ss`, 환경변수 설정 |\n| JSH shell/runtime | Machbase Neo JSH 내부 shell 또는 `machbase-neo jsh` | `/work/scripts/*.js`, `/work/app/server.js` |\n\n중요:\n\n- JavaScript 코드는 Machbase Neo JSH에서 실행합니다.\n- JSH shell은 Linux shell이 아니므로 `export`, `unzip`, redirection 같은 OS shell 명령을 직접 실행하지 않습니다.\n- 이 package는 Machbase Neo 설치 디렉토리의 `public/neo-humanoid-demo` 아래에 있고, JSH에서는 package root가 `/work`로 보인다고 가정합니다.\n- Linux shell에서 한 번씩 검증 실행할 때는 `machbase-neo jsh -v /work=\"$(pwd)\" ...`를 사용합니다. 이 방식도 Node.js가 아니라 Machbase Neo JSH 런타임입니다.\n- Neo HTTP shell 클라이언트로 JSH에 접속할 때는 HTTP 포트 `5654`를 사용합니다. DB 접속 포트 `5656`은 `machcli`가 쓰는 native DB 포트입니다.\n\n기본 DB 접속값:\n\n```text\nhost: 127.0.0.1\nport: 5656\nuser: sys\npassword: manager\nHTTP: 5654\n```\n\n다른 접속값을 써야 하면 Machbase Neo/JSH를 시작하기 전에 Linux shell에서 설정합니다.\n\n```sh\nexport PHY_DB_HOST=127.0.0.1\nexport PHY_DB_PORT=5656\nexport PHY_DB_USER=sys\nexport PHY_DB_PASSWORD=manager\n```\n\nJSH를 Linux shell에서 한 번 실행하는 예:\n\n```sh\nexport NEO_HOME=\u003cNEO_HOME\u003e\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\n\"$NEO_HOME/machbase-neo\" jsh -v /work=\"$(pwd)\" \\\n  /work/scripts/check-data.js --data-root data/raw/humanoid-everyday\n```\n\nJSH shell에 접속해서 여러 명령을 실행하는 예:\n\n```sh\nexport NEO_HOME=\u003cNEO_HOME\u003e\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\n\"$NEO_HOME/machbase-neo\" shell \\\n  -server 127.0.0.1:5654 \\\n  -user sys \\\n  -password manager \\\n  -v /work=\"$(pwd)\"\n```\n\nJSH prompt 안에서는 `/work` 기준으로 실행합니다. shell 클라이언트 종류에 따라 `cd`가 지원되지 않을 수 있으므로, 서버처럼 하위 디렉토리의 스크립트는 `/work/app/server.js`처럼 절대 경로로 실행하면 안전합니다.\n\n## 전체 흐름\n\n1. Linux shell에서 `\u003cNEO_HOME\u003e` 아래에 repository를 준비합니다.\n2. JSH shell에서 Google Sheet 기반 catalog를 생성합니다.\n3. Linux shell에서 대표 task ZIP을 다운로드하고 첫 번째 episode만 압축 해제합니다.\n4. JSH shell에서 데이터 구조를 확인합니다.\n5. JSH shell에서 스키마 생성과 ingest를 실행합니다.\n6. JSH shell에서 데모 서버를 실행하고 브라우저로 접속합니다.\n\n## 1. 프로젝트 준비\n\nLinux shell:\n\n```sh\ncd \u003cNEO_HOME\u003e/public\ngit clone \u003crepository-url\u003e neo-humanoid-demo\ncd neo-humanoid-demo\ngit status\n```\n\nJSH shell에서 보이는 경로:\n\n```text\n/work\n```\n\n주요 디렉토리:\n\n```text\napp/             JSH HTTP server\ncgi-bin/         Machbase package CGI entrypoints\nlib/             DB/API/Humanoid Everyday helper code\nscripts/         download, schema, check, ingest scripts\npublic/          browser frontend\ndata/            local dataset directory\n```\n\n## 2. 데이터 다운로드\n\n대표 task catalog는 Humanoid Everyday Google Sheet에서 category별 첫 5개씩 뽑아 만듭니다. 현재 catalog는 7개 category x 5개 task = 35개 task입니다.\n\nJSH shell:\n\n```text\n/work \u003e ./scripts/build-catalog-from-sheet.js --per-category 5 --out data/catalog/humanoid-category-tasks.json\n```\n\n현재 category 분포:\n\n```text\narticulate_object        5\nbasic_manipulation      5\ndeformable_object       5\nhigh_precision          5\nhuman_robot_interaction 5\nloco_manipulation       5\ntool_use                5\n```\n\nLinux shell에서 catalog의 ZIP을 다운로드하고 각 ZIP의 첫 번째 episode만 추출합니다. 이 명령은 기존 archive가 있으면 재사용하고, 새로 받은 archive는 추출 후 기본적으로 삭제합니다.\n\n먼저 첫 task 1개로 다운로드와 압축 해제를 검증합니다. 2026-06-07 로컬 검증에서 첫 archive(`press_the_stapler_g1`)는 약 2.4GB를 다운로드했고, 첫 episode 추출 후 로컬 데이터는 약 111MB였습니다.\n\n```sh\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\nKEEP_NEW_ARCHIVES=0 LIMIT=1 scripts/prepare-catalog-episodes.sh \\\n  data/catalog/humanoid-category-tasks.json \\\n  data/raw/humanoid-everyday\n```\n\n첫 task가 정상 준비되면 전체 catalog를 처리합니다. 전체 35개 task도 각 task ZIP 전체를 한 번씩 내려받아 첫 episode만 추출하므로 네트워크 시간과 임시 디스크 여유가 필요합니다. 2026-06-07 로컬 full 검증에서는 `KEEP_NEW_ARCHIVES=0` 기준으로 최종 raw 데이터가 약 12GB였고, 다운로드 완료 후 archive 잔여 파일은 0개였습니다.\n\n```sh\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\nKEEP_NEW_ARCHIVES=0 scripts/prepare-catalog-episodes.sh \\\n  data/catalog/humanoid-category-tasks.json \\\n  data/raw/humanoid-everyday\n```\n\n유용한 옵션:\n\n```text\nKEEP_NEW_ARCHIVES=1  새로 받은 ZIP을 추출 후에도 보관\nOVERWRITE=1          이미 추출된 episode를 다시 다운로드/추출\nLIMIT=1              START_AT부터 지정 개수만 처리\nSTART_AT=10          catalog index 10부터 시작\n```\n\n## 3. OpenHE 모션 데이터 추가\n\n선택 데이터셋:\n\n```text\nOpenHE Unitree G1 Retargeted Motion Dataset\nhttps://huggingface.co/datasets/openhe/g1-retargeted-motions\n```\n\nOpenHE 데이터는 Unitree G1용 30 FPS retargeted motion `.pkl` 파일입니다. 원본에는 LiDAR/RGB/depth가 없고, root pose, quaternion, 23 DOF joint, foot contact가 들어 있습니다. 이 데모에서는 Linux shell에서 `.pkl`을 내려받아 기존 `data.json` episode 구조로 변환한 뒤, JSH ingest로 timeline에 append합니다.\n\nLinux shell:\n\n```sh\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\nscripts/prepare-openhe-motion.sh lafan1_retargeted/walk1_subject1.pkl\n```\n\n기본 출력:\n\n```text\ndata/raw/openhe/g1-retargeted-motions/lafan1_retargeted/walk1_subject1.pkl\ndata/raw/openhe-converted/openhe_walk1_subject1/episode_0/data.json\n```\n\n스크립트는 `/tmp/neo-openhe-venv`에 Python venv를 만들고 `numpy`, `joblib`을 설치해 pickle을 변환합니다. task prefix는 기본값 `openhe`이며, 필요하면 `TASK_PREFIX=\u003cprefix\u003e`로 바꿀 수 있습니다.\n\nJSH shell에서 기존 sequence 뒤에 이어 붙입니다:\n\n```text\n/work \u003e ./scripts/ingest.js \\\n  --data-root data/raw/openhe-converted \\\n  --dataset humanoid-everyday \\\n  --sequence humanoid-everyday-10m \\\n  --minutes 0 \\\n  --append \\\n  --flush-every 500 \\\n  --point-frame-stride 999999\n```\n\n주의: `--append`는 현재 sequence의 마지막 `frame_id/time` 뒤에 새 frame을 이어 붙입니다. 같은 변환 데이터를 반복 실행하면 같은 motion이 다시 append되므로, 재구성하려면 `./scripts/reset-schema.js` 후 기본 Humanoid Everyday ingest부터 다시 실행합니다.\n\n이번 OpenHE 로컬 추가 검증 결과:\n\n```text\nsource: openhe/g1-retargeted-motions lafan1_retargeted/walk1_subject1.pkl\ntask: openhe_walk1_subject1\nframes: 7840\ndurationMinutes: 4.36\npointFrames: 0\nsequence frameCount: 23645\nepisodeCount: 36\nframe range: 15805..23644\n```\n\nOpenHE motion은 UI의 `Motion` select 첫 번째 항목에 `openhe_motion / openhe_walk1_subject1`로 표시됩니다. OpenHE `walk1`은 직선 보행이 아니라 루프/곡선 보행에 가까우므로, 브라우저 시각화는 episode 시작점에서 전체 끝점까지의 벡터가 아니라 시작 후 약 240 frame 지점까지의 초반 이동 벡터를 전진축으로 사용합니다. LiDAR point는 원본에 없으므로 센서 패널에서 `0 points`로 표시되는 것이 정상입니다.\n\n## 4. 압축 해제 결과\n\n전체 ZIP을 모두 보관하거나 풀면 매우 큰 공간이 필요합니다. 위 준비 스크립트는 각 대표 task에서 `data.json`이 있는 첫 번째 episode prefix만 찾아 표준 위치로 옮깁니다.\n\n데모가 기대하는 episode 구조:\n\n```text\ndata/raw/humanoid-everyday/\u003ctask\u003e/episode_0/data.json\ndata/raw/humanoid-everyday/\u003ctask\u003e/episode_0/color/*.jpg 또는 *.png\ndata/raw/humanoid-everyday/\u003ctask\u003e/episode_0/depth/*\ndata/raw/humanoid-everyday/\u003ctask\u003e/episode_0/lidar/*.pcd\n```\n\n`data.json`은 배열이거나 `data`, `steps`, `frames`, `episode` 중 하나의 배열 필드를 포함할 수 있습니다. 각 step에서 가능한 센서 필드를 읽고, 파일 경로가 명시되지 않은 RGB/depth/LiDAR는 `color/`, `depth/`, `lidar/` 디렉토리의 정렬 순서를 frame index에 맞춰 사용합니다.\n\n## 5. 데이터 확인\n\nJSH shell:\n\n```text\n/work \u003e ./scripts/check-data.js --data-root data/raw/humanoid-everyday\n```\n\n확인할 항목:\n\n```text\nepisodes              발견한 episode 수\nsteps                 전체 step 수\ndurationMinutes       30Hz 기준 또는 data.json time 기준 누적 길이\nmedia.lidar           PCD LiDAR frame 수\nmedia.rgb             RGB frame 수\nmedia.depth           depth frame 수\n```\n\n첫 task smoke 검증 결과:\n\n```text\nepisodes: 1\nsteps: 445\ndurationMinutes: 0.25\nmedia.lidar: 405\nmedia.rgb: 445\nmedia.depth: 445\n```\n\n전체 35개 task 준비를 끝내면 `episodes`가 35개 이상이어야 합니다. 기존에 다른 task를 풀어 둔 경우 `check-data.js`의 episode 수는 더 크게 보일 수 있지만, 아래 ingest 명령은 `--catalog-only`로 catalog task만 적재합니다.\n\n2026-06-07 전체 catalog 검증 결과:\n\n```text\nepisodes: 35\nsteps: 15805\ndurationMinutes: 8.76\nmedia.lidar: 14179\nmedia.rgb: 15805\nmedia.depth: 15805\n```\n\n## 6. 스키마 생성\n\nJSH shell:\n\n```text\n/work \u003e ./scripts/schema.js\n```\n\n생성 테이블:\n\n```text\nPHY_TIMELINE\nPHY_POINT_FRAME\nPHY_EPISODE_INDEX\n```\n\n주요 저장 관계:\n\n| 원본 | Machbase 저장 |\n|---|---|\n| `data.json` step | `PHY_TIMELINE.value` JSON |\n| step time 또는 30Hz frame time | `PHY_TIMELINE.time`, `PHY_POINT_FRAME.time` |\n| `lidar/*.pcd` | `PHY_POINT_FRAME.value` binary |\n| RGB/depth 파일 | `PHY_TIMELINE.value.media` 상대 경로 |\n| episode 요약 | `PHY_EPISODE_INDEX` |\n\n스키마만 출력:\n\n```text\n/work \u003e ./scripts/schema.js --print\n```\n\n초기화가 필요할 때:\n\n```text\n/work \u003e ./scripts/reset-schema.js\n```\n\n## 7. 데이터 적재\n\n대표 catalog만 적재합니다. 각 task에서 첫 번째 episode 1개만 사용하고, 로봇 timeline은 모든 프레임을 저장하되 LiDAR point cloud는 10프레임마다 저장합니다.\n\n```text\n/work \u003e ./scripts/reset-schema.js\n/work \u003e ./scripts/ingest.js \\\n  --data-root data/raw/humanoid-everyday \\\n  --catalog data/catalog/humanoid-category-tasks.json \\\n  --catalog-only \\\n  --episode-limit-per-task 1 \\\n  --minutes 0 \\\n  --flush-every 200 \\\n  --point-stride 4 \\\n  --point-frame-stride 10\n```\n\n옵션 의미:\n\n```text\n--catalog-only             catalog에 정의된 대표 task만 적재\n--episode-limit-per-task 1 task별 첫 번째 episode 1개만 적재\n--minutes 0                시간 제한 없이 위 조건을 만족하는 전체 episode 적재\n--point-stride 4           PCD point를 4개마다 1개 저장\n--point-frame-stride 10    LiDAR point frame을 10프레임마다 저장\n```\n\n첫 task smoke 적재 검증 결과:\n\n```json\n{\n  \"ok\": true,\n  \"dataset\": \"humanoid-everyday\",\n  \"sequence\": \"humanoid-everyday-10m\",\n  \"sourceEpisodes\": 1,\n  \"frames\": 445,\n  \"pointFrames\": 39,\n  \"tasks\": 1,\n  \"episodeLimitPerTask\": 1,\n  \"pointStride\": 4,\n  \"pointFrameStride\": 10,\n  \"durationMs\": 14833\n}\n```\n\n2026-06-07 전체 catalog 적재 검증 결과:\n\n```json\n{\n  \"ok\": true,\n  \"dataset\": \"humanoid-everyday\",\n  \"sequence\": \"humanoid-everyday-10m\",\n  \"sourceEpisodes\": 35,\n  \"frames\": 15805,\n  \"pointFrames\": 1428,\n  \"tasks\": 35,\n  \"episodeLimitPerTask\": 1,\n  \"pointStride\": 4,\n  \"pointFrameStride\": 10,\n  \"durationMs\": 526833\n}\n```\n\n`sourceEpisodes`는 raw 디렉토리에서 발견한 전체 episode 수입니다. 전체 catalog 준비 후 기본 Humanoid Everyday ingest를 실행하면 API와 UI에는 `--catalog-only`로 적재된 35개 episode가 표시됩니다. OpenHE append까지 실행하면 API와 UI에는 36개 episode가 표시됩니다.\n\n기존 timeline을 유지한 채 episode index만 다시 만들 때:\n\n```text\n/work \u003e ./scripts/rebuild-episode-index.js \\\n  --dataset humanoid-everyday \\\n  --sequence humanoid-everyday-10m\n```\n\n`/api/episodes`는 초기 화면에서 이 index table을 우선 사용합니다. index가 없으면 기존처럼 `PHY_TIMELINE.value` 전체 JSON을 scan해서 episode 목록을 만들기 때문에 초기 `Initializing` 시간이 길어질 수 있습니다. 이번 로컬 기준으로 전체 timeline scan 방식은 약 8.6초, `PHY_EPISODE_INDEX` 사용 방식은 약 0.01초가 걸렸습니다.\n\n기본 dataset/sequence:\n\n```text\ndataset: humanoid-everyday\nsequence: humanoid-everyday-10m\n```\n\n현재 35개 대표 episode의 실제 길이는 약 8.76분입니다. `humanoid-everyday-10m`은 sequence 이름으로 유지되는 식별자입니다.\n\nPCD LiDAR는 API가 바로 전송할 수 있도록 점 하나를 16 bytes의 little-endian float32 값으로 저장합니다.\n\n```text\nx float32\ny float32\nz float32\nintensity float32\n```\n\nPCD에 intensity 필드가 없으면 `1.0`으로 저장합니다.\n\n## 8. 데모 서버 실행\n\nJSH shell:\n\n```text\n/work \u003e /work/app/server.js --host 127.0.0.1 --port 56802\n```\n\n또는 Linux shell에서 JSH 런타임으로 바로 실행합니다.\n\n```sh\nexport NEO_HOME=\u003cNEO_HOME\u003e\ncd \u003cNEO_HOME\u003e/public/neo-humanoid-demo\n\"$NEO_HOME/machbase-neo\" jsh -v /work=\"$(pwd)\" \\\n  /work/app/server.js --host 127.0.0.1 --port 56802\n```\n\n기대 출력:\n\n```text\nneo-humanoid-demo server started tcp 127.0.0.1:56802\n```\n\n브라우저:\n\n```text\nhttp://127.0.0.1:56802/\n```\n\nNeo file explorer 또는 package 화면에서 `public/index.html`을 직접 열어 사용할 때는 URL에 `?apiBase=http://127.0.0.1:56802`를 붙입니다. `/db/tql/...` 경로는 TQL 실행 API이므로 이 HTML 파일을 여는 경로로 사용하지 않습니다.\n\n## API 확인\n\nLinux shell:\n\n```sh\ncurl http://127.0.0.1:56802/api/health\ncurl http://127.0.0.1:56802/api/manifest\ncurl http://127.0.0.1:56802/api/episodes\ncurl 'http://127.0.0.1:56802/api/frame?frameId=100'\ncurl 'http://127.0.0.1:56802/api/points?frameId=100\u0026lod=2'\ncurl 'http://127.0.0.1:56802/api/export/timeline-query?episode=0'\ncurl -D - 'http://127.0.0.1:56802/api/export/episode.zip?episode=0' -o /tmp/neo-humanoid-episode0.zip\n```\n\n첫 task smoke 적재 후 `/api/episodes`는 `episodeCount: 1`을 반환합니다. 대표 catalog 전체 적재가 끝나면 `episodeCount: 35`, OpenHE append까지 끝나면 `episodeCount: 36`을 반환합니다. 각 episode에는 `task`, `category`, `robotType`이 포함됩니다.\n\n2026-06-07 전체 catalog API 검증 결과:\n\n```text\n/api/manifest        frameCount: 15805, durationMs: 526800, source: machbase\n/api/episodes        episodeCount: 35\n/api/frame?frameId=100  ok: true, sourceEpisode: align_a_stack_of_books/episode_0\n/api/points.bin?frameId=100\u0026lod=2  HTTP 200, x-neo-point-count: 142\n/api/export/episode.zip?episode=0  HTTP 200, manifest/timeline/lidar files 포함\n```\n\n`/api/points.bin`은 browser UI가 우선 사용하는 binary endpoint입니다.\n\n```sh\ncurl -D - 'http://127.0.0.1:56802/api/points.bin?frameId=100\u0026lod=2' -o /tmp/neo-humanoid-points.bin\n```\n\n응답 header:\n\n```text\nx-neo-source\nx-neo-lod\nx-neo-frame-id\nx-neo-point-count\nx-neo-byte-count\n```\n\n센서 데이터 다운로드:\n\n- UI의 `Download sensors`는 선택된 episode의 센서 데이터를 ZIP으로 받습니다.\n- UI의 `Copy query`는 같은 episode를 Machbase Neo HTTP API `/db/query`에서 NDJSON으로 받을 수 있는 URL을 복사합니다.\n- ZIP에는 `manifest.json`, `timeline.ndjson`, `lidar.frames.ndjson`, `lidar/*.xyzi.bin`이 포함됩니다.\n- `timeline.ndjson`은 `PHY_TIMELINE`의 한 frame당 한 줄 JSON이고, `lidar/*.xyzi.bin`은 점 하나를 little-endian float32 `x,y,z,intensity` 16 bytes로 저장합니다.\n- 직접 링크는 Machbase Neo HTTP Query API 형식인 `/db/query?q=...\u0026format=ndjson\u0026compress=gzip\u0026timeformat=ns`를 사용합니다. 참고: https://docs.machbase.com/neo/api-http/query/\n\n공간 포인트 downsample 정책:\n\n```text\nLOD 0: 원본 point에 가장 가까움\nLOD 1: 4개 중 1개 point 사용\nLOD 2: 12개 중 1개 point 사용\n```\n\nDB에는 LOD별 데이터를 따로 저장하지 않고, API가 요청 시 downsample합니다. `--point-frame-stride`로 일부 프레임에만 LiDAR를 저장한 경우 `/api/points.bin`은 같은 episode 안에서 요청 프레임 이전의 가장 가까운 point frame을 재사용합니다. OpenHE처럼 원본에 LiDAR가 없는 episode는 `0 points`를 반환합니다.\n\n## 화면 구성\n\n브라우저 데모는 다음을 표시합니다.\n\n- Unitree G1 공식 mesh 기반 3D 휴머노이드와 시간별 관절 동작\n- 로봇 주변 공간 포인트와 센서 시야\n- frame, robot type, episode/source step, speed\n- 관절 activity bar, IMU attitude, hand pressure strip\n- RGB/depth/LiDAR media 상태\n- Machbase query latency\n- task/category 선택, episode 선택, 재생/정지와 timeline 이동\n- Follow, High Follow, Orbit, Top, Front, Side, Rear, Shoulder 카메라 view point 선택\n\n## 3D 모델 리소스\n\n브라우저 렌더링에는 Unitree의 공식 G1 29DOF URDF/STL visual asset을 사용합니다.\n\n```text\nsource: https://github.com/unitreerobotics/unitree_ros\nfiles:\n  robots/g1_description/g1_29dof_rev_1_0.urdf\n  robots/g1_description/meshes/*.STL\nlocal:\n  public/assets/robots/unitree_g1/\nlicense: BSD 3-Clause\n```\n\n`public/unitree-g1.js`는 브라우저에서 URDF를 파싱하고 STL mesh를 로드한 뒤, Humanoid Everyday의 `leg_state`와 `arm_state` 배열을 G1 joint 이름에 매핑합니다. 모델 로딩에 실패하면 `public/app.js`의 procedural humanoid가 fallback으로 그대로 표시됩니다.\n\n## 문제 해결\n\n데이터가 없는데 UI가 뜨는 경우:\n\n```text\nsourceLabel이 synthetic fallback until data is ingested 로 표시됩니다.\n```\n\nMachbase DB 연결 실패:\n\n```text\nPHY_DB_HOST, PHY_DB_PORT, PHY_DB_USER, PHY_DB_PASSWORD 값을 확인합니다.\nMachbase Neo DB가 127.0.0.1:5656에서 실행 중인지 확인합니다.\n```\n\n대표 task가 35개보다 적게 보이는 경우:\n\n```text\n/work/scripts/check-data.js --data-root data/raw/humanoid-everyday\ncurl http://127.0.0.1:56802/api/episodes\n```\n\n`/api/episodes`의 `episodeCount`가 35보다 작으면 catalog 생성, ZIP 다운로드, 첫 episode 추출, `--catalog-only --episode-limit-per-task 1` ingest 명령을 다시 확인합니다.\n\nPCD point가 보이지 않는 경우:\n\n```text\nmedia.lidar 값이 0인지 확인합니다.\nepisode 내부 lidar/*.pcd 경로 또는 data.json의 lidar_path 계열 필드를 확인합니다.\n```\n\nNeo file explorer 또는 package 화면에서 `public/index.html`은 열리지만 API가 실패하는 경우:\n\n```text\n?apiBase=http://127.0.0.1:56802 를 URL에 붙입니다.\n데모 API 서버가 /work/app/server.js로 실행 중인지 확인합니다.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmachbase%2Fneo-humanoid-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmachbase%2Fneo-humanoid-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmachbase%2Fneo-humanoid-demo/lists"}