Criei uma estrela usando Java
Eu estava estudando um jogo chamado Capitalism Lab, que em curtas palavras é um simulador de empresas, economia e capital. Meu objetivo era criar um clone dele para Web, já que ele não tem release para Linux. Passei um tempo tentando ter uma economia "factível" como a do jogo, mas aí percebi que a economia é moldada através do meio. Poxa, sei que isso é meio obvio (agora), mas quando comecei eu só queria ter uma conta em um banco e começar minha rede de varejo! Só que caí num loop de modelagem: para ter conta bancaria precisa existir banco, para o banco funcionar precisa existir dinheiro, para o dinheiro existir precisei definir de onde ele vinha, e aí foram surgindo outras entidades e regras...
Quando tomei a ideia de fazer o mais realista que eu conseguisse, tudo tinha que fazer sentido lógico, mesmo que não fosse apresentado explicitamente para quem estiver jogando. O Capitalism Lab não tem estações, e estações influenciam a economia. Não tem eventos climáticos adversos, e esses eventos influenciam a economia. Meu objetivo passou a ser ter o simulador mais factível que eu pudesse, sem excluir elementos auxiliares como eventos scriptados.
Para chegar nesse resultado, parei o que estava fazendo e fui estudar qual seria a camada mais alta para uma simulação econômica. A conclusão que eu cheguei foi o Sol.
Mas veja bem, o que eu sei sobre astronomia é bem superficial. Graças a internet e a IA, cheguei em um resultado que me agradou muito. Esse artigo é a consolidação dos estudos que fiz para gerar uma estrela tendo como base: o Sol.
"Para fazer uma torta de maçã do zero, primeiro você precisa inventar o universo."- Carl Sagan
O código de uma estrela
Só uma recapitulação importante: "Uma estrela é uma enorme esfera de plasma mantida unida pela gravidade, dentro da qual acontecem processos de fusão nuclear que liberam uma quantidade gigantesca de energia." Stars: Crash Course Astronomy #26. Outros materias, caso se interesse, estão lincados nas fontes no final do artigo.
public class Star {
private UUID id;
private String name;
private SpectralType spectralType;
private Double massSolar;
private Double radiusSolar;
private Integer temperatureKelvin;
private Double luminositySolar;
Repara que massa, raio e luminosidade estão em unidades relativas ao Sol. É convenção comum em astrofísica, pois evita carregar a massa solar de 1,989 × 10³⁰ kg: dá para dizer só que ela possui 2 M☉. Repare também que, apesar de ser constante no código, M☉, R☉ e L☉ são valores de referência baseados no Sol. Não são constantes fundamentais da natureza, como a constante gravitacional (G) ou a velocidade da luz (c).
Estrelas são classificadas de acordo com a temperatura da superfície, usando o sistema O, B, A, F, G, K, M. Para representar isso no modelo, temos o Enum SpectralType.
Essa classificação combinada com a luminosidade dá origem ao Diagrama de Hertzsprung-Russell: um gráfico de luminosidade × temperatura onde cerca de 90% das estrelas (o Sol incluído) caem numa faixa diagonal chamada "sequência principal". É de onde vêm as faixas de massa, raio e temperatura do enum. Estudei isso em três lugares: o laboratório interativo The Hertzsprung-Russell Diagram (NAAP), o material O Diagrama HR, da UFRGS, e a Wikipédia em português sobre o Diagrama de Hertzsprung-Russell. Saber a luminosidade importa porque ela influencia direto a temperatura de um planeta em órbita, mas isso fica para um próximo artigo...
No código, cada classe espectral carrega a faixa de massa, raio e temperatura que uma estrela real daquele tipo costuma ter:
public enum SpectralType {
O(16.0, 90.0, 6.6, 15.0, 30_000, 52_000),
B(2.1, 16.0, 1.8, 6.6, 10_000, 30_000),
A(1.4, 2.1, 1.4, 1.8, 7_500, 10_000),
F(1.04, 1.4, 1.15, 1.4, 6_000, 7_500),
G(0.8, 1.04, 0.96, 1.15, 5_300, 6_000),
K(0.45, 0.8, 0.7, 0.96, 3_900, 5_300),
M(0.08, 0.45, 0.1, 0.7, 2_300, 3_900);
private final double minMassSolar, maxMassSolar;
private final double minRadiusSolar, maxRadiusSolar;
private final int minTemperatureKelvin, maxTemperatureKelvin;
// construtor + getters
}
Essas faixas impedem o gerador de inventar uma estrela azul de 30.000 K com massa de anã vermelha. Pense como uma salvaguarda fisicamente correta.
Sorteando uma estrela (ou quase)
Se eu sorteasse a classe espectral com probabilidade uniforme teria um universo irreal. Cerca de 76% das estrelas são anãs M (pequenas, frias, vermelhas), e estrelas O são quase inexistentes (~0,00003%). É o que mostram o vídeo Anãs vermelhas e vida no Universo, da série O Universo: Mistérios Revelados (HISTORY), e a matéria Quais são os tipos de estrelas anãs: o que você precisa saber, da National Geographic Brasil. Anãs vermelhas dominam porque nuvens gigantes de gás se fragmentam bem mais fácil em pedaços pequenos do que em massas gigantescas durante o colapso gravitacional que dá origem às estrelas (Formação Estelar, também da UFRGS).
private static final double[] SPECTRAL_TYPE_WEIGHTS = {
0.0000003, // O
0.0013, // B
0.006, // A
0.03, // F
0.076, // G
0.121, // K
0.7657 // M — soma dos pesos = 1.0
};
Esse mesmo problema apareceria se eu gerasse massa, raio e temperatura de forma independente, ia cair na situação de estrela impossível, que é algo que eu não entendi muito bem mas vou estudar mais sobre.
Um ponto que preciso deixar claro antes de você continuar vendo o código: tem muita simplificação aqui. Afinal, eu não sei tudo que precisa para gerar uma estrela. Não saberia calcular átomo por átomo para chegar no resultado correto.
public Star generate(String name, RandomGenerator random) {
SpectralType spectralType = pickSpectralType(random);
double positionInClass = random.nextDouble();
double massSolar = RandomRanges.interpolate(
spectralType.getMinMassSolar(), spectralType.getMaxMassSolar(), positionInClass);
double radiusSolar = RandomRanges.interpolate(
spectralType.getMinRadiusSolar(), spectralType.getMaxRadiusSolar(), positionInClass);
int temperatureKelvin =
(int) Math.round(RandomRanges.interpolate(
spectralType.getMinTemperatureKelvin(), spectralType.getMaxTemperatureKelvin(), positionInClass));
Star star = new Star();
star.setId(RandomIds.nextUuid(random));
star.setName(name);
star.setSpectralType(spectralType);
star.setMassSolar(massSolar);
star.setRadiusSolar(radiusSolar);
star.setTemperatureKelvin(temperatureKelvin);
star.setLuminositySolar(starPropertiesCalculator.calculateLuminositySolar(star));
return star;
}
Luminosidade é calculada
E essa é uma das regras de negocio mais importantes dessa primeira parte, e nunca pode ser um valor aleatório. Precisa ser consequência das propriedades físicas da própria estrela, principalmente do raio e da temperatura efetiva, via Lei de Stefan-Boltzmann. Em termos simples, todo corpo com temperatura acima do zero absoluto emite radiação térmica, e a energia emitida por unidade de área cresce com a quarta potência da temperatura. Ou seja, mantendo o resto constante, dobrar a temperatura não dobra a energia emitida: ela aumenta por um fator de 2⁴ = 16.
Estudei essa relação na Wikipédia em português sobre a Lei de Stefan-Boltzmann e no material Extremos de luminosidade, raios e densidades, do Instituto de Física da UFRGS. E agradeço ao meu colega e amigo Victor Hugo pela solicitação para destrinchar a fórmula para que todos possamos entender seu funcionamento.
A Lei de Stefan-Boltzmann descreve a potência térmica emitida por unidade de área de uma superfície. Para uma estrela, dá para aproximar a superfície como a de um corpo esférico. Então precisamos considerar não só a energia emitida por cada unidade de área, mas toda a superfície da estrela. A área de uma esfera é:
A = 4πR²
A potência emitida por unidade de área é dada por:
F = σT⁴
Multiplicando uma coisa pela outra, chegamos à luminosidade total:
L = 4πR² · σT⁴
É essa equação que interessa para a simulação. Mas vale parar aqui e entender o que cada termo significa, porque cada parte tem uma consequência física diferente:
- L = luminosidade da estrela, ou seja, a quantidade total de energia emitida por segundo em watts (W)
- R = raio da estrela
- T = temperatura efetiva da superfície da estrela, medida em kelvin (K)
- σ = constante de Stefan-Boltzmann
- 4πR² = área da superfície da estrela, considerando ela como uma esfera
A fórmula combina duas coisas: quanto de energia cada pedaço da superfície emite e quanto de superfície existe para emitir. É por isso que o raio aparece ao quadrado e a temperatura à quarta potência. O termo R² não surgiu arbitrariamente, ele vem da geometria de uma esfera. Se aumentarmos o raio de uma estrela, não estamos aumentando só uma dimensão linear, a superfície inteira cresce. Uma estrela com o dobro do raio terá:
4π(2R)² = 4π · 4R² = 4(4πR²)
Ou seja, quatro vezes a área. Se duas estrelas tem a mesma temperatura mas uma tem o dobro do raio da outra, a maior terá quatro vezes a luminosidade. Dá para representar essa dependência como:
L ∝ R²
O símbolo ∝ significa "é proporcional a". Nesse caso, significa que, mantendo a temperatura constante, a luminosidade cresce com o quadrado do raio.
Outra coisa importante é que a Lei de Stefan-Boltzmann diz que a potência emitida por unidade de área é proporcional a T⁴. Isso produz uma dependência muito forte da luminosidade em relação à temperatura. Se mantivermos o raio constante e dobrarmos a temperatura:
L ∝ (2T)⁴
L ∝ 2⁴T⁴
L ∝ 16T⁴
Portanto, a estrela emite 16 vezes mais energia por unidade de área. Perceba que esse efeito independe do aumento de área causado pelo raio. O raio controla quanto de superfície existe, a temperatura controla quanto de energia cada unidade emite. Resumindo:
L ∝ R²T⁴
Isso já dá uma intuição importante: pequenas mudanças na temperatura produzem mudanças enormes na luminosidade, enquanto o raio tem influência quadrática. Mas... nessa primeira etapa o objetivo não é a luminosidade absoluta em watts (pode ser útil no futuro, mas não agora). O que interessa é saber como a luminosidade de uma estrela se compara à do Sol. Em vez de trabalhar com a fórmula cheia, dá para usar o próprio Sol como referência aplicando a mesma equação:
L / L☉ = (4πR²σT⁴) / (4πR☉²σT☉⁴)
E é aqui que a fórmula começa a ficar interessante. Como 4π aparece tanto no numerador quanto no denominador, ele se cancela. A constante de Stefan-Boltzmann σ também aparece nos dois lados e, portanto, também se cancela:
L / L☉ = (R²T⁴) / (R☉²T☉⁴)
Podemos então separar os termos:
L / L☉ = (R² / R☉²) · (T⁴ / T☉⁴)
E utilizar uma propriedade básica das potências:
a² / b² = (a/b)²
a⁴ / b⁴ = (a/b)⁴
Assim chegamos finalmente a:
L / L☉ = (R / R☉)² · (T / T☉)⁴
A vantagem é enorme: todas as constantes necessárias para calcular a luminosidade em watts somem da equação. O resultado vira uma razão adimensional. Se der 1, a estrela tem a mesma luminosidade do Sol. Se der 10, dez vezes. Se der 0,5, metade.
Cara leitor, eu comecei esse projeto em 11 de junho. Parei de fazer questões de economia e me voltei somente para criar essa estrela. Até entender minimamente o que estava acontecendo, demorou até 29 de agosto. Eu tentei me privas bastante do uso de IA na confecção desse código, mas nessa parte de cálculo, confesso que usei bastante.
É exatamente essa forma normalizada que foi implementada:
public double calculateLuminositySolar(Star star) {
double radiusRatio = star.getRadiusSolar();
double temperatureRatio = star.getTemperatureKelvin() / PhysicalConstants.SUN_TEMPERATURE_KELVIN;
return Math.pow(radiusRatio, 2) * Math.pow(temperatureRatio, 4);
}
A linha do radiusRatio é a mais importante. O getRadiusSolar() não retorna o raio em quilômetros ou metros, ele representa o raio em unidades solares, ou seja, R / R☉. Se o valor for 1, a estrela tem o mesmo raio do Sol. Se for 2, o dobro. Se for 0.5, metade.
Depois calculamos a razão entre a temperatura da estrela e a do Sol, que corresponde a T / T☉. E o retorno é a implementação direta da equação:
L / L☉ = (R / R☉)² · (T / T☉)⁴
O mais legal é que o código não tem uma diferença tão grande da fórmula física, e isso deve permanecer para outras situações.
Por que usar o Sol como referência?
Usar o Sol como referência deixa o resultado bem mais conveniente para o resto da simulação. Em vez de carregar números gigantescos em watts, trabalho com uma escala em que o próprio Sol vale mais ou menos 1 L☉. A luminosidade vira uma propriedade relativa e fácil de interpretar. Uma estrela com L = 0,01 L☉ é cem vezes menos luminosa que o Sol, e uma com L = 100 L☉ é cem vezes mais. Também fica mais simples comparar estrelas de tamanhos e temperaturas diferentes sem espalhar constante física pela aplicação. Para fazer essa normalização preciso de uma temperatura solar de referência. Estou usando 5772 K, valor nominal adotado pela União Astronômica Internacional na Resolução B3 de 2015. Ele também aparece no cálculo da temperatura efetiva do Sol apresentado neste artigo acadêmico em português.
Ou seja, no código, PhysicalConstants.SUN_TEMPERATURE_KELVIN representa T☉ = 5772 K, e não um valor aleatório escolhido para a simulação. No fim, toda a regra pode ser resumida numa cadeia bem direta:
raio + temperatura → área superficial + emissão por área → luminosidade
Ou, matematicamente:
R e T → 4πR² · σT⁴ → L
E, na forma utilizada pela simulação:
R/R☉ e T/T☉ → (R/R☉)² · (T/T☉)⁴ → L/L☉
É por isso que a luminosidade não precisa ser sorteada. Dadas as propriedades físicas da estrela, ela é determinada pela relação entre raio e temperatura.
E para que serve saber a luminosidade?
Com a luminosidade em mãos, dá para calcular a zona habitável. De novo usei IA para entender a aproximação derivada do trabalho de Kasting, Whitmire & Reynolds, "Habitable Zones around Main Sequence Stars", que aparentemente é o paper fundador dessa área na astronomia moderna. Para entender o conceito em português, usei a Wikipédia sobre Zona Habitável e a dissertação Determinação da zona habitável de exoplanetas (UNESP):
d_interno (UA) = √(L / 1.1)
d_externo (UA) = √(L / 0.53)
public double calculateInnerBoundaryAu(Star star) {
return Math.sqrt(star.getLuminositySolar() / INNER_EDGE_FLUX_FACTOR); // 1.1
}
public double calculateOuterBoundaryAu(Star star) {
return Math.sqrt(star.getLuminositySolar() / OUTER_EDGE_FLUX_FACTOR); // 0.53
}
Para uma estrela com a luminosidade do Sol (L = 1 L☉), isso dá algo entre 0,95 e 1,37 UA, que bate com o que a literatura considera a zona habitável do Sistema Solar, visto que a Terra está a 1 UA, bem no meio.
Nada de Math.random()
Todo esse sistema só faz sentido se for determinístico: a mesma seed sempre precisa produzir a mesma estrela. Eu quero um universo reproduzível (pelo menos nessa parte). Por isso, em lugar nenhum do código aparece Math.random() ou new Random() sem seed explícita vinda de fora. Toda função recebe um RandomGenerator, interface da JEP 356: Enhanced Pseudo-Random Number Generators, do Java 17, que eu já expliquei mais a fundo aqui...
java
var random = new Random(42L);var star = new StarGenerator().generate("Sol", random);
Rode esse trecho hoje, rode de novo daqui a um ano, em outra máquina, em outro sistema operacional, e a estrela sai idêntica, porque a seed 42L sempre produz a mesma sequência de números dentro do Random.
Star{
id=4f083ce3-f12b-bb4b-46ee-9d82b52c856d,
name='Sol',
spectralType=M,
massSolar=0.33,
radiusSolar=0.51,
temperatureKelvin=3393,
luminositySolar=0.03
}
Eu chamei a estrela de "Sol", mas a seed 42 decidiu que ela é, na verdade, uma anã vermelha.
Pedi para o GPT gerar um texto descritivo de como seria uma tarde nesse planeta, e uma imagem dessa estrela.
No auge do verão, o céu permanecia sob a luz avermelhada de uma pequena estrela. O Sol parecia uma brasa distante, tingindo o horizonte de cobre e deixando o céu em tons profundos de azul e violeta. Sua luz era suave, mas suficiente para aquecer o solo e fazer a vegetação escura brilhar em tons de vermelho e púrpura.
Era uma tarde quente, mas diferente daquelas de um mundo banhado por uma estrela como o nosso Sol. As sombras permaneciam longas, o ar carregava o cheiro das plantas aquecidas e, acima de tudo, aquela pequena estrela vermelha dominava o céu, fraca para os padrões cósmicos, mas o coração daquele mundo.

Usando de fato
Eu não quero que a lógica de negocio fique sobre responsabilidade de outra coisa, e sei que tem gente por aí que pode se beneficiar disso para criar seus próprios simuladores. Então esse é meu primeiro projeto publicado no Maven Central. Sei que tem muito ponto de melhoria, mas estou trabalhando nisso com o tempo. Para usar com Maven é só:
<dependency>
<groupId>io.github.navelogic</groupId>
<artifactId>genesis-core</artifactId>
<version>0.1.0</version>
</dependency>
import io.github.navelogic.genesis.core.astronomy.star.model.Star;
import io.github.navelogic.genesis.core.astronomy.star.service.StarGenerator;
import io.github.navelogic.genesis.core.astronomy.star.service.HabitableZoneCalculator;
import java.util.Random;
var random = new Random(42L);
var star = new StarGenerator().generate("Sol", random);
System.out.println(star);
var zone = new HabitableZoneCalculator();
System.out.printf("Zona habitável: %.2f a %.2f UA%n",
zone.calculateInnerBoundaryAu(star), zone.calculateOuterBoundaryAu(star));
O que vem depois
O gerador de estrelas é só a primeira peça, a camada mais alta da pilha, como falei no começo. O próximo passo é usar a luminosidade e a zona habitável para gerar planetas plausíveis e só então voltar para a simulação econômica que me trouxe até aqui. Claro que esses dados podem servir para uma LLM gerar coisas interessantes na simulação, principalmente descritivos, livros, filmes, preferencias de produtos e etc.
O código do Genesis é aberto, então se você quiser ver a implementação completa, sugerir uma correção ou só saber o que vem a seguir, é só dar uma olhada no repositório.