Category Archives: Stuff

Competing in STEM Racing

This year, I’m joining STEM Racing—a global competition where students design, build, and race miniature CO₂ -powered cars. But it’s not just about building a fast car, it’s a complete engineering and business challenge. Teams are judged on everything from design and manufacturing to marketing, project management, and teamwork.

STEM Racing Hong Kong logo

Why STEM Racing Appeals to Me

Let’s start with why I joined. For me, this is the ultimate mix of everything I love:

  • Engineering: We’re applying physics, aerodynamics, and materials science to a real-world project. There’s CAD design, wind tunnel testing, and endless tweaking to reach the best design. A lot to learn. In 2022 I tried to learn CAD modelling but did not have some place to apply my skills, causing me to give up, this is a great opportunity for me to learn new skills.
  • It’s not enough to have a fast car. We need branding, a sponsorship plan, and a full-on pitch to judges.
  • Knowing that teams from around the world are competing just pushes me to work harder. The idea of making it to Regional Finals, or even World Finals—is a massive motivator.

This competition feels real. It’s not a classroom exercise, it’s an engineering challenge that demands creativity, precision, and hustle.


The Name Change

STEM Racing used to be called F1 in Schools, and I think that name is much more recognizable. The Formula 1 connection made it feel prestigious and exciting, even for students who weren’t hardcore racing fans.

Now it’s just… STEM Racing.

I get that they want to highlight the educational side more. But STEM Racing feels generic and bland in comparison. I can’t help thinking they’re missing a trick by changing that name.


The Rules. A bit too tight?

The technical regulations are tight.

And while rules are essential (for fairness and safety), some of them feel overly restrictive. For example:

  • The car must fit perfectly into a specific sizing box.
  • A “no-go-zone”.
  • Material limits.
  • Every tiny detail is controlled, down to millimeters.

It takes the fun out of experimenting and pushing boundaries. Isn’t engineering all about innovation? Right now, it feels more like ticking boxes to comply, rather than creating original designs.


Money and Pay-to-Win

This competition is expensive.

Between:

  • Car materials and manufacturing,
  • CAD software and testing,
  • Hardware for CFD analysis,
  • Pit displays and presentation materials,

And while we’re working hard to get sponsors, it’s obvious that teams with more funding have a huge advantage.

That makes me wonder: are we really rewarding the best ideas and skills, or just the best-funded teams?

STEM Racing feels a bit like a pay-to-win competition

To be competitive at the top level, it’s not just about skill and hard work, it’s about resources.

The best-performing teams run multiple rounds of CFD simulations, wind tunnel tests, and extensive prototyping, all of which cost real money. With more money they also get access to better materials (for wings and stuff).

Outsourcing design

And here’s something else: even though the rules clearly say students must design and build their cars themselves, we all know there are ways around that.

Some well-funded teams quietly outsource parts of the process, whether it’s hiring professionals to assist with CAD design, having external engineers “advise” (do a lot of the heavy lifting), or getting display materials built by companies rather than the team. It’s technically against the spirit (and often the letter) of the rules, but it’s hard to police.

This creates an even bigger gap between teams. There are students who are genuinely learning everything themselves, and then others who have quiet behind-the-scenes help that gives them a polished and profession product.

Sometimes it feels discouraging. It’s like being in a race where some cars secretly have a turbo button you’re not allowed to use.

May be STEM Racing can organize some skill-based competitions focusing on specific aspects of engineering, allowing teams to showcase abilities regardless of resources.


Despite the criticisms, I’m excited to compete. This is a unique chance to learn, grow, and push myself in ways school doesn’t normally offer. And even if I think STEM Racing has room to improve, that’s part of what makes it interesting. Competitions should evolve.

If you are interested, our team’s name is Arion. Visit our website at teamarion.org.

As I progress in the competition you can expect updates.

My Journey to a 365-Day Duolingo Streak

“Do you know what a foreign accent is? It’s a sign of bravery.”
― Amy Chua
, Battle Hymn of the Tiger Mother

Learning a language may seem to be hard (it is hard of course), but the ability to communicate with people from different cultures and backgrounds has always fascinated me. 


Last May, a friend told me that he was trying to learn Spanish on Duolingo. I had heard of the app many times before but had no idea how it worked. Nevertheless, I decided to try it out by using it to learn Japanese and German. Little did I know that this journey would lead me to a 365-day streak.

How it started

As mentioned above, a friend told me that he was on Duolingo. I decided that maybe I should join Duolingo and learn a language with him. Duolingo’s learning approach is gamified, making learning a language just like playing a game. With Duolingo’s bite-sized lessons, it seemed like the perfect tool to make language learning a part of my daily routine.

