ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A single HTML file that records a student's conversation with an AI, exactly as it happens, and hands back one JSON file per student ready for analysis.
Try it → — a student's messages go to the AI provider to get replies, and nowhere else. The session file is saved by the student, in their own browser.
Three pages, each a single self-contained file:
| the activity | what a student opens |
| setup | build your own activity, without installing anything |
| reader | drop a session file in and read it as a transcript |
Conversations exported from Alkimi can be read too, once converted: see Reading Alkimi exports.
Research on how students talk with AI usually collects what the student produced — a conversation copied into a document, reformatted, and handed in. Everything about who said what then has to be inferred from the text, and it cannot always be recovered. In the study this was built for, two researchers annotating the same transcripts reached Krippendorff's α between 0.21 and 0.66 on speaker attribution alone: below the threshold for even a tentative claim.
This captures the conversation as it happens instead, so role is a fact about where the
text came from rather than an inference about what it looks like.
The activity is whatever you make it. The prompt the AI receives and the questions
asked at the end are both yours — swap them and the same instrument runs a different study.
The Taylor Series activity in examples/ is simply the one it was first built for.
One JSON file per student, conforming to a written schema:
student or ai at creation. No inference, no unknown.The schema is tea-taylor-session/2. A /2 file may contain tombstones, so anything
reading one must expect a turn with no text — see below.
A student can delete an exchange before handing the file in: the bin at the corner of a turn removes it and the reply that belongs with it. This is narrower than it sounds, and the shape of it is the point.
What is deleted is the words. What survives is the shape. A deleted turn keeps its
position, its speaker, its length and the moment it was removed, and loses its text:
{ "i": 6, "role": "ai", "ts": "…", "deleted_utc": "…", "word_count": 47, "char_count": 260 }
Both halves matter. A file that dropped the turns outright would stop being evidence of its own completeness — turn counts and the words-per-speaker ratio, which are measurements this instrument exists to make, would quietly become wrong, and nobody reading the file could tell a short session from an edited one. A file that kept the text would hand back the very sentence the student was trying to withdraw, which on a page that insists on codes rather than names is the worse failure. So: counted, but gone.
"Start over" follows the same rule. It sets the whole conversation aside instead of
discarding it: the turns and the calculator tape move into set_aside, stamped with the
time, and the AI opens a fresh conversation in the same session. The file still shows every
restart, and the reader counts them among the session facts and shows each set-aside
conversation after the one handed in. Discarding would leave nothing behind; both of these
leave a trace.
"Back to the start" is for a wrong code, or a change of AI. It sets the conversation aside the
same way, with the codes and the AI it was had under, and returns to the start screen with the
codes filled in. Starting again keeps the same session file and records any change in
codes_changed: the old codes, the new ones and the time. The reader lists each change among
the session facts. The reader draws deleted exchanges in place, as an outline rather than a gap,
for the same reason.
Replies arrive as Markdown with mathematics in them, whether or not anything asks for
that. The page draws both, so a Taylor series appears as a Taylor series instead of as
x^3x^3x^3, and a step appears as Step 2 instead of **Step 2**.
Mathematics is TeX between dollars — $…$ inline, $$…$$ set apart — and the activity
prompts ask for it explicitly. Markdown is bold, italics, headings, lists, tables, inline
code and rules.
Two decisions worth knowing about, because both trade a feature for not being wrong:
x_1 is a subscript, not an underline, and \frac{a}{b} is full of braces Markdown
would rather not see. Taking the TeX out first means the Markdown pass only ever sees
prose.2*3*4 as an emphasised 3,
which in a mathematics transcript quietly eats two multiplication signs. Here an opening
* must begin a line or follow a space, so *Hint:* is emphasis and 2*3*4 is a
product. _ is never emphasis — outside the dollars it is a subscript. A lone $5 in a
sentence about money is left alone for the same reason.The recorded turn is never touched. Rendering builds a view of the text; the JSON the
student hands in still holds the characters the model actually sent, asterisks and dollars
and all. What was said and how it was displayed stay separate, which is the same reason
role is recorded at creation rather than inferred later.
The reply rules still tell the model not to use headings, bold or lists. That rule is about reading load and the sixty-word cap, and it stands. The renderer is what happens when the model ignores it, which it does.
richtext.js and richtext.css hold all of this, in one copy, inlined into both the
activity and the reader at build time — so a transcript read afterwards cannot come to look
different from the conversation it recorded. Drawing the mathematics is
Temml (MIT), vendored in vendor/, converting TeX to MathML that the
browser draws itself. Nothing is fetched: a page opened from file:// on a classroom
machine with no network still shows mathematics. Anything Temml cannot parse falls back to
the raw characters rather than vanishing.
A drawer on the right, opened from the calculator button beside the symbol palette in the reply bar. Three panes of the same shape: what has been entered is listed at the top, the prompt to add another is at the bottom, and the keypad is under that, so a key lands in the line just above the thumb that pressed it. It is drawn from the page's own colours, so it follows the light and dark the rest of the page is in.
Calculate keeps a tape. Entries stay on screen and ANS carries the last result
forward, so 0.2-0.2^3/3! then ANS*3 builds on what came before instead of a number being
copied back in by hand. ANS is untouched by a failed entry.
Graph and Table are feeds too. Each entry gets its own plot or its own table, one after another, newest at the bottom, and they scroll. A plot keeps a fixed shape and never stretches to fill whatever height is available — a graph two thousand pixels tall and three hundred wide says nothing about a function, and on a tall screen filling the space is exactly what you get. The controls sit under the feed rather than inside it, so stacking never pushes them out of view.
Commas at the top level put several functions on one pair of axes — sin(x), cos(x) draws
both together, each in its own pen from the page's palette, so a function and its derivative
can be read against one another. Inside brackets a comma still belongs to max(a, b). Axis
numbers can be switched off if the room is wanted instead.
The work stays put. Switching between Calculate, Graph and Table keeps each feed as it
was, and so does closing the drawer — and a reload rebuilds all three from tool_uses, so a
student who refreshes finds their working where they left it rather than an empty tape beside
a conversation that survived. There is no second copy to disagree with the record: the file
is what the feeds are read back from.
Any line can be deleted, with the bin at its right, exactly as an exchange can. It leaves a tombstone for the same reason: the expression goes, and that the student reached for the calculator at that point in the conversation stays.
Written rather than borrowed, and each part of that was a decision:
file:// or from a classroom with no
network. The engine is about two hundred lines and carries no licence.2x, 3(x+1) and 2sin(x) all work, and
1/2x means (1/2)·x. Powers group right, so 2^3^2 is 512, and -2^2 is −4.Every attempt is recorded, in tool_uses, beside the turns and deliberately not among
them — including the ones that did not work. An expression that will not parse is written
down with what went wrong:
{ "after_turn": 2, "ts": "…", "kind": "evaluate", "expr": "sin(",
"error": "the expression stops early", "deg": false }
Recording only what succeeded would make the file a record of what worked rather than of
what the student did, and where somebody got stuck is worth as much as where they did not.
Both the report and the reader name a failed attempt as an attempt, rather than printing a
result that never existed. A successful one carries result instead:
"tool_uses": [
{ "after_turn": 2, "ts": "…", "kind": "evaluate",
"expr": "0.2 - 0.2^3/3!", "result": 0.19866666666666669, "deg": false }
]
A calculator whose use went unrecorded would make the file quietly incomplete again — the
page would know the student computed something and the file would not. Putting it among the
turns instead would put the turn counts and the words-per-speaker ratio wrong, in exactly
the way tombstones were designed to avoid. after_turn says where in the conversation it
happened, so the reader can put it back in place: a number worked out before the AI
offered it is a different thing from one worked out after.
The field is additive, so /2 readers written before it go on working.
Every page says so, quietly, at the foot: verbatim 1.1.0 · 1f9d4d48. The first half is
the version, bumped by hand when a change is worth telling someone about. The second is a
digest of everything that makes a page — the assembled template with its libraries, and the
reader source — so it changes when, and only when, the thing it identifies changes.
There is no timestamp in it, deliberately. A rebuild of unchanged sources stays byte-identical, which is what lets the built pages be committed and diffed, and it means two people building the same commit get the same id.
The same marks go into the session file:
"app": { "name": "verbatim", "version": "1.1.0", "build": "1f9d4d48" }
A page is handed out as a file and then lives on its own; by the time a transcript raises a
question, the page that produced it is usually long gone. The reader shows this back as
Recorded with, and says not recorded for a session made before it was kept. The field
is additive — nothing in /2 changed shape, so a reader written for /2 goes on working.
Both build routes are stamped from the same place. The version is baked into the template
when the generator loads, so a page built in the browser through setup.html carries the
version of the generator that produced that setup.html, without setup.src.html having
to remember a token. Which route built a given page is already recorded separately, in
activity_prompt.source.
The activity follows the machine, and the ☾ button in the corner overrides it either way. The choice is remembered in that browser, and is deliberately never written into the session file: what a student found comfortable to read is not data about what they did. The reader is light only.
Fill in the title, the prompt, the questions at the end, a thumbnail and a footer, then press Download the activity. You get a single HTML file with all of it baked in — that file is what you give your students. Nothing to install, no command line.
It is the same generator as below, running in the browser: it fills in the same template through the same fields, so the two cannot produce different pages from the same answers.
# build the page for a class
python3 make_session_capture.py --prompt my-activity.txt --survey my-questions.json \
--section S4 --title "Sequences and series"
# check what you just built, before it goes anywhere
python3 check_page.py
# look at the interface with canned replies — no key, no network
python3 make_session_capture.py --demo
Hand out the resulting file, or serve it. Both work: a page opened directly from a file
sends Origin: null, and every provider offered accepts that.
--help lists the rest: branding, submission, provider selection. Every build also
writes setup.html beside the activity, so the browser builder always carries the template it
was built from, and reader.html, so the reader carries the same maths renderer the activity
does. Both are generated — edit setup.src.html and reader.src.html, not the built files.
Alkimi saves a chat as its own kind of JSON file (alkimi.chat-export), which the reader
does not open. convert_alkimi.py turns a batch of them into files
it does:
# a folder of exports, converted into another folder
python3 convert_alkimi.py ~/Downloads/alkimi-exports -o ~/Downloads/converted
# or name the files; each converted copy lands beside its original
python3 convert_alkimi.py chat1.json chat2.json
Each export becomes <name>.verbatim.json. Folders are searched all the way down. Any file
that is not an Alkimi export is skipped and named, and nothing is overwritten unless
--force is given. Then drop the converted files into the
reader.
A converted file is not a capture. Alkimi tags who said what, so role carries over, and so
does how long each reply took. But the export has no prompt, no typing time and no deleted
turns, so those are left out rather than guessed, and the schema field says the file was
converted. The student's name, which Alkimi puts in every message, is dropped: participant,
group and section are left blank.
Two unlinked addresses open an editor for whoever is running the session:
…/index.html?reset-teacher-prompt
…/index.html?reset-survey-questions
Use # instead of ? when the page was opened as a local file — a local address will not
carry a query string.
A change made there applies to that browser only. It is the right tool for trying a
prompt before class or showing a colleague what a change would look like. To change the
activity for a whole class, rebuild and hand out that file, so every student is
demonstrably working from the same one. Either way activity_prompt.source records which
it was, so no session file is ambiguous about what produced it.
Students bring their own key, or use one the instructor reads out, or run a model on their own machine with no key at all. The page tries each service when it loads and lists only the ones that answer from that network — so nobody is offered an option that cannot work for them, and nothing is asked about their connection or where they are.
A key is checked the moment it is entered: its shape first, then a real call. Nothing is baked into the built file, and no key ever leaves the student's browser.
Python 3.9+ to build. Nothing to install. Node is optional and only used by
check_page.py, which will say so and skip those checks if it is absent.
PolyForm Noncommercial 1.0.0. Schools, charities, public research organisations, public health and safety bodies and government institutions count as noncommercial whatever their funding, so research use is covered without asking. Selling it, or using it for commercial advantage, needs a separate licence — ask.
Not an open-source licence, deliberately: restricting the field of use fails both the Open Source Definition and the Free Software Definition. The source is published and may be read, run, modified and shared. "Source available" rather than open source.
Versions published before 13 October 2026 were offered under MIT — this repository was
public from 14 August and the README said so. Copies taken under that notice stay under MIT;
nothing revokes them. Temml, in vendor/, is MIT and unaffected.
If you use it in research, a citation is welcome — see CITATION.cff in the study
repository.