I am planning a small offline uLisp project for a handheld device with a display, a few buttons and an SD card.
The device would contain a fixed set of 78 illustrated cards. The user would press a button to select one or more cards, and the display would show:
- Card title
- Upright or reversed orientation
- A short interpretation
- Position within the current spread
- Previously selected cards in the session
This is the current reference list for the 78 card IDs, titles and meanings:
https://deckaura.com/blogs/guide/tarot-card-meanings
Disclosure: I maintain the linked website. I am sharing it only to show the real data set that I would convert into a compact offline format.
The project would not need WiFi after the data had been copied to the SD card.
I am considering a LilyGO T-Deck, PicoCalc-class device or another board with:
- A colour display
- An SD card slot
- At least three input buttons
- Enough RAM to keep a small session state
- uLisp support
I am trying to decide how much data should remain in RAM and how much should be read from the SD card when a card is selected.
A simplified card record might contain:
("the-world"
"The World"
"Completion, integration and achievement."
"Incomplete closure, delay or unfinished work.")
There would be 78 records in total.
My current idea is to store them as Lisp-readable expressions rather than JSON, so that uLisp can read the data directly without requiring a separate JSON parser.
I have several questions about the best architecture.
1. One large file or one file per card?
The simplest option would be one file containing 78 expressions:
(
("the-fool" "The Fool" "..." "...")
("the-magician" "The Magician" "..." "...")
...
)
However, reading the complete list may consume more RAM than necessary.
The alternatives I am considering are:
- One large file loaded completely into memory
- One large file scanned until the requested record is found
- One small file per card
- A compact index file containing byte offsets into a larger text file
- A generated Lisp source file containing only IDs and titles, with longer descriptions stored separately
For a data set this small, which approach would you use?
2. Lisp expressions versus a custom text format
Would Lisp-readable records be the most natural uLisp format?
For example:
(card
:id "the-world"
:title "The World"
:upright "Completion and integration."
:reversed "Unfinished work or delayed closure.")
Or would a simpler delimiter-separated format use substantially less memory while reading?
I like the idea that the file could be inspected and edited as normal Lisp data, but long strings may make the complete expression expensive to load.
3. Random selection without repeats
For a one-card draw, this is straightforward:
(random 78)
For a multi-card spread, the same card should not appear twice.
Would you recommend:
- Building a list of the integers from 0 to 77 and shuffling it
- Selecting random values and rejecting duplicates
- Using a 78-element bit array to mark cards that have already been selected
- Persisting a shuffled deck order and advancing through it
A bit array seems attractive because only the selected state needs to remain in memory.
4. Upright and reversed orientation
The orientation can be selected independently with something similar to:
(if (= (random 2) 0) 'upright 'reversed)
Would you store upright and reversed descriptions in the same record, or keep them as separate records so only the required text needs to be read?
5. Efficient access to an SD file
If all cards are stored in one large file, scanning from the beginning for every draw is simple but inefficient.
Is there an established uLisp pattern for:
- Recording the byte position of each expression
- Seeking directly to a stored position
- Reading one expression without loading the rest of the file
- Rebuilding the index when the content file changes
I have seen that SD streams support read and read-line, but I am not sure what the most portable random-access pattern is across uLisp platforms.
6. Keeping long strings outside the Lisp workspace
The titles and IDs are short, but some descriptions could be several hundred characters.
Would it be better to keep only this data in memory:
(index id title file-position)
and retrieve the description from the SD card only when the card is displayed?
The device would show only one description at a time, so retaining all text in RAM seems unnecessary.
7. Session history
I would like the user to be able to select between one and ten cards and review the current session.
A session entry might contain only:
(card-index orientation position)
Would a small array be preferable to a list for this fixed maximum size?
The session could optionally be saved to the SD card as a timestamped Lisp expression.
8. Text wrapping on a small display
The descriptions will need to wrap across multiple display lines.
Would you preprocess the text into display-width-specific lines on a computer, or implement word wrapping in uLisp so the same source data can work on different screen sizes?
Preprocessing would reduce runtime work, but it would tie the data file to one particular display width and font.
9. Updating the data
I would like the card content to be replaceable by copying a new file to the SD card without rebuilding the uLisp firmware.
Would you include a small header such as:
(version 1)
(card-count 78)
(content-hash "...")
so the program can verify that the data file is complete and compatible before using it?
10. Suitable first platform
For the first prototype, I only need:
- A display
- An SD card
- A select button
- A back button
- Enough memory for one record and a ten-card session
Would a T-Deck, PicoCalc or another existing uLisp platform be the easiest starting point?
My first milestone would be much smaller than the final project:
- Load 78 IDs and titles
- Select one random card
- Select its orientation
- Retrieve one description from SD
- Display the result
- Avoid repeating the same card during the current session
I would appreciate recommendations on the most idiomatic uLisp data representation before I start converting the complete data set.