The early days

My streak calendar of June 2023, when I just started.

The early days were rough because I would constantly forget to do a lesson and lose a streak. Sometimes I think that there is no point learning this language as I have little chance actually using it. But gradually, each day, I would carve out a few minutes to immerse myself in the world of new words, and grammar rules. Duolingo’s interface was really fun to use, and the streak and leaderboards system really stressed me into doing it. 

As the research shows, if you repeat an action often enough in the same context, the act of doing it will start to feel automatic. With a steady habit, something as daunting as studying a new language can feel as natural to your daily routine as brushing your teeth. (Duolingo Blog Article)

The gamification elements, such as earning points and unlocking achievements, added a sense of accomplishment to my progress.

Challanges

As with any long-term endeavor, maintaining a streak for 365 days was not without its challenges. There were days when I felt tired or overwhelmed with other stuff, making it tempting to skip a lesson. However, I quickly realized that consistency was key (there are streak freezes on Duolingo so it is fine skipping a day or few). I reminded myself of the personal satisfaction I derived from each successful lesson and the progress I was making, no matter how small. This motivation pushed me to persevere, even during the toughest moments (sometimes the lessons just become so incredibly difficult). 

The leaderboards system

Along the way, I made the decision to let go of the Duolingo leaderboard system. After three weeks in the Obsidian league, the pursuit of Diamond 💎 became overwhelming. I chose to prioritize the joy of language learning over the competitive aspect, refocusing on the intrinsic value of the journey itself.

For those who are not familiar with Duolingo: there are 10 leagues (Bronze, Silver, Gold, Sapphire, Ruby, Emerald, Amethyst, Pearl, Obsidian, Diamond) and users get assigned to a leaderboard every week. Users at the top of the leaderboard by the end of each week advances to the next league, users in the lower end of the league will get demoted to the previous league.

My highest position before I gave up.

The Achievement

This Duolingo streak didn’t improve my language skills by much, but it had a positive impact on my habits and growth. By making language learning a daily priority, I developed a sense of consistency. This newfound dedication went into other areas of my life, helping me become more focused and committed.

You can find me on Duolingo at ymarku.

Breathing in Data: A Week of CO2 Monitoring in My Room

Have you ever wondered about the air quality in your own living space? We often take the air we breathe for granted, assuming it’s clean and safe. But what if there was a way to measure the quality of the air around us? That’s exactly what I set out to do when I recently acquired a new air quality monitor. Over the past week, I did an experiment to monitor the levels of Carbon Dioxide (CO2) in my room.

I do know that this method of data collection is not exactly accurate, there are a lot of variables I did not control. I did this only because I was curious about the CO2 levels, just to find its general trend.

CO2 concentrations have historically been below 300 ppmv but exceeded that threshold in 1912. Since then, levels have been steadily rising, reaching 420 ppmv in 2021. The annual growth rate has increased from less than 1 ppmv per year in 1959 to around 2.5 ppmv per year. (EPA, NOAA 2021)

The device I used for this experiment

Before we dive into the results, let’s talk about why monitoring CO2 levels is important. Carbon Dioxide is a gas that is naturally present in the atmosphere and is a byproduct of human breathing. However, high concentrations of CO2 indoors can lead to adverse health effects such as headaches, dizziness, and fatigue. Monitoring CO2 levels can provide valuable insights into the air quality and ventilation in a particular space.

Part of the data collected

For my experiment, I placed the air quality monitor in a central location in my room, away from direct sunlight and potential sources of CO2 emissions, such as plants. I wanted to get a baseline measurement of the CO2 levels in my room before making any adjustments. The monitor provided real-time readings, and has a function to export the data for further analysis. From the data collected, the mean of the CO2 concentration before I made any changes was 671 ppm, the maximum was 1015 ppm.

During this week, I made the following changes to my room:
– My ventilation system was always on
– I removed all the plants from my room
– I opened the windows for 10 minutes every 4hours from 9am to 6pm.

The impact of these changes was noticeable. The CO2 levels in my room decreased significantly, and I really felt a difference in the air quality. I felt more alert and focused, I was able to concentrate much better, and I stopped feeling sleepy, it really was effective. The mean of CO2 concentration has decreased to 491ppm, the maximum was 710ppm.

This shows the importance of monitoring CO2 levels in indoor spaces and the impact it can have on our well-being. While this is not an accurate scientific study, it provided insights into the air quality in my room and how simple adjustments can make a difference.

CO2 as an Indicator of Airborne Infection Risk Transmission¹

During the COVID-19 pandemic, recommendations have been made to use indoor CO2 measurement as an indicator of the risk of airborne infection transmission. All else being equal, higher CO2 concentrations correspond to lower outdoor air ventilation rates and the potential for an increased risk of airborne transmission.

¹Recommended by the Centers for Disease Control and Prevention [CDC 2021] in the United States; Federation of European Heating, Ventilation and Air Conditioning Associations [REHVA 2021] in Europe; and Environmental Modelling Group and Scientific Pandemic Insights Group on Behaviours [EMG/SPI-B 2021] in the United Kingdom.

Random Number Generation on Scientific Calculators

Introduction

The Casio fx-991EX is a widely used scientific calculator popular among students and educators. One of its features is the Ran# function, which generates a random decimal between 0 and 1. At first glance, it seems surprising that a simple handheld device without internet access, a system clock, or complex operating system components can produce random numbers.

This report explores the mechanisms by which the fx-991EX generates these random numbers, describing its likely internal methods, limitations, and initial data observations.


Understanding Randomness

There are two broad types of randomness:

  • True randomness: Derived from physical processes that are inherently unpredictable (e.g., radioactive decay, atmospheric noise).
  • Pseudo-randomness: Numbers generated by deterministic algorithms that appear random but are fully determined by an initial input called a “seed.”

Casio calculators, including the fx-991EX, rely on pseudo-random number generators (PRNGs) due to hardware constraints and cost-efficiency.


Core Mechanism of Pseudo-Random Number Generator (PRNG)

A PRNG uses a mathematical formula to generate a sequence of numbers that mimics true randomness. The core components include:

  • Seed: The initial value from which the sequence is generated.
  • Algorithm: The mathematical procedure, often a lightweight one like a linear congruential generator (LCG), which uses a formula of the form:
Xₙ₊₁ = (a * Xₙ + c) mod m

Where:

  • Xₙ is the current state,
  • a, c, and m are fixed constants,
  • mod ensures the result stays within a set range.

This process is efficient and can run on simple microcontrollers inside calculators, like the one inside modern Casio scientific calculators.


Where Does the Randomness Come From?

Unlike computers, the fx-991EX lacks a real-time clock or user-settable seed. So how does it avoid always producing the same numbers?

Likely entropy sources include:

  • Startup hardware state: Electrical noise or slight differences in how circuits initialize when powered on.
  • Button press timing: The precise moment you press the Ran# button might capture a different internal timer value.
  • Uninitialized memory: Leftover data in RAM or registers after startup.
  • Battery or solar cell voltage variations: While less likely, small variations in power supply might theoretically introduce variability if sampled by the microcontroller, though no confirmed evidence shows Casio explicitly uses this.

These factors help introduce subtle differences, but these are just my theory


Insights from the Calculator Community

Recent community discussions (such as those on Reddit) suggest that Casio’s graphing calculators, like the fx-CG50, may use a combined linear congruential generator (CLCG) rather than a simple LCG. A CLCG combines multiple LCG streams to achieve a much longer period (potentially up to 10^18 iterations) and improve statistical uniformity.

Community testers advise testing the raw Ran# function, as higher-level probability simulators (like dice-roll apps) may add extra layers of code that obscure the PRNG’s raw behavior. Advanced testers apply statistical methods like the chi-squared test or autocorrelation analysis to check fairness over large datasets.

An example set of outputs I gathered from an fx-991EX (sampled manually) includes:

2/125, 111/1000, 31/100, 11/1000, 159/1000, 153/1000, 453/500

While no obvious pattern emerges at a glance, detecting the underlying mathematical pattern would require thousands of samples and advanced statistical tools.

A known online tool for exploring PRNG behavior in TI calculators can be found here: TI-30X Calculator Tricks. Although this tool focuses on TI-30X devices, its concepts may guide similar investigations into Casio’s systems.


Could We Find the Pattern?

To uncover the exact algorithm, one would need to:

  • Collect a large dataset: Several thousand or even tens of thousands of random outputs.
  • Look for repetitions: Detecting the cycle length (period) of the PRNG.
  • Fit known formulas: Testing whether an LCG or CLCG formula fits the progression of outputs.
  • Analyze constants: Reverse-engineering the a, c, and m parameters.
  • Potential firmware analysis: For full certainty, one might attempt to extract the calculator firmware, disassemble it, and locate the exact random generation routine—though this is highly technical and may void warranties or breach usage terms.

While theoretically possible, I don’t have time.


Limitations

It is important to know:

  • The calculator’s random numbers are not cryptographically secure.
  • Given enough outputs, the PRNG’s sequence will eventually loop (within a finite period).

Hypothesis: What Casio Likely Uses Internally

Based on industry knowledge, embedded system design, and community research, it is reasonable to hypothesize that:

  • The fx-991EX employs a combined linear congruential generator (CLCG) or a similarly robust PRNG.
  • It initializes the seed using a mix of button timing, internal counters, startup state, and possibly minor hardware variations.
  • Each call to the random function advances the PRNG to the next number in the deterministic sequence.

Experimental Validation

To test these hypotheses, one can perform experiments:

  • Record long sequences of random numbers.
  • Check for repeating cycles or recognizable formula patterns.
  • Apply statistical methods like chi-squared tests, frequency analysis, or serial correlation.
  • Compare behaviors across multiple devices or after controlled resets.

Such experiments can reveal how much entropy is injected during startup and help identify the algorithm’s structure. If you are interested please do this experiment, I really want to know.


Appendix

  • PRNG (Pseudo-Random Number Generator): An algorithm that generates a sequence of numbers that appear random.
  • Seed: The initial value that determines the sequence generated by the PRNG.
  • LCG (Linear Congruential Generator): A simple and widely used type of PRNG.
  • CLCG (Combined Linear Congruential Generator): A PRNG that combines multiple LCGs to achieve longer periods and better randomness.
  • Entropy: A measure of unpredictability or randomness in a system.

To learn more about RNG, check out its Wikipedia article.


If you’re reading this and you own a Casio calculator (or even another brand), I’d love to know:

  • Have you ever noticed patterns in the random numbers?
  • Can you run a hard reset and see if two devices match?
  • Are you interested in helping collect and log large data batches?

Let me know in the comments!


Extras: Cloudflare Lava Lamp Wall

While Casio calculators rely on mathematical formulas to simulate randomness, companies like Cloudflare take it to the next level. In their San Francisco office, they famously use a wall of real, physical lava lamps as an entropy source to seed their cryptographic random number generators.

The constantly shifting, unpredictable light patterns from the lava lamps are captured by cameras and turned into raw randomness — an example of using true physical entropy instead of math-based pseudo-randomness. It’s a fun reminder that in high-security systems, randomness often comes straight from the messy, chaotic physical world! Check out Tom Scott’s video below.

Another video to help:

Thanks for reading!

london new york tokyo and moscow clocks

Handling Nonexistent Times in Software

The Problem

When your app lets users pick a date and time, like scheduling a meeting or setting a reminder, there’s a weird case you might never expect: What if the time they select… never existed?

It happens because of Daylight Saving Time (DST) changes.

For example, in Beijing on April 10, 1988, DST started and clocks jumped from 2:00 AM straight to 3:00 AM. That means:

What you expectWhat actually happened
1988-04-10 02:30 AMNever existed

So if a user picks 2:30 AM on that day, they’ve chosen a phantom time.

What Happens in Your Frontend

Let’s say you’re using JavaScript on the frontend.

Example:

const d = new Date('1988-04-10T02:30:00+08:00');
console.log(d.toString());

What you’d expect:

“Sun Apr 10 1988 02:30:00 GMT+0800 (China Standard Time)”

What might actually happen:

“Sun Apr 10 1988 03:30:00 GMT+0900 (China Daylight Time)”

Why?
Because JavaScript’s Date object silently corrects the time to a valid equivalent. Instead of throwing an error, it shifts forward to the next valid moment—losing 1 hour in the process.

This can break an app’s logic:

  • A booking might shift forward without warning.
  • An event could appear at the wrong time.
  • Logs might misalign with reality.

Time Zone History, a Bigger Problem

People think time zones are just UTC offsets (like UTC+8), but sometimes it is not true:

  • Time zones change.
  • DST rules change.
  • Historical quirks are baked into “Asia/Shanghai” and other zones.

For example, the Asia/Shanghai zone was UTC+8 in 1987, but UTC+9 during DST from 1986–1991. And in some TZ database versions, the rules were even wrong because of bad historical data.

This means that even past times aren’t always consistent across systems.

The Best Practice is Always Use UTC Internally

  • Store all times in UTC (e.g., as ISO strings or Unix timestamps).
  • Convert to local time zones only when displaying to the user.

Why? Because local time zones can change in the future. If you stored “2022-01-15 12:00 America/São Paulo” and Brazil canceled DST (they did), your saved time would have a different meaning later on.

It is important that you use timestamps in one standard timezone (UTC). A timestamp is absolute. It tells you exactly when something happened, but it doesn’t say what local time the user saw. A timestamp has no timezone context.


The Solution

Detecting Invalid Times

If you’re using Luxon, you can check if a time is valid:

import { DateTime } from 'luxon';

const dt = DateTime.fromObject(
  { year: 1988, month: 4, day: 10, hour: 2, minute: 30 },
  { zone: 'Asia/Shanghai' }
);

console.log(dt.isValid); // false
console.log(dt.invalidReason); // 'unsupported zone or time'

This helps identifying the error early.

Automatically Correcting It

Luxon (and Moment.js with timezone) will auto-correct the time by default unless you configure it otherwise. For example:

console.log(dt.toString());
// 1988-04-10T03:30:00.000+09:00

It is good practice to warn the user or refuse invalid inputs to avoid silent correction.

Converting Between Time Zones

Use Intl.DateTimeFormat if you don’t want extra libraries.

function convertTimeZone(date, timeZone) {
  return new Date(date.toLocaleString('en-US', { timeZone }));
}

const dt = new Date('1988-04-10T02:30:00Z'); // UTC time
const shanghaiTime = convertTimeZone(dt, 'Asia/Shanghai');

console.log(shanghaiTime.toString());
// Might shift automatically to valid time if the original is invalid.

Implementing in a Frontend App

User Flow

  1. User picks a date/time:
    → e.g., 1988-04-10 02:30 AM in Shanghai time.
  2. Frontend:
    • Converts to UTC using Luxon or native Intl API.
    • Validates the time:
      • If it’s valid: Save it.
      • If it’s invalid: Show a warning: “The time you selected doesn’t exist due to daylight saving changes. Please pick a different time.”

Handling Invalid Times with Luxon

import { DateTime } from 'luxon';

function validateDateTime(dateStr, zone) {
  const dt = DateTime.fromISO(dateStr, { zone });
  if (!dt.isValid) {
    alert('The time you picked is invalid because of DST! Please choose a different time.');
  } else {
    console.log('All good:', dt.toUTC().toISO());
    // Save to backend in UTC!
  }
}

validateDateTime('1988-04-10T02:30:00', 'Asia/Shanghai');

Why This Affects Your App

  • Silent bugs:
    Your app might “move” user-specified times forward (spring forward) or duplicate events during the fall back (2 AM might happen twice).
  • Confused users:
    Users might book something at 2:30 AM but it actually happens at 3:30 AM—leading to frustration.
  • Cross-platform inconsistencies:
    Different devices or browsers might have different timezone data, especially on older OSes or browsers.

Best Practices

Do ThisDon’t Do This
Store times in UTCStore raw local times
Use timezone-aware libraries (Luxon)Rely only on native JS Date everywhere
Validate user input in local timeAssume all times are valid
Always tag your times with time zoneOmit time zone info in string formats
Update timezone databases regularlyAssume timezone rules never change

What About Databases?

  • PostgreSQL: Great TZ support, but auto-adjusts invalid times (no error). Always store in UTC.
  • MySQL: Basic TZ support, often outdated unless you load TZ data manually. Handle conversions in app code.
  • MongoDB: No built-in time zone magic; dates are stored as UTC. You need to handle TZ logic in your app.

Basically: DBs are good at storing time, but you (the app) must handle time zone logic.

What’s a Time Zone Database?

To figure out what offset applies to a time zone at a specific moment, your app/library uses a time zone database.

  • Linux & Mac: use the IANA TZ Database.
  • Windows: has a different TZ database (cause mismatches).
  • JavaScript (modern browsers): uses IANA data internally via Intl API.

Problem:
If your TZ data is outdated, your app gets the wrong time.

Good practice: update your libraries regularly.


Looking Ahead: Temporal API

TC39’s Temporal API (currently a Stage 3 proposal) will finally fix all these messy Date problems in JS with a proper, built-in ZonedDateTime.

Example:

const zdt = Temporal.ZonedDateTime.from({
  timeZone: 'Asia/Shanghai',
  year: 1988,
  month: 4,
  day: 10,
  hour: 2,
  minute: 30,
});
console.log(zdt.toString());
// It will immediately tell you if it’s invalid or shift properly.

TL;DR

  • DST creates gaps (nonexistent times) and overlaps (duplicate times).
  • Your web app’s frontend might silently shift times without warning.
  • Always validate times when working in local zones, especially around DST change dates.
  • Store everything in UTC + use timezone-aware libraries to handle conversions safely.

The distinction between past, present, and future is only a stubbornly persistent illusion. — Albert Einstein